初始版本,目前线上可用

This commit is contained in:
2025-11-19 12:49:16 +08:00
commit cb7f1c45e8
178 changed files with 30336 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
<template>
<div>你寻找的页面失踪了</div>
</template>
<script lang='ts' setup>
import { reactive,onMounted} from 'vue'
const data = reactive({})
</script>
<style lang='scss' scoped>
</style>

View File

@@ -0,0 +1,582 @@
<template>
<div>
<el-row>
<baseTableHeader
title="结账登记"
@resetSearch="methods.resetSearch"
@search="methods.search">
<template #content>
<el-form :model="searchForm" :inline="true" label-position="right">
<el-form-item label="逝者姓名">
<el-input
v-model="searchForm.name"
placeholder="请输入逝者姓名"></el-input>
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="searchForm.gender">
<el-radio-button value="男" label="男"></el-radio-button>
<el-radio-button value="女" label="女"></el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="结账日期">
<el-date-picker
v-model="searchForm.purchaseDate"
type="datetimerange"
range-separator=""
start-placeholder="选择日期"
end-placeholder="选择日期"
@change="dataChange" />
</el-form-item>
</el-form>
</template>
</baseTableHeader>
</el-row>
<el-card style="margin-top: 8px">
<base-table
:option="tableOption"
ref="table"
border
show-summary
:summary-method="getSummaries">
<template #toolsBar>
<el-radio-group
v-model="searchForm.retailState"
size="small"
style="margin-left: 15px">
<el-radio-button label="未结账" :value="0" />
<el-radio-button label="已结账" :value="1" />
</el-radio-group>
</template>
<template #colunm>
<el-table-column
type="index"
width="80"
fixed="left"
align="center"></el-table-column>
<el-table-column
prop="retailState"
label="结账状态"
width="160"
fixed="left"
align="center">
<template #default="{ row }">
<el-tag
:type="
row.retailState === 0
? 'danger'
: row.retailState === 1
? 'success'
: 'info'
">
{{
row.retailState === 0
? "未结账"
: row.retailState === 1
? "已结账"
: "未知"
}}
</el-tag>
</template>
</el-table-column>
<el-table-column
prop="deceased.name"
label="逝者姓名"
width="120"
fixed="left"
align="center" />
<el-table-column prop="deceased.gender" label="性别" align="center" />
<el-table-column prop="deceased.age" label="年龄" align="center" />
<el-table-column
prop="deceased.idNumber"
label="身份证"
align="center"
width="180" />
<el-table-column
prop="deceased.familyPhone"
label="购买人电话"
align="center"
width="150" />
<el-table-column
prop="salesAmount"
:label="'销售金额'"
width="150"
align="center" />
<el-table-column
v-if="searchForm.retailState === 1"
label="现金支付"
width="150"
prop="paymentRecord.cashAmount"
align="center">
<template #default="{ row }">
{{ row.paymentRecord?.cashAmount || "0.00" }}
</template>
</el-table-column>
<el-table-column
prop="paymentRecord.unionPayAmount"
label="银联支付"
width="150"
v-if="searchForm.retailState === 1"
align="center">
<template #default="{ row }">
{{ row.paymentRecord?.unionPayAmount || "0.00" }}
</template>
</el-table-column>
<el-table-column
prop="paymentRecord.cardAmount"
label="刷卡金额"
width="150"
v-if="searchForm.retailState === 1"
align="center">
<template #default="{ row }">
{{ row.paymentRecord?.cardAmount || "0.00" }}
</template>
</el-table-column>
<el-table-column
prop="paymentRecord.publicTransferAmount"
label="对公转账"
width="150"
v-if="searchForm.retailState === 1"
align="center">
<template #default="{ row }">
{{ row.paymentRecord?.publicTransferAmount || "0.00" }}
</template>
</el-table-column>
<el-table-column
prop="paymentRecord.workshopPayment"
label="车间支付"
v-if="searchForm.retailState === 1"
width="150"
align="center">
<template #default="{ row }">
{{ row.paymentRecord?.workshopPayment || "0.00" }}
</template>
</el-table-column>
<el-table-column
prop="createDate"
:label="searchForm.retailState === 0 ? '录单时间' : '结账时间'"
align="center"
width="200">
<template #default="{ row }">
<span v-if="searchForm.retailState === 0">{{
dayjs(row.createDate).format("YYYY-MM-DD HH:mm:ss")
}}</span>
<span v-if="searchForm.retailState === 1">
{{
dayjs(row.checkoutDate).format("YYYY-MM-DD HH:mm:ss")
}}</span
>
</template>
</el-table-column>
<el-table-column
prop="deceased.name"
label="购买人姓名"
align="center"
width="150" />
<el-table-column prop="remark" label="备注" align="center" />
<el-table-column
width="230"
fixed="right"
align="center"
label="操作">
<template #default="{ row }">
<div style="display: flex; width: 100%; justify-content: center">
<el-button
v-if="row.retailState === 0"
size="small"
type="primary"
@click="methods.add(row)">
结账登记
</el-button>
<el-button
v-if="row.retailState === 1"
size="small"
type="primary"
@click="methods.view(row)">
查看
</el-button>
<el-button
v-if="row.retailState === 1"
size="small"
type="default"
@click="methods.cancel(row)">
作废
</el-button>
<el-button
v-if="row.retailState === 1"
size="small"
type="default"
@click="methods.print(row)">
打印
<template #icon>
<img
src="/assets/icon/打印机.svg"
style="margin-right: 5px"
width="20"
height="20" />
</template>
</el-button>
<el-button
v-if="row.retailState === 0"
size="small"
type="default"
@click="methods.print(row)">
打印
</el-button>
</div>
</template>
</el-table-column>
</template>
</base-table>
</el-card>
<base-curd-dialog
v-model="pageVisibleState.show2LevelPage.value"
:title="pageVisibleState.dialogTitle.value"
width="70%"
:before-close="methods.handleDialogClose"
@addConfim="methods.addConfim"
@editConfim="methods.eidthConfirm"
@closeConfirm="methods.handleDialogClose"
confirmText="结账"
destroy-on-close
:showPageType="pageVisibleState.showPageType">
<template #content>
<checkoutAddOrEdit
:executeType="pageVisibleState.executeType.value"
:deceased="currentRetail"
v-model="currentPayment">
</checkoutAddOrEdit>
</template>
<template #footer>
<el-button @click="methods.print">打印</el-button>
</template>
</base-curd-dialog>
<base-dialog
v-model="cancelState.showDialog"
title="账单作废"
width="50%"
destroy-on-close
@closeConfirm="methods.cancleClose">
<el-form :model="cancelForm" label-width="120px" style="padding: 20px">
<el-row :gutter="20">
<el-col :span="12">
<!-- 注销申请人 -->
<el-form-item label="作废申请人">
<el-input disabled v-model="cancelForm.cancelPerson" />
</el-form-item>
</el-col>
<el-col :span="12">
<!-- 注销日期 -->
<el-form-item label="作废日期">
<el-date-picker
disabled
v-model="cancelForm.cancelDate"
type="date" />
</el-form-item>
</el-col>
<el-col :span="24">
<!-- 注销原因 -->
<el-form-item label="作废原因">
<el-input
v-model="cancelForm.cancelReason"
type="textarea"
placeholder="请输入作废原因" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="flex-center">
<el-button type="primary" @click="methods.submitCancel"
>作废</el-button
>
<el-button @click="methods.resetCancel">取消</el-button>
</div>
</template>
</base-dialog>
<!-- <base-dialog v-model="showPrint" title="打印预览">
<printRetailPage v-model="currentData"></printRetailPage>
</base-dialog>
<base-dialog v-model="showPrint1" title="打印预览">
<printServicesPage v-model="currentData"></printServicesPage>
</base-dialog> -->
<PrintRetailPage
v-model="showPrint"
:deceased="deceased"
:serviceUrl="serviceUrl">
</PrintRetailPage>
<printServicesPage
v-model="showPrint1"
:deceased="deceased"
:serviceUrl="serviceUrl">
</printServicesPage>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, onMounted, watch, nextTick } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { userInfor } from "@/store/user/user";
import checkoutAddOrEdit from "./page/checkoutAddOrEdit.vue";
import api from "@/lib/request";
import pageVisible from "@/components/curdDialog/pageVisibleState";
import { tableOptionType } from "@/types/table";
import dayjs from "dayjs";
import { defaultPaymentForm } from "@/defaultForm/defaultPaymentForm";
import { defaultRetail } from "@/defaultForm/defaultRetail";
import PrintRetailPage from "@/components/printPage/printRetailPage.vue";
import printServicesPage from "@/components/printPage/printServicesPage.vue";
const pageVisibleState = new pageVisible({
add: "结账登记",
edit: "登记修改",
view: "查看登记",
});
const globaUser = userInfor().userInfor;
const cancelState = ref({
showDialog: false,
});
const cancelForm = ref({
cancelPerson: globaUser.name,
cancelDate: dayjs().format(),
cancelReason: "",
});
const searchForm = ref({
name: "", // 逝者姓名
gender: "", // 性别 (male / female / other)
checkoutDate: "", // 结账日期 (YYYY-MM-DD)
retailState: 0,
startDate: "",
endDate: "",
purchaseDate: ["", ""],
});
let currentRetail = ref<RegisForm>(defaultRetail());
let showPrint = ref(false);
let showPrint1 = ref(false);
let deceased = ref();
let serviceUrl = ref();
let table = ref();
let currentData = ref();
let tableOption = ref<tableOptionType>({
url: "/checkout-retail/list?retailState=0",
searchUrl: "/checkout-retail/query",
searchParams: searchForm,
executeType: "list",
});
let currentPayment = ref<PaymentForm>(defaultPaymentForm());
watch(
() => searchForm.value.retailState,
(newData) => {
tableOption.value.url =
"/checkout-retail/list?retailState=" + Number(newData);
table.value.methods.setDataType("list");
}
);
const methods = {
view(row: RegisForm) {
currentRetail.value = row;
currentData = row;
currentPayment.value = row.paymentRecord || defaultPaymentForm();
pageVisibleState.showPageType.view = true;
pageVisibleState.showPageType.add = false;
pageVisibleState.executeType.value = "view";
},
add(row: RegisForm) {
currentData.value = row;
currentRetail.value = row;
currentPayment.value = row.paymentRecord || defaultPaymentForm();
if (row.retailState === 1) {
pageVisibleState.showPageType.view = true;
} else {
pageVisibleState.showPageType.add = true;
}
pageVisibleState.executeType.value = "add";
},
print(row: RegisForm) {
deceased.value = row;
serviceUrl.value =
"/deceased-retail/selected-service?retailType=0&deceasedId=" +
row.deceased.id;
if (searchForm.value.retailState === 0) {
showPrint1.value = true;
} else {
showPrint.value = true;
}
},
cancleClose() {
currentRetail.value = defaultRetail();
currentPayment.value = defaultPaymentForm();
},
async addConfim() {
await ElMessageBox.confirm("确定结账吗?", { type: "warning" });
let sendData = {
deceasedRetail: {
...currentRetail.value,
},
currentPayment: {
...currentPayment.value,
},
id: currentData.value.id,
};
const res = await api().post("/checkout-retail/confirmCheckout", sendData);
if (res.code === 200) {
ElMessage.success("结账成功!");
pageVisibleState.show2LevelPage.value = false;
table.value.methods.setDataType("list");
} else {
ElMessage.error(res.msg);
}
},
eidthConfirm() {},
search() {
table.value.methods.setDataType("search");
},
resetSearch() {
let retailState = searchForm.value.retailState;
searchForm.value = {
name: "", // 逝者姓名
gender: "", // 性别 (male / female / other)
checkoutDate: "", // 结账日期 (YYYY-MM-DD)
startDate: "",
endDate: "",
purchaseDate: ["", ""],
retailState: retailState,
};
nextTick(() => {
table.value.methods.setDataType("reset");
});
},
handleDialogClose() {
pageVisibleState.show2LevelPage.value = false;
},
cancel(row: RegisForm) {
currentRetail.value = row;
cancelState.value.showDialog = true;
},
async submitCancel() {
if (!cancelForm.value.cancelReason) {
return ElMessage.error("请输入作废原因");
}
await ElMessageBox.confirm("确定作废吗?", { type: "warning" });
let sendData = {
deceasedRetailId: currentRetail.value.id,
cancelForm: { ...cancelForm.value },
cancelType: 0,
};
api()
.post("/cancel/cancel", sendData)
.then((res) => {
if (res.code === 200) {
ElMessage.success("作废提交成功!");
methods.resetCancel();
cancelForm.value = {
cancelPerson: globaUser.name,
cancelDate: dayjs().format(),
cancelReason: "",
};
table.value.methods.setDataType("list");
} else {
ElMessage.error(res.msg);
}
});
},
resetCancel() {
cancelForm.value = {
cancelPerson: globaUser.name,
cancelDate: dayjs().format(),
cancelReason: "",
};
cancelState.value.showDialog = false;
},
};
function dataChange(val: Date[]) {
searchForm.value.startDate = val[0];
searchForm.value.endDate = val[1];
}
const getSummaries = (param: { columns: any[]; data: any[] }) => {
const { columns, data } = param;
const sums: any[] = [];
// 需要统计的字段列表
const sumKeys = [
"salesAmount",
"paymentRecord.cashAmount",
"paymentRecord.unionPayAmount",
"paymentRecord.cardAmount",
"paymentRecord.publicTransferAmount",
"paymentRecord.workshopPayment",
];
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = "合计";
return;
}
if (sumKeys.includes(column.property)) {
const values = data.map((item) => {
const keys = column.property.split(".");
let value = item;
for (const key of keys) {
value = value?.[key] || 0;
}
return Number(value) || 0;
});
if (!values.every((value) => isNaN(value))) {
const sum = values.reduce((prev, curr) => {
return prev + curr;
}, 0);
sums[index] = `${sum.toFixed(2)}`;
} else {
sums[index] = "0.00 元";
}
} else {
sums[index] = "";
}
});
return sums;
};
onMounted(async () => {});
</script>
<style lang="scss" scoped>
/* 添加汇总行样式 */
:deep(.el-table__footer) {
.cell {
font-weight: 600;
color: #606266;
}
td {
background-color: #f5f7fa !important;
}
}
</style>

View File

@@ -0,0 +1,303 @@
<template>
<div>
<inforCard title="逝者信息">
<el-form :model="checkoutForm" label-width="120px" disabled>
<el-row :gutter="20" v-if="!deceased.deceased">
<el-col :span="8">
<el-form-item label="姓名">
<el-input
:prefix-icon="User"
v-model="deceased.name"
disabled
placeholder="姓名">
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="性别">
<el-radio-group v-model="deceased.gender" disabled>
<el-radio-button value="男" label="男"></el-radio-button>
<el-radio-button value="女" label="女"></el-radio-button>
</el-radio-group> </el-form-item
></el-col>
<el-col :span="8">
<el-form-item label="证件号码" prop="idNumber">
<el-input v-model="deceased.idNumber" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="年龄">
<el-input v-model="deceased.age" disabled placeholder="年龄">
</el-input> </el-form-item
></el-col>
<el-col :span="8">
<el-form-item label="购买人" prop="buyer">
<el-input v-model="deceased.familyName" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="购买人电话" prop="buyer">
<el-input v-model="deceased.familyPhone" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="购买日期" prop="purchaseDate">
<el-date-picker
v-model="deceased.purchaseDate"
type="datetime"
format="YYYY-MM-DD: HH:mm:ss" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="销售金额" prop="salesAmount">
<el-input v-model.number="deceased.salesAmount" type="number">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" v-else">
<el-col :span="8">
<el-form-item label="姓名">
<el-input
:prefix-icon="User"
v-model="deceased.deceased.name"
disabled
placeholder="姓名">
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="性别">
<el-radio-group v-model="deceased.deceased.gender" disabled>
<el-radio-button value="男" label="男"></el-radio-button>
<el-radio-button value="女" label="女"></el-radio-button>
</el-radio-group> </el-form-item
></el-col>
<el-col :span="8">
<el-form-item label="证件号码" prop="idNumber">
<el-input v-model="deceased.deceased.idNumber" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="年龄">
<el-input v-model="deceased.deceased.age" disabled placeholder="年龄">
</el-input> </el-form-item
></el-col>
<el-col :span="8">
<el-form-item label="购买人" prop="buyer">
<el-input v-model="deceased.deceased.familyName" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="购买人电话" prop="buyer">
<el-input v-model="deceased.deceased.familyPhone" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="购买日期" prop="purchaseDate">
<el-date-picker
v-model="deceased.deceased.purchaseDate"
type="datetime"
format="YYYY-MM-DD: HH:mm:ss" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="销售金额" prop="salesAmount">
<el-input v-model.number="deceased.deceased.salesAmount" type="number">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
</inforCard>
<inforCard title="已选服务项目">
<el-table
:data="servicesList"
style="max-height: 160px; overflow-y: auto">
<el-table-column type="index" label="序号" width="80" />
<el-table-column prop="name" label="项目名称" />
<el-table-column prop="quantity" label="数量" />
<el-table-column prop="unit" label="单位" />
<el-table-column prop="price" label="单价">
<template #default="{ row }"> {{ row.price }} </template>
</el-table-column>
<el-table-column prop="price" label="总金额">
<template #default="{ row }">
{{ row.price * row.quantity }}
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" />
</el-table>
</inforCard>
<inforCard title="结账列表">
<el-form
ref="formRef"
:model="checkoutForm"
label-width="120px"
label-position="right">
<el-row :gutter="20">
<!-- 第一行 -->
<el-col :span="8">
<el-form-item label="结账日期" prop="checkoutDate">
<el-date-picker
v-model="checkoutForm.checkoutDate"
type="date"
placeholder="结账日期"
format="YYYY-MM-DD"
disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="结算日期" prop="settlementDate">
<el-date-picker
v-model="checkoutForm.settlementDate"
type="date"
placeholder="选择日期"
format="YYYY-MM-DD" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="经办人" prop="handler">
<el-input
disabled
v-model="checkoutForm.handler"
placeholder="经办人" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="现金金额" prop="cash">
<el-input
v-model.number="checkoutForm.cashAmount"
type="number"
:min="0"
@input="caleValue('cashAmount')">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="银联" prop="unionPay">
<el-input
v-model.number="checkoutForm.unionPayAmount"
type="number"
:min="0"
@input="caleValue('unionPayAmount')">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="银行卡" prop="cardPay">
<el-input
v-model.number="checkoutForm.cardAmount"
type="number"
:min="0"
@input="caleValue('cardAmount')">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="对公" prop="transfer">
<el-input
v-model.number="checkoutForm.publicTransferAmount"
type="number"
:min="0"
@input="caleValue('publicTransferAmount')">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="车间支付" prop="transfer">
<el-input
v-model.number="checkoutForm.workshopPayment"
type="number"
:min="0"
@input="caleValue('workshopPayment')">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
</inforCard>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, watch } from "vue";
// @ts-ignore
import { User } from "@element-plus/icons-vue";
import dayjs from "dayjs";
import { userInfor } from "@/store/user/user";
import { defaultRetail } from "@/defaultForm/defaultRetail";
import { defaultPaymentForm } from "@/defaultForm/defaultPaymentForm";
import request from "@/lib/request";
const props = withDefaults(
defineProps<{
executeType?: string;
deceased: RegisForm;
}>(),
{
deceased: () => defaultRetail(),
}
);
const emit = defineEmits(["updateData"]);
const userInforStore = userInfor().userInfor;
let checkoutForm = defineModel({ default: defaultPaymentForm() });
let servicesList = ref([]);
onMounted(() => {
checkoutForm.value.handler =
checkoutForm.value.handler || (userInforStore.name as string);
});
let keys = [
"cashAmount",
"unionPayAmount",
"cardAmount",
"publicTransferAmount",
"workshopPayment",
];
function caleValue(keyVal: string) {
let total = 0;
let currentVal = checkoutForm.value[keyVal];
keys.forEach((key) => {
if (keyVal !== key) {
let tempVal = Number(checkoutForm.value[key]).toFixed(2);
total = Number(total) + Number(tempVal);
}
});
let salesAmount = Number(props.deceased.salesAmount);
if (salesAmount - (total + currentVal) < 0) {
checkoutForm.value[keyVal] = salesAmount - total;
}
}
request()
.get(
"/deceased-retail/selected-service?deceasedId=" +
props.deceased.deceased.id +
"&retailType=0"
)
.then((res) => {
servicesList.value = res.data?.list || [];
});
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,444 @@
<template>
<div>
<el-row>
<baseTableHeader
title="逝者零售"
@resetSearch="methods.resetSearch"
@search="methods.search">
<template #content>
<el-form :model="searchForm" label-position="right">
<el-row :gutter="12">
<el-col :span="6">
<el-form-item label="逝者姓名">
<el-input
v-model="searchForm.deceased.name"
placeholder="请输入逝者姓名"></el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="购买人姓名">
<el-input
v-model="searchForm.retail.familyName"
placeholder="请输入购买人姓名"></el-input> </el-form-item
></el-col>
<el-col :span="6">
<el-form-item label="引导员">
<GuideList v-model="searchForm.retail.guide"></GuideList>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label="购买日期">
<el-date-picker
v-model="searchForm.retail.purchaseDate"
type="datetimerange"
range-separator=""
start-placeholder="选择日期"
end-placeholder="选择日期"
@change="dataChange" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
</baseTableHeader>
</el-row>
<el-card style="margin-top: 8px">
<div style="margin-bottom: 15px">
<el-button
size="small"
type="primary"
@click="methods.add"
v-if="route.path === '/noDepartedSaint'">
<template #icon>
<el-icon>
<CirclePlus />
</el-icon> </template
>新增</el-button
>
</div>
<base-table
:option="tableOption"
ref="table"
border
show-summary
:summary-method="getSummaries">
<template #colunm>
<el-table-column
type="index"
width="80"
fixed="left"
align="center"></el-table-column>
<el-table-column
prop="name"
label="结账"
width="120"
align="center"
fixed="left">
<template #default="{ row }">
<el-tag type="success" v-if="row.retailState === 1"
>已结账</el-tag
>
<el-tag type="danger" v-if="row.retailState === 0">未结账</el-tag>
</template>
</el-table-column>
<el-table-column
prop="deceased.name"
label="逝者姓名"
fixed="left"
align="center" />
<el-table-column
prop="familyName"
label="购买人"
align="center"
fixed="left">
<template #default="{ row }">
<span v-if="row.deceased.familyName">
{{ row.deceased.familyName }}
</span>
<span v-else>--</span>
</template>
</el-table-column>
<el-table-column
prop="createDate"
label="购买日期"
width="200"
align="center" />
<el-table-column
prop="createDate"
label="结算日期"
width="200"
align="center">
<template #default="{ row }">
<span v-if="row.retailState === 1"> {{ row.checkoutDate }} </span>
<span v-else style="color: #999; font-style: italic">--</span>
</template>
</el-table-column>
<el-table-column
prop="salesAmount"
label="销售金额"
width="150"
align="center" />
<el-table-column
label="现金支付"
width="150"
prop="payment.cashAmount"
align="center">
<template #default="{ row }">
{{ row.payment?.cashAmount || "0.00" }}
</template>
</el-table-column>
<el-table-column
prop="payment.unionPayAmount"
label="银联支付"
width="150"
align="center">
<template #default="{ row }">
{{ row.payment?.unionPayAmount || "0.00" }}
</template>
</el-table-column>
<el-table-column
prop="payment.cardAmount"
label="刷卡金额"
width="150"
align="center">
<template #default="{ row }">
{{ row.payment?.cardAmount || "0.00" }}
</template>
</el-table-column>
<el-table-column
prop="payment.publicTransferAmount"
label="对公转账"
width="150"
align="center">
<template #default="{ row }">
{{ row.payment?.publicTransferAmount || "0.00" }}
</template>
</el-table-column>
<el-table-column
prop="payment.workshopPayment"
label="车间支付"
width="150"
align="center">
<template #default="{ row }">
{{ row.payment?.workshopPayment || "0.00" }}
</template>
</el-table-column>
<el-table-column prop="handler" label="经办人" align="center" />
<el-table-column label="引导员" align="center">
<template #default="{ row }">
<span> {{ row.guide ?? row.deceased.guide }} </span>
</template>
</el-table-column>
<el-table-column
width="320"
label="操作"
align="center"
fixed="right">
<template #default="{ row }">
<el-row align="middle" justify="center">
<el-button
v-if="row.retailState !== 1"
size="small"
type="primary"
@click="methods.update(row)">
修改</el-button
>
<el-button @click="methods.viewProduct(row)" size="small">
零售清单</el-button
>
<el-button
size="small"
type="default"
@click="methods.print(row)"
>打印</el-button
>
<el-button
size="small"
type="danger"
v-if="row.retailState !== 1"
@click="methods.delete(row)">
删除</el-button
>
</el-row>
</template>
</el-table-column>
</template>
</base-table>
</el-card>
<retailList v-model="showDialog" :option="retailListTable"></retailList>
<baseDialog
v-model="retailRegisState.showDialog"
top="50px"
:title="
handlerType === 'add'
? '新增登记'
: handlerType === 'update'
? '修改零售登记'
: '零售登记'
"
@close="methods.dialogClose">
<retailRegis
:retailType="1"
v-model="regisForm"
type="零售修改"
:showList="['逝者信息']"
:add="handlerType === 'add'"></retailRegis>
<template #footer>
<el-row justify="center" align="middle" style="margin-top: 15px">
<el-button type="primary" @click="methods.confirmUpdate"
>保存</el-button
>
<el-button type="danger" @click="methods.close">关闭</el-button>
</el-row>
</template>
</baseDialog>
<printServicesPage
v-model="showPrint"
:deceased="deceased"
:serviceUrl="serviceUrl">
</printServicesPage>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, onMounted, watch } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { resetParams } from "@/util/globalMethods";
import { globalState } from "@/store";
import retailRegis from "../publicComponents/retailRegis.vue";
import api from "@/lib/request";
import { tableOptionType } from "@/types/table";
import { useRoute } from "vue-router";
import { defaultRetail } from "@/defaultForm/defaultRetail";
import PrintRetailPage from "@/components/printPage/printServicesPage.vue";
import printServicesPage from "@/components/printPage/printServicesPage.vue";
import GuideList from "@/components/guideList/guideList.vue";
const retailRegisState = ref({
showDialog: false,
});
let currentInfor = ref(undefined);
let showDialog = ref(false);
let showPrint = ref(false);
let deceased = ref();
let serviceUrl = ref();
const handlerType = ref("");
const searchForm = reactive({
deceased: {
name: "", // 逝者姓名
},
retail: {
guide: "", // 引导员
purchaseDate: "", // 购买日期
retailType: 1,
startDate: "",
endDate: "",
familyName: "",
},
});
const route = useRoute();
const regisForm = ref<RegisForm>();
let table = ref();
let tableOption = ref<tableOptionType>({
url: "/deceased-retail/list?retailType=1",
searchUrl: "/deceased-retail/query",
searchParams: searchForm,
executeType: "list",
});
let retailListTable = ref<tableOptionType>({
url: "/deceased-retail/selected-service",
searchUrl: "",
searchParams: {},
executeType: "list",
});
const methods = {
update(row: RegisForm) {
regisForm.value = row;
handlerType.value = "update";
retailRegisState.value.showDialog = true;
},
async confirmUpdate() {
await ElMessageBox.confirm("确定保存修改吗?", { type: "warning" });
let sendData = { ...regisForm.value };
api()
.post("/deceased-retail/update", sendData)
.then((res) => {
if (res.code === 200) {
ElMessage.success("修改成功");
table.value.methods.setDataType("list");
retailRegisState.value.showDialog = false;
regisForm.value = defaultRetail();
}
});
},
async delete(row: RegisForm) {
await ElMessageBox.confirm("确定删除该条信息吗?", { type: "warning" });
api()
.get("/deceased-retail/delete", {
params: {
id: row.id,
},
})
.then((res) => {
if (res.code === 200) {
ElMessage.success("删除成功");
table.value.methods.setDataType("list");
}
});
},
add() {
retailRegisState.value.showDialog = true;
handlerType.value = "add";
},
async viewProduct(row: RegisForm) {
retailListTable.value.url =
"/deceased-retail/selected-service?retailType=1&retailId=" +
(row.retailId || row.id);
showDialog.value = true;
},
drawerClose() {
currentInfor.value = undefined;
showDialog.value = false;
},
close() {
retailRegisState.value.showDialog = false;
},
async print(row: RegisForm) {
deceased.value = row;
showPrint.value = true;
serviceUrl.value =
"/deceased-retail/selected-service?retailType=1&retailId=" +
(row.retailId || row.id);
},
dialogClose() {
regisForm.value = defaultRetail();
},
search() {
searchForm.retail.retailType = 1;
table.value.methods.setDataType("search");
},
resetSearch() {
resetParams(searchForm);
table.value.methods.setDataType("reset");
},
};
onMounted(async () => {});
const getSummaries = (param: { columns: any[]; data: any[] }) => {
const { columns, data } = param;
const sums: any[] = [];
// 需要统计的字段列表
const sumKeys = [
"salesAmount",
"payment.cashAmount",
"payment.unionPayAmount",
"payment.cardAmount",
"payment.publicTransferAmount",
"payment.workshopPayment",
];
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = "合计";
return;
}
if (sumKeys.includes(column.property)) {
const values = data.map((item) => {
const keys = column.property.split(".");
let value = item;
for (const key of keys) {
value = value?.[key] || 0;
}
return Number(value) || 0;
});
if (!values.every((value) => isNaN(value))) {
const sum = values.reduce((prev, curr) => {
return prev + curr;
}, 0);
sums[index] = `${sum.toFixed(2)}`;
} else {
sums[index] = "0.00 元";
}
} else {
sums[index] = "";
}
});
return sums;
};
function dataChange(val: Date[]) {
searchForm.retail.startDate = val[0];
searchForm.retail.endDate = val[1];
}
</script>
<style lang="scss" scoped>
/* 添加汇总行样式 */
:deep(.el-table__footer) {
.cell {
font-weight: 600;
color: #606266;
}
td {
background-color: #f5f7fa !important;
}
}
</style>

