初始版本,目前线上可用
This commit is contained in:
52
frontEnd/src/components/baseEcharts/baseEcharts.vue
Normal file
52
frontEnd/src/components/baseEcharts/baseEcharts.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div ref="echartDom" style="width: 100%; height: 100%"></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as echarts from "echarts";
|
||||
import { ref, onMounted, onBeforeUnmount, nextTick } from "vue";
|
||||
let echartDom = ref(<HTMLElement | null>null);
|
||||
let emits = defineEmits(["update"]);
|
||||
let props = defineProps({
|
||||
option: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
title: {
|
||||
text: "ECharts 入门示例",
|
||||
},
|
||||
tooltip: {},
|
||||
xAxis: {
|
||||
data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"],
|
||||
},
|
||||
yAxis: {},
|
||||
series: [
|
||||
{
|
||||
name: "销量",
|
||||
type: "bar",
|
||||
data: [5, 20, 36, 10, 10, 20],
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
function echartResize() {
|
||||
let chartInstance = echarts.getInstanceByDom(echartDom.value as HTMLElement);
|
||||
chartInstance?.resize();
|
||||
}
|
||||
let resizeObserver = new ResizeObserver(() => {
|
||||
echartResize();
|
||||
});
|
||||
onMounted(() => {
|
||||
let chartInstance = echarts.init(echartDom.value);
|
||||
chartInstance.setOption(props.option);
|
||||
|
||||
resizeObserver.observe(echartDom.value as HTMLElement);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
resizeObserver.disconnect();
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
34
frontEnd/src/components/curdDialog/baseCurdDialog.vue
Normal file
34
frontEnd/src/components/curdDialog/baseCurdDialog.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<baseDialog v-bind="$attrs">
|
||||
<slot name="content"> </slot>
|
||||
<slot></slot>
|
||||
<template #footer>
|
||||
<div class="flex-center">
|
||||
<el-button @click="emits('addConfim')" v-if="showPageType.add">{{
|
||||
confirmText || "确定新增"
|
||||
}}</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="emits('editConfim')"
|
||||
v-if="showPageType.edit"
|
||||
>确认修改</el-button
|
||||
>
|
||||
<slot name="footer"></slot>
|
||||
<el-button type="danger" @click="emits('closeConfirm')">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</baseDialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted } from "vue";
|
||||
const props = defineProps<{
|
||||
/**
|
||||
* 根据增删改查的字段控制按钮的显示和隐藏
|
||||
*/
|
||||
showPageType: { [key: string]: boolean };
|
||||
confirmText?: string;
|
||||
}>();
|
||||
const emits = defineEmits(["addConfim", "editConfim", "closeConfirm"]);
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
76
frontEnd/src/components/curdDialog/pageVisibleState.ts
Normal file
76
frontEnd/src/components/curdDialog/pageVisibleState.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { ref, reactive, watch } from "vue"
|
||||
interface titleDict {
|
||||
add: string;
|
||||
edit: string;
|
||||
view: string;
|
||||
}
|
||||
|
||||
class pageVisible {
|
||||
/**
|
||||
* 需要操作的页面类型
|
||||
*/
|
||||
executeType = ref('');
|
||||
/**
|
||||
* 实际渲染的二级页面或者dialog的标题
|
||||
*/
|
||||
dialogTitle = ref("");
|
||||
/**
|
||||
* 是否显示二级页面或者dialog
|
||||
*/
|
||||
show2LevelPage = ref(false); // 是否显示二级页面或者dialog
|
||||
/**
|
||||
* 标题的字典,用于在控制页面显示的时候映射对应的标题
|
||||
*/
|
||||
dialogTitleDict = ref<{ [key: string]: any }>({});
|
||||
/**
|
||||
* 显示页面的类型
|
||||
*/
|
||||
showPageType = reactive({
|
||||
add: false,
|
||||
edit: false,
|
||||
view: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* @param dialogTitleDict 传入一个对象,有三个字段,分别是add、edit和view页面的标题<br>
|
||||
* { <br>
|
||||
* add: '添加页面的标题',<br>
|
||||
* view: '查看页面的标题',<br>
|
||||
* edit: '编辑页面的标题'<br>
|
||||
* }
|
||||
*/
|
||||
constructor(dialogTitleDict: titleDict) {
|
||||
this.dialogTitleDict.value = dialogTitleDict;
|
||||
|
||||
// 根据展示的类型更换标题
|
||||
watch(
|
||||
() => this.showPageType,
|
||||
(newVal) => {
|
||||
for (let [key, value] of Object.entries(newVal)) {
|
||||
if (value) {
|
||||
this.show2LevelPage.value = true;
|
||||
this.dialogTitle.value = this.dialogTitleDict.value[key];
|
||||
if(['edit', 'add'].includes(key)) this.executeType.value = key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
// 页面关闭,展示的类型全部不显示
|
||||
watch(
|
||||
() => this.show2LevelPage,
|
||||
(newVal) => {
|
||||
if (!newVal.value) {
|
||||
this.showPageType.add = false;
|
||||
this.showPageType.edit = false;
|
||||
this.showPageType.view = false;
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default pageVisible;
|
||||
17
frontEnd/src/components/dialog/baseDialog.vue
Normal file
17
frontEnd/src/components/dialog/baseDialog.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-bind="$attrs"
|
||||
center
|
||||
class="base-dialog"
|
||||
width="70%"
|
||||
align-center
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false">
|
||||
<slot name="header"></slot>
|
||||
<slot name="default"></slot>
|
||||
<slot name="footer"></slot>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
<style lang="scss" scoped></style>
|
||||
30
frontEnd/src/components/guideList/guideList.vue
Normal file
30
frontEnd/src/components/guideList/guideList.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-select
|
||||
v-model="guide"
|
||||
style="width: 150px"
|
||||
filterable
|
||||
placeholder="请选择引导员">
|
||||
<el-option
|
||||
v-for="item in guideOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value" />
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import request from "@/lib/request";
|
||||
import { ref } from "vue";
|
||||
|
||||
let guide = defineModel({ default: "" });
|
||||
const guideOptions = ref<guideOption[]>([]);
|
||||
|
||||
request()
|
||||
.get("public/guide")
|
||||
.then((res) => {
|
||||
guideOptions.value = res.data;
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
30
frontEnd/src/components/inforCard/inforCard.vue
Normal file
30
frontEnd/src/components/inforCard/inforCard.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div>
|
||||
<base-line-title :title="title"></base-line-title>
|
||||
<div class="infor-card-content">
|
||||
<slot name="content"></slot><slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
|
||||
const title = props.title;
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.infor-card-content {
|
||||
padding: 15px;
|
||||
}
|
||||
:deep(.base-line-title) {
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
background-color: #f1f3f4;
|
||||
border-bottom: none;
|
||||
}
|
||||
</style>
|
||||
43
frontEnd/src/components/lineTitle/baseLineTitle.vue
Normal file
43
frontEnd/src/components/lineTitle/baseLineTitle.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div class="base-line-title">
|
||||
<span class="left-border"></span>
|
||||
{{ props.title }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, onMounted, ref } from "vue";
|
||||
let props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.base-line-title {
|
||||
font-size: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 1.8rem;
|
||||
.left-border {
|
||||
background-color: var(--el-color-primary);
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
width: 4px;
|
||||
margin-right: 10px;
|
||||
border-radius: 12px;
|
||||
animation: leftBorder 0.6s ease-in;
|
||||
animation-fill-mode: forwards;
|
||||
// animation-delay: 1s;
|
||||
}
|
||||
}
|
||||
@keyframes leftBorder{
|
||||
0% {
|
||||
height: 0;
|
||||
}
|
||||
100%{
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
304
frontEnd/src/components/printPage/printRetailPage.vue
Normal file
304
frontEnd/src/components/printPage/printRetailPage.vue
Normal file
@@ -0,0 +1,304 @@
|
||||
<!-- PrintTemplate.vue -->
|
||||
<template>
|
||||
<div>
|
||||
<base-dialog v-model="showPrint" title="打印预览" wdith="210mm">
|
||||
<div v-loading="showLoading" element-loading-text="正在准备打印数据...">
|
||||
<div ref="printContainer" class="print-container" id="print-container">
|
||||
<!-- 告知书部分 -->
|
||||
<div class="title">文明节俭治丧告知书</div>
|
||||
<div class="content">尊敬的服务对象:</div>
|
||||
<div class="content" style="text-indent: 7rem">
|
||||
您好!感谢您的信任,选择到我司办理治丧业务。为深化殡葬改革,推进移风易俗,现就推行文明节俭治丧有关事宜向您倡导如下:
|
||||
</div>
|
||||
<div
|
||||
class="content"
|
||||
style="text-indent: 5rem; padding-right: 15px"
|
||||
id="first-content">
|
||||
一、文明治丧,节俭办事,革除陈规陋习,抵制封建迷信,提倡厚养薄葬,弘扬精神美德;
|
||||
</div>
|
||||
<div class="content" style="text-indent: 5rem; padding-right: 15px">
|
||||
二、我司为满足个性化需求,提供优质的非基本服务和丧葬用品,请您在自主选择时杜绝铺张浪费,树立文明新风。
|
||||
</div>
|
||||
|
||||
<!-- 服务清单表格 -->
|
||||
<div class="title">殡仪服务项目清单</div>
|
||||
<table class="service-table service-table-header">
|
||||
<tr>
|
||||
<td style="width: 60pt">逝者姓名</td>
|
||||
<td style="width: 90pt">
|
||||
{{ deceased.name || deceased.deceased?.name }}
|
||||
</td>
|
||||
<td style="width: 60pt">逝者年龄</td>
|
||||
<td style="width: 70pt">
|
||||
{{ deceased.age ?? deceased.deceased?.age }}
|
||||
</td>
|
||||
<td style="width: 50pt">购买人</td>
|
||||
<td style="width: 80pt">
|
||||
{{ deceased.familyName || deceased.deceased?.familyName }}
|
||||
</td>
|
||||
<td style="width: 80pt">购买人电话</td>
|
||||
<td>
|
||||
{{ deceased.familyPhone ?? deceased.deceased?.familyPhone }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="service-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 40pt">序号</th>
|
||||
<th style="width: 80pt">服务项目</th>
|
||||
<th style="width: 35pt">单位</th>
|
||||
<th style="width: 35pt">数量</th>
|
||||
<th style="width: 60pt">收费标准</th>
|
||||
<th style="width: 60pt">小计</th>
|
||||
<th colspan="3">备注</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tr v-for="(item, index) in services" :key="index">
|
||||
<td>{{ index + 1 }}</td>
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.unit }}</td>
|
||||
<td>{{ item.quantity }}</td>
|
||||
<td>{{ item.price }}元</td>
|
||||
<td>{{ (item.quantity * item.price).toFixed(2) }}</td>
|
||||
<td colspan="3">{{ item.remark }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="5">总计:{{ total }}元</td>
|
||||
<td colspan="2">服务引导员</td>
|
||||
<td style="width: 125pt">
|
||||
{{ deceased.guide || deceased.deceased?.guide }}
|
||||
</td>
|
||||
<td style="width: 125px">家属签字</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="service-table service-table-footer">
|
||||
<tr>
|
||||
<td style="width: 60pt">消费确认</td>
|
||||
<td>
|
||||
<div>
|
||||
<span
|
||||
class="confirm-text no-border"
|
||||
v-for="text in confirmText">
|
||||
{{ text }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="confirm-text" v-for="text in confirmText2">
|
||||
{{ text }}
|
||||
</span>
|
||||
<span class="confirm-text">,</span>
|
||||
<span
|
||||
class="confirm-text"
|
||||
style="position: relative; top: 5px"></span>
|
||||
<span
|
||||
class="confirm-text"
|
||||
style="position: relative; top: 5px"></span>
|
||||
<span
|
||||
class="confirm-text"
|
||||
style="position: relative; top: 5px"></span>
|
||||
<span class="confirm-text">。</span>
|
||||
</div>
|
||||
</td>
|
||||
<td style="width: 125px"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="width: 100%; text-align: right">
|
||||
服务时间:{{ deceased.retail.checkoutDate.split(" ")[0] }}
|
||||
</div>
|
||||
<!-- 打印按钮 -->
|
||||
</div>
|
||||
<button class="print-btn" v-print="print">打印单据</button>
|
||||
</div>
|
||||
</base-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defaultRetail } from "@/defaultForm/defaultRetail";
|
||||
import { computed, nextTick, onMounted, ref, watch } from "vue";
|
||||
import api from "@/lib/request";
|
||||
|
||||
let printContainer = ref();
|
||||
let showLoading = ref(true);
|
||||
const print = {
|
||||
id: "print-container",
|
||||
beforeOpenCallback: () => {
|
||||
showLoading.value = true;
|
||||
},
|
||||
closeCallback() {
|
||||
showLoading.value = false;
|
||||
},
|
||||
};
|
||||
|
||||
let services = ref([]);
|
||||
|
||||
let total = computed(() => {
|
||||
const sum = services.value.reduce((sum, item) => {
|
||||
return sum + parseFloat(parseFloat(item.price).toFixed(2)) * item.quantity;
|
||||
}, 0);
|
||||
return parseFloat(sum.toFixed(2)).toFixed(2); // 最终结果保留两位小数
|
||||
});
|
||||
|
||||
let confirmText = "以上消费项目我已经认真审核,无异议。";
|
||||
let confirmText2 = "以上消费项目我已经认真审核";
|
||||
|
||||
let props = withDefaults(
|
||||
defineProps<{
|
||||
deceased: RegisForm;
|
||||
serviceUrl: string;
|
||||
}>(),
|
||||
{
|
||||
deceased: () => defaultRetail(),
|
||||
serviceUrl: "/deceased-retail/selected-service",
|
||||
}
|
||||
);
|
||||
let showPrint = defineModel();
|
||||
|
||||
watch(
|
||||
() => showPrint.value,
|
||||
() => {
|
||||
showLoading.value = true;
|
||||
api()
|
||||
.get(props.serviceUrl)
|
||||
.then((res) => {
|
||||
if (res.code === 200) {
|
||||
showLoading.value = false;
|
||||
services.value = res.data.list;
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.service-table-header {
|
||||
tr td {
|
||||
border-bottom: none !important;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
margin-bottom: -10pt !important;
|
||||
}
|
||||
.service-table-footer {
|
||||
tr td {
|
||||
border-top: none !important;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
margin-top: -10pt !important;
|
||||
}
|
||||
/* 基础样式 */
|
||||
.print-container {
|
||||
width: 230mm;
|
||||
margin: 0 auto;
|
||||
font-size: 14pt;
|
||||
color: #000 !important;
|
||||
font-family: Microsoft YaHei, "SimSun", serif !important;
|
||||
padding: 0 5mm;
|
||||
box-sizing: border-box; /* 包含边框计算 */
|
||||
size: auto;
|
||||
|
||||
/* 移除可能引起居中的属性 */
|
||||
display: block !important;
|
||||
align-items: unset !important;
|
||||
justify-content: unset !important;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 20pt;
|
||||
text-align: center;
|
||||
margin-top: 5pt;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.service-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 10pt 0;
|
||||
|
||||
td,
|
||||
th {
|
||||
border: 1pt solid #000; // 使用pt单位
|
||||
padding: 0.8mm 1.2mm; // 使用mm单位
|
||||
font-size: 10.5pt;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.total {
|
||||
font-weight: bold;
|
||||
margin: 20px 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.print-btn {
|
||||
display: block;
|
||||
width: 120px;
|
||||
margin: 30px auto;
|
||||
padding: 10px;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 打印媒体查询 */
|
||||
@media print {
|
||||
/* 重置body和html的布局方式 */
|
||||
body,
|
||||
html {
|
||||
display: block !important;
|
||||
height: auto !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* 确保打印容器从顶部开始 */
|
||||
.print-container {
|
||||
min-height: 297mm; /* A4纸高度 */
|
||||
display: block !important;
|
||||
vertical-align: top !important;
|
||||
position: relative;
|
||||
top: 0;
|
||||
transform: none !important;
|
||||
}
|
||||
@page {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* 移除可能存在的flex布局影响 */
|
||||
.el-dialog__wrapper,
|
||||
.el-dialog,
|
||||
.el-dialog__body {
|
||||
display: block !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
/* 移除对话框的padding */
|
||||
:deep(.el-dialog__body) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
.confirm-text {
|
||||
border: 1px solid #000;
|
||||
margin: 0 3px;
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 18px;
|
||||
}
|
||||
.no-border {
|
||||
border: 1px solid #fff;
|
||||
}
|
||||
:deep(.base-dialog .el-dialog__header) {
|
||||
padding-bottom: none;
|
||||
}
|
||||
</style>
|
||||
272
frontEnd/src/components/printPage/printServicesPage.vue
Normal file
272
frontEnd/src/components/printPage/printServicesPage.vue
Normal file
@@ -0,0 +1,272 @@
|
||||
<!-- PrintTemplate.vue -->
|
||||
<template>
|
||||
<div>
|
||||
<base-dialog v-model="showPrint" title="打印预览" wdith="210mm">
|
||||
<div v-loading="showLoading" element-loading-text="正在准备打印数据...">
|
||||
<div ref="printContainer" class="print-container" id="print-container">
|
||||
<!-- 服务清单表格 -->
|
||||
<div class="title">零售清单</div>
|
||||
<table class="service-table service-table-header">
|
||||
<tr>
|
||||
<td style="width: 60pt">逝者姓名</td>
|
||||
<td style="width: 90pt">
|
||||
{{
|
||||
deceased.name ||
|
||||
deceased.deceased?.name ||
|
||||
deceased.deceasedName
|
||||
}}
|
||||
</td>
|
||||
<td style="width: 60pt">逝者年龄</td>
|
||||
<td style="width: 70pt">
|
||||
{{ deceased.age ?? deceased.deceased?.age }}
|
||||
</td>
|
||||
<td style="width: 50pt">购买人</td>
|
||||
<td style="width: 80pt">
|
||||
{{ deceased.familyName ?? deceased.deceased?.familyName }}
|
||||
</td>
|
||||
<td style="width: 80pt">购买人电话</td>
|
||||
<td>
|
||||
{{ deceased.familyPhone ?? deceased.deceased?.familyPhone }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="service-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 40pt">序号</th>
|
||||
<th style="width: 80pt">服务项目</th>
|
||||
<th style="width: 35pt">单位</th>
|
||||
<th style="width: 35pt">数量</th>
|
||||
<th style="width: 60pt">收费标准</th>
|
||||
<th style="width: 60pt">小计</th>
|
||||
<th colspan="3">备注</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tr v-for="(item, index) in services" :key="index">
|
||||
<td>{{ index + 1 }}</td>
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.unit }}</td>
|
||||
<td>{{ item.quantity }}</td>
|
||||
<td>{{ item.price }}元</td>
|
||||
<td>{{ (item.quantity * item.price).toFixed(2) }}</td>
|
||||
<td colspan="3">{{ item.remark }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="6">总计:{{ total }}元</td>
|
||||
<td colspan="2">服务引导员</td>
|
||||
<td style="width: 125pt">
|
||||
{{ deceased.guide || deceased.deceased?.guide }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div
|
||||
style="width: 100%; text-align: right"
|
||||
v-if="deceased.checkoutDate || deceased.retail">
|
||||
服务时间:{{
|
||||
deceased.retail
|
||||
? deceased.retail?.checkoutDate.split(" ")[0]
|
||||
: deceased.checkoutDate.split(" ")[0]
|
||||
}}
|
||||
</div>
|
||||
<!-- 打印按钮 -->
|
||||
</div>
|
||||
<button class="print-btn" v-print="print">打印单据</button>
|
||||
</div>
|
||||
</base-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defaultRetail } from "@/defaultForm/defaultRetail";
|
||||
import { computed, nextTick, onMounted, ref, watch } from "vue";
|
||||
import api from "@/lib/request";
|
||||
|
||||
let printContainer = ref();
|
||||
let showLoading = ref(true);
|
||||
const print = {
|
||||
printStyle: `
|
||||
@page { size: auto; margin: 0;padding: 0;}
|
||||
table { border-collapse: collapse; }
|
||||
td { padding: 3mm; border: 1px solid #666; }
|
||||
.title {
|
||||
margin: 5pt 0;
|
||||
}
|
||||
`,
|
||||
id: "print-container",
|
||||
beforeOpenCallback: () => {
|
||||
showLoading.value = true;
|
||||
},
|
||||
closeCallback() {
|
||||
showLoading.value = false;
|
||||
},
|
||||
};
|
||||
|
||||
let services = ref([]);
|
||||
|
||||
let total = computed(() => {
|
||||
const sum = services.value.reduce((sum, item) => {
|
||||
return sum + parseFloat(parseFloat(item.price).toFixed(2)) * item.quantity;
|
||||
}, 0);
|
||||
return parseFloat(sum.toFixed(2)).toFixed(2); // 最终结果保留两位小数
|
||||
});
|
||||
|
||||
let props = withDefaults(
|
||||
defineProps<{
|
||||
deceased: RegisForm;
|
||||
serviceUrl: string;
|
||||
}>(),
|
||||
{
|
||||
deceased: () => defaultRetail(),
|
||||
serviceUrl: "/deceased-retail/selected-service",
|
||||
}
|
||||
);
|
||||
let showPrint = defineModel();
|
||||
|
||||
watch(
|
||||
() => showPrint.value,
|
||||
() => {
|
||||
showLoading.value = true;
|
||||
api()
|
||||
.get(props.serviceUrl)
|
||||
.then((res) => {
|
||||
if (res.code === 200) {
|
||||
showLoading.value = false;
|
||||
services.value = res.data.list;
|
||||
console.log(props.deceased);
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.service-table-header {
|
||||
tr td {
|
||||
border-bottom: none !important;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
margin-bottom: -10pt !important;
|
||||
}
|
||||
.service-table-footer {
|
||||
tr td {
|
||||
border-top: none !important;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
margin-top: -10pt !important;
|
||||
}
|
||||
/* 基础样式 */
|
||||
.print-container {
|
||||
width: 230mm;
|
||||
margin: 0 auto;
|
||||
font-size: 14pt;
|
||||
color: #000 !important;
|
||||
font-family: Microsoft YaHei, "SimSun", serif !important;
|
||||
padding: 0 5mm;
|
||||
box-sizing: border-box; /* 包含边框计算 */
|
||||
size: auto;
|
||||
|
||||
/* 移除可能引起居中的属性 */
|
||||
display: block !important;
|
||||
align-items: unset !important;
|
||||
justify-content: unset !important;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 20pt;
|
||||
text-align: center;
|
||||
margin-top: 5pt;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.service-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 10pt 0;
|
||||
|
||||
td,
|
||||
th {
|
||||
border: 1pt solid #000; // 使用pt单位
|
||||
padding: 0.8mm 1.2mm; // 使用mm单位
|
||||
font-size: 10.5pt;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.total {
|
||||
font-weight: bold;
|
||||
margin: 20px 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.print-btn {
|
||||
display: block;
|
||||
width: 120px;
|
||||
margin: 30px auto;
|
||||
padding: 10px;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 打印媒体查询 */
|
||||
@media print {
|
||||
/* 重置body和html的布局方式 */
|
||||
body,
|
||||
html {
|
||||
display: block !important;
|
||||
height: auto !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* 确保打印容器从顶部开始 */
|
||||
.print-container {
|
||||
min-height: 297mm; /* A4纸高度 */
|
||||
display: block !important;
|
||||
vertical-align: top !important;
|
||||
position: relative;
|
||||
top: 0;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
@page {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* 移除可能存在的flex布局影响 */
|
||||
.el-dialog__wrapper,
|
||||
.el-dialog,
|
||||
.el-dialog__body {
|
||||
display: block !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
/* 移除对话框的padding */
|
||||
:deep(.el-dialog__body) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
.confirm-text {
|
||||
border: 1px solid #000;
|
||||
margin: 0 3px;
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 18px;
|
||||
}
|
||||
.no-border {
|
||||
border: 1px solid #fff;
|
||||
}
|
||||
:deep(.base-dialog .el-dialog__header) {
|
||||
padding-bottom: none;
|
||||
}
|
||||
</style>
|
||||
69
frontEnd/src/components/retailList/retailList.vue
Normal file
69
frontEnd/src/components/retailList/retailList.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div>
|
||||
<base-dialog v-model="show" :title="props.title || '零售清单'">
|
||||
<base-table
|
||||
:option="props.option"
|
||||
border
|
||||
:showToolsBar="false"
|
||||
@tableUpdate="tableUpdate">
|
||||
<template #toolsBar>
|
||||
总金额:
|
||||
<span style="font-size: 18px; font-weight: bold"
|
||||
>{{ totalNum }}元</span
|
||||
>
|
||||
</template>
|
||||
<template #colunm>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
width="80"
|
||||
align="center" />
|
||||
<el-table-column
|
||||
prop="name"
|
||||
label="名称"
|
||||
width="150"
|
||||
align="center" />
|
||||
<el-table-column prop="unit" label="单位" width="60" align="center" />
|
||||
<el-table-column
|
||||
prop="quantity"
|
||||
label="数量"
|
||||
width="80"
|
||||
align="center" />
|
||||
<el-table-column prop="price" label="金额" width="120" align="center">
|
||||
<template #default="{ row }"> {{ row.price }} 元 </template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" align="center" />
|
||||
</template>
|
||||
</base-table>
|
||||
</base-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { tableOptionType } from "@/types/table";
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
let show = defineModel({ default: false });
|
||||
|
||||
let props = defineProps<{
|
||||
option: tableOptionType;
|
||||
title?: string;
|
||||
}>();
|
||||
let tableData = ref([]);
|
||||
|
||||
let totalNum = computed(() => {
|
||||
let value = 0.0;
|
||||
if (tableData.value.length) {
|
||||
tableData.value.forEach((item) =>
|
||||
(value = value + Number(item.price).toFixed(2) * item.quantity).toFixed(2)
|
||||
);
|
||||
}
|
||||
|
||||
return value.toFixed(2);
|
||||
});
|
||||
|
||||
function tableUpdate(data) {
|
||||
tableData.value = data;
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
42
frontEnd/src/components/serviceSelect/serviceSelect.vue
Normal file
42
frontEnd/src/components/serviceSelect/serviceSelect.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-tree-select
|
||||
check-strictly
|
||||
:check-on-click-node="true"
|
||||
v-model="data"
|
||||
:props="{ label: 'name', value: 'id' }"
|
||||
:data="categoryOptions"
|
||||
:render-after-expand="false"
|
||||
style="width: 240px" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import api from "@/lib/request";
|
||||
import { ElMessage } from "element-plus";
|
||||
const data = defineModel({
|
||||
default: 0,
|
||||
});
|
||||
|
||||
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>
|
||||
<style lang="scss" scoped></style>
|
||||
238
frontEnd/src/components/table/baseTable.vue
Normal file
238
frontEnd/src/components/table/baseTable.vue
Normal file
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<!-- <el-affix :offset="65" v-if="props.showToolsBar">
|
||||
|
||||
</el-affix> -->
|
||||
<BaseToolBar
|
||||
@refresh="refresh"
|
||||
style="background-color: #fff; padding: 15px 0; margin-top: -15px">
|
||||
<!-- <download-excel :data="tableData.data" :fields="fields">
|
||||
<el-button size="small" style="margin-left: 15px"
|
||||
><template #icon>
|
||||
<el-tooltip placement="top" effect="light">
|
||||
<template #content>导出当前</template>
|
||||
<el-icon>
|
||||
<Download />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-button>
|
||||
</download-excel>
|
||||
<download-excel :fetch="requestAllData" :fields="fields">
|
||||
<el-tooltip placement="top" effect="light">
|
||||
<template #content>导出全部</template>
|
||||
<el-button size="small" style="margin-left: 15px"
|
||||
><template #icon
|
||||
><i class="base-system base-system-rizhixiazai-xiazaisuoyou"></i>
|
||||
</template>
|
||||
</el-button> </el-tooltip
|
||||
></download-excel> -->
|
||||
|
||||
<div style="margin-left: 15px"><slot name="toolsBar"></slot></div>
|
||||
|
||||
<template #rightContent>
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next, jumper, ->, total"
|
||||
v-if="showPagination"
|
||||
:page-size="tableData.pageSize"
|
||||
:total="tableData.total"
|
||||
:current-page="tableData.pageNumber"
|
||||
size="small"
|
||||
@current-change="currentPageChange" />
|
||||
</template>
|
||||
</BaseToolBar>
|
||||
|
||||
<el-table
|
||||
ref="dataTable"
|
||||
:data="tableData.data"
|
||||
style="
|
||||
width: 100%;
|
||||
min-height: 130px;
|
||||
padding-top: 15px;
|
||||
padding-bottom: 20px;
|
||||
"
|
||||
v-loading="tableLoadingData.show"
|
||||
empty-text="暂无数据"
|
||||
highlight-current-row
|
||||
:element-loading-text="tableLoadingData.text"
|
||||
v-bind="$attrs">
|
||||
<slot></slot>
|
||||
<slot name="colunm"></slot>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
reactive,
|
||||
ref,
|
||||
onMounted,
|
||||
watch,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
} from "vue";
|
||||
import api from "@/lib/request";
|
||||
import { ElMessage } from "element-plus";
|
||||
import BaseToolBar from "./toolBar/baseToolBar.vue";
|
||||
import { tableOptionType, tableDataType } from "@/types/table";
|
||||
let tableLoadingData = reactive({
|
||||
show: false,
|
||||
text: "正在加载数据...",
|
||||
});
|
||||
|
||||
const tableData = ref<tableDataType>({
|
||||
data: [],
|
||||
total: 0,
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
const dataTable = ref<any>(null);
|
||||
const executeType = ref("");
|
||||
|
||||
const methods = {
|
||||
async setDataType(type: "reset" | "list" | "search") {
|
||||
nextTick(async () => {
|
||||
if (executeType.value !== type) {
|
||||
tableData.value.pageNumber = 1;
|
||||
tableData.value.pageSize = 10;
|
||||
}
|
||||
executeType.value = type;
|
||||
|
||||
if (type === "list") await initData();
|
||||
if (type === "search") await queryData();
|
||||
if (type === "reset") {
|
||||
tableData.value.pageNumber = 1;
|
||||
tableData.value.pageSize = 10;
|
||||
await initData();
|
||||
}
|
||||
emits("update");
|
||||
});
|
||||
},
|
||||
};
|
||||
defineExpose({ methods, tableData });
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
option: tableOptionType;
|
||||
showPagination?: boolean;
|
||||
showToolsBar?: boolean;
|
||||
resize?: boolean;
|
||||
fields?: string[];
|
||||
}>(),
|
||||
{
|
||||
option: () => {
|
||||
return {
|
||||
url: "",
|
||||
searchParams: () => {
|
||||
return {};
|
||||
},
|
||||
searchUrl: "",
|
||||
executeType: "list",
|
||||
};
|
||||
},
|
||||
showToolsBar: true,
|
||||
showPagination: true,
|
||||
resize: true,
|
||||
}
|
||||
);
|
||||
|
||||
const emits = defineEmits(["tableUpdate", "searchUpdate", "update"]);
|
||||
|
||||
let tableHeight = ref(300);
|
||||
const updateTableHeight = () => {
|
||||
// 计算高度,减去 header 和分页器的高度
|
||||
const headerHeight = 55; // 你的标题栏高度
|
||||
const toolBarHeight = 147; // 你的分页器高度
|
||||
const padding = 150; // 额外的间距
|
||||
|
||||
tableHeight.value =
|
||||
window.innerHeight - headerHeight - toolBarHeight - padding;
|
||||
};
|
||||
|
||||
// watch(
|
||||
// () => tableData.value.pageNumber,
|
||||
// () => {
|
||||
// nextTick(() => {
|
||||
// methods.setDataType(executeType.value);
|
||||
// });
|
||||
// }
|
||||
// );
|
||||
|
||||
// watch(
|
||||
// () => tableData.value.pageSize,
|
||||
// () => {
|
||||
// nextTick(() => {
|
||||
// methods.setDataType(executeType.value);
|
||||
// });
|
||||
// }
|
||||
// );
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("resize", updateTableHeight);
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
await initData();
|
||||
updateTableHeight();
|
||||
window.addEventListener("resize", updateTableHeight);
|
||||
});
|
||||
|
||||
async function initData() {
|
||||
tableLoadingData.show = true;
|
||||
let dataList = await api().get(props.option.url, {
|
||||
params: {
|
||||
pageSize: tableData.value.pageSize,
|
||||
pageNumber: tableData.value.pageNumber,
|
||||
},
|
||||
});
|
||||
if (dataList.code === 200) {
|
||||
tableData.value.data = dataList.data.list;
|
||||
tableData.value.total = dataList.data.total;
|
||||
emits("tableUpdate", tableData.value.data);
|
||||
}
|
||||
tableLoadingData.show = false;
|
||||
}
|
||||
|
||||
async function queryData() {
|
||||
tableLoadingData.show = true;
|
||||
|
||||
let { pageNumber, pageSize } = tableData.value;
|
||||
let dataList = await api().post(props.option.searchUrl, {
|
||||
...props.option.searchParams,
|
||||
pageSize,
|
||||
pageNumber,
|
||||
});
|
||||
|
||||
if (dataList.code === 200) {
|
||||
tableData.value.data = dataList.data.list;
|
||||
tableData.value.total = dataList.data.total;
|
||||
emits("searchUpdate", tableData.value.data);
|
||||
} else {
|
||||
ElMessage.error("查询出错了,请稍后重试!");
|
||||
}
|
||||
tableLoadingData.show = false;
|
||||
}
|
||||
|
||||
function currentPageChange(newVal: number) {
|
||||
tableData.value.pageNumber = newVal;
|
||||
if (executeType.value === "search") queryData();
|
||||
if (["list", "reset"].includes(executeType.value)) initData();
|
||||
if (!executeType.value) initData();
|
||||
}
|
||||
|
||||
async function requestAllData() {
|
||||
let dataList = await api().get(props.option.url, {
|
||||
params: {
|
||||
pageSize: 999999,
|
||||
pageNumber: 1,
|
||||
...props.option.searchParams,
|
||||
},
|
||||
});
|
||||
return dataList.data.list;
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
if (executeType.value === "search") queryData();
|
||||
if (["list", "reset"].includes(executeType.value)) initData();
|
||||
if (!executeType.value) queryData();
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<el-row class="table-header">
|
||||
<el-col :span="24">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<base-line-title :title="title"></base-line-title>
|
||||
</template>
|
||||
<slot name="content"></slot>
|
||||
<el-form-item :label-width="labelWidth + 'px'">
|
||||
<el-button
|
||||
@click="emits('search')"
|
||||
size="small"
|
||||
style="margin-left: 15px">
|
||||
<template #icon>
|
||||
<el-icon>
|
||||
<Search />
|
||||
</el-icon> </template
|
||||
>查询</el-button
|
||||
>
|
||||
<el-button type="warning" @click="emits('resetSearch')" size="small">
|
||||
<template #icon>
|
||||
<el-icon>
|
||||
<RefreshRight />
|
||||
</el-icon> </template
|
||||
>重置</el-button
|
||||
>
|
||||
<slot name="operateBtns"></slot>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, onMounted, ref } from "vue";
|
||||
let props = defineProps<{
|
||||
title: string;
|
||||
labelWidth?: number | string;
|
||||
}>();
|
||||
|
||||
let emits = defineEmits(["resetSearch", "search"]);
|
||||
|
||||
function resetSearch() {
|
||||
emits("resetSearch");
|
||||
}
|
||||
|
||||
function search() {
|
||||
emits("search");
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.table-header {
|
||||
width: 100%;
|
||||
}
|
||||
:deep(.el-card__body) {
|
||||
padding-bottom: 10px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
||||
26
frontEnd/src/components/table/toolBar/baseToolBar.vue
Normal file
26
frontEnd/src/components/table/toolBar/baseToolBar.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<el-row>
|
||||
<el-col :span="24" class="tool-bar">
|
||||
<el-tooltip placement="top" effect="light">
|
||||
<template #content>刷新</template>
|
||||
<el-button size="small" @click="emits('refresh')">
|
||||
<el-icon><Refresh /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<slot></slot>
|
||||
<div style="position: absolute; right: 15px">
|
||||
<slot name="rightContent"></slot>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const emits = defineEmits(["refresh", "downCurrent", "downAll"]);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.tool-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user