View File

@@ -0,0 +1,6 @@
<template>
<router-view></router-view>
</template>
<script lang="ts" setup></script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,453 @@
<template>
<div>
<el-row>
<baseTableHeader
title="无逝者零售"
@resetSearch="methods.resetSearch"
@search="methods.search">
<template #content>
<el-form :model="searchForm" label-position="right">
<el-row :gutter="12">
<el-col :span="6">
<el-form-item label="逝者姓名">
<el-input
v-model="searchForm.deceased.name"
placeholder="请输入逝者姓名"></el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="购买人姓名">
<el-input
v-model="searchForm.retail.familyName"
placeholder="请输入购买人姓名"></el-input> </el-form-item
></el-col>
<el-col :span="6">
<el-form-item label="引导员">
<GuideList v-model="searchForm.retail.guide"></GuideList>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label="购买日期">
<el-date-picker
v-model="searchForm.retail.purchaseDate"
type="datetimerange"
range-separator=""
start-placeholder="选择日期"
end-placeholder="选择日期"
@change="dataChange" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
</baseTableHeader>
</el-row>
<el-card style="margin-top: 8px">
<base-table
:option="tableOption"
ref="table"
border
show-summary
:summary-method="getSummaries">
<template #toolsBar>
<el-button size="small" type="primary" @click="methods.add">
<template #icon>
<el-icon>
<CirclePlus />
</el-icon> </template
>新增</el-button
>
</template>
<template #colunm>
<el-table-column
type="index"
width="80"
fixed="left"
align="center"></el-table-column>
<el-table-column label="结账" align="center" width="120" fixed="left">
<template #default="{ row }">
<el-tag type="success" v-if="row.retailState === 1"
>已结账</el-tag
>
<el-tag type="danger" v-if="row.retailState === 0">未结账</el-tag>
</template>
</el-table-column>
<el-table-column
prop="familyName"
label="购买人"
align="center"
fixed="left">
<template #default="{ row }">
<span v-if="row.familyName">{{ row.familyName }}</span>
<span v-else>--</span>
</template>
</el-table-column>
<el-table-column
prop="deceasedName"
label="逝者姓名"
align="center"
fixed="left">
<template #default="{ row }">
<span v-if="row.deceasedName">{{ row.deceasedName }}</span>
<span v-else>--</span>
</template>
</el-table-column>
<el-table-column
prop="createDate"
label="购买日期"
width="200"
align="center" />
<el-table-column
prop="createDate"
label="结算日期"
width="200"
align="center">
<template #default="{ row }">
<span v-if="row.retailState === 1"> {{ row.checkoutDate }} </span>
<span v-else style="color: #999; font-style: italic">--</span>
</template>
</el-table-column>
<el-table-column prop="salesAmount" label="销售金额" align="center" />
<el-table-column
label="现金支付"
width="150"
prop="payment.cashAmount"
align="center">
<template #default="{ row }">
{{ row.payment?.cashAmount || "0.00" }}
</template>
</el-table-column>
<el-table-column
prop="payment.unionPayAmount"
label="银联支付"
width="150"
align="center">
<template #default="{ row }">
{{ row.payment?.unionPayAmount || "0.00" }}
</template>
</el-table-column>
<el-table-column
prop="payment.cardAmount"
label="刷卡金额"
width="150"
align="center">
<template #default="{ row }">
{{ row.payment?.cardAmount || "0.00" }}
</template>
</el-table-column>
<el-table-column
prop="payment.publicTransferAmount"
label="对公转账"
width="150"
align="center">
<template #default="{ row }">
{{ row.payment?.publicTransferAmount || "0.00" }}
</template>
</el-table-column>
<el-table-column
prop="payment.workshopPayment"
label="车间支付"
width="150"
align="center">
<template #default="{ row }">
{{ row.payment?.workshopPayment || "0.00" }}
</template>
</el-table-column>
<el-table-column prop="handler" label="经办人" align="center" />
<el-table-column prop="guide" label="引导员" align="center" />
<el-table-column
width="320"
label="操作"
align="center"
fixed="right">
<template #default="{ row }">
<el-row align="middle" justify="center">
<el-button
v-if="row.retailState === 0"
size="small"
type="primary"
@click="methods.update(row)">
修改</el-button
>
<el-button @click="methods.viewProduct(row)" size="small">
零售清单</el-button
>
<el-button
size="small"
type="default"
@click="methods.print(row)"
>打印</el-button
>
<!-- <el-button
type="primary"
v-if="row.retailState === 0"
@click="methods.checkout(row)"
>结账</el-button
> -->
<el-button
size="small"
type="danger"
v-if="row.retailState !== 1"
@click="methods.delete(row)">
删除</el-button
>
</el-row>
</template>
</el-table-column>
</template>
</base-table>
</el-card>
<retailList v-model="showDialog" :option="retailListTable"></retailList>
<baseDialog
v-model="retailRegisState.showDialog"
top="30px"
:title="
handlerType === 'add'
? '新增无逝者零售'
: handlerType === 'update'
? '修改无逝者登记'
: '零售登记'
"
@close="methods.dialogClose">
<retailRegis
v-model="regisForm"
:add="handlerType === 'add'"
:retailType="2"
:type="
handlerType === 'add'
? '新增无逝者零售'
: handlerType === 'update'
? '修改无逝者登记'
: '零售登记'
"></retailRegis>
<template #footer>
<el-row justify="center" align="middle" style="margin-top: 15px">
<el-button type="primary" @click="methods.addConfirm">保存</el-button>
<el-button type="danger" @click="methods.close">关闭</el-button>
</el-row>
</template>
</baseDialog>
<printServicesPage
v-model="showPrint"
:deceased="deceased"
:serviceUrl="serviceUrl">
</printServicesPage>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, onMounted, watch } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { resetParams } from "@/util/globalMethods";
import { globalState } from "@/store";
import retailRegis from "../publicComponents/retailRegis.vue";
import api from "@/lib/request";
import { tableOptionType } from "@/types/table";
import { defaultRetail } from "@/defaultForm/defaultRetail";
import printServicesPage from "@/components/printPage/printServicesPage.vue";
import GuideList from "@/components/guideList/guideList.vue";
let retailListTable = ref<tableOptionType>({
url: "/deceased-retail/selected-service",
searchUrl: "",
searchParams: {},
executeType: "list",
});
let showPrint = ref(false);
let deceased = ref();
let serviceUrl = ref();
const retailRegisState = ref({
showDialog: false,
});
let currentInfor = ref(undefined);
let showDialog = ref(false);
const handlerType = ref("新增无逝者零售");
const searchForm = reactive({
deceased: {
name: "",
},
retail: {
guide: "", // 引导员
purchaseDate: "", // 购买日期
retailType: 2,
familyName: "",
},
});
const regisForm = ref<RegisForm>();
let table = ref();
let tableOption = ref<tableOptionType>({
url: "/deceased-retail/list?retailType=2",
searchUrl: "/deceased-retail/query",
searchParams: searchForm,
executeType: "list",
});
const methods = {
update(row: RegisForm) {
regisForm.value = row;
handlerType.value = "update";
retailRegisState.value.showDialog = true;
},
async delete(row: RegisForm) {
await ElMessageBox.confirm("确定删除该条信息吗?", { type: "warning" });
const res = await api().get("deceased-retail/delete", {
params: {
id: row.id,
},
});
if (res.code === 200) {
ElMessage.success("删除成功");
table.value.methods.setDataType("list");
} else {
ElMessage.error(res);
}
},
add() {
retailRegisState.value.showDialog = true;
handlerType.value = "add";
},
viewProduct(row: RegisForm) {
retailListTable.value.url =
"/deceased-retail/selected-service?retailType=2&retailId=" + row.id;
showDialog.value = true;
},
drawerClose() {
currentInfor.value = undefined;
showDialog.value = false;
},
async addConfirm() {
let url = "/no-deceased-retail/add";
if (handlerType.value === "update") url = "/no-deceased-retail/update";
await ElMessageBox.confirm("确定保存吗?", {
type: "warning",
});
let sendData = { ...regisForm.value };
api()
.post(url, sendData)
.then((res) => {
if (res.code === 200) {
ElMessage.success("保存成功!");
retailRegisState.value.showDialog = false;
table.value.methods.setDataType("list");
} else {
ElMessage.error(res.msg);
}
});
},
async checkout(row: RegisForm) {
await ElMessageBox.confirm("确定结账吗?", { type: "warning" });
api()
.get("/no-deceased-retail/checkout", {
params: {
id: Number(row.id),
},
})
.then((res) => {
if (res.code === 200) {
ElMessage.success("结账成功!");
retailRegisState.value.showDialog = false;
table.value.methods.setDataType("list");
} else {
ElMessage.error(res.msg);
}
});
},
close() {
retailRegisState.value.showDialog = false;
},
async print(row: any) {
deceased.value = row;
showPrint.value = true;
serviceUrl.value =
"/deceased-retail/selected-service?retailType=2&retailId=" + row.id;
},
dialogClose() {
regisForm.value = defaultRetail();
},
search() {
searchForm.retail.retailType = 2;
table.value.methods.setDataType("search");
},
resetSearch() {
resetParams(searchForm);
table.value.methods.setDataType("reset");
},
};
const getSummaries = (param: { columns: any[]; data: any[] }) => {
const { columns, data } = param;
const sums: any[] = [];
// 需要统计的字段列表
const sumKeys = [
"salesAmount",
"payment.cashAmount",
"payment.unionPayAmount",
"payment.cardAmount",
"payment.publicTransferAmount",
"payment.workshopPayment",
];
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = "合计";
return;
}
if (sumKeys.includes(column.property)) {
const values = data.map((item) => {
const keys = column.property.split(".");
let value = item;
for (const key of keys) {
value = value?.[key] || 0;
}
return Number(value) || 0;
});
if (!values.every((value) => isNaN(value))) {
const sum = values.reduce((prev, curr) => {
return prev + curr;
}, 0);
sums[index] = `${sum.toFixed(2)}`;
} else {
sums[index] = "0.00 元";
}
} else {
sums[index] = "";
}
});
return sums;
};
function dataChange(val: Date[]) {
searchForm.retail.startDate = val[0];
searchForm.retail.endDate = val[1];
}
</script>
<style lang="scss" scoped>
/* 添加汇总行样式 */
:deep(.el-table__footer) {
.cell {
font-weight: 600;
color: #606266;
}
td {
background-color: #f5f7fa !important;
}
}
</style>

View File

@@ -0,0 +1,512 @@
<template>
<el-form
style="max-height: 72vh; overflow-y: auto"
ref="formRef"
:model="regisForm"
label-width="120px"
label-position="right">
<inforCard title="逝者信息" v-if="showList.includes('逝者信息')">
<el-row :gutter="20" v-if="!regisForm.deceased">
<el-col :span="8">
<el-form-item label="逝者姓名" prop="name">
<el-input
v-model="regisForm.name"
:disabled="
props.type !== '服务修改' && props.type !== '服务登记'
" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="证件号码" prop="idNumber">
<el-input
v-model="regisForm.idNumber"
:disabled="
props.type !== '服务修改' && props.type !== '服务登记'
" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="性别" prop="gender">
<el-radio-group
v-model="regisForm.gender"
:disabled="
props.type !== '服务修改' && props.type !== '服务登记'
">
<el-radio-button label="男" value="男"></el-radio-button>
<el-radio-button label="女" value="女"></el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="年龄" prop="age">
<el-input
v-model="regisForm.age"
:disabled="
props.type !== '服务修改' && props.type !== '服务登记'
" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" v-else>
<el-col :span="8">
<el-form-item label="逝者姓名" prop="name">
<el-input
v-model="regisForm.deceased.name"
:disabled="
props.type !== '服务修改' && props.type !== '服务登记'
" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="证件号码" prop="idNumber">
<el-input
v-model="regisForm.deceased.idNumber"
:disabled="
props.type !== '服务修改' && props.type !== '服务登记'
" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="性别" prop="gender">
<el-radio-group
v-model="regisForm.deceased.gender"
:disabled="
props.type !== '服务修改' && props.type !== '服务登记'
">
<el-radio-button label="男" value="男"></el-radio-button>
<el-radio-button label="女" value="女"></el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="年龄" prop="age">
<el-input
v-model="regisForm.deceased.age"
:disabled="
props.type !== '服务修改' && props.type !== '服务登记'
" />
</el-form-item>
</el-col>
</el-row>
</inforCard>
<inforCard title="购买信息">
<el-row :gutter="20">
<el-col :span="8" v-if="props.retailType === 2">
<el-form-item label="逝者姓名" prop="deceasedName">
<el-input
v-model="regisForm.deceasedName"
placeholder="请输入逝者姓名" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form v-model="regisForm"></el-form>
<el-form-item label="引导员" prop="guide">
<GuideList v-model="regisForm.guide"></GuideList>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="购买人" prop="buyer">
<el-input
:disabled="
props.type !== '服务修改' &&
props.type !== '服务登记' &&
props.type !== '新增无逝者零售' &&
props.type !== '修改无逝者登记'
"
v-model="regisForm.familyName"
placeholder="请输入购买人姓名" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="购买人联系电话" prop="buyer">
<el-input
:disabled="
props.type !== '服务修改' &&
props.type !== '服务登记' &&
props.type !== '新增无逝者零售' &&
props.type !== '修改无逝者登记'
"
v-model="regisForm.familyPhone"
placeholder="请输入购买人电话" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="所在区域">
<el-cascader
:disabled="
props.type !== '服务修改' &&
props.type !== '服务登记' &&
props.type !== '新增无逝者零售' &&
props.type !== '修改无逝者登记'
"
:options="pcaTextArrData"
v-model="selectedOptions"
@change="methods.pcaChange"></el-cascader> </el-form-item
></el-col>
<el-col :span="8">
<el-form-item label="详细地址">
<el-input
v-model="regisForm.address"
:disabled="
props.type !== '服务修改' &&
props.type !== '服务登记' &&
props.type !== '新增无逝者零售' &&
props.type !== '修改无逝者登记'
"
placeholder="请输入你的地址"></el-input> </el-form-item
></el-col>
<el-col :span="8">
<el-form-item label="购买日期" prop="purchaseDate">
<el-date-picker
v-model="regisForm.purchaseDate"
type="datetime"
placeholder="选择日期"
format="YYYY-MM-DD: HH:mm:ss" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="经办人" prop="handler">
<el-input
v-model="regisForm.handler"
disabled
placeholder="请输入经办人" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="销售金额" prop="salesAmount">
<el-input
v-model.number="regisForm.salesAmount"
disabled
type="number">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
</el-row>
</inforCard>
<inforCard title="已选服务项目">
<div style="display: flex; align-items: center">
<el-button
type="primary"
size="small"
@click="methods.addServiceItem"
v-if="props.type !== '零售结算'"
>新增项目</el-button
>
<el-button
type="default"
size="small"
@click="methods.customInput"
v-if="props.type !== '零售结算'"
>手工录入</el-button
>
</div>
<el-table
:data="regisForm.services"
:style="{ marginTop: (props.type !== '零售结算' ? 15 : 0) + 'px' }">
<el-table-column type="index" label="序号" width="80" />
<el-table-column prop="name" label="项目名称" />
<el-table-column prop="quantity" label="数量" width="80">
<template #default="{ row }">
<span v-if="row.name === '殡仪定制服务'">
<el-input v-model="row.quantity"></el-input>
</span>
<span v-else>{{ row.quantity }}</span>
</template>
</el-table-column>
<el-table-column prop="unit" label="单位" width="80">
<template #default="{ row }">
<span v-if="row.name === '殡仪定制服务'">
<el-input v-model="row.unit"></el-input>
</span>
<span v-else>{{ row.unit }}</span>
</template>
</el-table-column>
<el-table-column prop="price" label="单价(元)">
<template #default="{ row }">
<span v-if="row.name === '殡仪定制服务'">
<el-input v-model="row.price"></el-input>
</span>
<span v-else>
{{ row.price }}
</span>
</template>
</el-table-column>
<el-table-column prop="price" label="总金额">
<template #default="{ row }">
{{ row.price * row.quantity }}
</template>
</el-table-column>
<el-table-column prop="remark" width="200" label="备注">
<template #default="{ row }">
<el-tooltip
class="box-item"
effect="dark"
:content="row.remark"
placement="top">
<p
style="
white-space: nowrap; /* 强制不换行 */
overflow: hidden; /* 隐藏溢出内容 */
text-overflow: ellipsis; /* 显示省略号 */
">
{{ row.remark }}
</p>
</el-tooltip>
</template>
</el-table-column>
<el-table-column>
<template #default="{ row }">
<el-button
type="danger"
size="small"
v-if="props.showServiceDelBtn"
@click="methods.removeProduct(row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
</inforCard>
<base-curd-dialog
v-model="customServiceState.show2LevelPage.value"
title="手工录入服务"
width="50%"
@addConfim="addConfim"
@closeConfirm="handleDialogClose"
:showPageType="customServiceState.showPageType">
<template #content>
<serviceListaddOrEdit
type="手工录入服务"
:executeType="customServiceState.executeType.value"
v-model="currentServiceItem"></serviceListaddOrEdit>
</template>
<template #footer>
<el-button type="primary" @click="methods.confirmCustomService"
>确定</el-button
>
</template>
</base-curd-dialog>
<el-drawer
v-model="serviceItemState.showDrawer"
size="50%"
:with-header="false">
<serviceItemSelect v-model="regisForm.services"></serviceItemSelect>
</el-drawer>
</el-form>
</template>
<script lang="ts" setup>
import { computed, nextTick, onMounted, ref, watch } from "vue";
import { userInfor } from "@/store/user/user";
import serviceItemSelect from "./serviceItemSelect.vue";
import dayjs from "dayjs";
import api from "@/lib/request";
import pageVisible from "@/components/curdDialog/pageVisibleState";
import { ElMessage, ElMessageBox } from "element-plus";
import serviceListaddOrEdit from "../../serviceList/serviceItem/page/addOrEdit.vue";
import { defaultRetail } from "@/defaultForm/defaultRetail";
import { defaultServiceItem } from "@/defaultForm/defaultSerivceItem";
import { pcaTextArr } from "element-china-area-data";
import GuideList from "@/components/guideList/guideList.vue";
const formRef = ref();
const userStoreState = userInfor();
const serviceItemState = ref({
showDrawer: false,
});
const customServiceState = new pageVisible({
add: "新增服务项目",
edit: "服务项目编辑",
view: "服务项目查看",
});
// 表单数据
const regisForm = defineModel<RegisForm>({
default: defaultRetail(),
});
const selectedOptions = ref([
regisForm.value.province,
regisForm.value.city,
regisForm.value.area,
]);
const pcaTextArrData = ref(pcaTextArr);
const props = withDefaults(
defineProps<{
add?: boolean;
type?: RegistrationType;
showServiceDelBtn?: boolean;
retailType?: number;
showList?: string[];
}>(),
{
add: false,
showServiceDelBtn: true,
type: "服务登记",
retailType: 0,
showList: () => [],
}
);
if (["零售登记", "服务登记"].includes(props.type)) {
regisForm.value.serviceItems = "";
regisForm.value.services = [];
}
const methods = {
addServiceItem() {
if (!regisForm.value.services?.length) {
regisForm.value.services = [];
}
serviceItemState.value.showDrawer = true;
},
removeProduct(row: Product) {
let index = regisForm.value.services?.findIndex(
(item: any) => item.name === row.name
);
regisForm.value.services = regisForm.value.services?.filter(
(_, i) => i !== index
);
},
customInput() {
customServiceState.show2LevelPage.value = true;
},
async confirmCustomService() {
if (!currentServiceItem.value.name)
return ElMessage.warning("服务项目名称不能为空!");
if (
currentServiceItem.value.price <= 0 ||
currentServiceItem.value.quantity <= 0
) {
await ElMessageBox.confirm(
"该商品售价或商品数量为0是否继续",
"提示",
{
type: "warning",
}
);
if (!regisForm.value.services) regisForm.value.services = [];
regisForm.value.services.push({ ...currentServiceItem.value });
customServiceState.show2LevelPage.value = false;
currentServiceItem.value = defaultServiceItem();
return;
}
await ElMessageBox.confirm("确定新增该条信息吗?", "提示", {
type: "warning",
});
if (!regisForm.value.services) regisForm.value.services = [];
regisForm.value.services.push({ ...currentServiceItem.value });
customServiceState.show2LevelPage.value = false;
currentServiceItem.value = defaultServiceItem();
},
pcaChange(data: { [key: string]: any }) {
regisForm.value.province = data[0];
regisForm.value.city = data[1];
regisForm.value.area = data[2];
},
};
onMounted(async () => {
if (
!["零售登记", "服务登记"].includes(props.type) &&
(regisForm.value.deceasedId || regisForm.value.id)
) {
let url =
"/deceased-retail/selected-service?deceasedId=" +
(regisForm.value.deceasedId || regisForm.value.id) +
"&retailType=" +
props.retailType;
if (regisForm.value.id && props.retailType === 2) {
url =
"/deceased-retail/selected-service?retailId=" +
regisForm.value.id +
"&retailType=" +
props.retailType;
}
if (["零售修改", "零售结算"].includes(props.type)) {
url =
"/deceased-retail/selected-service?retailId=" +
regisForm.value.id +
"&retailType=" +
props.retailType;
}
const selectedService = await api().get(url);
regisForm.value.services = selectedService.data.list;
}
watch(
() => regisForm.value.services,
(newVal) => {
if (newVal) {
regisForm.value.salesAmount = 0;
newVal?.forEach(
(item) => (regisForm.value.salesAmount += item.price * item.quantity)
);
if (props.type === "服务登记") {
regisForm.value.serviceItems = newVal
.map((item) => item.id)
.join(",");
}
}
},
{ deep: true }
);
regisForm.value = {
...regisForm.value,
handler: regisForm.value.handler || userStoreState.userInfor.name || "",
purchaseDate:
regisForm.value.purchaseDate ||
dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss"),
};
});
let currentServiceItem = ref<ServiceItemType>(defaultServiceItem());
async function addConfim() {
await ElMessageBox.confirm("确定新增该条信息吗?", "提示", {
type: "warning",
});
if (!regisForm.value.services) regisForm.value.services = [];
regisForm.value.services.push({ ...currentServiceItem.value });
// await ElMessageBox.confirm("确定新增该条信息吗?", "提示", {
// type: "warning",
// });
// let res = await api().post("/service-item/add", currentServiceItem.value);
// if (res.code === 200) {
// ElMessage.success("添加成功");
// customServiceState.show2LevelPage.value = false;
// } else {
// ElMessage.error(res.msg);
// }
}
function handleDialogClose() {
customServiceState.show2LevelPage.value = false;
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,353 @@
<template>
<div class="service-selection">
<!-- 标题 -->
<h2 class="title">服务项目选择</h2>
<!-- 搜索框 -->
<el-form>
<el-row :gutter="15" style="margin-top: 15px">
<el-col :span="8">
<el-form-item label="商品名称">
<el-input
v-model="searchKeyword"
placeholder="请输入商品名称"
clearable />
</el-form-item>
</el-col>
<el-col :span="16">
<el-form-item label="价格">
<el-input-number
v-model="price"
:min="0"
clearable
placeholder="请输入价格"
type="number"></el-input-number>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-divider style="margin: 8px 0; margin-bottom: 30px" />
<!-- 两列布局 -->
<div
class="content"
v-loading="lodaing"
element-loading-text="数据加载中....">
<!-- 左侧树结构 -->
<div class="left">
<el-tree
ref="tree"
:data="categoryTree"
:props="treeProps"
show-checkbox
check-strictly
@check="getCheckedKeys" />
</div>
<!-- 右侧商品列表 -->
<div class="right">
<div
v-for="(item, index) in categoryServiceList"
:key="index"
class="item">
<!-- 商品信息 -->
<div class="item-info">
<div class="item-name">商品名称{{ item.name }}</div>
<div class="item-group">
所属分类{{
categoryTreeAll.find((fitem) => fitem.id === item.parentId)
?.name || "无"
}}
</div>
<div class="item-price">
{{ item.price }} / {{ item.unit || "(暂无单位)" }}
</div>
</div>
<!-- 添加/删除按钮 -->
<div class="item-actions">
<!-- <el-button
v-if="!item.quantity || item.quantity === 0"
type="primary"
@click="addItem(item)">
<span style="color: #fff"> 添加</span>
</el-button>
<div v-else class="quantity-control">
<el-button type="danger" @click="removeItem(item)"></el-button>
<span class="quantity">{{ item.quantity }}</span>
<el-button type="primary" @click="addItem(item)"> </el-button>
</div> -->
<el-input-number
style="margin-top: 5px"
v-model="item.quantity"
:min="0"
@change="methods.quantityChange(item)" />
</div>
</div>
</div>
</div>
<!-- 底部悬浮框 -->
<div class="footer">
<div class="footer-item">
<span>总金额</span>
<span
><span class="red-font">{{ totalAmount }} </span></span
>
</div>
<!-- <div class="footer-item">
<span>减免金额</span>
<span
><span class="green-font">
{{ discountAmount }}
</span>
</span
>
</div>
<div class="footer-item">
<span>实收金额</span>
<span
><span class="red-font">
{{ actualAmount }}
</span>
</span
>
</div> -->
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, watch } from "vue";
import api from "@/lib/request";
const emits = defineEmits(["selectedProduct"]);
const products = defineModel<Product[]>();
// 搜索关键词
const searchKeyword = ref("");
const price = ref(0);
const lodaing = ref(false);
// 分类树数据
const categoryTree = ref([]);
const categoryTreeAll = ref([]);
const categoryServiceAll = ref<Product[]>([]);
const selectedCategory = ref([]);
const tree = ref();
const categoryServiceList = computed(() => {
let resData: Product[] = [];
selectedCategory.value.forEach((item) => {
let findData = categoryServiceAll.value.filter(
(fitem) => fitem.parentId === item.id
);
if (findData) {
resData = [...resData, ...findData];
}
});
let res = selectedCategory.value.length ? resData : categoryServiceAll.value;
if (searchKeyword.value) {
res = res.filter((item) => item.name.includes(searchKeyword.value));
let includeCategory = categoryTreeAll.value.filter((category) =>
category.name.includes(searchKeyword.value)
);
categoryServiceAll.value.forEach((item) => {
includeCategory.forEach((iitem) => {
if (
item.parentId === iitem.id &&
!res.find((fitem) => fitem.name === item.name)
) {
res.unshift(item);
}
});
});
}
if (price.value && price.value !== 0) {
res = res.filter((fitem) => Number(fitem.price) === price.value);
}
return res;
});
// 树结构配置
const treeProps = {
children: "children",
label: "name",
};
const syncProductsWithCategoryServiceAll = () => {
if (!products.value) return; // 防止 products 为空时报错
categoryServiceAll.value.forEach((item) => {
const findItem = products.value?.find(
(newItem) => newItem.name === item.name
);
if (findItem) {
item.quantity = findItem.quantity;
} else {
item.quantity = 0;
}
});
};
watch(
() => products.value,
(newVal) => {
syncProductsWithCategoryServiceAll();
},
{
deep: true,
immediate: true,
}
);
// 总金额
const totalAmount = computed(() => {
return categoryServiceAll.value.reduce(
(sum, item) => sum + item.price * (item.quantity || 0),
0
);
});
const methods = {
quantityChange(item: any) {
let findData = products.value?.find(
(findItem) => findItem.name === item.name
);
if (!findData) {
products.value?.push(item);
} else {
findData.quantity = item.quantity;
}
},
};
const getCheckedKeys = (data: any) => {
selectedCategory.value = tree.value.getCheckedNodes();
};
onMounted(async () => {
lodaing.value = true;
let treeList = await api().get("/service-category/list");
let allTreeList = await api().get("/service-category/list", {
params: { all: true },
});
let allService = await api().get("/service-item/list", {
params: { all: true },
});
categoryTree.value = treeList.data.list;
categoryServiceAll.value = allService.data.list;
categoryTreeAll.value = allTreeList.data.list;
syncProductsWithCategoryServiceAll();
lodaing.value = false;
});
</script>
<style lang="scss" scoped>
.service-selection {
position: relative;
overflow: hidden;
.title {
font-size: 18px;
font-weight: bold;
}
.search-box {
margin-bottom: 20px;
width: 50%;
}
.left {
width: 200px;
margin-right: 20px;
}
.right {
flex: 1;
overflow-y: auto;
padding-bottom: 30px;
max-height: calc(100vh - 150px);
flex-wrap: wrap;
display: flex;
width: 100%;
justify-content: space-between;
align-items: flex-start;
}
.content {
display: flex;
justify-content: space-between;
.item {
display: flex;
flex-wrap: wrap;
width: calc(50% - 10px);
align-items: center;
margin-bottom: 10px;
padding: 10px;
border: 1px solid #eee;
border-radius: 4px;
flex-direction: column;
// .item-image {
// width: 100px;
// height: 100px;
// margin-right: 10px;
// }
.item-info {
flex: 1;
}
.item-name {
font-weight: bold;
}
.item-category,
.item-group,
.item-price {
font-size: 12px;
color: #666;
}
.item-actions {
margin-left: 10px;
}
}
}
}
.quantity-control {
display: flex;
align-items: center;
}
.quantity {
margin: 0 10px;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
width: 50%;
z-index: 10;
background: #fff;
padding: 10px 20px;
border-top: 1px solid #eee;
display: flex;
justify-content: space-around;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
.footer-item {
font-size: 14px;
}
.red-font,
.green-font {
font-size: 18px;
color: #f04b22;
font-weight: bold;
padding-right: 8px;
}
.green-font {
color: #67c23a;
}
}
</style>

View File

@@ -0,0 +1,307 @@
<template>
<div>
<inforCard title="逝者信息">
<el-form :model="checkoutForm" label-width="120px" disabled>
<el-row :gutter="20" v-if="deceased.deceased">
<el-col :span="8">
<el-form-item label="姓名">
<el-input
:prefix-icon="User"
v-model="deceased.deceased.name"
disabled
placeholder="姓名">
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="性别">
<el-radio-group v-model="deceased.deceased.gender" disabled>
<el-radio-button value="男" label="男"></el-radio-button>
<el-radio-button value="女" label="女"></el-radio-button>
</el-radio-group> </el-form-item
></el-col>
<el-col :span="8">
<el-form-item label="证件号码" prop="idNumber">
<el-input v-model="deceased.deceased.idNumber" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="年龄">
<el-input
v-model="deceased.deceased.age"
disabled
placeholder="年龄">
</el-input> </el-form-item
></el-col>
<el-col :span="8">
<el-form-item label="购买人名" prop="buyer">
<el-input v-model="deceased.deceased.familyName" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="购买人电话" prop="buyer">
<el-input v-model="deceased.deceased.familyPhone" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="购买日期" prop="purchaseDate">
<el-date-picker
v-model="deceased.purchaseDate"
type="datetime"
format="YYYY-MM-DD: HH:mm:ss" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="销售金额" prop="salesAmount">
<el-input v-model.number="deceased.salesAmount" type="number">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" v-else>
<!-- <el-col :span="8">
<el-form-item label="姓名">
<el-input
:prefix-icon="User"
v-model="deceased.name"
disabled
placeholder="姓名">
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="性别">
<el-radio-group v-model="deceased.gender" disabled>
<el-radio-button value="男" label="男"></el-radio-button>
<el-radio-button value="女" label="女"></el-radio-button>
</el-radio-group> </el-form-item
></el-col>
<el-col :span="8">
<el-form-item label="证件号码" prop="idNumber">
<el-input v-model="deceased.idNumber" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="年龄">
<el-input v-model="deceased.age" disabled placeholder="年龄">
</el-input> </el-form-item
></el-col> -->
<el-col :span="8">
<el-form-item label="购买人姓名" prop="buyer">
<el-input v-model="deceased.familyName" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="购买人电话" prop="buyer">
<el-input v-model="deceased.familyPhone" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="购买日期" prop="purchaseDate">
<el-date-picker
v-model="deceased.purchaseDate"
type="datetime"
format="YYYY-MM-DD: HH:mm:ss" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="销售金额" prop="salesAmount">
<el-input v-model.number="deceased.salesAmount" type="number">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
</inforCard>
<inforCard title="已选服务项目">
<el-table
:data="servicesList"
style="max-height: 160px; overflow-y: auto">
<el-table-column type="index" label="序号" width="80" />
<el-table-column prop="name" label="项目名称" />
<el-table-column prop="quantity" label="数量" />
<el-table-column prop="unit" label="单位" />
<el-table-column prop="price" label="单价">
<template #default="{ row }"> {{ row.price }} </template>
</el-table-column>
<el-table-column prop="price" label="总金额">
<template #default="{ row }">
{{ row.price * row.quantity }}
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" />
</el-table>
</inforCard>
<inforCard title="结账列表">
<el-form
ref="formRef"
:model="checkoutForm"
label-width="120px"
label-position="right">
<el-row :gutter="20">
<!-- 第一行 -->
<el-col :span="8">
<el-form-item label="结账日期" prop="checkoutDate">
<el-date-picker
v-model="checkoutForm.checkoutDate"
type="date"
placeholder="结账日期"
format="YYYY-MM-DD"
disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="结算日期" prop="settlementDate">
<el-date-picker
v-model="checkoutForm.settlementDate"
type="date"
placeholder="选择日期"
format="YYYY-MM-DD" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="经办人" prop="handler">
<el-input
disabled
v-model="checkoutForm.handler"
placeholder="经办人" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="现金金额" prop="cash">
<el-input
v-model.number="checkoutForm.cashAmount"
type="number"
:min="0"
@input="caleValue('cashAmount')">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="银联" prop="unionPay">
<el-input
v-model.number="checkoutForm.unionPayAmount"
type="number"
:min="0"
@input="caleValue('unionPayAmount')">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="银行卡" prop="cardPay">
<el-input
v-model.number="checkoutForm.cardAmount"
type="number"
:min="0"
@input="caleValue('cardAmount')">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="对公" prop="transfer">
<el-input
v-model.number="checkoutForm.publicTransferAmount"
type="number"
:min="0"
@input="caleValue('publicTransferAmount')">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="车间支付" prop="transfer">
<el-input
v-model.number="checkoutForm.workshopPayment"
type="number"
:min="0"
@input="caleValue('workshopPayment')">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
</inforCard>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, watch } from "vue";
// @ts-ignore
import { User } from "@element-plus/icons-vue";
import dayjs from "dayjs";
import { userInfor } from "@/store/user/user";
import { defaultRetail } from "@/defaultForm/defaultRetail";
import { defaultPaymentForm } from "@/defaultForm/defaultPaymentForm";
import request from "@/lib/request";
const props = withDefaults(
defineProps<{
executeType?: string;
deceased: RegisForm;
}>(),
{
deceased: () => {
return {
...defaultRetail(),
deceased: {},
};
},
}
);
const emit = defineEmits(["updateData"]);
const userInforStore = userInfor().userInfor;
let checkoutForm = defineModel({ default: defaultPaymentForm() });
let servicesList = ref([]);
onMounted(() => {
checkoutForm.value.handler =
checkoutForm.value.handler || (userInforStore.name as string);
request()
.get(
"/deceased-retail/selected-service?retailId=" +
props.deceased.id +
"&retailType=" +
props.deceased.retailType
)
.then((res) => {
servicesList.value = res.data?.list || [];
});
});
let keys = [
"cashAmount",
"unionPayAmount",
"cardAmount",
"publicTransferAmount",
"workshopPayment",
];
function caleValue(keyVal: string) {
let total = 0;
let currentVal = checkoutForm.value[keyVal];
keys.forEach((key) => {
if (keyVal !== key) {
let tempVal = Number(checkoutForm.value[key]).toFixed(2);
total = Number(total) + Number(tempVal);
}
});
let salesAmount = Number(props.deceased.salesAmount);
if (salesAmount - (total + currentVal) < 0) {
checkoutForm.value[keyVal] = salesAmount - total;
}
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,643 @@
<template>
<div>
<el-row>
<baseTableHeader
title="零售结算"
@resetSearch="methods.resetSearch"
@search="methods.search">
<template #content>
<el-form :model="searchForm" label-position="right">
<el-row :gutter="12">
<el-col :span="6">
<el-form-item label="逝者姓名">
<el-input
v-model="searchForm.deceased.name"
placeholder="请输入逝者姓名"></el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="购买人姓名">
<el-input
v-model="searchForm.retail.familyName"
placeholder="请输入购买人姓名"></el-input> </el-form-item
></el-col>
<el-col :span="6">
<el-form-item label="引导员">
<GuideList
v-model="
searchForm.retail.guide
"></GuideList> </el-form-item
></el-col>
<el-col :span="8">
<el-form-item label="购买日期">
<el-date-picker
v-model="searchForm.retail.purchaseDate"
type="datetimerange"
range-separator="至"
start-placeholder="选择日期"
end-placeholder="选择日期"
@change="dataChange" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
</baseTableHeader>
</el-row>
<el-card style="margin-top: 8px">
<div style="margin-bottom: 15px">
<el-button
size="small"
type="primary"
@click="methods.add"
v-if="route.path === '/noDepartedSaint'">
<template #icon>
<el-icon>
<CirclePlus />
</el-icon> </template
>新增</el-button
>
</div>
<div>
<base-table
:option="tableOption"
ref="table"
border
show-summary
:summary-method="getSummaries">
<template #toolsBar>
<el-radio-group
v-model="searchForm.retail.retailType"
size="small"
style="margin-left: 15px">
<el-radio-button label="逝者零售" :value="1" />
<el-radio-button label="无逝者零售" :value="2" />
</el-radio-group>
</template>
<template #colunm>
<el-table-column
type="index"
width="80"
fixed="left"
align="center"></el-table-column>
<el-table-column
prop="deceased.name"
label="逝者姓名"
width="100"
fixed="left"
v-if="searchForm.retail.retailType === 1"
align="center">
</el-table-column>
<el-table-column
prop="name"
label="结账"
width="100"
fixed="left"
align="center">
<template #default="{ row }">
<el-tag
:type="
row.retailState === 0
? 'danger'
: row.retailState === 1
? 'success'
: 'info'
">
{{
row.retailState === 0
? "未结账"
: row.retailState === 1
? "已结账"
: "未知"
}}
</el-tag>
</template>
</el-table-column>
<el-table-column label="购买人" align="center" fixed="left">
<template #default="{ row }">
<span>{{ row.familyName || row.deceased?.name }}</span>
</template>
</el-table-column>
<el-table-column
prop="familyPhone"
label="购买人电话"
width="120"
align="center">
<template #default="{ row }">
<span>{{ row.deceased?.familyPhone || row.familyPhone }}</span>
</template>
</el-table-column>
<el-table-column
prop="purchaseDate"
label="购买日期"
width="200"
align="center" />
<el-table-column
prop="checkoutDate"
label="结算日期"
width="200"
align="center">
<template #default="{ row }">
<span v-if="row.retailState === 1">
{{ row.checkoutDate }}
</span>
<span v-else>--</span>
</template>
</el-table-column>
<el-table-column prop="handler" label="经办人" align="center" />
<el-table-column
width="100"
prop="salesAmount"
label="销售金额"
align="center" />
<el-table-column
label="现金支付"
prop="payment.cashAmount"
width="100"
align="center">
<template #default="{ row }">
{{ row.payment?.cashAmount || "0.00" }} 元
</template>
</el-table-column>
<el-table-column
prop="payment.unionPayAmount"
label="银联支付"
width="100"
align="center">
<template #default="{ row }">
{{ row.payment?.unionPayAmount || "0.00" }} 元
</template>
</el-table-column>
<el-table-column
prop="payment.cardAmount"
label="刷卡金额"
width="100"
align="center">
<template #default="{ row }">
{{ row.payment?.cardAmount || "0.00" }} 元
</template>
</el-table-column>
<el-table-column
prop="payment.publicTransferAmount"
label="对公转账"
width="100"
align="center">
<template #default="{ row }">
{{ row.payment?.publicTransferAmount || "0.00" }} 元
</template>
</el-table-column>
<el-table-column
prop="payment.workshopPayment"
label="车间支付"
width="100"
align="center">
<template #default="{ row }">
{{ row.payment?.workshopPayment || "0.00" }} 元
</template>
</el-table-column>
<el-table-column prop="guide" label="引导员" align="center" />
<el-table-column
width="300"
label="操作"
align="center"
fixed="right">
<template #default="{ row }">
<el-row align="middle" justify="center">
<el-button
size="small"
type="primary"
v-if="row.retailState === 1"
@click="methods.hanlderView(row)">
查看</el-button
>
<el-button
type="primary"
size="small"
@click="methods.checkout(row)"
v-if="row.retailState === 0"
>结账</el-button
>
<el-button
v-if="row.retailState === 1"
size="small"
type="default"
@click="methods.cancel(row)">
作废
</el-button>
<el-button @click="methods.viewProduct(row)" size="small">
零售清单</el-button
>
<el-button @click="methods.print(row)" size="small"
>打印</el-button
>
</el-row>
</template>
</el-table-column>
</template>
</base-table>
</div>
</el-card>
<retailList v-model="showDialog" :option="retailListTable"></retailList>
<baseDialog
v-model="retailRegisState.showDialog"
top="50px"
title="零售结算">
<retailRegis
v-model="regisForm"
:retailType="searchForm.retail.retailType"
:showServiceDelBtn="handlerType !== 'view'"
:add="handlerType === 'add'"
type="零售结算"></retailRegis>
<template #footer>
<el-row justify="center" align="middle" style="margin-top: 15px">
<el-button type="danger" @click="methods.close">关闭</el-button>
</el-row>
</template>
</baseDialog>
<base-curd-dialog
v-model="pageVisibleState.show2LevelPage.value"
:title="pageVisibleState.dialogTitle.value"
width="70%"
:before-close="methods.handleDialogClose"
@addConfim="methods.addConfim"
@closeConfirm="methods.handleDialogClose"
confirmText="结账"
destroy-on-close
:showPageType="pageVisibleState.showPageType">
<template #content>
<checkoutAddOrEdit
:executeType="pageVisibleState.executeType.value"
:deceased="currentData"
v-model="currentPayment">
</checkoutAddOrEdit>
</template>
<template #footer>
<el-button type="primary" @click="methods.addConfim"
>确定结账</el-button
></template
>
</base-curd-dialog>
<printServicesPage
v-model="showPrint"
:deceased="deceased"
:serviceUrl="serviceUrl">
</printServicesPage>
<base-dialog
v-model="cancelState.showDialog"
title="账单作废"
width="50%"
@closeConfirm="methods.cancleClose">
<el-form :model="cancelForm" label-width="120px" style="padding: 20px">
<el-row :gutter="20">
<el-col :span="12">
<!-- 注销申请人 -->
<el-form-item label="作废申请人">
<el-input disabled v-model="cancelForm.cancelPerson" />
</el-form-item>
</el-col>
<el-col :span="12">
<!-- 注销日期 -->
<el-form-item label="作废日期">
<el-date-picker
disabled
v-model="cancelForm.cancelDate"
type="date" />
</el-form-item>
</el-col>
<el-col :span="24">
<!-- 注销原因 -->
<el-form-item label="作废原因">
<el-input
v-model="cancelForm.cancelReason"
type="textarea"
placeholder="请输入作废原因" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="flex-center">
<el-button type="primary" @click="methods.submitCancel"
>作废</el-button
>
<el-button @click="methods.resetCancel">取消</el-button>
</div>
</template>
</base-dialog>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, onMounted, watch, nextTick } from "vue";
import { dayjs, ElMessage, ElMessageBox } from "element-plus";
import { resetParams } from "@/util/globalMethods";
import { globalState } from "@/store";
import retailRegis from "../publicComponents/retailRegis.vue";
import api from "@/lib/request";
import { tableOptionType } from "@/types/table";
import { useRoute } from "vue-router";
import pageVisible from "@/components/curdDialog/pageVisibleState";
import { defaultPaymentForm } from "@/defaultForm/defaultPaymentForm";
import checkoutAddOrEdit from "./page/checkoutAddOrEdit.vue";
import printServicesPage from "@/components/printPage/printServicesPage.vue";
import { defaultRetail } from "@/defaultForm/defaultRetail";
import { userInfor } from "@/store/user/user";
import GuideList from "@/components/guideList/guideList.vue";
const pageVisibleState = new pageVisible({
add: "结账登记",
edit: "登记修改",
view: "查看登记",
});
let retailListTable = ref<tableOptionType>({
url: "/deceased-retail/selected-service",
searchUrl: "",
searchParams: {},
executeType: "list",
});
const retailRegisState = ref({
showDialog: false,
});
let currentRetail = ref<RegisForm>(defaultRetail());
let currentInfor = ref(undefined);
let showDialog = ref(false);
let showPrint = ref(false);
let deceased = ref();
let serviceUrl = ref();
const handlerType = ref("");
const searchForm = ref({
retail: {
guide: "", // 引导员
startDate: "",
endDate: "",
purchaseDate: ["", ""],
retailType: 1,
familyName: "",
},
deceased: {
name: "", // 逝者姓名
familyName: "",
},
});
let currentPayment = ref<PaymentForm>(defaultPaymentForm());
let currentData = ref();
const route = useRoute();
const regisForm = ref<RegisForm>();
const cancelState = ref({
showDialog: false,
});
const globaUser = userInfor().userInfor;
const cancelForm = ref({
cancelPerson: globaUser.name,
cancelDate: dayjs().format(),
cancelReason: "",
});
let table = ref();
let tableOption = ref<tableOptionType>({
url: "/deceased-retail/list?retailType=" + searchForm.value.retail.retailType,
searchUrl: "/deceased-retail/query",
searchParams: searchForm.value,
executeType: "list",
});
const methods = {
add() {
retailRegisState.value.showDialog = true;
handlerType.value = "add";
},
cancel(row: RegisForm) {
currentRetail.value = row;
cancelState.value.showDialog = true;
},
viewProduct(row: RegisForm) {
retailListTable.value.url =
"/deceased-retail/selected-service?retailId=" + row.id;
showDialog.value = true;
},
drawerClose() {
currentInfor.value = undefined;
showDialog.value = false;
},
async checkout(row: RegisForm) {
currentData.value = row;
pageVisibleState.dialogTitle.value = "零售结算";
pageVisibleState.show2LevelPage.value = true;
},
async hanlderView(row: RegisForm) {
// await ElMessageBox.confirm("确定结账吗?");
regisForm.value = row;
retailRegisState.value.showDialog = true;
handlerType.value = "view";
pageVisibleState.dialogTitle.value = "零售查看";
},
close() {
retailRegisState.value.showDialog = false;
},
async cancle() {
await ElMessageBox.confirm("确定作废该条吗?", { type: "error" });
retailRegisState.value.showDialog = false;
ElMessage.success("作废成功!");
},
print(row: RegisForm) {
deceased.value = row;
serviceUrl.value = `/deceased-retail/selected-service?retailType=${searchForm.value.retail.retailType}&retailId=${row.id}`;
nextTick(() => {
showPrint.value = true;
});
},
search() {
table.value.methods.setDataType("search");
},
resetSearch() {
searchForm.value = {
retail: {
guide: "", // 引导员
startDate: "",
endDate: "",
purchaseDate: ["", ""],
retailType: 1,
familyName: "",
},
deceased: {
name: "", // 逝者姓名
familyName: "",
},
};
tableOption.value.searchParams = searchForm.value;
nextTick(() => {
table.value.methods.setDataType("search");
});
},
async addConfim() {
await ElMessageBox.confirm("确定结账吗?", { type: "warning" });
let sendData = {
deceased: {
...currentData.value,
},
currentPayment: {
...currentPayment.value,
},
id: currentData.value.id,
};
let url = "/checkout/deceasedCheckout";
const res = await api().post(url, sendData);
if (res.code === 200) {
ElMessage.success("结账成功!");
pageVisibleState.show2LevelPage.value = false;
nextTick(() => {
table.value.methods.setDataType("search");
currentPayment.value = defaultPaymentForm();
currentData.value = undefined;
});
} else {
ElMessage.error(res.msg);
}
},
handleDialogClose() {
pageVisibleState.show2LevelPage.value = false;
currentData.value = undefined;
currentPayment.value = defaultPaymentForm();
},
cancleClose() {
cancelForm.value = {
cancelPerson: globaUser.name,
cancelDate: dayjs().format(),
cancelReason: "",
};
},
async submitCancel() {
if (!cancelForm.value.cancelReason) {
return ElMessage.error("请输入作废原因");
}
await ElMessageBox.confirm("确定作废吗?", { type: "warning" });
let sendData = {
deceasedRetailId: currentRetail.value.id,
cancelForm: { ...cancelForm.value },
cancelType: 0,
};
api()
.post("/cancel/cancel", sendData)
.then((res) => {
if (res.code === 200) {
ElMessage.success("作废提交成功!");
methods.resetCancel();
cancelForm.value = {
cancelPerson: globaUser.name,
cancelDate: dayjs().format(),
cancelReason: "",
};
table.value.methods.setDataType("search");
} else {
ElMessage.error(res.msg);
}
});
},
resetCancel() {
cancelForm.value = {
cancelPerson: globaUser.name,
cancelDate: dayjs().format(),
cancelReason: "",
};
cancelState.value.showDialog = false;
},
};
watch(
() => searchForm.value.retail.retailType,
() => {
table.value.methods.setDataType("search");
}
);
function dataChange(val: Date[]) {
searchForm.value.retail.startDate = val[0];
searchForm.value.retail.endDate = val[1];
}
const getSummaries = (param: { columns: any[]; data: any[] }) => {
const { columns, data } = param;
const sums: any[] = [];
// 需要统计的字段列表
const sumKeys = [
"salesAmount",
"payment.cashAmount",
"payment.unionPayAmount",
"payment.cardAmount",
"payment.publicTransferAmount",
"payment.workshopPayment",
];
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = "合计";
return;
}
if (sumKeys.includes(column.property)) {
const values = data.map((item) => {
const keys = column.property.split(".");
let value = item;
for (const key of keys) {
value = value?.[key] || 0;
}
return Number(value) || 0;
});
if (!values.every((value) => isNaN(value))) {
const sum = values.reduce((prev, curr) => {
return prev + curr;
}, 0);
sums[index] = `${sum.toFixed(2)}`;
} else {
sums[index] = "0.00 元";
}
} else {
sums[index] = "";
}
});
return sums;
};
</script>
<style lang="scss" scoped>
/* 添加汇总行样式 */
:deep(.el-table__footer) {
.cell {
font-weight: 600;
color: #606266;
}
td {
background-color: #f5f7fa !important;
}
}
</style>

View File

@@ -0,0 +1,460 @@
<template>
<div>
<el-row>
<baseTableHeader
title="殡仪服务"
@resetSearch="resetSearch"
@search="search">
<template #content>
<el-form
:model="searchForm"
:inline="true"
label-position="right"
label-width="80px">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="逝者姓名">
<el-input
v-model="searchForm.name"
placeholder="请输入逝者姓名"></el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="性别">
<el-radio-group v-model="searchForm.gender">
<el-radio-button value="男" label="男"></el-radio-button>
<el-radio-button value="女" label="女"></el-radio-button>
</el-radio-group> </el-form-item
></el-col>
<el-col :span="6">
<el-form-item label="购买人">
<el-input
v-model="searchForm.familyName"
placeholder="请输入购买人姓名"></el-input> </el-form-item
></el-col>
<el-col :span="6">
<el-form-item label="购买人手机号">
<el-input
v-model="searchForm.familyPhone"
placeholder="请输入购买人手机号"></el-input> </el-form-item
></el-col>
<el-col :span="6"
><el-form-item label="引导员">
<GuideList
v-model="searchForm.guide"></GuideList> </el-form-item
></el-col>
<el-col :span="6">
<el-form-item label="录单日期">
<el-date-picker
v-model="searchForm.createDate"
type="date"
value-format="YYYY-MM-DD"
placeholder="录单日期" /> </el-form-item
></el-col>
<!-- <el-col :span="6">
<el-form-item label="结账日期">
<el-date-picker
v-model="searchForm.checkoutDate"
type="date"
placeholder="结账日期" /></el-form-item
></el-col>
<el-col :span="6">
<el-form-item label="录单日期">
<el-date-picker
v-model="searchForm.purchaseDate"
type="datetime"
placeholder="录单日期" /></el-form-item
></el-col> -->
</el-row>
</el-form>
</template>
</baseTableHeader>
</el-row>
<el-card style="margin-top: 8px">
<base-table :option="tableOption" ref="table" border>
<template #toolsBar>
<el-button size="small" type="primary" @click="methods.handleService">
服务办理</el-button
>
</template>
<template #colunm>
<el-table-column
type="index"
width="60"
label="序号"
align="center" />
<el-table-column
prop="deceased.name"
label="逝者姓名"
align="center" />
<el-table-column prop="deceased.gender" label="性别" align="center" />
<el-table-column
prop="deceased.age"
label="年龄"
width="80"
align="center" />
<el-table-column
prop="deceased.familyName"
label="购买人姓名"
align="center" />
<el-table-column
prop="deceased.familyPhone"
label="购买人手机号"
width="180"
align="center" />
<el-table-column prop="deceased.name" label="购买人" align="center" />
<el-table-column
prop="retail.purchaseDate"
label="录单日期"
width="180"
align="center" />
<el-table-column prop="handler" label="经办人" align="center" />
<el-table-column
prop="retail.salesAmount"
label="销售金额"
align="center"
width="180">
<template #default="{ row }">
{{ row.retail.salesAmount || 0 }}
</template>
</el-table-column>
<el-table-column
prop="guide"
label="引导员"
width="120"
align="center" />
<el-table-column
label="操作"
align="center"
width="350"
fixed="right">
<template #default="{ row }">
<el-button
size="small"
type="primary"
@click="methods.addRetailRegis(row)">
零售登记
</el-button>
<el-button
v-if="row.retail?.retailState !== 1"
size="small"
type="primary"
@click="methods.update(row)">
修改</el-button
>
<el-button @click="methods.viewProduct(row)" size="small">
服务清单</el-button
>
<el-button size="small" type="default" @click="methods.print(row)"
>打印</el-button
>
</template>
</el-table-column>
</template>
</base-table>
</el-card>
<baseDialog
v-model="retailRegisState.showAddRetial"
top="50px"
:title="retailTitle"
@close="methods.addRetialClose">
<retailRegis
v-model="retailRegisForm"
:type="retailRegisState.type"
:showList="['逝者信息']"></retailRegis>
<template #footer>
<el-row justify="center" align="middle" style="margin-top: 15px">
<el-button type="primary" @click="methods.addConfim">保存</el-button>
<el-button type="danger" @click="methods.addClose">关闭</el-button>
</el-row>
</template>
</baseDialog>
<baseDialog
v-model="retailRegisState.showUpdateDialog"
top="50px"
:title="retailRegisState.title"
@close="methods.resetRegisForm">
<div
v-loading="retailRegisState.showLoding"
element-loading-text="正在处理...">
<retailRegis
v-model="regisForm"
:type="retailRegisState.type"
:showList="['逝者信息']"></retailRegis>
<el-row :justify="'center'">
<el-button type="primary" @click="methods.addConfim">确定</el-button>
<el-button type="warning" @click="methods.updateClose"
>关闭</el-button
>
</el-row>
</div>
</baseDialog>
<PrintRetailPage
v-model="showPrint"
:deceased="deceased"
:serviceUrl="serviceUrl">
</PrintRetailPage>
<retailList
v-model="showRetailList"
title="服务清单"
:option="retailListTable"></retailList>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, onMounted, watch } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { resetParams } from "@/util/globalMethods";
import retailRegis from "../funeralRetail/publicComponents/retailRegis.vue";
import api from "@/lib/request";
import { tableOptionType } from "@/types/table";
import { defaultRetail } from "@/defaultForm/defaultRetail";
import PrintRetailPage from "@/components/printPage/printRetailPage.vue";
import GuideList from "@/components/guideList/guideList.vue";
interface deceasedForm extends RegisForm {
deceased: RegisForm;
retailType: number;
}
const retailRegisState = ref({
showAddRetial: false,
title: "服务登记",
type: "服务登记" as RegistrationType,
showLoding: false,
showUpdateDialog: false,
});
const serviceState = ref({
showDialog: false,
});
let showRetailList = ref(false);
let retailTitle = ref("服务登记");
let showPrint = ref(false);
let deceased = ref();
let serviceUrl = ref();
let retailListTable = ref<tableOptionType>({
url: "/deceased-retail/selected-service",
searchUrl: "",
searchParams: {},
executeType: "list",
});
const searchForm = reactive({
name: "", // 逝者姓名
gender: "", // 性别
phone: "", // 手机号
guide: "", // 引导员
purchaseDate: "", // 购买日期
createDate: "",
familyName: "",
familyPhone: "",
});
const regisForm = ref<deceasedForm>(defaultRetail());
const retailRegisForm = ref<RegisForm>(defaultRetail());
let table = ref();
const tableOption = ref<tableOptionType>({
url: "/deceased/list", // 获取列表数据的接口
searchUrl: "/deceased/query", // 搜索接口
searchParams: searchForm, // 搜索表单数据
executeType: "list", // 默认执行类型
});
const methods = {
// 零售登记
addRetailRegis(item: RegisForm) {
retailRegisForm.value = {
...regisForm.value,
...item,
serviceItems: [],
services: [],
};
retailRegisState.value.title = "零售登记";
retailRegisState.value.type = "零售登记";
retailRegisState.value.showAddRetial = true;
retailTitle.value = "零售登记";
},
async print(row: any) {
deceased.value = row;
showPrint.value = true;
serviceUrl.value =
"/deceased-retail/selected-service?retailType=0&deceasedId=" + row.id;
},
drawerClose() {
showRetailList.value = false;
},
async viewProduct(row: RegisForm) {
retailListTable.value.url =
"/deceased-retail/selected-service?retailType=0&deceasedId=" + row.id;
showRetailList.value = true;
},
update(row: RegisForm) {
regisForm.value = row;
retailTitle.value = "服务修改";
retailRegisState.value.title = "服务修改";
retailRegisState.value.type = "服务修改";
retailRegisState.value.showUpdateDialog = true;
},
// 服务办理
handleService() {
retailRegisForm.value = defaultRetail();
serviceState.value.showDialog = true;
retailRegisState.value.showAddRetial = true;
retailRegisState.value.title = "服务登记";
retailRegisState.value.type = "服务登记";
retailTitle.value = "服务登记";
},
// 重置登记表单
resetRegisForm() {
regisForm.value = defaultRetail();
},
// 取消操作
cancle() {
retailRegisState.value.showAddRetial = false;
methods.resetRegisForm();
},
updateClose() {
retailRegisState.value.showUpdateDialog = false;
},
addClose() {
retailRegisState.value.showAddRetial = false;
regisForm.value = defaultRetail();
},
async confirmUpdate() {
await ElMessageBox.confirm("确定保存修改吗?", { type: "warning" });
let sendData = { ...regisForm.value };
api()
.post("/deceased-retail/update", sendData)
.then((res) => {
if (res.code === 200) {
ElMessage.success("修改成功");
table.value.methods.setDataType("list");
retailRegisState.value.showAddRetial = false;
regisForm.value = defaultRetail();
}
});
},
async addConfim() {
if (retailRegisState.value.type === "服务登记") {
if (!retailRegisForm.value.idNumber || !retailRegisForm.value.name) {
return ElMessage.error("逝者姓名和证件号码不能为空!");
}
}
let confirmContent = "";
if (retailRegisState.value.type === "服务登记")
confirmContent = "确定登记该服务吗?";
if (retailRegisState.value.type === "零售登记")
confirmContent = "确定登记该信息吗?";
if (retailRegisState.value.type === "服务修改")
confirmContent = "确定修改该信息吗?";
await ElMessageBox.confirm(confirmContent, "提示", {
type: "warning",
});
if (retailRegisState.value.type === "服务登记") {
let sendData = { ...retailRegisForm.value };
sendData.retailType = 0;
api()
.post("/deceased/add", sendData) // 修改为 Deceased 的接口
.then((res) => {
if (res.code === 200) {
ElMessage.success("新增成功!");
table.value.methods.setDataType("list");
retailRegisState.value.showAddRetial = false;
retailRegisState.value.showLoding = false;
methods.resetRegisForm();
} else {
ElMessage.error(res.msg);
}
});
}
if (retailRegisState.value.type === "零售登记") {
let sendData = { ...retailRegisForm.value };
sendData.retailType = 1;
sendData.deceasedId = sendData.id;
let url = "/deceased-retail/add?type=1";
api()
.post(url, sendData) // 修改为 Deceased 的接口
.then((res) => {
if (res.code === 200) {
ElMessage.success("新增成功!");
table.value.methods.setDataType("list");
retailRegisState.value.showAddRetial = false;
retailRegisState.value.showLoding = false;
methods.resetRegisForm();
} else {
ElMessage.error(res.msg);
}
});
}
if (retailRegisState.value.type === "服务修改") {
let sendData = { ...regisForm.value };
sendData.retailType = 0;
sendData.deceased.familyName = sendData.familyName;
sendData.deceased.familyPhone = sendData.familyPhone;
sendData.deceased.address = sendData.address;
sendData.deceased.area = sendData.area;
sendData.deceased.province = sendData.province;
sendData.deceased.city = sendData.city;
sendData.deceased.guide = sendData.guide;
api()
.post("/deceased-retail/updateRetail", sendData) // 修改为 Deceased 的接口
.then((res) => {
if (res.code === 200) {
ElMessage.success("修改成功!");
table.value.methods.setDataType("list");
retailRegisState.value.showAddRetial = false;
retailRegisState.value.showLoding = false;
retailRegisState.value.showUpdateDialog = false;
methods.resetRegisForm();
} else {
ElMessage.error(res.msg);
}
});
}
},
addRetialClose() {
retailRegisState.value.showAddRetial = false;
regisForm.value = defaultRetail();
},
};
onMounted(async () => {});
function search() {
table.value.methods.setDataType("search");
}
function resetSearch() {
resetParams(searchForm);
table.value.methods.setDataType("reset");
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,143 @@
<template>
<el-form
:model="user"
label-width="100px"
:inline="true"
label-suffix=""
v-loading="loading"
element-loading-text="正在初始化数据...">
<el-form-item label="姓名">
<el-input :prefix-icon="User" v-model="user.name" placeholder="姓名">
</el-input>
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="user.sex">
<el-radio value="男"></el-radio>
<el-radio value="女"></el-radio>
<el-radio value="保密">保密</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="电话">
<el-input
:prefix-icon="Iphone"
v-model="user.phone"
placeholder="电话"></el-input>
</el-form-item>
<el-form-item label="所属角色">
<el-select
v-model="user.role"
clearable
filterable
placeholder="请选择角色"
style="width: 230px">
<el-option
v-for="item in roleList"
:key="item.id"
:label="item.name"
:value="String(item.id)"></el-option>
</el-select>
</el-form-item>
<el-form-item label="生日">
<el-date-picker
format="YYYYH-MM-DD"
v-model="user.birthday"
type="date"
placeholder="生日"
size="large" />
</el-form-item>
<el-form-item label="所在区域">
<el-cascader
:options="pcaTextArrData"
v-model="selectedOptions"
@change="pcaChange"></el-cascader>
</el-form-item>
<el-form-item label="详细地址">
<el-input
:prefix-icon="Location"
v-model="user.address"
placeholder="请输入详细地址"></el-input>
</el-form-item>
<el-form-item label="账号状态">
<el-switch
v-model="user.userState"
:active-value="1"
:inactive-value="0"
active-text="启用"
width="65px"
inline-prompt
inactive-text="禁用"></el-switch>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { onMounted, ref, watch } from "vue";
import { userType } from "@/types/user";
// @ts-ignore
import { pcaTextArr } from "element-china-area-data";
import { Avatar, Location, Iphone, User } from "@element-plus/icons-vue";
import { roleType } from "@/types/role";
import { roleDataList } from "@/lib/api/publicApiList";
const pcaTextArrData = ref(pcaTextArr);
const selectedOptions = ref([]);
const props = defineProps<{
data?: userType;
executeType?: string;
}>();
const emit = defineEmits(["updateData"]);
let loading = ref(true);
let roleList = ref<roleType[]>();
let user = ref<userType>({
createDate: "",
name: "",
sex: "男",
phone: "",
userState: 1,
role: "",
birthday: "",
province: "",
city: "",
area: "",
address: "",
});
onMounted(async () => {
roleList.value = await roleDataList();
watch(
() => user,
(newVal) => {
emit("updateData", newVal);
},
{ deep: true }
);
loading.value = false;
});
if (props.executeType === "edit") {
user.value = { ...props.data };
selectedOptions.value = [
user.value.province as never,
user.value.city as never,
user.value.area as never,
];
}
function pcaChange(data: { [key: string]: any }) {
user.value.province = data[0];
user.value.city = data[1];
user.value.area = data[2];
}
</script>
<style lang="scss" scoped>
:deep(.el-input) {
width: 230px;
}
:deep(.el-form-item) {
margin-right: 0;
}
</style>

View File

@@ -0,0 +1,374 @@
<template>
<div>
<el-row>
<baseTableHeader
title="作废审核"
@resetSearch="resetSearch"
@search="search">
<template #content>
<el-form
:model="searchForm"
label-position="right"
label-width="90px">
<el-row :gutter="15">
<!-- 逝者姓名 -->
<el-col :span="8">
<el-form-item label="逝者姓名">
<el-input
v-model="searchForm.name"
placeholder="请输入逝者姓名" />
</el-form-item>
</el-col>
<!-- 作废申请人 -->
<el-col :span="8">
<el-form-item label="作废申请人" style="width: 100%">
<el-select
v-model="searchForm.cancelPerson"
placeholder="请选择申请人">
<el-option
v-for="item in guideOptions"
:key="item.value"
:label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
</el-col>
<!-- 结账时间 -->
<!-- <el-col :span="6">
<el-form-item label="结账时间">
<el-date-picker
value-format="YYYY-MM-DD"
v-model="searchForm.checkoutDate"
type="date"
placeholder="选择结账时间"
format="YYYY-MM-DD" />
</el-form-item>
</el-col> -->
<!-- 申请时间 -->
<el-col :span="8">
<el-form-item label="申请时间">
<el-date-picker
value-format="YYYY-MM-DD"
v-model="searchForm.cancelDate"
type="date"
placeholder="选择申请时间"
format="YYYY-MM-DD" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
</baseTableHeader>
</el-row>
<el-card style="margin-top: 8px">
<base-table :option="tableOption" ref="table" border>
<template #toolsBar>
<el-radio-group v-model="searchForm.examineState" size="small">
<el-radio-button :value="0" label="未处理"></el-radio-button>
<el-radio-button :value="1" label="已通过"></el-radio-button>
<!-- <el-radio-button :value="2" label="已拒绝"></el-radio-button> -->
</el-radio-group>
</template>
<template #colunm>
<!-- 序号 -->
<el-table-column
type="index"
label="序号"
width="80"
align="center" />
<!-- 状态 -->
<el-table-column label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag type="primary" v-if="row.examineState === 0"
>未审核</el-tag
>
<el-tag type="success" v-if="row.examineState === 1"
>已审核</el-tag
>
<el-tag type="success" v-if="row.examineState === 2"
>已拒绝</el-tag
>
</template>
</el-table-column>
<el-table-column label="逝者姓名" align="center">
<template #default="{ row }">
<span v-if="row.retail.retailType !== 2">
{{ row.deceased.name }}</span
>
<span v-else> {{ row.retail.familyName }}</span>
</template>
</el-table-column>
<el-table-column prop="deceased.gender" label="性别" align="center">
<template #default="{ row }">
<span v-if="row.retail.retailType !== 2">
{{ row.deceased.gender }}</span
>
<span v-else> {{ row.retail.gender || "" }}</span>
</template>
</el-table-column>
<el-table-column
prop="deceased.age"
label="年龄"
width="80"
align="center">
<template #default="{ row }">
<span v-if="row.retail.retailType !== 2">
{{ row.deceased.age }}</span
>
<span v-else> {{ row.retail.age || "" }}</span>
</template>
</el-table-column>
<el-table-column
prop="cancelReason"
label="作废原因"
width="200"
align="center" />
<el-table-column
prop="cancelPerson"
label="作废申请人"
align="center" />
<el-table-column
prop="cancelDate"
label="申请时间"
align="center"
width="180" />
<el-table-column
prop="retail.checkoutDate"
label="结账时间"
width="180"
align="center" />
<el-table-column
prop="payment.cashAmount"
label="现金支付"
width="100"
align="center">
<template #default="{ row }">
{{ row.payment?.cashAmount || "0.00" }}
</template>
</el-table-column>
<el-table-column
prop="payment.unionPayAmount"
label="银联支付"
width="100"
align="center">
<template #default="{ row }">
{{ row.payment?.unionPayAmount || "0.00" }}
</template>
</el-table-column>
<el-table-column
prop="payment.cardAmount"
label="刷卡金额"
width="100"
align="center">
<template #default="{ row }">
{{ row.payment?.cardAmount || "0.00" }}
</template>
</el-table-column>
<el-table-column
prop="payment.publicTransferAmount"
label="对公转账"
width="100"
align="center">
<template #default="{ row }">
{{ row.payment?.publicTransferAmount || "0.00" }}
</template>
</el-table-column>
<el-table-column
prop="payment.workshopPayment"
label="车间支付"
width="100"
align="center">
<template #default="{ row }">
{{ row.payment?.workshopPayment || "0.00" }}
</template>
</el-table-column>
<el-table-column
width="200"
label="操作"
align="center"
fixed="right">
<template #default="{ row }">
<el-button
size="small"
type="primary"
v-if="row.examineState === 0"
@click="methods.invalid(row)">
审核</el-button
>
<el-button
size="small"
type="primary"
v-if="row.examineState === 1"
@click="methods.view(row)">
查看</el-button
>
</template>
</el-table-column>
</template>
</base-table>
</el-card>
<baseDialog
v-model="invalidReviewState.showDialog"
top="50px"
:title="invalidReviewState.title"
@close="methods.resetRegisForm">
<div
v-loading="invalidReviewState.showLoding"
element-loading-text="正在处理...">
<reviewDetails v-model="currentData"></reviewDetails>
<el-row :justify="'center'">
<el-button
type="primary"
@click="methods.confirm"
v-if="invalidReviewState.title !== '作废查看'"
>确定</el-button
>
<el-button
type="primary"
v-if="invalidReviewState.title !== '作废查看'"
@click="methods.cancel"
>拒绝</el-button
>
<el-button type="danger" @click="methods.close">关闭</el-button>
</el-row>
</div>
</baseDialog>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, onMounted, watch } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { resetParams } from "@/util/globalMethods";
import { globalState } from "@/store";
import reviewDetails from "./reviewDetails.vue";
import { tableOptionType } from "@/types/table";
import { defaultRetail } from "@/defaultForm/defaultRetail";
import { defaultPaymentForm } from "@/defaultForm/defaultPaymentForm";
import api from "@/lib/request";
const invalidReviewState = ref({
showDialog: false,
title: "作废审核",
type: "服务登记" as RegistrationType,
showLoding: false,
});
const guideOptions = ref<guideOption[]>([]);
const searchForm = reactive({
examineState: 0, // 状态
name: "", // 逝者姓名
cancelPerson: "", // 作废申请人
checkoutDate: "", // 结账时间
cancelDate: "", // 申请时间
});
const currentData = ref({
payment: defaultPaymentForm(),
deceased: {},
retail: defaultRetail(),
});
const regisForm = ref<RegisForm>(defaultRetail());
let table = ref();
let tableOption = ref<tableOptionType>({
url: "/cancel/list",
searchUrl: "/cancel/query",
searchParams: searchForm,
executeType: "list",
});
const methods = {
resetRegisForm() {
regisForm.value = defaultRetail();
},
async confirm() {
let confirmContent = "确定【通过】该信息吗?";
await ElMessageBox.confirm(confirmContent, "提示", {
type: "warning",
});
let sendData = { ...currentData.value };
sendData.examineState = 1;
api()
.post("/cancel/examine", sendData)
.then((res) => {
if (res.code === 200) {
invalidReviewState.value.showDialog = false;
ElMessage.success("成功通过!");
table.value.methods.setDataType("list");
} else {
ElMessage.error(res.msg);
}
});
},
invalid(row: any) {
currentData.value = row;
invalidReviewState.value.showDialog = true;
invalidReviewState.value.title = "作废审核";
},
async cancel() {
let confirmContent = "确定【拒绝】该信息吗?";
await ElMessageBox.confirm(confirmContent, { type: "warning" });
let sendData = { ...currentData.value };
sendData.examineState = 2;
api()
.post("/cancel/examine", sendData)
.then((res) => {
if (res.code === 200) {
invalidReviewState.value.showDialog = false;
ElMessage.success("成功拒绝!");
table.value.methods.setDataType("list");
} else {
ElMessage.error(res.msg);
}
});
},
close() {
invalidReviewState.value.showDialog = false;
invalidReviewState.value.title = "";
methods.resetRegisForm();
},
view(row: any) {
invalidReviewState.value.showDialog = true;
currentData.value = row;
invalidReviewState.value.title = "作废查看";
},
};
api()
.get("public/guide")
.then((res) => {
guideOptions.value = res.data;
});
onMounted(async () => {});
watch(
() => searchForm.examineState,
(newData) => {
table.value.methods.setDataType("search");
}
);
function search() {
table.value.methods.setDataType("search");
}
function resetSearch() {
resetParams(searchForm);
table.value.methods.setDataType("reset");
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,259 @@
<template>
<el-form
style="max-height: 72vh; overflow-y: auto"
ref="formRef"
:model="formData"
label-width="120px"
label-position="right">
<inforCard title="逝者信息">
<el-form :model="formData" label-width="120px" disabled>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="姓名">
<el-input
v-model="formData.deceased.name"
disabled
placeholder="姓名">
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="性别">
<el-radio-group v-model="formData.deceased.gender" disabled>
<el-radio-button value="男" label="男"></el-radio-button>
<el-radio-button value="女" label="女"></el-radio-button>
</el-radio-group> </el-form-item
></el-col>
<el-col :span="8">
<el-form-item label="证件号码" prop="idNumber">
<el-input v-model="formData.deceased.idNumber" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="年龄">
<el-input
v-model="formData.deceased.age"
disabled
placeholder="年龄">
</el-input> </el-form-item
></el-col>
<el-col :span="8">
<el-form-item label="购买人姓名" prop="buyer">
<el-input v-model="formData.deceased.familyName" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="购买人电话" prop="buyer">
<el-input v-model="formData.deceased.familyPhone" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="购买日期" prop="purchaseDate">
<el-date-picker
v-model="formData.deceased.purchaseDate"
type="datetime"
format="YYYY-MM-DD: HH:mm:ss" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</inforCard>
<inforCard title="结账列表">
<el-form
ref="formRef"
:model="formData"
label-width="120px"
label-position="right">
<el-row :gutter="20">
<!-- 第一行 -->
<el-col :span="8">
<el-form-item label="结账日期" prop="checkoutDate">
<el-date-picker
v-model="formData.payment.checkoutDate"
type="date"
placeholder="结账日期"
format="YYYY-MM-DD"
disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="结算日期" prop="settlementDate">
<el-date-picker
v-model="formData.payment.settlementDate"
type="date"
disabled
placeholder="选择日期"
format="YYYY-MM-DD" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="经办人" prop="handler">
<el-input
disabled
v-model="formData.payment.handler"
placeholder="经办人" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="现金金额" prop="cash">
<el-input
disabled
v-model="formData.payment.cashAmount"
:min="0"
@change="caleValue('cashAmount')"
type="number">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="银联" prop="unionPay">
<el-input
disabled
v-model="formData.payment.unionPayAmount"
:min="0"
@change="caleValue('cashAmount')"
type="number">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="银行卡" prop="cardPay">
<el-input
disabled
v-model="formData.payment.cardAmount"
:min="0"
@change="caleValue('cashAmount')"
type="number">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="对公" prop="transfer">
<el-input
disabled
v-model="formData.payment.publicTransferAmount"
:min="0"
@change="caleValue('cashAmount')"
type="number">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="车间支付" prop="transfer">
<el-input
disabled
v-model="formData.payment.workshopPayment"
:min="0"
@change="caleValue('cashAmount')"
type="number">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
</inforCard>
<inforCard title="作废信息">
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="作废申请人">
<el-input
disabled
v-model="formData.cancelPerson"
placeholder="请输入作废申请人" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="作废时间">
<el-date-picker
disabled
v-model="formData.cancelDate"
type="datetime"
placeholder="选择作废时间" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="作废原因">
<el-input
disabled
v-model="formData.cancelReason"
type="textarea"
placeholder="请输入作废原因" />
</el-form-item>
</el-col>
</el-row>
</inforCard>
</el-form>
</template>
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import { defaultPaymentForm } from "@/defaultForm/defaultPaymentForm";
import { defaultRetail } from "@/defaultForm/defaultRetail";
const formRef = ref();
// 表单数据
const formData = defineModel<any>({
default: {
payment: defaultPaymentForm(),
deceased: {},
retail: defaultRetail(),
},
});
if (!formData.value.payment) {
formData.value.payment = defaultPaymentForm();
}
if (!formData.value.deceased) {
formData.value.deceased = { ...formData.value.retail };
}
const props = withDefaults(
defineProps<{
add?: boolean;
type?: "审核" | "申请";
}>(),
{
add: false,
type: "审核",
}
);
onMounted(() => {});
let keys = [
"cashAmount",
"unionPayAmount",
"cardAmount",
"publicTransferAmount",
"workshopPayment",
];
function caleValue(keyVal: string) {
let total = 0;
let currentVal = formData.value.payment[keyVal];
keys.forEach((key) => {
if (keyVal !== key) {
let tempVal = Number(formData.value.payment[key]).toFixed(2);
total = Number(total) + Number(tempVal);
}
});
let salesAmount = Number(formData.value.deceased.salesAmount);
if (salesAmount - (total + currentVal) < 0) {
formData.value[keyVal] = salesAmount - total;
}
}
</script>
<style lang="scss" scoped></style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 KiB

View File

@@ -0,0 +1,123 @@
<template>
<div
class="logon-main-content"
v-loading="loading"
:element-loading-text="'正在登录...'">
<div class="login">
<h2 class="title">欢迎使用德孝善延伸服务系统</h2>
<el-form :model="data.loginForm" class="login-form">
<el-form-item>
<el-input
v-model="data.loginForm.name"
placeholder="请输入账户"></el-input>
</el-form-item>
<el-form-item>
<el-input
v-model="data.loginForm.pwd"
type="password"
placeholder="请输入密码"></el-input>
</el-form-item>
<el-form-item label="">
<el-button class="login-btn" type="default" @click="login"
>登陆</el-button
>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, onBeforeMount, onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import { userInfor } from "@/store/user/user";
import MD5 from "js-md5";
import api from "@/lib/request";
import { ElMessage, ElMessageBox } from "element-plus";
const data = reactive({
name: "测试",
loginForm: {
name: "",
pwd: "",
},
});
let loading = ref(false);
const router = useRouter();
const userInforState = userInfor();
onBeforeMount(() => {});
onMounted(() => {});
function login() {
loading.value = true;
api()
.post("/login", {
name: data.loginForm.name,
pwd: MD5.md5(data.loginForm.pwd).toLocaleUpperCase(),
})
.then((res: any) => {
if (res.code === 200) {
userInforState.setLoginState(res.data.user);
ElMessage.success("登录成功!");
let userRouter: [] = res.data.user.routerMenue;
let noChildren = userRouter.find((item) => !item.children.length);
router.replace({ name: noChildren.name });
userInforState.setToken(res.data.token);
userInforState.setRefToken(res.data.refreshToken);
} else {
ElMessage.error(res.msg || res);
}
loading.value = false;
});
}
</script>
<style lang="scss" scoped>
.logon-main-content {
background-image: url("./img/login-bck.jpg");
background-size: 100% 100%;
background-repeat: no-repeat;
width: 100%;
height: 100vh;
.login {
width: 40%;
height: 60%;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
background-image: linear-gradient(135deg, #5efce8 10%, #736efe 100%);
.title {
margin: 30px 0;
font-size: 30px;
}
.copy-info {
text-align: center;
p {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
}
.login-form {
display: flex;
flex-direction: column;
align-content: center;
.login-btn {
width: 100%;
}
}
}
}
</style>

View File

@@ -0,0 +1,167 @@
<template>
<div>
<el-row class="width-100" justify="space-between">
<el-row :gutter="20" class="width-100">
<el-col :span="6">
<el-card>
<template #header>
<el-row :gutter="15" justify="space-between">
<el-col :span="12">
<h2>框架访问人数</h2>
</el-col>
<el-col :span="12">
<h1>100</h1>
</el-col>
</el-row>
</template>
今天的天气也很好呀
</el-card>
</el-col>
<el-col :span="6">
<el-card>
<template #header>
<el-row :gutter="15" justify="space-between">
<el-col :span="12">
<h2>留言总数</h2>
</el-col>
<el-col :span="12">
<h1>3205</h1>
</el-col>
</el-row>
</template>
每一天都有希望
</el-card>
</el-col>
<el-col :span="6">
<el-card>
<template #header>
<el-row :gutter="15" justify="space-between">
<el-col :span="12">
<h2>用户数量</h2>
</el-col>
<el-col :span="12">
<h1>421</h1>
</el-col>
</el-row>
</template>
期待美好的事情发生
</el-card>
</el-col>
<el-col :span="6">
<el-card>
<template #header>
<el-row :gutter="15" justify="space-between">
<el-col :span="12">
<h2>总文章数</h2>
</el-col>
<el-col :span="12">
<h1>421</h1>
</el-col>
</el-row>
</template>
明天将会发生什么呢
</el-card>
</el-col>
</el-row>
</el-row>
<el-row style="margin-top: 15px" :gutter="20" class="width-100">
<el-col :span="18" style="height: 500px">
<el-card class="height-100" body-style="height:100%">
<baseEcharts></baseEcharts>
</el-card>
</el-col>
<el-col :span="6">
<el-card style="height: 500px; overflow-y: auto">
<template #header> 更新动态 </template>
<p v-for="i in 20" :key="i">今天这里是第{{ i }}篇文章</p>
</el-card>
</el-col>
<el-col :span="24" style="margin-top: 15px">
<el-card>
<baseEcharts :option="lineStack" style="height: 500px"></baseEcharts>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script lang="ts" setup>
import { reactive } from "vue";
import baseEcharts from "@/components/baseEcharts/baseEcharts.vue";
const data = reactive({});
const lineStack = reactive({
title: {
text: "Stacked Line",
},
tooltip: {
trigger: "axis",
},
legend: {
data: ["Email", "Union Ads", "Video Ads", "Direct", "Search Engine"],
},
grid: {
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true,
},
toolbox: {
feature: {
saveAsImage: {},
},
},
xAxis: {
type: "category",
boundaryGap: false,
data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
},
yAxis: {
type: "value",
},
series: [
{
name: "Email",
type: "line",
stack: "Total",
data: [120, 132, 101, 134, 90, 230, 210],
},
{
name: "Union Ads",
type: "line",
stack: "Total",
data: [220, 182, 191, 234, 290, 330, 310],
},
{
name: "Video Ads",
type: "line",
stack: "Total",
data: [150, 232, 201, 154, 190, 330, 410],
},
{
name: "Direct",
type: "line",
stack: "Total",
data: [320, 332, 301, 334, 390, 330, 320],
},
{
name: "Search Engine",
type: "line",
stack: "Total",
data: [820, 932, 901, 934, 1290, 1330, 1320],
},
],
});
</script>
<style lang="scss" scoped>
.el-carousel__item h3 {
display: flex;
color: #475669;
opacity: 0.75;
line-height: 300px;
margin: 0;
text-align: center;
width: 100%;
}
</style>

View File

@@ -0,0 +1,104 @@
<template>
<el-descriptions
title="个人信息"
style="padding: 15px"
border
size="large"
:column="2"
v-loading="loading"
element-loading-text="正在初始化数据...">
<el-descriptions-item>
<template #label>
<el-icon>
<User />
</el-icon>
姓名 </template
>{{ user.name }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<el-icon>
<Female v-if="user.sex === 0 || user.sex === '女'" />
<Male v-if="user.sex === 1 || user.sex === '男'" />
</el-icon>
性别 </template
>{{ user.sex }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<el-icon>
<Iphone />
</el-icon>
电话 </template
>{{ user.phone }}</el-descriptions-item
>
<el-descriptions-item>
<template #label>
<el-icon> <Avatar /> </el-icon>状态
</template>
<el-tag type="success" v-if="user.userState || user.userState === 1"
>正常</el-tag
>
<el-tag type="danger" v-if="!user.userState || user.userState === 0"
>未启用</el-tag
>
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<el-icon> <UserFilled /> </el-icon>角色
</template>
<el-tag>
{{
roles.find((item) => Number(item.id) === Number(user.role))?.name ||
"无"
}}
</el-tag>
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<el-icon> <TakeawayBox /> </el-icon>生日 </template
>{{ dayjs(user.birthday).format("YYYYH-MM-DD") }}</el-descriptions-item
>
<!-- <el-descriptions-item label="年龄">{{ user.age }}</el-descriptions-item> -->
<el-descriptions-item>
<template #label>
<el-icon> <OfficeBuilding /> </el-icon>所在省市区
</template>
{{ user.province + "/" + user.city + "/" + user.area }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<el-icon><LocationInformation /></el-icon>详细地址
</template>
{{ user.address }}
</el-descriptions-item>
</el-descriptions>
</template>
<script lang="ts" setup>
import { ref, onMounted } from "vue";
import { userType } from "@/types/user";
import dayjs from "dayjs";
import { roleDataList } from "@/lib/api/publicApiList";
import { roleType } from "@/types/role";
const props = defineProps<{
data: userType;
}>();
const user = props.data;
let roles = ref<roleType[]>([]);
let loading = ref(true);
onMounted(async () => {
roles.value = await roleDataList();
loading.value = false;
});
</script>
<style lang="scss" scoped>
:deep(.el-descriptions__label) {
display: flex;
align-items: center;
.el-icon {
margin: 0 5px;
}
}
</style>

View File

@@ -0,0 +1,65 @@
<template>
<el-form
:model="formData"
label-width="100px"
inline
style="padding-bottom: 20px">
<el-form-item label="服务分类名称">
<el-input
v-model="formData.name"
placeholder="请输入服务分类名称"></el-input>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="formData.remark" placeholder="请输入备注"></el-input>
</el-form-item>
<el-row>
<el-form-item label="分类" style="width: 100%">
<el-tree-select
check-strictly
:check-on-click-node="true"
v-model="formData.parentId"
:props="{ label: 'name', value: 'id' }"
:data="categoryOptions"
:render-after-expand="false"
style="width: 240px" />
</el-form-item>
</el-row>
</el-form>
</template>
<script lang="ts" setup>
import { ref, onMounted } from "vue";
import { ElMessage } from "element-plus";
import api from "@/lib/request";
// 表单数据
const formData = defineModel({
default: {
name: "", // 服务项目名称
remark: "", // 备注
parentId: 0, // 分类ID
},
});
// 分类选项数据
const categoryOptions = ref<{ id: number; name: string }[]>([]);
// 获取分类树并转换为下拉框选项
const loadCategoryTree = async () => {
try {
const res = await api().get("/service-category/list");
if (res.data.list.length) {
categoryOptions.value = [{ id: 0, name: "无" }, ...res.data.list];
} else {
categoryOptions.value = [{ id: 0, name: "无" }];
}
} catch (err) {
ElMessage.error("获取分类树失败");
}
};
// 页面加载时获取分类树
onMounted(() => {
loadCategoryTree();
});
</script>

View File

@@ -0,0 +1,20 @@
<template>
<el-descriptions title="服务项目详情" border style="margin-bottom: 30px">
<el-descriptions-item label="服务项目名称">{{
data.name
}}</el-descriptions-item>
<el-descriptions-item label="数量">{{
data.quantity
}}</el-descriptions-item>
<el-descriptions-item label="单位">{{ data.unit }}</el-descriptions-item>
<el-descriptions-item label="售价">{{ data.price }}</el-descriptions-item>
<el-descriptions-item label="备注">{{ data.remark }}</el-descriptions-item>
<el-descriptions-item label="分类">{{
data.category
}}</el-descriptions-item>
</el-descriptions>
</template>
<script lang="ts" setup>
const data = defineModel<ServiceItemType>({ required: true });
</script>

View File

@@ -0,0 +1,215 @@
<template>
<div>
<baseTableHeader
title="服务分类"
@resetSearch="resetSearch"
@search="search">
<template #content>
<el-form :model="searchForm" :inline="true" label-position="right">
<el-form-item label="分类名称:">
<el-input
v-model="searchForm.name"
placeholder="请输入分类名称"></el-input>
</el-form-item>
<!-- <el-form-item label="分类:">
<el-input
v-model="searchForm.category"
placeholder="请输入分类"></el-input>
</el-form-item> -->
</el-form>
</template>
<template #operateBtns>
<el-button type="primary" @click="add" size="small"
><el-icon> <CirclePlus /> </el-icon>新增</el-button
>
</template>
</baseTableHeader>
<el-card style="margin-top: 8px">
<base-table
:option="tableOption"
ref="table"
style="width: 100%"
:tree-props="{ children: 'children' }"
row-key="id"
:indent="30"
:showPagination="false"
@addConfim="addConfim"
@editConfim="editConfim">
<template #colunm>
<el-table-column prop="name" label="服务分类名称" />
<el-table-column prop="remark" label="备注" align="center" />
<el-table-column
#default="{ row }"
label="操作"
align="center"
fixed="right"
width="250">
<!-- <el-button size="small" type="primary" @click="examine(row)"
>查看</el-button
> -->
<el-button size="small" type="warning" @click="update(row)"
>修改</el-button
>
<el-button size="small" type="danger" @click="deleteData(row)"
>删除</el-button
>
</el-table-column>
</template>
</base-table>
<base-curd-dialog
v-model="pageVisibleState.show2LevelPage.value"
:title="pageVisibleState.dialogTitle.value"
width="50%"
:before-close="handleDialogClose"
@addConfim="addConfim"
@editConfim="editConfim"
@closeConfirm="handleDialogClose"
:showPageType="pageVisibleState.showPageType">
<template #content>
<addOrEdit
:executeType="pageVisibleState.executeType.value"
v-model="currentServiceItem"
v-if="
pageVisibleState.showPageType.edit ||
pageVisibleState.showPageType.add
"></addOrEdit>
<serviceItemView
v-if="pageVisibleState.showPageType.view"
v-model="currentServiceItem"></serviceItemView>
</template>
</base-curd-dialog>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { reactive, onMounted, ref, watch } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { resetParams } from "@/util/globalMethods";
import addOrEdit from "./page/addOrEdit.vue";
import serviceItemView from "./page/view.vue";
import pageVisible from "@/components/curdDialog/pageVisibleState";
import api from "@/lib/request";
import { tableOptionType } from "@/types/table";
const pageVisibleState = new pageVisible({
add: "新增服务项目",
edit: "服务项目编辑",
view: "服务项目查看",
});
const searchForm = reactive({
name: "",
category: 0,
});
let currentServiceItem = ref<ServiceItemType>({
name: "",
remark: "",
parentId: 0,
});
watch(
() => pageVisibleState.show2LevelPage,
(newVal) => {
if (newVal.value === false) {
resetServiceItem();
}
},
{
deep: true,
}
);
let table = ref();
let tableOption = ref<tableOptionType>({
url: "/service-category/list",
searchUrl: "/service-category/query",
searchParams: searchForm,
executeType: "list",
});
function add() {
pageVisibleState.show2LevelPage.value = true;
pageVisibleState.showPageType.add = true;
}
function examine(row: ServiceItemType) {
currentServiceItem.value = row;
pageVisibleState.show2LevelPage.value = true;
pageVisibleState.showPageType.view = true;
}
function update(row: ServiceItemType) {
currentServiceItem.value = { ...row };
pageVisibleState.show2LevelPage.value = true;
pageVisibleState.showPageType.edit = true;
}
async function deleteData(row: ServiceItemType) {
await ElMessageBox.confirm("确定要删除该服务分类吗?", {
type: "error",
});
let respone = await api().get("/service-category/delete?id=" + row.id);
if (respone.code === 200) {
ElMessage.success("服务项目删除成功!");
table.value.methods.setDataType("list");
} else {
ElMessage.error(respone.msg);
}
}
function search() {
table.value.methods.setDataType("search");
}
function resetSearch() {
resetParams(searchForm);
table.value.methods.setDataType("list");
ElMessage.success("重置成功!");
}
function handleDialogClose() {
pageVisibleState.show2LevelPage.value = false;
}
async function addConfim() {
await ElMessageBox.confirm("确定新增该分类吗?", "提示", {
type: "warning",
});
let res = await api().post("/service-category/add", currentServiceItem.value);
if (res.code === 200) {
ElMessage.success("添加成功");
pageVisibleState.show2LevelPage.value = false;
table.value.methods.setDataType("list");
} else {
ElMessage.error(res.msg);
}
}
async function editConfim() {
ElMessageBox.confirm("确认修改该条信息吗?", "提示", {
type: "warning",
}).then(async () => {
let respone = await api().post(
"/service-category/update",
currentServiceItem.value
);
if (respone.code == 200) {
ElMessage.success("修改成功!");
pageVisibleState.show2LevelPage.value = false;
table.value.methods.setDataType("list");
} else {
ElMessage.error(respone.msg);
}
});
}
function resetServiceItem() {
currentServiceItem.value = {
name: "",
quantity: 0,
category: 0,
unit: "",
price: 0,
remark: "",
};
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,57 @@
<template>
<el-form
:model="formData"
label-width="100px"
inline
style="padding-bottom: 20px">
<el-form-item label="服务项目名称">
<el-input
v-model="formData.name"
placeholder="请输入服务项目名称"></el-input>
</el-form-item>
<el-form-item label="单位">
<el-input v-model="formData.unit" placeholder="请输入单位"></el-input>
</el-form-item>
<el-form-item label="售价">
<el-input v-model="formData.price" placeholder="请输入售价"></el-input>
</el-form-item>
<el-form-item label="数量" v-if="props.type === '手工录入服务'">
<el-input v-model="formData.quantity" placeholder="请输入数量"></el-input>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="formData.remark" placeholder="请输入备注"></el-input>
</el-form-item>
<el-row v-if="props.type !== '手工录入服务'">
<el-form-item label="分类" style="width: 100%">
<serviceSelect v-model="formData.parentId"></serviceSelect>
</el-form-item>
</el-row>
</el-form>
</template>
<script lang="ts" setup>
import { ref, onMounted, watch } from "vue";
import { ElMessage } from "element-plus";
import api from "@/lib/request";
let props = defineProps<{
type: "手工录入服务";
}>();
// 表单数据
const formData = defineModel({
default: {
name: "", // 服务项目名称
quantity: 1, // 数量
unit: "", // 单位
price: 0, // 售价
remark: "", // 备注
parentId: 0, // 分类ID
},
});
// 分类选项数据
const categoryOptions = ref<{ id: number; name: string }[]>([]);
// 页面加载时获取分类树
onMounted(() => {});
</script>

View File

@@ -0,0 +1,20 @@
<template>
<el-descriptions title="服务项目详情" border style="margin-bottom: 30px">
<el-descriptions-item label="服务项目名称">{{
data.name
}}</el-descriptions-item>
<el-descriptions-item label="数量">{{
data.quantity
}}</el-descriptions-item>
<el-descriptions-item label="单位">{{ data.unit }}</el-descriptions-item>
<el-descriptions-item label="售价">{{ data.price }}</el-descriptions-item>
<el-descriptions-item label="备注">{{ data.remark }}</el-descriptions-item>
<el-descriptions-item label="分类">{{
data.category
}}</el-descriptions-item>
</el-descriptions>
</template>
<script lang="ts" setup>
const data = defineModel<ServiceItemType>({ required: true });
</script>

View File

@@ -0,0 +1,247 @@
<template>
<div>
<baseTableHeader
title="服务项目"
@resetSearch="resetSearch"
@search="search">
<template #content>
<el-form :model="searchForm" :inline="true" label-position="right">
<el-form-item label="服务项目名称:">
<el-input
v-model="searchForm.name"
placeholder="请输入服务项目名称"></el-input>
</el-form-item>
<el-form-item label="分类:">
<serviceSelect v-model="searchForm.category"></serviceSelect>
</el-form-item>
</el-form>
</template>
<template #operateBtns>
<el-button type="primary" @click="add" size="small"
><el-icon> <CirclePlus /> </el-icon>新增</el-button
>
</template>
</baseTableHeader>
<el-card style="margin-top: 8px">
<base-table
:option="tableOption"
ref="table"
style="width: 100%"
border
@addConfim="addConfim"
@editConfim="editConfim">
<template #colunm>
<el-table-column
type="index"
label="序号"
width="80"
align="center"></el-table-column>
<el-table-column prop="name" label="服务项目名称" align="center" />
<!-- <el-table-column
prop="quantity"
label="数量"
align="center"
width="80" /> -->
<el-table-column prop="unit" label="单位" align="center" width="80" />
<el-table-column
prop="price"
label="售价"
align="center"
width="120" />
<el-table-column prop="remark" label="备注" align="center" />
<el-table-column prop="parentId" label="分类" align="center">
<template #default="{ row }">
{{
serviceCaregoryOptions.find((item) => item.id === row.parentId)
?.name
}}
</template>
</el-table-column>
<el-table-column
#default="{ row }"
label="操作"
align="center"
fixed="right"
width="250">
<el-button size="small" type="primary" @click="examine(row)"
>查看</el-button
>
<el-button size="small" type="warning" @click="update(row)"
>修改</el-button
>
<el-button size="small" type="danger" @click="deleteData(row)"
>删除</el-button
>
</el-table-column>
</template>
</base-table>
<base-curd-dialog
v-model="pageVisibleState.show2LevelPage.value"
:title="pageVisibleState.dialogTitle.value"
width="50%"
:before-close="handleDialogClose"
@addConfim="addConfim"
@editConfim="editConfim"
@closeConfirm="handleDialogClose"
:showPageType="pageVisibleState.showPageType">
<template #content>
<addOrEdit
:executeType="pageVisibleState.executeType.value"
v-model="currentServiceItem"
v-if="
pageVisibleState.showPageType.edit ||
pageVisibleState.showPageType.add
"></addOrEdit>
<serviceItemView
v-if="pageVisibleState.showPageType.view"
v-model="currentServiceItem"></serviceItemView>
</template>
</base-curd-dialog>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { reactive, onMounted, ref, watch } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { resetParams } from "@/util/globalMethods";
import addOrEdit from "./page/addOrEdit.vue";
import serviceItemView from "./page/view.vue";
import pageVisible from "@/components/curdDialog/pageVisibleState";
import serviceSelect from "@/components/serviceSelect/serviceSelect.vue";
import api from "@/lib/request";
import { tableOptionType } from "@/types/table";
const pageVisibleState = new pageVisible({
add: "新增服务项目",
edit: "服务项目编辑",
view: "服务项目查看",
});
const searchForm = reactive({
name: "",
category: 0,
});
let currentServiceItem = ref<ServiceItemType>({
name: "",
quantity: 0,
category: 0,
unit: "",
price: 0,
remark: "",
});
const serviceCaregoryOptions = ref<{ id: number; name: string }[]>([]);
onMounted(async () => {
const res = await api().get("/service-category/list");
serviceCaregoryOptions.value = [{ id: 0, name: "无" }, ...res.data.list];
});
watch(
() => pageVisibleState.show2LevelPage,
(newVal) => {
if (newVal.value === false) {
resetServiceItem();
}
},
{
deep: true,
}
);
let table = ref();
let tableOption = ref<tableOptionType>({
url: "/service-item/list",
searchUrl: "/service-item/query",
searchParams: searchForm,
executeType: "list",
});
function add() {
pageVisibleState.show2LevelPage.value = true;
pageVisibleState.showPageType.add = true;
}
function examine(row: ServiceItemType) {
currentServiceItem.value = row;
pageVisibleState.show2LevelPage.value = true;
pageVisibleState.showPageType.view = true;
}
function update(row: ServiceItemType) {
currentServiceItem.value = { ...row };
pageVisibleState.show2LevelPage.value = true;
pageVisibleState.showPageType.edit = true;
}
async function deleteData(row: ServiceItemType) {
await ElMessageBox.confirm("确定要删除该服务项目吗?", {
type: "error",
});
let respone = await api().get("/service-item/delete?id=" + row.id);
if (respone.code === 200) {
ElMessage.success("服务项目删除成功!");
table.value.methods.setDataType("list");
} else {
ElMessage.error(respone.msg);
}
}
function search() {
table.value.methods.setDataType("search");
}
function resetSearch() {
resetParams(searchForm);
table.value.methods.setDataType("list");
ElMessage.success("重置成功!");
}
function handleDialogClose() {
pageVisibleState.show2LevelPage.value = false;
}
async function addConfim() {
await ElMessageBox.confirm("确定新增该条信息吗?", "提示", {
type: "warning",
});
let res = await api().post("/service-item/add", currentServiceItem.value);
if (res.code === 200) {
ElMessage.success("添加成功");
pageVisibleState.show2LevelPage.value = false;
table.value.methods.setDataType("list");
} else {
ElMessage.error(res.msg);
}
}
async function editConfim() {
ElMessageBox.confirm("确认修改该条信息吗?", "提示", {
type: "warning",
}).then(async () => {
let respone = await api().post(
"/service-item/update",
currentServiceItem.value
);
if (respone.code == 200) {
ElMessage.success("修改成功!");
pageVisibleState.show2LevelPage.value = false;
table.value.methods.setDataType("list");
} else {
ElMessage.error(respone.msg);
}
});
}
function resetServiceItem() {
currentServiceItem.value = {
name: "",
quantity: 0,
category: 0,
unit: "",
price: 0,
remark: "",
};
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,20 @@
<template>
<div>
<el-tabs type="border-card">
<el-tab-pane>
<template #label>服务项目</template>
<serviceItemList></serviceItemList>
</el-tab-pane>
<el-tab-pane>
<template #label> 服务分类 </template>
<serviceCategory> </serviceCategory>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script lang="ts" setup>
import serviceItemList from "./serviceItem/serviceItemList.vue";
import serviceCategory from "./serviceCategory/serviceCategory.vue";
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,713 @@
<template>
<div style="background-color: #fff; padding: 15px">
<baseTableHeader
title="公司日收入统计"
@resetSearch="reset"
@search="query">
<template #content>
<el-form v-model="searchForm">
<el-row :gutter="15">
<el-col>
<el-form-item label="购买日期">
<el-date-picker
v-model="searchForm.purchaseDate"
type="datetimerange"
range-separator=""
start-placeholder="选择日期"
end-placeholder="选择日期"
@change="dataChange" />
</el-form-item>
<!-- <div>
<el-button
type="primary"
style="margin-left: 15px"
@click="query"
>查询</el-button
><el-button
type="warning"
style="margin-left: 15px"
@click="reset"
>重置</el-button
>
<el-button v-print="print">打印单据</el-button>
</div> -->
</el-col>
</el-row>
</el-form>
</template>
<template #operateBtns>
<el-button v-print="print"
><img
src="/assets/icon/打印机.svg"
style="margin-right: 5px"
width="20"
height="20" />打印单据</el-button
>
<el-button @click="exportFile"
><img
src="/assets/images/Excel.svg"
alt=""
width="20"
height="20" />导出</el-button
>
</template>
</baseTableHeader>
<div id="print-container">
<h2
style="
color: #000;
text-align: center;
font-size: 20pt;
padding: 15px;
font-weight: bold;
">
殡仪服务公司日收入统计表
</h2>
<table
id="print-table"
class=""
style="
font-family: 仿宋;
table-layout: fixed;
width: 100%;
border-collapse: collapse;
">
<tbody>
<tr>
<td
colspan="4"
class="l"
style="
text-align: left;
font-weight: bold;
font-size: 25px;
font-family: 宋体;
">
<span>
统计时间{{
dayjs(searchForm.startDate).format("YYYY-MM-DD HH:mm:ss")
}}
{{ dayjs(searchForm.endDate).format("YYYY-MM-DD HH:mm:ss") }}
</span>
<!-- <span v-else>
开始时间{{ dayjs(searchForm.startDate).format("YYYY-MM-DD") }}
</span> -->
</td>
<td
colspan="2"
class="l"
style="
text-align: left;
font-weight: bold;
font-size: 25px;
font-family: 宋体;
">
单位
</td>
</tr>
<tr>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
<b></b>
</td>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
<b>小计</b>
</td>
<td
colspan="2"
style="height: 30px; border: 1px solid black; text-align: center">
<b>总计</b>
</td>
<td
colspan="2"
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
<b>备注</b>
</td>
</tr>
<tr>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
现金
</td>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
{{ statsData.service.cashAmount + statsData.retail.cashAmount }}
</td>
<td
rowspan="6"
colspan="2"
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
{{
statsData.service.cashAmount +
statsData.service.unionPayAmount +
statsData.service.cardAmount +
statsData.service.publicTransferAmount +
statsData.service.workshopPayment +
statsData.retail.cashAmount +
statsData.retail.unionPayAmount +
statsData.retail.cardAmount +
statsData.retail.publicTransferAmount +
statsData.retail.workshopPayment
}}
</td>
<td
colspan="2"
style="
height: 30px;
border: 1px solid black;
text-align: center;
"></td>
</tr>
<tr>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
银联
</td>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
{{
statsData.service.unionPayAmount +
statsData.retail.unionPayAmount
}}
</td>
<td
colspan="2"
style="
height: 30px;
border: 1px solid black;
text-align: center;
"></td>
</tr>
<tr>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
银行卡
</td>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
{{ statsData.service.cardAmount + statsData.retail.cardAmount }}
</td>
<td
colspan="2"
style="
height: 30px;
border: 1px solid black;
text-align: center;
"></td>
</tr>
<tr>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
车间支付
</td>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
{{
statsData.service.workshopPayment +
statsData.retail.workshopPayment
}}
</td>
<td
colspan="2"
style="
height: 30px;
border: 1px solid black;
text-align: center;
"></td>
</tr>
<tr>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
对公转账
</td>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
{{
statsData.service.publicTransferAmount +
statsData.retail.publicTransferAmount
}}
</td>
<td
colspan="2"
style="
height: 30px;
border: 1px solid black;
text-align: center;
"></td>
</tr>
<tr>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
"></td>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
"></td>
<td
colspan="2"
style="
height: 30px;
border: 1px solid black;
text-align: center;
"></td>
</tr>
<tr>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
<b>现金合计</b>
</td>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
<b>微信合计</b>
</td>
<td
colspan="2"
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
<b>银行卡合计</b>
</td>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
<b>车间支付合计</b>
</td>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
<b>对公转账合计</b>
</td>
</tr>
<tr>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
{{ statsData.total.cashAmount }}
</td>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
{{ statsData.total.unionPayAmount }}
</td>
<td
colspan="2"
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
{{ statsData.total.cardAmount }}
</td>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
{{ statsData.total.workshopPayment }}
</td>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
{{ statsData.total.publicTransferAmount }}
</td>
</tr>
<tr>
<td
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
<b>总合计</b>
</td>
<td
colspan="5"
style="
height: 30px;
border: 1px solid black;
text-align: center;
">
{{
statsData.total.cashAmount +
statsData.total.unionPayAmount +
statsData.total.cardAmount +
statsData.total.workshopPayment +
statsData.total.publicTransferAmount
}}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script lang="ts" setup>
import { nextTick, ref } from "vue";
import request from "@/lib/request";
import dayjs from "dayjs";
import ExcelJS from "exceljs";
export type PaymentStats = {
cashAmount: number;
unionPayAmount: number;
cardAmount: number;
publicTransferAmount: number;
workshopPayment: number;
};
// 完整响应类型
export type StatsResponse = {
retail: PaymentStats;
service: PaymentStats;
total: PaymentStats;
};
let searchForm = ref({
startDate: dayjs().startOf("day").toDate(),
endDate: new Date(),
purchaseDate: [dayjs().startOf("day").toDate(), new Date()],
});
let statsData = ref<StatsResponse>({
retail: {
cashAmount: 0,
unionPayAmount: 0,
cardAmount: 0,
publicTransferAmount: 0,
workshopPayment: 0,
},
service: {
cashAmount: 0,
unionPayAmount: 0,
cardAmount: 0,
publicTransferAmount: 0,
workshopPayment: 0,
},
total: {
cashAmount: 0,
unionPayAmount: 0,
cardAmount: 0,
publicTransferAmount: 0,
workshopPayment: 0,
},
});
let searched = ref(false);
request()
.get("/stats/dayIncome", {
params: {
startDate: dayjs().startOf("day").toDate(),
endDate: new Date(),
},
})
.then((res) => {
statsData.value = res.data;
});
const print = {
id: "print-container",
};
function query() {
searched.value = false;
request()
.get("/stats/dayIncome", {
params: {
startDate: dayjs(searchForm.value.startDate).format(
"YYYY-MM-DD HH:mm:ss"
),
endDate: dayjs(searchForm.value.endDate).format("YYYY-MM-DD HH:mm:ss"),
},
})
.then((res) => {
statsData.value = res.data;
});
}
function reset() {
searchForm.value = {
startDate: dayjs().startOf("day").toDate(),
endDate: new Date(),
purchaseDate: [dayjs().startOf("day").toDate(), new Date()],
};
query();
}
function dataChange(val: Date[]) {
searchForm.value.startDate = val[0];
searchForm.value.endDate = val[1];
}
async function exportFile() {
try {
// 获取模板文件
const response = await fetch("/excelTemple/日收入统计.xlsx");
const buffer = await response.arrayBuffer();
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.load(buffer);
const sheet = workbook.getWorksheet(1); // 默认第一个工作表
const startDate = dayjs(searchForm.value.startDate).format(
"YYYY-MM-DD HH:mm:ss"
);
const endDate = dayjs(searchForm.value.endDate).format(
"YYYY-MM-DD HH:mm:ss"
);
// 工具函数:填充值并保留样式
const setCellValueWithStyle = (cellAddress, value) => {
const cell = sheet.getCell(cellAddress);
const style = { ...cell.style };
cell.value = value;
cell.style = style;
};
// 填充各项内容(保持样式)
setCellValueWithStyle("C2", `${startDate}${endDate}`);
setCellValueWithStyle("B4", statsData.value.total.cashAmount);
setCellValueWithStyle("B5", statsData.value.total.unionPayAmount);
setCellValueWithStyle("B6", statsData.value.total.cardAmount);
setCellValueWithStyle("B7", statsData.value.total.workshopPayment);
setCellValueWithStyle("B8", statsData.value.total.publicTransferAmount);
const total =
statsData.value.service.cashAmount +
statsData.value.service.unionPayAmount +
statsData.value.service.cardAmount +
statsData.value.service.publicTransferAmount +
statsData.value.service.workshopPayment +
statsData.value.retail.cashAmount +
statsData.value.retail.unionPayAmount +
statsData.value.retail.cardAmount +
statsData.value.retail.publicTransferAmount +
statsData.value.retail.workshopPayment;
setCellValueWithStyle("C4", total);
setCellValueWithStyle("A11", statsData.value.total.cashAmount);
setCellValueWithStyle("B11", statsData.value.total.unionPayAmount);
setCellValueWithStyle("C11", statsData.value.total.cardAmount);
setCellValueWithStyle("D11", statsData.value.total.workshopPayment);
setCellValueWithStyle("E11", statsData.value.total.publicTransferAmount);
const grandTotal =
statsData.value.total.cashAmount +
statsData.value.total.unionPayAmount +
statsData.value.total.cardAmount +
statsData.value.total.workshopPayment +
statsData.value.total.publicTransferAmount;
setCellValueWithStyle("B12", grandTotal);
// 导出文件
const newBuffer = await workbook.xlsx.writeBuffer();
const blob = new Blob([newBuffer], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "日收入统计.xlsx";
a.click();
URL.revokeObjectURL(url);
} catch (error) {
console.error("导出 Excel 文件时出错:", error);
}
}
</script>
<style lang="scss" scoped>
tr,
td {
font-size: 14px !important;
}
#print-container {
width: 100% !important;
margin: 0 !important;
padding: 0 5mm !important;
font-size: 14pt;
color: #000 !important;
font-family: Microsoft YaHei, "SimSun", serif !important;
/* 添加表格布局优化 */
table {
width: 100% !important;
table-layout: fixed;
border-collapse: collapse !important;
td,
th {
padding: 5px 2mm !important;
word-wrap: break-word;
}
}
}
@media print {
/* 重置body和html的布局方式 */
body,
html {
display: block !important;
height: auto !important;
margin: 0 !important;
padding: 0 !important;
}
@page {
margin: 15pt;
}
#print-container {
min-height: 297mm; /* A4纸高度 */
width: 100% !important;
vertical-align: top !important;
position: relative;
top: 0;
transform: none !important;
}
/* 隐藏不需要打印的元素 */
.el-form,
.el-button {
display: none !important;
}
}
</style>

View File

@@ -0,0 +1,6 @@
<template>
<div>引导员销售统计</div>
</template>
<script lang="ts" setup></script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,452 @@
<template>
<div style="background-color: #fff; padding: 15px">
<baseTableHeader title="公司销售明细" @resetSearch="reset" @search="query">
<template #content>
<el-form v-model="searchForm" label-width="100">
<el-row :gutter="25">
<el-col :span="10">
<el-form-item label="统计日期">
<el-date-picker
v-model="searchForm.dateRange"
type="datetimerange"
range-separator=""
start-placeholder="选择开始日期"
end-placeholder="选择结束日期"
value-format="YYYY-MM-DD HH:mm:ss"
@change="dateChange" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="逝者姓名">
<el-input
v-model="searchForm.deceasedName"
placeholder="输入逝者名称" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="购买人">
<el-input
v-model="searchForm.familyName"
placeholder="输入购买人" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="服务名称">
<el-input
v-model="searchForm.serviceName"
placeholder="输入服务名称" />
</el-form-item>
</el-col>
<el-col :span="6" label="引导员">
<el-form-item label="引导员">
<guideList v-model="searchForm.guide"></guideList>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<template #operateBtns>
<el-button @click="exportSalesDetailsExcel('当前')"
><img
src="/assets/images/Excel.svg"
alt=""
width="20"
height="20" />导出当前</el-button
>
<el-button @click="exportSalesDetailsExcel('所有')"
><img
src="/assets/images/Excel.svg"
alt=""
width="20"
height="20" />导出所有</el-button
>
</template>
</baseTableHeader>
<el-card style="margin-top: 8px">
<base-table
:option="tableOption"
ref="table"
border
show-summary
:summary-method="getSummaries">
<template #colunm>
<el-table-column
type="index"
width="80"
fixed="left"
label="序号"
align="center"></el-table-column>
<el-table-column
width="120"
fixed="left"
prop="deceasedRetail.checkoutDate"
label="结账日期"
align="center">
<template #default="{ row }">
{{ row.deceasedRetail.checkoutDate.split(" ")[0] }}
</template>
</el-table-column>
<el-table-column
prop="deceased.name"
label="逝者姓名"
width="120"
fixed="left"
align="center">
<template #default="{ row }">
<span>{{
row.deceased.name ||
row.deceased.familyName ||
row.deceasedRetail.deceasedName
}}</span>
</template>
</el-table-column>
<el-table-column
prop="deceasedRetail.guide"
label="引导员"
fixed="left"
align="center" />
<el-table-column prop="deceased.gender" label="性别" align="center" />
<el-table-column prop="deceased.age" label="年龄" align="center" />
<el-table-column
prop="deceased.idNumber"
label="身份证"
align="center"
width="180" />
<el-table-column label="地址" align="center" width="280">
<template #default="{ row }">
<div style="text-align: left">
<span
v-if="row.deceased.province || row.deceasedRetail.province"
>{{
row.deceased.province || row.deceasedRetail.province
}}/</span
>
<span v-if="row.deceased.city || row.deceasedRetail.city"
>{{ row.deceased.city || row.deceasedRetail.city }}/</span
>
<span
v-if="row.deceased.address || row.deceasedRetail.address"
>{{
row.deceased.address || row.deceasedRetail.address
}}</span
>
</div>
</template>
</el-table-column>
<el-table-column
prop="deceased.name"
label="购买人姓名"
align="center"
width="120">
<template #default="{ row }">
<span>{{
row.deceased.familyName || row.deceasedRetail.deceasedName
}}</span>
</template>
</el-table-column>
<el-table-column
prop="deceased.familyPhone"
label="购买人电话"
align="center"
width="150" />
<el-table-column
prop="name"
label="项目名称"
align="center"
width="150" />
<el-table-column
prop="price"
label="单价"
align="center"
width="150" />
<el-table-column
prop="quantity"
label="数量"
align="center"
width="150" />
<el-table-column prop="sum" label="金额" align="center" width="150">
</el-table-column>
<el-table-column
prop="remark"
label="备注"
width="280"
align="center" />
</template>
</base-table>
</el-card>
</div>
</template>
<script lang="ts" setup>
import request from "@/lib/request";
import { tableOptionType } from "@/types/table";
import { ElMessage } from "element-plus";
import { nextTick, onMounted, ref } from "vue";
import ExcelJS from "exceljs";
const categories = ref<Array<{ id: number; name: string }>>([]);
let searchForm = ref({
dateRange: [],
serviceName: "",
categoryName: "",
categories: "",
startDate: "",
endDate: "",
deceasedName: "",
guide: "",
familyName: "",
});
let tableOption = ref<tableOptionType>({
url: "/stats/salesDetails",
searchUrl: "/stats/salesDetails",
searchParams: searchForm,
executeType: "list",
});
let table = ref();
const fetchCategories = async () => {
try {
const res = await request().get("/public/service-categories");
categories.value = res.data.map((item: any) => ({
id: item.value,
name: item.label,
}));
} catch (error) {
ElMessage.error("分类加载失败");
}
};
const getSummaries = (param: { columns: any[]; data: any[] }) => {
const { columns, data } = param;
const sums: any[] = [];
// 需要统计的字段列表
const sumKeys = ["price", "quantity", "sum"];
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = "合计";
return;
}
if (sumKeys.includes(column.property)) {
const values = data.map((item) => {
const keys = column.property.split(".");
let value = item;
for (const key of keys) {
value = value?.[key] || 0;
}
return Number(value) || 0;
});
if (!values.every((value) => isNaN(value))) {
const sum = values.reduce((prev, curr) => {
return prev + curr;
}, 0);
sums[index] = `${sum.toFixed(2)}`;
} else {
sums[index] = "0.00 元";
}
} else {
sums[index] = "";
}
});
return sums;
};
const dateChange = (val: string[]) => {
if (val?.length === 2) {
searchForm.value.dateRange = val;
searchForm.value.startDate = val[0];
searchForm.value.endDate = val[1];
}
};
function reset() {
searchForm.value = {
dateRange: [],
serviceName: "",
categoryName: "",
categories: "",
startDate: "",
endDate: "",
deceasedName: "",
familyName: "",
};
table.value.methods.setDataType("reset");
}
function query() {
table.value.methods.setDataType("search");
}
onMounted(() => {
fetchCategories();
});
async function exportSalesDetailsExcel(type: "当前" | "所有") {
try {
let list = [];
if (type === "当前") {
list = table.value.tableData.data;
} else {
// 1. 获取所有数据
const res = await request().post("/stats/salesDetails", {
pageSize: 999999999,
pageNumber: 1,
dateRange: [],
serviceName: "",
categoryName: "",
categories: "",
startDate: "",
endDate: "",
deceasedName: "",
guide: "",
familyName: "",
});
list = res.data?.list;
}
// 2. 获取模板文件
const templateRes = await fetch("/excelTemple/销售明细.xlsx");
const buffer = await templateRes.arrayBuffer();
// 3. 加载 Excel 模板
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.load(buffer);
const sheet = workbook.getWorksheet(1); // 默认取第一个 sheet
// 4. 从第 3 行开始填充数据
list.forEach((item, index) => {
const row = sheet.getRow(index + 2); // 第3行开始
const deceased = item.deceased || {};
const retail = item.deceasedRetail || {};
row.getCell(1).value = index + 1;
row.getCell(2).value = retail.checkoutDate || "";
row.getCell(3).value = deceased.name || "";
row.getCell(4).value = retail.guide || "";
row.getCell(5).value = deceased.gender || "";
row.getCell(6).value = deceased.age || "";
row.getCell(7).value = deceased.idNumber || "";
row.getCell(8).value = deceased.address || "";
row.getCell(9).value = deceased.familyName || "";
row.getCell(10).value = deceased.familyPhone || "";
row.getCell(11).value = item.name || "";
row.getCell(12).value = item.price || "";
row.getCell(13).value = item.quantity || "";
row.getCell(14).value = item.sum || "";
row.getCell(15).value = item.remark || "";
row.eachCell((cell) => {
cell.alignment = { vertical: "middle", horizontal: "center" };
});
row.commit(); // 应用修改
});
// 5. 导出为 blob
const blob = await workbook.xlsx.writeBuffer();
const file = new Blob([blob], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});
// 6. 触发下载
const url = URL.createObjectURL(file);
const a = document.createElement("a");
a.href = url;
a.download = "销售明细.xlsx";
a.click();
URL.revokeObjectURL(url);
} catch (error) {
console.error("导出销售明细失败:", error);
}
}
</script>
<style scoped lang="scss">
.loading-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.9);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.is-loading {
animation: rotating 2s linear infinite;
}
@keyframes rotating {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
th,
td {
border: 1px solid #000;
padding: 8px;
font-size: 14px;
text-align: center !important;
}
@media print {
/* 移除全局布局影响 */
body,
html {
display: block !important;
height: auto !important;
margin: 2mm !important; /* 添加安全边距 */
}
#print-container {
width: auto !important; /* 改为自动宽度 */
margin: 0 auto !important; /* 居中显示 */
font-size: 12pt !important; /* 调整字号 */
min-height: 297mm;
}
table {
table-layout: fixed;
width: 100% !important;
}
/* 优化单元格内边距 */
td,
th {
padding: 4px 2mm !important;
line-height: 1.4 !important;
}
}
tr,
td {
font-size: 14px !important;
}
#print-container {
width: 100% !important;
margin: 0 !important;
padding: 0 5mm !important;
font-size: 14pt;
color: #000 !important;
font-family: Microsoft YaHei, "SimSun", serif !important;
}
/* 添加表格布局优化 */
table {
width: 100% !important;
table-layout: fixed;
border-collapse: collapse !important;
td,
th {
padding: 10px 2mm !important;
word-wrap: break-word;
}
}
</style>

View File

@@ -0,0 +1,498 @@
<template>
<div style="background-color: #fff; padding: 15px">
<baseTableHeader title="销售统计报表" @resetSearch="reset" @search="query">
<template #content>
<el-form v-model="searchForm">
<el-row :gutter="25">
<el-col :span="12">
<el-form-item label="统计日期">
<el-date-picker
v-model="searchForm.dateRange"
type="datetimerange"
range-separator=""
start-placeholder="选择开始日期"
end-placeholder="选择结束日期"
value-format="YYYY-MM-DD HH:mm:ss"
@change="dateChange" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="服务名称">
<el-input
v-model="searchForm.serviceName"
placeholder="输入服务名称" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="项目分类">
<el-select
v-model="searchForm.categoryName"
placeholder="选择分类"
clearable>
<el-option
v-for="category in categories"
:key="category.id"
:label="category.name"
:value="category.name" />
<el-option label="其他服务" value="其他服务" />
</el-select>
</el-form-item>
</el-col>
<!-- <div>
<el-button
type="primary"
style="margin-left: 15px"
@click="query"
:loading="loading">
查询
</el-button>
<el-button type="warning" @click="reset">重置</el-button>
</div> -->
</el-row>
</el-form>
</template>
<template #operateBtns>
<el-button size="small" v-print="print"
><img
src="/assets/icon/打印机.svg"
style="margin-right: 5px"
width="20"
height="20" />打印单据</el-button
>
<el-button @click="exportTableToExcel"
><img
src="/assets/images/Excel.svg"
alt=""
width="20"
height="20" />导出</el-button
>
</template>
</baseTableHeader>
<!-- 加载状态 -->
<div v-if="loading" class="loading-mask">
<el-icon class="is-loading" color="#409EFC" :size="30">
<Loading />
</el-icon>
<span style="margin-left: 10px">数据加载中...</span>
</div>
<div id="print-container">
<h2
style="
color: #000;
text-align: center;
font-size: 20pt;
padding: 15px;
font-weight: bold;
">
销售统计报表
</h2>
<table
v-show="!loading"
style="
table-layout: fixed;
width: 100%;
border-collapse: collapse;
font-family: 宋体;
">
<!-- 表头 -->
<thead>
<tr>
<th style="border: 1px solid #000; padding: 8px">序号</th>
<th style="border: 1px solid #000; padding: 8px">服务项目</th>
<th style="border: 1px solid #000; padding: 8px">单价</th>
<th style="border: 1px solid #000; padding: 8px">数量</th>
<th style="border: 1px solid #000; padding: 8px">小计</th>
</tr>
</thead>
<tbody>
<!-- 新增空状态 -->
<tr v-if="showEmpty">
<td
colspan="4"
style="text-align: center; color: #999; padding: 20px">
未查询到相关数据
</td>
</tr>
<!-- 分类数据 -->
<template
v-for="category in statsData.categories"
:key="category.categoryName">
<tr>
<td
colspan="5"
style="font-weight: bold; padding: 8px; text-align: center">
{{ category.categoryName }}
</td>
</tr>
<tr
v-for="(service, index) in category.services"
:key="service.serviceName">
<td style="padding: 8px">{{ index + 1 }}</td>
<td style="border: 1px solid #000; padding: 8px">
{{ service.serviceName }}
</td>
<td
style="border: 1px solid #000; text-align: right; padding: 8px">
{{ service.price.toFixed(2) }}
</td>
<td
style="border: 1px solid #000; text-align: right; padding: 8px">
{{ service.quantity }}
</td>
<td
style="border: 1px solid #000; text-align: right; padding: 8px">
{{ service.subtotal.toFixed(2) }}
</td>
</tr>
<!-- <tr style="font-weight: bold">
<td colspan="2" style="border: 1px solid #000; padding: 8px">
分类合计
</td>
<td
style="border: 1px solid #000; text-align: right; padding: 8px">
{{ category.totalQuantity }}
</td>
<td
style="border: 1px solid #000; text-align: right; padding: 8px">
{{ category.totalAmount.toFixed(2) }}
</td>
</tr> -->
</template>
<!-- 其他服务 -->
<tr v-if="statsData.otherCategory.services.length">
<td colspan="5" style="font-weight: bold; padding: 8px">
{{ statsData.otherCategory.categoryName }}
</td>
</tr>
<tr
v-if="statsData.otherCategory.services.length"
v-for="(service, index) in statsData.otherCategory.services"
:key="'other-' + service.serviceName">
<td style="padding: 8px">{{ index + 1 }}</td>
<td style="border: 1px solid #000; padding: 8px">
{{ service.serviceName }}
</td>
<td style="border: 1px solid #000; text-align: right; padding: 8px">
{{ service.price.toFixed(2) }}
</td>
<td style="border: 1px solid #000; text-align: right; padding: 8px">
{{ service.quantity }}
</td>
<td style="border: 1px solid #000; text-align: right; padding: 8px">
{{ service.subtotal.toFixed(2) }}
</td>
</tr>
<!-- 总计行 -->
<tr>
<td
colspan="3"
style="
border: 1px solid #000;
padding: 8px;
font-weight: bold;
font-size: 20px;
">
数量和金额总计
</td>
<td
style="
border: 1px solid #000;
text-align: right;
padding: 8px;
font-weight: bold;
font-size: 20px;
">
{{ statsData.grandTotalQuantity }}
</td>
<td
style="
border: 1px solid #000;
text-align: right;
padding: 8px;
font-weight: bold;
font-size: 20px;
">
{{ statsData.grandTotalAmount.toFixed(2) }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from "vue";
import { ElMessage } from "element-plus";
import { Loading } from "@element-plus/icons-vue";
import request from "@/lib/request";
import dayjs from "dayjs";
import * as XLSX from "xlsx";
interface ServiceStatsItem {
serviceName: string;
price: number;
unit: string;
quantity: number;
subtotal: number;
}
interface CategoryStats {
categoryName: string;
services: ServiceStatsItem[];
totalQuantity: number;
totalAmount: number;
}
interface StatsResponse {
categories: CategoryStats[];
otherCategory: CategoryStats;
grandTotalQuantity: number;
grandTotalAmount: number;
}
const print = {
id: "print-container",
};
// 保持原有响应式数据结构
const loading = ref(false);
const searchForm = ref({
dateRange: [
dayjs().startOf("day").format("YYYY-MM-DD 00:00:00"),
dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss"),
] as string[],
serviceName: "",
categoryName: "",
});
const statsData = ref<StatsResponse>({
categories: [],
otherCategory: {
categoryName: "其他服务",
services: [],
totalQuantity: 0,
totalAmount: 0,
},
grandTotalQuantity: 0,
grandTotalAmount: 0,
});
const categories = ref<Array<{ id: number; name: string }>>([]);
const showEmpty = computed(
() =>
statsData.value?.categories?.length === 0 &&
statsData.value.otherCategory.services.length === 0
);
onMounted(async () => {
try {
await fetchCategories();
await query();
} catch (error) {
handleError(error);
}
});
const fetchCategories = async () => {
try {
const res = await request().get("/public/service-categories");
categories.value = res.data.map((item: any) => ({
id: item.value,
name: item.label,
}));
} catch (error) {
ElMessage.error("分类加载失败");
}
};
const query = async () => {
try {
loading.value = true;
const params = {
startDate: searchForm.value.dateRange[0],
endDate: searchForm.value.dateRange[1],
serviceName: searchForm.value.serviceName,
categoryName: searchForm.value.categoryName,
};
const res = await request().get("/stats/servicesStats", { params });
statsData.value = res.data || res;
} catch (error) {
ElMessage.error(`查询失败: ${(error as Error).message}`);
} finally {
loading.value = false;
}
};
const reset = () => {
searchForm.value = {
dateRange: [
dayjs().startOf("day").format("YYYY-MM-DD 00:00:00"),
dayjs().format("YYYY-MM-DD 23:59:59"),
],
serviceName: "",
categoryName: "",
};
query();
};
const dateChange = (val: string[]) => {
if (val?.length === 2) {
searchForm.value.dateRange = val;
}
};
const handleError = (error: unknown) => {
ElMessage({
type: "error",
message: "系统异常,请稍后重试",
duration: 3000,
});
};
function exportTableToExcel() {
const table = document.querySelector(
"#print-container table"
) as HTMLTableElement;
if (!table) {
console.error("找不到表格");
return;
}
// 将 HTML 表格转换为 worksheet
const worksheet = XLSX.utils.table_to_sheet(table, {
raw: true,
});
// 获取范围
const range = XLSX.utils.decode_range(worksheet["!ref"]!);
// 遍历每个单元格,设置样式
for (let R = range.s.r; R <= range.e.r; ++R) {
for (let C = range.s.c; C <= range.e.c; ++C) {
const cellAddress = XLSX.utils.encode_cell({ r: R, c: C });
const cell = worksheet[cellAddress];
if (!cell || !cell.v) continue;
// 是否是最后一行(数量和金额总计行)
const isLastRow = R === range.e.r;
cell.s = {
alignment: {
horizontal: "center",
vertical: "center",
wrapText: true,
},
font: {
bold: isLastRow, // 最后一行加粗
sz: 12,
},
border: {
top: { style: "thin", color: { rgb: "000000" } },
bottom: { style: "thin", color: { rgb: "000000" } },
left: { style: "thin", color: { rgb: "000000" } },
right: { style: "thin", color: { rgb: "000000" } },
},
};
}
}
// 创建工作簿并导出
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "销售统计");
XLSX.writeFile(workbook, "销售统计报表.xlsx");
}
</script>
<style scoped lang="scss">
.loading-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.9);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.is-loading {
animation: rotating 2s linear infinite;
}
@keyframes rotating {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
th,
td {
border: 1px solid #000;
padding: 8px;
font-size: 14px;
text-align: center !important;
}
@media print {
/* 移除全局布局影响 */
body,
html {
display: block !important;
height: auto !important;
margin: 2mm !important; /* 添加安全边距 */
}
#print-container {
width: auto !important; /* 改为自动宽度 */
margin: 0 auto !important; /* 居中显示 */
font-size: 12pt !important; /* 调整字号 */
min-height: 297mm;
}
table {
table-layout: fixed;
width: 100% !important;
}
/* 优化单元格内边距 */
td,
th {
padding: 4px 2mm !important;
line-height: 1.4 !important;
}
}
tr,
td {
font-size: 14px !important;
}
#print-container {
width: 100% !important;
margin: 0 !important;
padding: 0 5mm !important;
font-size: 14pt;
color: #000 !important;
font-family: Microsoft YaHei, "SimSun", serif !important;
}
/* 添加表格布局优化 */
table {
width: 100% !important;
table-layout: fixed;
border-collapse: collapse !important;
td,
th {
padding: 10px 2mm !important;
word-wrap: break-word;
}
}
</style>

View File

@@ -0,0 +1,218 @@
<template>
<div>
<el-row>
<baseTableHeader
title="菜单管理"
@resetSearch="resetSearch"
@search="search">
<template #content>
<el-form :model="searchForm" :inline="true" label-position="right">
<el-form-item label="菜单名:">
<el-input
v-model="searchForm.name"
placeholder="请输入菜单名"></el-input>
</el-form-item>
<el-form-item label="菜单路径:">
<el-input
v-model="searchForm.path"
placeholder="请输入菜单路径"></el-input>
</el-form-item>
</el-form>
</template>
<template #operateBtns>
<el-button type="primary" @click="add" size="small"
><el-icon> <CirclePlus /> </el-icon>新增</el-button
>
</template>
</baseTableHeader>
</el-row>
<el-card style="margin-top: 8px">
<base-table
:option="tableOption"
ref="table"
style="width: 100%"
row-key="id"
:indent="30"
@addConfim="addConfim"
@editConfim="editConfim"
:tree-props="{ children: 'children' }">
<template #colunm>
<el-table-column
prop="name"
label="菜单名"
align="center"
width="180" />
<el-table-column
prop="createDate"
label="创建时间"
width="180"
align="center" />
<el-table-column label="图标" width="180" align="center">
<template #default="scope">
<el-icon>
<component :is="scope.row.icon"></component>
</el-icon>
<span style="padding: 0 15px">{{ scope.row.icon }}</span>
</template>
</el-table-column>
<el-table-column
prop="path"
label="路径"
width="180"
align="center" />
<el-table-column label="是否显示" align="center">
<template #default="scope">
<el-switch
v-model="scope.row.show"
size="large"
width="65px"
inline-prompt
active-text=""
inactive-text="" />
</template>
</el-table-column>
<el-table-column
#default="scope"
width="300"
label="操作"
align="center">
<el-button size="small" type="primary" @click="examine(scope.row)"
>查看</el-button
>
<el-button size="small" type="warning" @click="update(scope.row)"
>修改</el-button
>
<el-button size="small" type="danger" @click="deleteData(scope.row)"
>删除</el-button
>
</el-table-column>
</template>
</base-table>
<base-curd-dialog
v-model="pageVisibleState.show2LevelPage.value"
:title="pageVisibleState.dialogTitle.value"
width="50%"
:before-close="handleDialogClose"
@addConfim="addConfim"
@editConfim="editConfim"
@closeConfirm="handleDialogClose"
:showPageType="pageVisibleState.showPageType">
<template #content>
<addOrEdit
:executeType="pageVisibleState.executeType.value"
:data="currentMenue"
@updateData="getUserData"
v-if="
pageVisibleState.showPageType.edit ||
pageVisibleState.showPageType.add
"></addOrEdit>
<menueView
v-if="pageVisibleState.showPageType.view"
:data="currentMenue"></menueView>
</template>
</base-curd-dialog>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { reactive, onMounted, ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { resetParams } from "@/util/globalMethods";
import addOrEdit from "./page/addOrEdit/addOrEdit.vue";
import menueView from "./page/view/view.vue";
import pageVisible from "@/components/curdDialog/pageVisibleState";
import { systemMenueType } from "@/types/systemMenue";
import api from "@/lib/request";
import { tableOptionType } from "@/types/table";
const pageVisibleState = new pageVisible({
add: "新增菜单",
edit: "菜单编辑",
view: "菜单查看",
});
const searchForm = reactive({
name: "",
path: "",
});
let currentMenue = ref<systemMenueType>({});
let table = ref();
let tableOption = ref<tableOptionType>({
url: "/system-menue/list",
searchUrl: "/system-menue/query",
searchParams: searchForm,
executeType: "list",
});
function add() {
pageVisibleState.show2LevelPage.value = true;
pageVisibleState.showPageType.add = true;
}
function examine(row: systemMenueType) {
currentMenue.value = row;
pageVisibleState.show2LevelPage.value = true;
pageVisibleState.showPageType.view = true;
}
function update(row: systemMenueType) {
currentMenue.value = row;
pageVisibleState.show2LevelPage.value = true;
pageVisibleState.showPageType.edit = true;
}
async function deleteData(row: systemMenueType) {
ElMessageBox.confirm("确定要删除该菜单吗?", "警告", {
type: "error",
}).then(async () => {
let respone = await api().get("/system-menue/delete?id=" + row.id);
if (respone.code === 200) {
ElMessage.success("菜单删除成功!");
table.value.methods.setDataType("list");
} else {
ElMessage.error(respone.msg);
}
});
}
function search() {
table.value.methods.setDataType("search");
}
function resetSearch() {
resetParams(searchForm);
table.value.methods.setDataType("list");
ElMessage.success("重置成功!");
}
function handleDialogClose() {
pageVisibleState.show2LevelPage.value = false;
}
function addConfim() {
ElMessageBox.confirm("确定新增该条信息吗?", "提示", {
type: "warning",
}).then(async () => {
let res = await api().post("/system-menue/add", currentMenue.value);
if (res.code === 200) {
ElMessage.success("添加成功");
pageVisibleState.show2LevelPage.value = false;
table.value.methods.setDataType("list");
}
});
}
function getUserData(newVal: systemMenueType) {
currentMenue.value = { ...newVal };
}
async function editConfim() {
ElMessageBox.confirm("确认该条信息吗?", "提示", {
type: "warning",
}).then(async () => {
let respone = await api().post("/system-menue/update", currentMenue.value);
if (respone.code == 200) {
ElMessage.success("修改成功!");
pageVisibleState.show2LevelPage.value = false;
table.value.methods.setDataType("list");
}
});
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,81 @@
<template>
<el-form
v-loading="loading"
element-loading-text="初始化数据中..."
:model="menueForm"
:inline="true"
label-suffix=":"
label-width="80px">
<el-form-item label="菜单名">
<el-input v-model="menueForm.name"></el-input>
</el-form-item>
<el-form-item label="路径">
<el-input v-model="menueForm.path"></el-input>
</el-form-item>
<el-form-item label="所属">
<el-select v-model="menueForm.parentId">
<el-option label="顶级" :value="0"></el-option>
<el-option
:label="item.name"
:value="((item.id) as number)"
v-for="(item, index) in menues"
:key="index">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="图标">
<el-input v-model="menueForm.icon"></el-input>
</el-form-item>
<el-form-item label="是否显示">
<el-switch v-model="menueForm.show"></el-switch>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { onMounted, ref, watch } from "vue";
import { systemMenueType } from "@/types/systemMenue";
import api from "@/lib/request";
const props = defineProps<{
data?: systemMenueType;
executeType?: string;
}>();
const emits = defineEmits(["updateData"]);
let menues = ref<systemMenueType[]>([]);
let loading = ref(true);
let menueForm = ref<systemMenueType>({
name: "",
path: "",
parentId: 0,
icon: "",
show: true,
});
if (props.executeType === "edit") {
menueForm.value = { ...props.data };
}
watch(
() => menueForm,
(newVal) => {
emits("updateData", newVal.value);
},
{ deep: true }
);
onMounted(async () => {
let respone = await api().get("/system-menue/list?all=true");
menues.value = respone.data.list;
loading.value = false;
});
</script>
<style lang="scss" scoped>
.el-input__wrapper {
width: 200px;
}
.el-select {
width: 192px;
}
</style>

View File

@@ -0,0 +1,47 @@
<template>
<el-descriptions
title="个人信息"
style="padding: 15px"
border
size="large"
:column="2">
<el-descriptions-item>
<template #label> 菜单名 </template>{{ menue.name }}
</el-descriptions-item>
<el-descriptions-item>
<template #label> 路径 </template>{{ menue.path }}
</el-descriptions-item>
<el-descriptions-item>
<template #label> 所属菜单 </template
>{{ menue.parentId }}</el-descriptions-item
>
<el-descriptions-item>
<template #label> 图标 </template>
<el-icon>
<component :is="menue.icon" />
</el-icon>
</el-descriptions-item>
<el-descriptions-item>
<template #label>是否显示 </template>
<el-tag type="success" v-if="menue.show">显示</el-tag>
<el-tag type="danger" v-else>隐藏</el-tag>
</el-descriptions-item>
</el-descriptions>
</template>
<script lang="ts" setup>
import { systemMenueType } from "@/types/systemMenue";
const props = defineProps<{
data: systemMenueType;
}>();
const menue = props.data;
</script>
<style lang="scss" scoped>
:deep(.el-descriptions__label) {
display: flex;
align-items: center;
.el-icon {
margin: 0 5px;
}
}
</style>

View File

@@ -0,0 +1,212 @@
<template>
<div>
<el-row>
<baseTableHeader
title="角色管理"
@resetSearch="resetSearch"
@search="search">
<template #content>
<el-form
:model="searchForm"
:inline="true"
label-position="right"
:label-suffix="''">
<!-- <el-form-item label="所属角色">
<el-select
v-model="searchForm.roleValue"
clearable
filterable
placeholder="请选择角色"
style="width: 150px">
<el-option
v-for="item in menues"
:key="item.id"
:label="item.name"
:value="String(item.id)">
</el-option>
</el-select>
</el-form-item> -->
<el-form-item label="角色名">
<el-input
v-model="searchForm.name"
placeholder="请输入角色名"></el-input>
</el-form-item>
</el-form>
</template>
<template #operateBtns>
<el-button size="small" type="primary" @click="add">
<el-icon> <CirclePlus /> </el-icon>新增
</el-button>
</template>
</baseTableHeader>
</el-row>
<el-card style="margin-top: 8px">
<base-table :option="tableOption" ref="table">
<el-table-column type="index"></el-table-column>
<el-table-column prop="name" label="角色名" align="center" />
<el-table-column prop="createDate" label="创建时间" align="center" />
<el-table-column
prop="updateData"
label="最后更改时间"
align="center" />
<!-- <el-table-column label="角色状态" align="center">
<template #default="scope">
<el-switch
v-model="scope.row.roleState"
size="large"
width="65px"
inline-prompt
active-text="启用"
:active-value="1"
:inactive-value="0"
@change="roleStateChange"
inactive-text="禁用" />
</template>
</el-table-column> -->
<el-table-column #default="scope" label="操作" align="center">
<el-button size="small" type="primary" @click="examine(scope.row)"
>查看</el-button
>
<el-button size="small" type="warning" @click="update(scope.row)"
>修改</el-button
>
<el-button size="small" type="danger" @click="deleteData(scope.row)"
>删除</el-button
>
</el-table-column>
</base-table>
</el-card>
<base-curd-dialog
v-model="pageVisibleState.show2LevelPage.value"
:title="pageVisibleState.dialogTitle.value"
width="50%"
:before-close="handleDialogClose"
@addConfim="addConfim"
@editConfim="editConfim"
@closeConfirm="handleDialogClose"
:showPageType="pageVisibleState.showPageType">
<template #content>
<addOrEdite
:data="currentRole"
@updateData="getRoleData"
:executeType="pageVisibleState.executeType.value"
v-if="
pageVisibleState.showPageType.edit ||
pageVisibleState.showPageType.add
"></addOrEdite>
<roleView :data="currentRole" v-if="pageVisibleState.showPageType.view">
</roleView>
</template>
</base-curd-dialog>
</div>
</template>
<script lang="ts" setup>
import { reactive, onMounted, ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import pageVisible from "@/components/curdDialog/pageVisibleState";
import api from "@/lib/request";
import { roleType } from "@/types/role";
import roleView from "./page/view.vue";
import addOrEdite from "./page/addOrEdit.vue";
import { systemMenueType } from "@/types/systemMenue";
import { tableOptionType } from "@/types/table";
const pageVisibleState = new pageVisible({
add: "新增角色",
edit: "角色编辑",
view: "角色查看",
});
let currentRole = ref<roleType>({});
let menues = ref<systemMenueType[]>();
const searchForm = reactive({
roleValue: "",
name: "",
});
let table = ref();
let tableOption = ref<tableOptionType>({
url: "/role/list",
searchUrl: "/role/query",
searchParams: searchForm,
executeType: "list",
});
onMounted(async () => {
let respone = await api().get("/system-menue/list?all=true");
menues.value = respone.data.list;
});
function examine(row: roleType) {
currentRole.value = row;
pageVisibleState.show2LevelPage.value = true;
pageVisibleState.showPageType.view = true;
}
function update(row: roleType) {
currentRole.value = row;
pageVisibleState.show2LevelPage.value = true;
pageVisibleState.showPageType.edit = true;
}
async function deleteData(row: roleType) {
ElMessageBox.confirm("确定要删除该用户吗?", "警告", {
type: "error",
}).then(async () => {
let respone = await api().get("/role/delete?id=" + row.id);
if (respone.code === 200) {
ElMessage.success("角色删除成功!");
table.value.methods.setDataType("list");
} else {
ElMessage.error(respone.msg);
}
});
}
function search() {
table.value.methods.setDataType("search");
}
function resetSearch() {
searchForm.roleValue = "";
searchForm.name = "";
table.value.methods.setDataType("list");
ElMessage.success("重置成功!");
}
function handleDialogClose() {
pageVisibleState.show2LevelPage.value = false;
}
function addConfim() {
ElMessageBox.confirm("确定增加数据吗?", "提示", { type: "warning" }).then(
async () => {
let res = await api().post("/role/add", currentRole.value);
if (res.code === 200) {
ElMessage.success("添加成功");
pageVisibleState.show2LevelPage.value = false;
table.value.methods.setDataType("list");
}
}
);
}
function editConfim() {
ElMessageBox.confirm("确定要修改数据吗?", "提示", { type: "warning" }).then(
async () => {
let respone = await api().post("/role/update", currentRole.value);
if (respone.code == 200) {
ElMessage.success("修改成功!");
pageVisibleState.show2LevelPage.value = false;
table.value.methods.setDataType("list");
}
}
);
}
function getRoleData(newVal: roleType) {
currentRole.value = { ...newVal };
}
function add() {
pageVisibleState.show2LevelPage.value = true;
pageVisibleState.showPageType.add = true;
}
function roleStateChange(state: any) {}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,75 @@
<template>
<el-form v-loading="loading" element-loading-text="初始化数据中..." :model="roleForm" :inline="true" label-suffix=":"
label-width="80px">
<el-form-item label="角色名">
<el-input v-model="roleForm.name"></el-input>
</el-form-item>
<el-form-item label="是否启用">
<el-switch v-model="roleForm.roleState" :active-value="1" :inactive-value="0"></el-switch>
</el-form-item>
<el-form-item style="width: 100%;" label="权限">
<el-tree :data="roles" :props="treeProps" show-checkbox default-expand-all node-key="id"
:default-checked-keys="roleForm.values?.split(',')" @check="handleCheckChange" />
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { onMounted, ref, watch } from "vue";
import { systemMenueType } from "@/types/systemMenue";
import api from "@/lib/request";
import { roleType } from "@/types/role";
const props = defineProps<{
data?: roleType;
executeType?: string;
}>();
const emits = defineEmits(["updateData"]);
let roles = ref<systemMenueType[]>([]);
let loading = ref(true);
let treeProps = { label: "name", children: "children" };
let auths = ref<string[]>([]);
let roleForm = ref<roleType>({
name: "",
roleState: 1,
values: ''
});
onMounted(async () => {
let respone = await api().get('/system-menue/list');
roles.value = respone.data.list;
loading.value = false;
if (props.executeType === "edit") {
roleForm.value = { ...props.data };
auths.value = roleForm.value.values?.split(',') as string[];
}
watch(
() => roleForm,
(newVal) => {
emits("updateData", newVal.value);
},
{ deep: true }
);
})
function handleCheckChange(val: string[], checkVal: any) {
auths.value = checkVal.checkedKeys.filter(item => String(item));
roleForm.value.values = auths.value.join(',');
}
</script>
<style lang="scss" scoped>
.el-input__wrapper {
width: 200px;
}
.el-select {
width: 192px;
}
</style>
@/types/systemMenue

View File

@@ -0,0 +1,61 @@
<template>
<el-descriptions
title="角色信息"
style="padding: 15px"
border
size="large"
:column="2">
<el-descriptions-item>
<template #label> 角色名 </template>{{ theRoleInfo.name }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>是否启用 </template>
<el-tag type="success" v-if="theRoleInfo.roleState">启用</el-tag>
<el-tag type="danger" v-else>禁用</el-tag>
</el-descriptions-item>
</el-descriptions>
<el-row>
<el-col>
<label>角色权限</label>
<el-tree
:data="roles"
:props="treeProps"
show-checkbox
node-key="id"
:default-checked-keys="theRoleInfo.values?.split(',')"
default-expand-all />
</el-col>
</el-row>
</template>
<script lang="ts" setup>
import { systemMenueType } from "@/types/systemMenue";
import { roleType } from "@/types/role";
import { ref, onMounted } from "vue";
import api from "@/lib/request";
const props = defineProps<{
data: roleType;
}>();
const role = props.data;
let roles = ref<systemMenueType[]>([]);
let loading = ref(true);
let treeProps = { label: "name", children: "children" };
let theRoleInfo = ref<roleType>({});
theRoleInfo.value = { ...props.data };
onMounted(async () => {
let respone = await api().get("/system-menue/list");
roles.value = respone.data.list;
loading.value = false;
});
</script>
<style lang="scss" scoped>
:deep(.el-descriptions__label) {
display: flex;
align-items: center;
.el-icon {
margin: 0 5px;
}
}
</style>

View File

@@ -0,0 +1,279 @@
<template>
<div>
<el-row>
<baseTableHeader
title="用户管理"
@resetSearch="resetSearch"
@search="search">
<template #content>
<el-form :model="searchForm" :inline="true" label-position="right">
<el-form-item label="姓名:">
<el-input
v-model="searchForm.name"
placeholder="请输入姓名"></el-input>
</el-form-item>
<el-form-item label="性别:">
<el-radio-group v-model="searchForm.sex">
<el-radio value="男"></el-radio>
<el-radio value="女"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="手机号:">
<el-input
v-model="searchForm.phone"
placeholder="请输入手机号"></el-input>
</el-form-item>
</el-form>
</template>
<template #operateBtns>
<el-button size="small" type="primary" @click="add">
<template #icon>
<el-icon>
<CirclePlus />
</el-icon> </template
>新增</el-button
>
</template>
</baseTableHeader>
</el-row>
<el-card style="margin-top: 8px">
<base-table :option="tableOption" ref="table">
<template #colunm>
<el-table-column type="index" width="50"></el-table-column>
<el-table-column
prop="name"
label="姓名"
width="180"
align="center" />
<el-table-column prop="sex" label="性别" align="center" />
<el-table-column prop="role" label="所属角色" align="center">
<template #default="scope">
{{
roleList?.find(
(item) => Number(item.id) === Number(scope.row.role)
)?.name || "无"
}}
</template>
</el-table-column>
<el-table-column label="账户状态" align="center">
<template #default="scope">
<el-switch
v-model="scope.row.userState"
size="large"
width="65px"
inline-prompt
active-text="启用"
@change="stateVal => userStatChange(stateVal as boolean, scope.row)"
:active-value="1"
:inactive-value="0"
inactive-text="禁用" />
</template>
</el-table-column>
<el-table-column prop="phone" label="手机号" align="center" />
<el-table-column
prop="createDate"
label="创建时间"
width="180"
align="center" />
<el-table-column #default="scope" width="300">
<el-button size="small" type="primary" @click="view(scope.row)"
>查看</el-button
>
<el-button size="small" type="warning" @click="edit(scope.row)"
>修改</el-button
>
<el-button size="small" type="danger" @click="deleteData(scope.row)"
>删除</el-button
>
<el-button size="small" type="danger" @click="resetPwd(scope.row)"
>重置密码</el-button
>
</el-table-column>
</template>
</base-table>
</el-card>
<base-curd-dialog
v-model="pageVisibleState.show2LevelPage.value"
:title="pageVisibleState.dialogTitle.value"
width="50%"
:before-close="handleDialogClose"
@addConfim="addConfim"
@editConfim="editConfim"
@closeConfirm="handleDialogClose"
:showPageType="pageVisibleState.showPageType">
<template #content>
<userAddOrEdit
:executeType="pageVisibleState.executeType.value"
v-if="
pageVisibleState.showPageType.edit ||
pageVisibleState.showPageType.add
"
:data="(currentUser as userType)"
@updateData="getUserData">
</userAddOrEdit>
<userView
v-if="pageVisibleState.showPageType.view"
:data="(currentUser as userType)">
</userView>
</template>
</base-curd-dialog>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, onMounted, watch } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { resetParams } from "@/util/globalMethods";
import { userType } from "@/types/user";
import { globalState } from "@/store";
import userAddOrEdit from "./page/userAddOrEdit.vue";
import userView from "@/pages/publicPages/user/userView.vue";
import api from "@/lib/request";
import pageVisible from "@/components/curdDialog/pageVisibleState";
import { roleDataList } from "@/lib/api/publicApiList";
import { roleType } from "@/types/role";
import { tableOptionType } from "@/types/table";
const pageVisibleState = new pageVisible({
add: "新增用户",
edit: "用户编辑",
view: "信息查看",
});
const globalStateStore = globalState();
const searchForm = reactive({
name: "",
sex: "",
phone: "",
});
let currentUser = ref<userType>({});
let roleList = ref<roleType[]>();
let table = ref();
let tableOption = ref<tableOptionType>({
url: "/user/list",
searchUrl: "/user/query",
searchParams: searchForm,
executeType: "list",
});
onMounted(async () => {
roleList.value = await roleDataList();
});
function view(row: userType) {
pageVisibleState.showPageType.view = true;
currentUser.value = row;
}
function edit(row: userType) {
pageVisibleState.showPageType.edit = true;
currentUser.value = row;
}
function add() {
pageVisibleState.showPageType.add = true;
}
function addConfim() {
ElMessageBox.confirm("确定新增该条信息吗?", "提示", {
type: "warning",
}).then(() => {
globalStateStore.setGlobalLoadingShow(true, "正在提交信息...");
api()
.post("/user/add", { ...currentUser.value })
.then((res) => {
if (res.code === 200) {
pageVisibleState.showPageType.add = false;
pageVisibleState.show2LevelPage.value = false;
ElMessage.success("新增成功!");
table.value.methods.setDataType("list");
} else {
ElMessage.error(res.msg);
}
globalStateStore.setGlobalLoadingShow(false);
});
});
}
function getUserData(newVal: any) {
currentUser.value = { ...newVal.value };
}
function editConfim() {
ElMessageBox.confirm("确定更改数据吗?", "提示", { type: "warning" }).then(
() => {
api()
.post("/user/update", currentUser.value)
.then((res) => {
if (res.code === 200) {
ElMessage.success("更改成功!");
pageVisibleState.show2LevelPage.value = false;
table.value.methods.setDataType("list");
}
});
}
);
}
function deleteData(row: userType) {
ElMessageBox.confirm("确定要删除该用户吗?", "警告", {
type: "error",
}).then(() => {
api()
.get("/user/delete?id=" + row.id)
.then((res) => {
if (res.code === 200) {
ElMessage.success(` ${row.name} 删除成功!`);
table.value.methods.setDataType("list");
} else {
ElMessage.warning(res.msg);
}
});
});
}
function search() {
table.value.methods.setDataType("search");
}
function resetSearch() {
resetParams(searchForm);
table.value.methods.setDataType("reset");
ElMessage.success("重置成功!");
}
function resetPwd(row: userType) {
ElMessageBox.confirm("确定要重置该用户的密码吗?", "警告", {
type: "error",
}).then(() => {
api()
.get("/user/resetPwd?id=" + row.id)
.then((res) => {
if (res.code === 200) {
ElMessageBox.confirm(
` ${row.name} 的密码重置成功为123456请告知用户`,
"提示",
{ type: "success" }
);
} else {
ElMessage.error(res.msg);
}
});
ElMessage.success("密码重置成功!");
});
}
function handleDialogClose() {
pageVisibleState.show2LevelPage.value = false;
}
function userStatChange(state: boolean, data: userType) {
api()
.post("/user/update", {
userState: state ? 1 : 0,
id: data.id,
})
.then(() => {
ElMessage.success("用户状态更改成功!");
table.value.methods.setDataType("list");
});
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,143 @@
<template>
<el-form
:model="user"
label-width="100px"
:inline="true"
label-suffix=""
v-loading="loading"
element-loading-text="正在初始化数据...">
<el-form-item label="姓名">
<el-input :prefix-icon="User" v-model="user.name" placeholder="姓名">
</el-input>
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="user.sex">
<el-radio value="男"></el-radio>
<el-radio value="女"></el-radio>
<el-radio value="保密">保密</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="电话">
<el-input
:prefix-icon="Iphone"
v-model="user.phone"
placeholder="电话"></el-input>
</el-form-item>
<el-form-item label="所属角色">
<el-select
v-model="user.role"
clearable
filterable
placeholder="请选择角色"
style="width: 230px">
<el-option
v-for="item in roleList"
:key="item.id"
:label="item.name"
:value="String(item.id)"></el-option>
</el-select>
</el-form-item>
<el-form-item label="生日">
<el-date-picker
format="YYYYH-MM-DD"
v-model="user.birthday"
type="date"
placeholder="生日"
size="large" />
</el-form-item>
<el-form-item label="所在区域">
<el-cascader
:options="pcaTextArrData"
v-model="selectedOptions"
@change="pcaChange"></el-cascader>
</el-form-item>
<el-form-item label="详细地址">
<el-input
:prefix-icon="Location"
v-model="user.address"
placeholder="请输入详细地址"></el-input>
</el-form-item>
<el-form-item label="账号状态">
<el-switch
v-model="user.userState"
:active-value="1"
:inactive-value="0"
active-text="启用"
width="65px"
inline-prompt
inactive-text="禁用"></el-switch>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { onMounted, ref, watch } from "vue";
import { userType } from "@/types/user";
// @ts-ignore
import { pcaTextArr } from "element-china-area-data";
import { Avatar, Location, Iphone, User } from "@element-plus/icons-vue";
import { roleType } from "@/types/role";
import { roleDataList } from "@/lib/api/publicApiList";
const pcaTextArrData = ref(pcaTextArr);
const selectedOptions = ref([]);
const props = defineProps<{
data?: userType;
executeType?: string;
}>();
const emit = defineEmits(["updateData"]);
let loading = ref(true);
let roleList = ref<roleType[]>();
let user = ref<userType>({
createDate: "",
name: "",
sex: "男",
phone: "",
userState: 1,
role: "",
birthday: "",
province: "",
city: "",
area: "",
address: "",
});
onMounted(async () => {
roleList.value = await roleDataList();
watch(
() => user,
(newVal) => {
emit("updateData", newVal);
},
{ deep: true }
);
loading.value = false;
});
if (props.executeType === "edit") {
user.value = { ...props.data };
selectedOptions.value = [
user.value.province as never,
user.value.city as never,
user.value.area as never,
];
}
function pcaChange(data: { [key: string]: any }) {
user.value.province = data[0];
user.value.city = data[1];
user.value.area = data[2];
}
</script>
<style lang="scss" scoped>
:deep(.el-input) {
width: 230px;
}
:deep(.el-form-item) {
margin-right: 0;
}
</style>

View File

@@ -0,0 +1,267 @@
<template>
<div>
<el-row :gutter="15">
<!-- <el-col :span="12">
<el-card style="height: 500px" class="info-show">
<ul class="info-list">
<li class="infor-list-item">
<label for="">姓名</label>
<span>{{ userInforStore.userInfor.name }}</span>
</li>
<li class="infor-list-item">
<label for="">性别</label>
<span>{{ userInforStore.userInfor.sex }}</span>
</li>
<li class="infor-list-item">
<label for="">手机号</label>
<span>{{ userInforStore.userInfor.phone }}</span>
</li>
<li class="infor-list-item">
<label for="">生日</label>
<span>{{
dayjs(userInforStore.userInfor.birthday).format("YYYY-MM-DD")
}}</span>
</li>
<li class="infor-list-item">
<label for="">地址</label>
<span>{{
userInforStore.userInfor.province +
"/" +
userInforStore.userInfor.city +
"/" +
userInforStore.userInfor.area +
"/" +
userInforStore.userInfor.address
}}</span>
</li>
</ul>
<el-divider />
</el-card>
</el-col> -->
<el-col :span="24" v-loading="loading">
<el-form :model="tempUserInfor" label-width="120px">
<el-form-item label="姓名:">
<el-input
v-model="tempUserInfor.name"
placeholder="请输入你的姓名"></el-input>
</el-form-item>
<el-form-item label="性别:">
<el-radio-group v-model="tempUserInfor.sex">
<el-radio value="男"></el-radio>
<el-radio value="女"></el-radio>
<el-radio value="保密">保密</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="生日:">
<el-date-picker
v-model="tempUserInfor.birthday"
type="date"
placeholder="选择你的生日呀"
size="large" />
</el-form-item>
<el-form-item label="手机号:">
<el-input
v-model="tempUserInfor.phone"
placeholder="请输入你的手机号"></el-input>
</el-form-item>
<el-form-item label="所在区域">
<el-cascader
:options="pcaTextArrData"
v-model="selectedOptions"
@change="pcaChange"></el-cascader>
</el-form-item>
<el-form-item label="详细地址:">
<el-input
v-model="tempUserInfor.address"
placeholder="请输入你的地址"></el-input>
</el-form-item>
<el-form-item label="密码:">
<el-input
v-model="tempUserInfor.pwd"
placeholder="请输入你的新密码"></el-input>
</el-form-item>
<el-form-item>
<el-button
type="primary"
size="large"
style="width: 60%"
@click="updatePersonInfo"
>更新我的信息</el-button
>
</el-form-item>
</el-form>
</el-col>
<!-- <el-col :span="24" style="margin-top: 15px">
<el-card>
<baseEcharts :option="lineStack" style="height: 300px"></baseEcharts>
</el-card>
</el-col> -->
</el-row>
</div>
</template>
<script lang="ts" setup>
import { reactive, onMounted, ref } from "vue";
import { ElMessage } from "element-plus";
import { userInfor } from "@/store/user/user";
import dayjs from "dayjs";
import { pcaTextArr } from "element-china-area-data";
import { userType } from "@/types/user";
import api from "@/lib/request";
import { md5 } from "js-md5";
const userInforStore = userInfor();
const pcaTextArrData = ref(pcaTextArr);
let tempUserInfor = ref<userType>({ ...userInforStore.userInfor, pwd: "" });
const selectedOptions = ref([
tempUserInfor.value.province,
tempUserInfor.value.city,
tempUserInfor.value.area,
]);
const lineStack = reactive({
title: {
text: "Stacked Line",
},
tooltip: {
trigger: "axis",
},
legend: {
data: ["Email", "Union Ads", "Video Ads", "Direct", "Search Engine"],
},
grid: {
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true,
},
toolbox: {
feature: {
saveAsImage: {},
},
},
xAxis: {
type: "category",
boundaryGap: false,
data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
},
yAxis: {
type: "value",
},
series: [
{
name: "Email",
type: "line",
stack: "Total",
data: [120, 132, 101, 134, 90, 230, 210],
},
{
name: "Union Ads",
type: "line",
stack: "Total",
data: [220, 182, 191, 234, 290, 330, 310],
},
{
name: "Video Ads",
type: "line",
stack: "Total",
data: [150, 232, 201, 154, 190, 330, 410],
},
{
name: "Direct",
type: "line",
stack: "Total",
data: [320, 332, 301, 334, 390, 330, 320],
},
{
name: "Search Engine",
type: "line",
stack: "Total",
data: [820, 932, 901, 934, 1290, 1330, 1320],
},
],
});
const loading = ref(false);
const infoList = ref([
{
label: "姓名",
value: "李嘉图",
},
{
label: "性别",
value: "男",
},
{
label: "生日",
value: "1996/08/17",
},
{
label: "手机号",
value: "1823079420",
},
{
label: "地址",
value: "中国贵州省贵阳市",
},
]);
function updatePersonInfo() {
loading.value = true;
let sendData = { ...tempUserInfor.value };
if (tempUserInfor.value.pwd) {
sendData.pwd = md5(tempUserInfor.value.pwd).toLocaleUpperCase();
}
api()
.post("/user/update", sendData)
.then((res) => {
if (res.code === 200) {
userInfor().setLoginState(res.data.userInfo);
loading.value = false;
ElMessage.success("用户信息更新成功!");
} else {
loading.value = false;
}
});
}
function pcaChange(data: { [key: string]: any }) {
tempUserInfor.value.province = data[0];
tempUserInfor.value.city = data[1];
tempUserInfor.value.area = data[2];
}
</script>
<style lang="scss" scoped>
.info-show {
display: flex;
justify-content: center;
.info-list {
display: flex;
flex-direction: column;
justify-content: center;
.infor-list-item {
display: flex;
justify-content: space-around;
label {
width: 30%;
text-align: right;
}
span {
width: 70%;
}
}
}
.sign {
display: inline-block;
color: rgba($color: #000000, $alpha: 0.5);
// transform: scale(0.8);
font-size: 0.7rem;
text-align: center;
width: 100%;
}
}
:deep(.el-divider--horizontal) {
margin: 8px 0;
}
</style>