初始版本,目前线上可用

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

14
frontEnd/src/App.vue Normal file
View File

@@ -0,0 +1,14 @@
<template>
<div class="view-content">
<router-view></router-view>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
.view-content {
min-height: 100vh;
min-width: 100vw;
}
</style>

View File

@@ -0,0 +1,540 @@
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url("https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834");
src: url("https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix")
format("embedded-opentype"),
url("https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834")
format("woff"),
url("https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834")
format("truetype"),
url("https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont")
format("svg");
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
normalize.css 中也包含这行 */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown > p,
.markdown > blockquote,
.markdown > .highlight,
.markdown > ol,
.markdown > ul {
width: 80%;
}
.markdown ul > li {
list-style: circle;
}
.markdown > ul li,
.markdown blockquote ul > li {
margin-left: 20px;
padding-left: 4px;
}
.markdown > ul li p,
.markdown > ol li p {
margin: 0.6em 0;
}
.markdown ol > li {
list-style: decimal;
}
.markdown > ol li,
.markdown blockquote ol > li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown > table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown > table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown > table th,
.markdown > table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown > table th {
background: #f7f7f7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown > br,
.markdown > p > br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: 0.5em 0;
overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: 0.1em;
border-radius: 0.3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: 0.7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.handler,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, 0.5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #dd4a68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View File

@@ -0,0 +1,832 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>iconfont Demo</title>
<link rel="shortcut icon" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg" type="image/x-icon"/>
<link rel="icon" type="image/svg+xml" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg"/>
<link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
<link rel="stylesheet" href="demo.css">
<link rel="stylesheet" href="iconfont.css">
<script src="iconfont.js"></script>
<!-- jQuery -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
<!-- 代码高亮 -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
<style>
.main .logo {
margin-top: 0;
height: auto;
}
.main .logo a {
display: flex;
align-items: center;
}
.main .logo .sub-title {
margin-left: 0.5em;
font-size: 22px;
color: #fff;
background: linear-gradient(-45deg, #3967FF, #B500FE);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
</style>
</head>
<body>
<div class="main">
<h1 class="logo"><a href="https://www.iconfont.cn/" title="iconfont 首页" target="_blank">
<img width="200" src="https://img.alicdn.com/imgextra/i3/O1CN01Mn65HV1FfSEzR6DKv_!!6000000000514-55-tps-228-59.svg">
</a></h1>
<div class="nav-tabs">
<ul id="tabs" class="dib-box">
<li class="dib active"><span>Unicode</span></li>
<li class="dib"><span>Font class</span></li>
<li class="dib"><span>Symbol</span></li>
</ul>
<a href="https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=2881943" target="_blank" class="nav-more">查看项目</a>
</div>
<div class="tab-container">
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon base-system">&#xe757;</span>
<div class="name">日志下载-下载所有</div>
<div class="code-name">&amp;#xe757;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe611;</span>
<div class="name">导航</div>
<div class="code-name">&amp;#xe611;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe601;</span>
<div class="name">qq</div>
<div class="code-name">&amp;#xe601;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe602;</span>
<div class="name">微信</div>
<div class="code-name">&amp;#xe602;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe61b;</span>
<div class="name">电话</div>
<div class="code-name">&amp;#xe61b;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe652;</span>
<div class="name">地址</div>
<div class="code-name">&amp;#xe652;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe635;</span>
<div class="name">姓名</div>
<div class="code-name">&amp;#xe635;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe612;</span>
<div class="name">top_联系方式</div>
<div class="code-name">&amp;#xe612;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe614;</span>
<div class="name">童装性别</div>
<div class="code-name">&amp;#xe614;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe68a;</span>
<div class="name">首页</div>
<div class="code-name">&amp;#xe68a;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe62a;</span>
<div class="name">设置</div>
<div class="code-name">&amp;#xe62a;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe639;</span>
<div class="name">账号管理</div>
<div class="code-name">&amp;#xe639;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe640;</span>
<div class="name">角色管理</div>
<div class="code-name">&amp;#xe640;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe65d;</span>
<div class="name">菜单</div>
<div class="code-name">&amp;#xe65d;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe609;</span>
<div class="name">菜单</div>
<div class="code-name">&amp;#xe609;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe686;</span>
<div class="name">部门</div>
<div class="code-name">&amp;#xe686;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe66d;</span>
<div class="name">版权</div>
<div class="code-name">&amp;#xe66d;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe89f;</span>
<div class="name">登陆-box-线</div>
<div class="code-name">&amp;#xe89f;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe600;</span>
<div class="name">3.1电话</div>
<div class="code-name">&amp;#xe600;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe641;</span>
<div class="name">个人信息</div>
<div class="code-name">&amp;#xe641;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe632;</span>
<div class="name">全屏</div>
<div class="code-name">&amp;#xe632;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe603;</span>
<div class="name">个人信息</div>
<div class="code-name">&amp;#xe603;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe689;</span>
<div class="name">关机</div>
<div class="code-name">&amp;#xe689;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe6b0;</span>
<div class="name">email</div>
<div class="code-name">&amp;#xe6b0;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe60f;</span>
<div class="name">密码</div>
<div class="code-name">&amp;#xe60f;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe619;</span>
<div class="name">验证码</div>
<div class="code-name">&amp;#xe619;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe78f;</span>
<div class="name">用户名</div>
<div class="code-name">&amp;#xe78f;</div>
</li>
<li class="dib">
<span class="icon base-system">&#xe6ae;</span>
<div class="name">用户登陆统计</div>
<div class="code-name">&amp;#xe6ae;</div>
</li>
</ul>
<div class="article markdown">
<h2 id="unicode-">Unicode 引用</h2>
<hr>
<p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
<ul>
<li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
<li>默认情况下不支持多色,直接添加多色图标会自动去色。</li>
</ul>
<blockquote>
<p>注意:新版 iconfont 支持两种方式引用多色图标SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)</p>
</blockquote>
<p>Unicode 使用步骤如下:</p>
<h3 id="-font-face">第一步:拷贝项目下面生成的 <code>@font-face</code></h3>
<pre><code class="language-css"
>@font-face {
font-family: 'base-system';
src: url('iconfont.woff2?t=1713884780448') format('woff2'),
url('iconfont.woff?t=1713884780448') format('woff'),
url('iconfont.ttf?t=1713884780448') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
<pre><code class="language-css"
>.base-system {
font-family: "base-system" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
<pre>
<code class="language-html"
>&lt;span class="base-system"&gt;&amp;#x33;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"base-system" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon base-system base-system-rizhixiazai-xiazaisuoyou"></span>
<div class="name">
日志下载-下载所有
</div>
<div class="code-name">.base-system-rizhixiazai-xiazaisuoyou
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-daohang"></span>
<div class="name">
导航
</div>
<div class="code-name">.base-system-daohang
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-qq"></span>
<div class="name">
qq
</div>
<div class="code-name">.base-system-qq
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-weixin"></span>
<div class="name">
微信
</div>
<div class="code-name">.base-system-weixin
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-dianhua"></span>
<div class="name">
电话
</div>
<div class="code-name">.base-system-dianhua
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-dizhi"></span>
<div class="name">
地址
</div>
<div class="code-name">.base-system-dizhi
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-xingming"></span>
<div class="name">
姓名
</div>
<div class="code-name">.base-system-xingming
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-top_lianxifangshi"></span>
<div class="name">
top_联系方式
</div>
<div class="code-name">.base-system-top_lianxifangshi
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-tongzhuangxingbie"></span>
<div class="name">
童装性别
</div>
<div class="code-name">.base-system-tongzhuangxingbie
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-home"></span>
<div class="name">
首页
</div>
<div class="code-name">.base-system-home
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-shezhi"></span>
<div class="name">
设置
</div>
<div class="code-name">.base-system-shezhi
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-zhanghaoguanli"></span>
<div class="name">
账号管理
</div>
<div class="code-name">.base-system-zhanghaoguanli
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-navicon-jsgl"></span>
<div class="name">
角色管理
</div>
<div class="code-name">.base-system-navicon-jsgl
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-caidan"></span>
<div class="name">
菜单
</div>
<div class="code-name">.base-system-caidan
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-caidan1"></span>
<div class="name">
菜单
</div>
<div class="code-name">.base-system-caidan1
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-bumen"></span>
<div class="name">
部门
</div>
<div class="code-name">.base-system-bumen
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-banquan"></span>
<div class="name">
版权
</div>
<div class="code-name">.base-system-banquan
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-denglu-box-xian"></span>
<div class="name">
登陆-box-线
</div>
<div class="code-name">.base-system-denglu-box-xian
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-31dianhua"></span>
<div class="name">
3.1电话
</div>
<div class="code-name">.base-system-31dianhua
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-gerenxinxi"></span>
<div class="name">
个人信息
</div>
<div class="code-name">.base-system-gerenxinxi
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-quanping"></span>
<div class="name">
全屏
</div>
<div class="code-name">.base-system-quanping
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-gerenxinxi1"></span>
<div class="name">
个人信息
</div>
<div class="code-name">.base-system-gerenxinxi1
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-guanji"></span>
<div class="name">
关机
</div>
<div class="code-name">.base-system-guanji
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-youxiang"></span>
<div class="name">
email
</div>
<div class="code-name">.base-system-youxiang
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-mima"></span>
<div class="name">
密码
</div>
<div class="code-name">.base-system-mima
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-yanzhengma"></span>
<div class="name">
验证码
</div>
<div class="code-name">.base-system-yanzhengma
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-yonghuming"></span>
<div class="name">
用户名
</div>
<div class="code-name">.base-system-yonghuming
</div>
</li>
<li class="dib">
<span class="icon base-system base-system-yonghudenglutongjigongju"></span>
<div class="name">
用户登陆统计
</div>
<div class="code-name">.base-system-yonghudenglutongjigongju
</div>
</li>
</ul>
<div class="article markdown">
<h2 id="font-class-">font-class 引用</h2>
<hr>
<p>font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。</p>
<p>与 Unicode 使用方式相比,具有如下特点:</p>
<ul>
<li>相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。</li>
<li>因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-fontclass-">第一步:引入项目下面生成的 fontclass 代码:</h3>
<pre><code class="language-html">&lt;link rel="stylesheet" href="./iconfont.css"&gt;
</code></pre>
<h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;span class="base-system base-system-xxx"&gt;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"
base-system" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-rizhixiazai-xiazaisuoyou"></use>
</svg>
<div class="name">日志下载-下载所有</div>
<div class="code-name">#base-system-rizhixiazai-xiazaisuoyou</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-daohang"></use>
</svg>
<div class="name">导航</div>
<div class="code-name">#base-system-daohang</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-qq"></use>
</svg>
<div class="name">qq</div>
<div class="code-name">#base-system-qq</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-weixin"></use>
</svg>
<div class="name">微信</div>
<div class="code-name">#base-system-weixin</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-dianhua"></use>
</svg>
<div class="name">电话</div>
<div class="code-name">#base-system-dianhua</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-dizhi"></use>
</svg>
<div class="name">地址</div>
<div class="code-name">#base-system-dizhi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-xingming"></use>
</svg>
<div class="name">姓名</div>
<div class="code-name">#base-system-xingming</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-top_lianxifangshi"></use>
</svg>
<div class="name">top_联系方式</div>
<div class="code-name">#base-system-top_lianxifangshi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-tongzhuangxingbie"></use>
</svg>
<div class="name">童装性别</div>
<div class="code-name">#base-system-tongzhuangxingbie</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-home"></use>
</svg>
<div class="name">首页</div>
<div class="code-name">#base-system-home</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-shezhi"></use>
</svg>
<div class="name">设置</div>
<div class="code-name">#base-system-shezhi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-zhanghaoguanli"></use>
</svg>
<div class="name">账号管理</div>
<div class="code-name">#base-system-zhanghaoguanli</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-navicon-jsgl"></use>
</svg>
<div class="name">角色管理</div>
<div class="code-name">#base-system-navicon-jsgl</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-caidan"></use>
</svg>
<div class="name">菜单</div>
<div class="code-name">#base-system-caidan</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-caidan1"></use>
</svg>
<div class="name">菜单</div>
<div class="code-name">#base-system-caidan1</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-bumen"></use>
</svg>
<div class="name">部门</div>
<div class="code-name">#base-system-bumen</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-banquan"></use>
</svg>
<div class="name">版权</div>
<div class="code-name">#base-system-banquan</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-denglu-box-xian"></use>
</svg>
<div class="name">登陆-box-线</div>
<div class="code-name">#base-system-denglu-box-xian</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-31dianhua"></use>
</svg>
<div class="name">3.1电话</div>
<div class="code-name">#base-system-31dianhua</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-gerenxinxi"></use>
</svg>
<div class="name">个人信息</div>
<div class="code-name">#base-system-gerenxinxi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-quanping"></use>
</svg>
<div class="name">全屏</div>
<div class="code-name">#base-system-quanping</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-gerenxinxi1"></use>
</svg>
<div class="name">个人信息</div>
<div class="code-name">#base-system-gerenxinxi1</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-guanji"></use>
</svg>
<div class="name">关机</div>
<div class="code-name">#base-system-guanji</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-youxiang"></use>
</svg>
<div class="name">email</div>
<div class="code-name">#base-system-youxiang</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-mima"></use>
</svg>
<div class="name">密码</div>
<div class="code-name">#base-system-mima</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-yanzhengma"></use>
</svg>
<div class="name">验证码</div>
<div class="code-name">#base-system-yanzhengma</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-yonghuming"></use>
</svg>
<div class="name">用户名</div>
<div class="code-name">#base-system-yonghuming</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#base-system-yonghudenglutongjigongju"></use>
</svg>
<div class="name">用户登陆统计</div>
<div class="code-name">#base-system-yonghudenglutongjigongju</div>
</li>
</ul>
<div class="article markdown">
<h2 id="symbol-">Symbol 引用</h2>
<hr>
<p>这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a href="">文章</a>
这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:</p>
<ul>
<li>支持多色图标了,不再受单色限制。</li>
<li>通过一些技巧,支持像字体那样,通过 <code>font-size</code>, <code>color</code> 来调整样式。</li>
<li>兼容性较差,支持 IE9+,及现代浏览器。</li>
<li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
<pre><code class="language-html">&lt;script src="./iconfont.js"&gt;&lt;/script&gt;
</code></pre>
<h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
<pre><code class="language-html">&lt;style&gt;
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
&lt;/style&gt;
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;svg class="icon" aria-hidden="true"&gt;
&lt;use xlink:href="#icon-xxx"&gt;&lt;/use&gt;
&lt;/svg&gt;
</code></pre>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('.tab-container .content:first').show()
$('#tabs li').click(function (e) {
var tabContent = $('.tab-container .content')
var index = $(this).index()
if ($(this).hasClass('active')) {
return
} else {
$('#tabs li').removeClass('active')
$(this).addClass('active')
tabContent.hide().eq(index).fadeIn()
}
})
})
</script>
</body>
</html>

View File

@@ -0,0 +1,127 @@
@font-face {
font-family: "base-system"; /* Project id 2881943 */
src: url('iconfont.woff2?t=1713884780448') format('woff2'),
url('iconfont.woff?t=1713884780448') format('woff'),
url('iconfont.ttf?t=1713884780448') format('truetype');
}
.base-system {
font-family: "base-system" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.base-system-rizhixiazai-xiazaisuoyou:before {
content: "\e757";
}
.base-system-daohang:before {
content: "\e611";
}
.base-system-qq:before {
content: "\e601";
}
.base-system-weixin:before {
content: "\e602";
}
.base-system-dianhua:before {
content: "\e61b";
}
.base-system-dizhi:before {
content: "\e652";
}
.base-system-xingming:before {
content: "\e635";
}
.base-system-top_lianxifangshi:before {
content: "\e612";
}
.base-system-tongzhuangxingbie:before {
content: "\e614";
}
.base-system-home:before {
content: "\e68a";
}
.base-system-shezhi:before {
content: "\e62a";
}
.base-system-zhanghaoguanli:before {
content: "\e639";
}
.base-system-navicon-jsgl:before {
content: "\e640";
}
.base-system-caidan:before {
content: "\e65d";
}
.base-system-caidan1:before {
content: "\e609";
}
.base-system-bumen:before {
content: "\e686";
}
.base-system-banquan:before {
content: "\e66d";
}
.base-system-denglu-box-xian:before {
content: "\e89f";
}
.base-system-31dianhua:before {
content: "\e600";
}
.base-system-gerenxinxi:before {
content: "\e641";
}
.base-system-quanping:before {
content: "\e632";
}
.base-system-gerenxinxi1:before {
content: "\e603";
}
.base-system-guanji:before {
content: "\e689";
}
.base-system-youxiang:before {
content: "\e6b0";
}
.base-system-mima:before {
content: "\e60f";
}
.base-system-yanzhengma:before {
content: "\e619";
}
.base-system-yonghuming:before {
content: "\e78f";
}
.base-system-yonghudenglutongjigongju:before {
content: "\e6ae";
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,205 @@
{
"id": "2881943",
"name": "默认系统",
"font_family": "base-system",
"css_prefix_text": "base-system-",
"description": "通用项目模板图标库",
"glyphs": [
{
"icon_id": "21182922",
"name": "日志下载-下载所有",
"font_class": "rizhixiazai-xiazaisuoyou",
"unicode": "e757",
"unicode_decimal": 59223
},
{
"icon_id": "4437590",
"name": "导航",
"font_class": "daohang",
"unicode": "e611",
"unicode_decimal": 58897
},
{
"icon_id": "26701",
"name": "qq",
"font_class": "qq",
"unicode": "e601",
"unicode_decimal": 58881
},
{
"icon_id": "77156",
"name": "微信",
"font_class": "weixin",
"unicode": "e602",
"unicode_decimal": 58882
},
{
"icon_id": "144688",
"name": "电话",
"font_class": "dianhua",
"unicode": "e61b",
"unicode_decimal": 58907
},
{
"icon_id": "658000",
"name": "地址",
"font_class": "dizhi",
"unicode": "e652",
"unicode_decimal": 58962
},
{
"icon_id": "908052",
"name": "姓名",
"font_class": "xingming",
"unicode": "e635",
"unicode_decimal": 58933
},
{
"icon_id": "17805508",
"name": "top_联系方式",
"font_class": "top_lianxifangshi",
"unicode": "e612",
"unicode_decimal": 58898
},
{
"icon_id": "18319239",
"name": "童装性别",
"font_class": "tongzhuangxingbie",
"unicode": "e614",
"unicode_decimal": 58900
},
{
"icon_id": "765406",
"name": "首页",
"font_class": "home",
"unicode": "e68a",
"unicode_decimal": 59018
},
{
"icon_id": "145433",
"name": "设置",
"font_class": "shezhi",
"unicode": "e62a",
"unicode_decimal": 58922
},
{
"icon_id": "2959116",
"name": "账号管理",
"font_class": "zhanghaoguanli",
"unicode": "e639",
"unicode_decimal": 58937
},
{
"icon_id": "3702619",
"name": "角色管理",
"font_class": "navicon-jsgl",
"unicode": "e640",
"unicode_decimal": 58944
},
{
"icon_id": "5283349",
"name": "菜单",
"font_class": "caidan",
"unicode": "e65d",
"unicode_decimal": 58973
},
{
"icon_id": "7720058",
"name": "菜单",
"font_class": "caidan1",
"unicode": "e609",
"unicode_decimal": 58889
},
{
"icon_id": "12184880",
"name": "部门",
"font_class": "bumen",
"unicode": "e686",
"unicode_decimal": 59014
},
{
"icon_id": "6415223",
"name": "版权",
"font_class": "banquan",
"unicode": "e66d",
"unicode_decimal": 58989
},
{
"icon_id": "12990693",
"name": "登陆-box-线",
"font_class": "denglu-box-xian",
"unicode": "e89f",
"unicode_decimal": 59551
},
{
"icon_id": "201577",
"name": "3.1电话",
"font_class": "31dianhua",
"unicode": "e600",
"unicode_decimal": 58880
},
{
"icon_id": "1831590",
"name": "个人信息",
"font_class": "gerenxinxi",
"unicode": "e641",
"unicode_decimal": 58945
},
{
"icon_id": "7461023",
"name": "全屏",
"font_class": "quanping",
"unicode": "e632",
"unicode_decimal": 58930
},
{
"icon_id": "9656282",
"name": "个人信息",
"font_class": "gerenxinxi1",
"unicode": "e603",
"unicode_decimal": 58883
},
{
"icon_id": "14329730",
"name": "关机",
"font_class": "guanji",
"unicode": "e689",
"unicode_decimal": 59017
},
{
"icon_id": "19457229",
"name": "email",
"font_class": "youxiang",
"unicode": "e6b0",
"unicode_decimal": 59056
},
{
"icon_id": "508253",
"name": "密码",
"font_class": "mima",
"unicode": "e60f",
"unicode_decimal": 58895
},
{
"icon_id": "8455373",
"name": "验证码",
"font_class": "yanzhengma",
"unicode": "e619",
"unicode_decimal": 58905
},
{
"icon_id": "10232786",
"name": "用户名",
"font_class": "yonghuming",
"unicode": "e78f",
"unicode_decimal": 59279
},
{
"icon_id": "21758126",
"name": "用户登陆统计",
"font_class": "yonghudenglutongjigongju",
"unicode": "e6ae",
"unicode_decimal": 59054
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,73 @@
/*图标库*/
@import url("./aliIcon/iconfont.css");
/*图标库*/
/*默认背景颜色*/
.default-bckcolor {
background-color: #f6f8fa !important;
box-sizing: border-box;
height: 100%;
}
// /* 自定义滚动条轨道 */
// *::-webkit-scrollbar {
// width: 7px;
// }
// /* 自定义滚动条滑块 */
// ::-webkit-scrollbar-thumb {
// width: 2px;
// background-color: #555;
// border: 1px solid #e8e9ea;
// }
.mask {
position: fixed;
width: 100vw;
height: 100vh;
background-color: rgba(#000, 0.4);
}
/*水平居中*/
.inline-center {
display: flex !important;
align-items: center;
}
/*垂直居中*/
.height-center {
display: flex !important;
justify-content: center;
}
.flex-center {
display: flex;
justify-content: center;
align-items: center;
}
.title {
padding: 15px;
}
.ui-menu {
height: 100%;
display: flex;
flex: 1;
align-items: center;
.ui-menu-item {
padding: 0 15px;
cursor: pointer;
height: 100%;
display: flex;
align-items: center;
transition: all 0.5s;
&:hover {
background-color: #e8e9ea;
}
}
}
.width-100 {
width: 100%;
}
.height-100 {
height: 100%;
}

View File

@@ -0,0 +1,133 @@
$base: #306dbb;
@forward "element-plus/theme-chalk/src/common/var.scss" with (
$colors: (
"white": #ffffff,
"black": #000000,
"primary": (
"base": $base,
),
"success": (
"base": #248067,
),
"warning": (
"base": #fb8b05,
),
"danger": (
"base": #f04b22,
),
"error": (
"base": #ec2b24,
),
"info": (
"base": #909399,
),
),
$switch: (
"on-color": "#248067",
),
$dialog: (
"border-radius": "8px",
)
);
@use "element-plus/theme-chalk/src/index.scss" as *;
.el-menu {
border: none;
}
// .el-dialog__header{
// background-color: $base;
// color: #fff;
// margin-right: 0;
// border-radius: 7px;
// border-bottom-left-radius: 0;
// border-bottom-right-radius: 0;
// .el-dialog__title{
// color: #fff;
// }
// .el-dialog__headerbtn{
// .el-dialog__close{
// color: #fff;
// font-size: 18px;
// }
// }
// }
.el-dialog__body {
padding-bottom: 15px !important;
}
.el-table {
th.el-table__cell {
background-color: #f1f3f4;
&:first-child {
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
}
&:last-child {
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
}
}
}
.el-table__header-wrapper tr th.el-table-fixed-column--right,
.el-table.is-scrolling-none th.el-table-fixed-column--right {
background-color: #f1f3f4;
}
.el-descriptions {
.el-descriptions__header {
position: relative;
background-color: #e8eef5;
padding: 6px;
margin-bottom: 10px;
.el-descriptions__title {
padding-left: 12px;
&::after {
content: "";
width: 5px;
position: absolute;
left: 0px;
top: 50%;
transform: translateY(-50%);
background-color: #15559a;
border-radius: 8px;
animation: menueLeftLine 0.45s ease;
animation-fill-mode: forwards;
}
}
}
}
.base-dialog {
.el-dialog__header {
padding-top: 10px !important;
padding-bottom: 35px !important;
}
}
button:focus,
button:focus-visible {
outline: none;
}
.el-message-box__container {
flex-direction: column;
.el-message-box__status {
svg {
transform: scale(2.5);
}
}
}
.el-message-box__btns {
justify-content: center;
margin: 8px 0;
}
@keyframes menueLeftLine {
0% {
height: 0%;
}
100% {
height: 100%;
}
}

View File

@@ -0,0 +1,8 @@
body,html {
padding: 0;
margin: 0;
}
* {
box-sizing: border-box;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View 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>

View 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>

View 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;

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -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>

View 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>

View File

@@ -0,0 +1,14 @@
import dayjs from "dayjs";
export function defaultPaymentForm(): PaymentForm {
return {
checkoutDate: dayjs().format(), // 结账日期
handler: "", // 经办人
settlementDate: dayjs().format(), // 结算日期
cashAmount: 0, // 现金金额
unionPayAmount: 0, // 银联支付金额
cardAmount: 0, // 刷卡金额
publicTransferAmount: 0, // 对公转账金额
workshopPayment: 0,
};
}

View File

@@ -0,0 +1,17 @@
export function defaultRetail(): RegisForm {
return {
name: "", // 逝者姓名
idNumber: "", // 证件号码
gender: "男", // 性别
age: 0, // 年龄
buyer: "", // 购买人
purchaseDate: "", // 购买日期
handler: "", // 经办人
salesAmount: 0, // 销售金额
guide: "", // 引导员
serviceItems: "",
familyName: "",
familyPhone: "",
services: [],
};
}

View File

@@ -0,0 +1,10 @@
export function defaultServiceItem(): ServiceItemType {
return {
name: "",
quantity: 0,
category: 0,
unit: "",
price: 0,
remark: "",
};
}

View File

@@ -0,0 +1,206 @@
<template>
<div class="head-content">
<div class="nav">
<div class="left">
<!-- <ul class="ui-menu">
<li
v-for="item in routerList"
class="ui-menu-item"
@click="selectTopMenu(item)">
{{ item.name }}
</li>
</ul> -->
<el-icon size="45px" class="folder" @click="expandToggle">
<Fold v-if="!expand" />
<Expand v-else="expand" />
</el-icon>
</div>
<div class="right">
<div class="right-toolbar">
<el-tooltip content="全屏" placement="bottom" effect="light">
<el-icon @click="fullScreen"><FullScreen /></el-icon>
</el-tooltip>
</div>
<div class="options">
<el-dropdown :hide-on-click="false">
<div class="user-heard" @click="changeInfo">
<label style="padding: 0 5px"
>你好<strong style="padding: 0 5px">{{
replaceUserName(userInforStore.userInfor.name as string)
}}</strong></label
>
<!-- <el-avatar
src="https://avatars.githubusercontent.com/u/34113411?v=4">
</el-avatar> -->
<el-icon>
<CaretBottom></CaretBottom>
</el-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="personal">个人信息</el-dropdown-item>
<el-dropdown-item @click="logout">退出登陆</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
<base-dialog v-model="showMyself"> <mySelf /></base-dialog>
</div>
</template>
<script lang="ts" setup>
import { nextTick, reactive, ref } from "vue";
import { RouteRecordRaw } from "vue-router";
import router from "@/routers";
import { ElMessageBox } from "element-plus";
import { globalState } from "@/store";
import { userInfor } from "@/store/user/user";
import { Expand } from "@element-plus/icons-vue";
import mySelf from "@/pages/system/user/personal.vue";
const globalStore = globalState();
const userInforStore = userInfor();
const emits = defineEmits(["change", "expandChange"]);
let showUserOption = ref(false);
let expand = ref(false);
let isFullScene = ref(false);
let showMyself = ref(false);
// 切换类名
function changeInfo() {
showUserOption.value = !showUserOption.value;
}
// 退出登陆
function logout() {
ElMessageBox.confirm("确定要退出系统吗?", "提示", {
type: "warning",
confirmButtonText: "确定退出",
cancelButtonText: "取消",
}).then(() => {
globalStore.setGlobalLoadingShow(true, "正在清空您的数据并退出系统...");
userInforStore.removeLoginState();
userInforStore.removeToken();
setTimeout(() => {
globalStore.setGlobalLoadingShow(false);
router.replace({ path: "/login" });
}, 2000);
});
}
function expandToggle() {
expand.value = !expand.value;
emits("expandChange", expand.value);
}
function personal() {
showMyself.value = true;
}
function fullScreen() {
if (!isFullScene.value) {
document.body.requestFullscreen();
} else {
document.exitFullscreen();
}
isFullScene.value = !isFullScene.value;
}
function replaceUserName(name: string) {
return name;
if (name.length <= 1) return name;
let nameLen = name.length;
return name.substring(1, nameLen);
}
</script>
<style lang="scss" scoped>
.head-content {
position: absolute;
top: 0;
margin-left: 200px;
width: calc(100% - 200px);
height: 50px;
display: flex;
flex-direction: column;
background-color: #f1f3f4;
.nav {
display: flex;
justify-content: space-between;
background-color: #fff;
// box-shadow: 20px -5px 28px #c2c2c2;
padding: 8px 15px;
position: absolute;
top: 0;
width: 100%;
height: 55px;
align-items: center;
z-index: 2;
.left {
height: 50px;
}
}
.route-list {
padding: 5px 15px;
background-color: #fff;
height: 30px;
display: flex;
align-items: center;
box-shadow: 0px 1px 0px #c2c2c2;
margin-top: 1px;
}
.right,
.left {
display: inline-block;
position: relative;
}
.right {
padding: 0 15px;
position: relative;
cursor: pointer;
display: flex;
align-items: center;
height: 100%;
.right-toolbar {
height: inherit;
display: flex;
align-items: center;
margin-right: 30px;
}
.options {
height: 100%;
display: flex;
.user-heard {
display: flex;
align-items: center;
}
}
}
}
.show-user-option {
visibility: visible !important;
opacity: 1 !important;
margin-top: 0 !important;
}
.folder svg {
width: 2em;
height: 2em;
cursor: pointer;
}
.right-toolbar svg {
transform: scale(1.5);
}
</style>

View File

@@ -0,0 +1,128 @@
<template>
<div
style="width: 100vw; height: 100vh"
v-loading="globalStateStore.globalLodingShow"
:element-loading-text="globalStateStore.globalLodingShowText">
<headMenuVue
@expand-change="expandChange"
class="header-menue"
:class="{ 'expand-control': expand }"></headMenuVue>
<leftMenuVue
class="left-menue"
:menue-data="realMenue"
:class="{ 'expand-close': expand }"></leftMenuVue>
<div
class="main"
:class="{ 'expand-control': expand }"
v-loading="globalStateStore.loadingShow"
:element-loading-text="globalStateStore.loadingText">
<router-view v-slot="{ Component }">
<transition name="transition" mode="out-in">
<component :is="Component"></component>
</transition>
</router-view>
</div>
</div>
</template>
<script lang="ts" setup>
import headMenuVue from "./headMenu.vue";
import leftMenuVue from "./leftMenu.vue";
import router from "@/routers/index";
import { globalState } from "@/store";
import { RouteRecordRaw } from "vue-router";
import { ref, onBeforeMount, onMounted, reactive } from "vue";
let filterRoute = ["首页"];
let leftMenue: Array<RouteRecordRaw> = router.options.routes.find(
(item: RouteRecordRaw) => item?.name === "主页"
)?.children as Array<RouteRecordRaw>;
let globalStateStore = globalState();
let realMenue = leftMenue.filter(
(item: RouteRecordRaw) => !filterRoute.includes(item.name as string)
);
let expand = ref(false);
onBeforeMount(() => {});
onMounted(() => {});
function expandChange(newVal: boolean) {
expand.value = newVal;
}
</script>
<style lang="scss" scoped>
.main {
width: calc(100vw - 200px);
height: calc(100vh - 50px);
position: absolute;
top: 50px;
margin-left: 200px;
overflow-y: auto;
background-color: #f1f3f4;
padding: 12px;
}
.left-menue {
transition: all 0.3s;
color: #fff;
user-select: none;
background-color: #fff;
:deep(.el-menu) {
.el-menu-item {
&:hover {
background-color: #f1f3f4;
color: #15559a;
}
}
.is-active {
background-color: #f1f3f4;
}
.el-sub-menu .is-active {
::after {
content: "";
width: 5px;
position: absolute;
left: -8px;
top: 50%;
transform: translateY(-50%);
background-color: #15559a;
border-radius: 8px;
animation: menueLeftLine 0.45s ease;
animation-fill-mode: forwards;
}
}
}
}
.expand {
left: 0;
opacity: 0;
}
.main,
.header-menue {
transition: all 0.3s;
}
.expand-control {
width: 100vw;
margin-left: 0;
}
.expand-close {
width: 0;
overflow: hidden;
}
.transition-enter-to {
opacity: 1;
transition: all 0.3s ease;
}
.transition-enter-from {
transform: translateY(5%);
opacity: 0;
}
@keyframes menueLeftLine {
0% {
height: 0%;
}
100% {
height: 120%;
}
}
</style>

View File

@@ -0,0 +1,183 @@
<template>
<div class="menue">
<div class="system-title flex-center">
<img src="@/assets/images/index.png" alt="" />
<span>baseSystem</span>
</div>
<el-menu
router
:default-active="router.currentRoute.value.path"
:unique-opened="true">
<!-- <el-menu-item index="/main"
><el-icon> <HomeFilled /> </el-icon>首页</el-menu-item
> -->
<template v-for="(item, index) in menueData">
<template v-if="!item.children">
<el-menu-item
:index="item.path"
v-if="hasParent(item)"
:class="{
routerActive: router.currentRoute.value.path === item.path,
}">
<template v-if="item.meta?.icon?.type === 'elem'">
<el-icon>
<component :is="item.meta!.icon.value"> </component>
</el-icon>
</template>
<template v-if="iconType.includes(item.meta?.icon?.type)">
<img
:src="'/assets/icon/' + item.meta?.icon?.value"
alt=""
class="nav-icon" />
</template>
{{ item.name }}
</el-menu-item>
</template>
<template v-else
><el-sub-menu :index="String(index)" v-if="hasParent(item)">
<template #title>
<template v-if="item.meta?.icon?.type === 'elem'">
<el-icon>
<component :is="item.meta!.icon.value"> </component>
</el-icon>
</template>
<template v-if="iconType.includes(item.meta?.icon?.type)">
<img
:src="'/assets/icon/' + item.meta?.icon?.value"
alt=""
class="nav-icon" /> </template
>{{ item.name }}
</template>
<template v-for="(childrenItem, index) in item.children">
<el-menu-item
:index="childrenItem.path"
v-if="hasChild(item, childrenItem)"
:class="{
routerActive:
router.currentRoute.value.path === childrenItem.path,
}">
<template v-if="childrenItem.meta?.icon?.type === 'elem'">
<el-icon>
<component :is="childrenItem.meta!.icon.value"> </component>
</el-icon>
</template>
<template
v-if="iconType.includes(childrenItem.meta?.icon?.type)">
<img
:src="'/assets/icon/' + childrenItem.meta?.icon?.value"
alt=""
class="nav-icon" />
</template>
{{ childrenItem.name }}
</el-menu-item>
</template>
</el-sub-menu></template
>
</template>
</el-menu>
</div>
</template>
<script lang="ts" setup>
import { RouteRecordRaw } from "vue-router";
import { userInfor } from "@/store/user/user";
import router from "@/routers/index";
import { ref } from "vue";
import { systemMenueType } from "@/types/systemMenue";
const props = withDefaults(
defineProps<{
menueData: RouteRecordRaw[];
expand?: boolean;
}>(),
{
expand: false,
}
);
const iconType = ref(["svg", "png", "jgp"]);
const globalUserInfor = userInfor();
let userMenue = globalUserInfor.userInfor.routerMenue;
function hasParent(item: RouteRecordRaw) {
let parent = userMenue?.find((routerItem) => routerItem.name === item.name);
let childn = item.children?.find((childrenItem) =>
userMenue?.find((userItem) => userItem.name === childrenItem.name)
);
return parent || childn;
}
function hasChild(parentItem: RouteRecordRaw, item: RouteRecordRaw) {
let parent = userMenue?.find(
(routerItem) => routerItem.name === parentItem.name
);
if (parent) {
return parent?.children?.find(
(userMenueItem) => userMenueItem.name === item.name
);
} else {
return userMenue?.find((userMneueItem) => userMneueItem.name === item.name);
}
}
</script>
<style lang="scss" scoped>
.parent-list {
padding: 5px 10px;
position: relative;
.list-border-left {
position: absolute;
left: 4px;
top: 50%;
height: 0;
border-left: 4px solid #66cccc;
border-radius: 15px;
transition: all 0.3s;
transform: translateY(-50%);
}
&:hover .list-border-left {
height: 70%;
}
}
.menue {
// box-shadow: #000 -25px 0 35px;
width: 200px;
height: 100vh;
overflow-y: auto;
position: absolute;
left: 0;
z-index: 2;
}
.routerActive {
background-color: #f1f3f4;
}
.system-title {
font-size: 20px;
font-weight: bold;
height: 95px;
display: flex;
position: relative;
flex-direction: column;
img {
width: 60px;
}
&::after {
content: "";
position: absolute;
width: 100%;
height: 1px;
bottom: 0;
}
}
.nav-icon {
width: 1em;
height: 1em;
font-size: 18px;
margin-right: 10px;
}
</style>

View File

@@ -0,0 +1,91 @@
<template>
<div class="menue">
<ul class="menue-list">
<li v-for="item in props.menueData">
<div
class="menue-list-item"
:class="{ 'parent-item': item.children?.length }"
@click="onRowClick(item)">
<span class="list-border-left"></span>
<span>{{ item.meta?.describe || item.name }}</span>
</div>
<menueComponent
:menueData="item.children"
v-if="item.children?.length"
class="children-menue">
</menueComponent>
</li>
</ul>
</div>
</template>
<script lang="ts" setup>
import menueComponent from "./menue.vue";
import { RouteRecordRaw } from "vue-router";
import { globalState } from "@/store/index";
import router from "@/routers/index";
const props = defineProps({
menueData: Array<RouteRecordRaw>,
});
const globalStateStore = globalState();
function onRowClick(row: RouteRecordRaw) {
if (!row.children || !row.children.length) {
globalStateStore.setSelectMenue(row);
globalStateStore.setLoadingShow(true);
router.push(row.path);
setTimeout(() => {
globalStateStore.setLoadingShow(false);
}, 1000);
} else {
}
}
</script>
<style lang="scss" scoped>
.parent-list {
padding: 5px 10px;
position: relative;
box-shadow: none;
.list-border-left {
position: absolute;
left: 4px;
top: 50%;
height: 0;
border-left: 4px solid #66cccc;
border-radius: 15px;
transition: all 0.3s;
transform: translateY(-50%);
}
&:hover .list-border-left {
height: 70%;
}
}
.menue-list {
width: 100%;
> li > .menue-list-item {
padding: 4px 15px;
width: 100%;
font-size: 16px;
transition: all 0.4s;
position: relative;
&:hover {
background-color: #99cc99;
// padding-top: 10px;
// padding-bottom: 10px;
}
}
}
.children-menue > .menue-list {
padding-left: 12px;
.menue-list-item {
font-size: 13px;
}
}
.meunue-active {
background-color: #99cc99;
}
.parent-item:hover {
background-color: transparent !important;
}
</style>

View File

@@ -0,0 +1,17 @@
import { roleType } from "@/types/role";
import { systemMenueType } from "@/types/systemMenue";
import api from "@/lib/request";
export async function roleDataList(): Promise<roleType[]> {
let roleList: roleType[] =[];
let getData = await api().get('/role/list');
roleList = getData.data.list;
return roleList
}
export async function menueDataList (): Promise<systemMenueType[]> {
let menueList: systemMenueType[] =[];
let getData = await api().get('/system-menue/list?all=true');
menueList = getData.data.list;
return menueList
}

View File

@@ -0,0 +1,96 @@
import axios, { AxiosRequestConfig } from "axios";
import { userInfor } from "@/store/user/user";
import { ElMessage } from "element-plus";
import router from "@/routers/index";
const userInforStore = userInfor();
interface apiOptions {
format: {
formData: boolean; // 表单提交
multipart: boolean; // 文件上传
};
}
interface dataFormat {
code: number;
msg?: string;
data: any;
}
let contentType: { [key: string]: any } = {
json: "appliation/json;charset=utf-8;",
formData: "application/x-www-form-urlencoded;",
multipart: "multipart/form-data;",
};
let typeKey = "json";
class api {
format = { formData: false, multipart: false };
private instance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
});
constructor(options?: apiOptions) {
if (options) {
this.format = { ...this.format, ...options.format };
if (this.format.formData) typeKey = "formData";
if (this.format.multipart) typeKey = "multipart";
}
this.instance.interceptors.request.use(
(config) => {
this.instance.defaults.headers["Content-Type"] = contentType[typeKey];
config.headers["Authorization"] = userInforStore.token;
config.headers["refreshToken"] = userInforStore.refreshToken;
return config;
},
(error) => {
return Promise.reject(error);
}
);
this.instance.interceptors.response.use(
(responese) => {
if (responese.data.code === 401) {
ElMessage.error(responese.data.msg);
userInforStore.removeLoginState();
userInforStore.removeToken();
router.replace("/login");
return responese;
}
if ([501, 501, 503, 500].includes(responese.data.code)) {
ElMessage.error(responese.data.msg);
return responese;
}
let newToken = responese.headers["refreshToken"];
if (newToken) {
userInforStore.setToken(newToken);
}
return responese;
},
(error) => {
if (error.status === 401) {
ElMessage.error(error.response.data.msg);
userInforStore.removeLoginState();
userInforStore.removeToken();
router.replace("/login");
}
return Promise.reject(error);
}
);
}
async get(url: string, config?: AxiosRequestConfig<any> | undefined) {
return (await this.instance.get(url, config)).data;
}
async post(
url: string,
data?: any,
config?: AxiosRequestConfig<any> | undefined
) {
return (await this.instance.post(url, data, config)).data;
}
}
export default function (options?: apiOptions) {
return new api(options);
}

65
frontEnd/src/main.ts Normal file
View File

@@ -0,0 +1,65 @@
import { createApp } from "vue";
import "./style.css";
import App from "./App.vue";
// 引入element-plush
import ElementPlus from "element-plus";
import "@/assets/css/element.scss";
// @ts-ignore
import zhCn from "element-plus/dist/locale/zh-cn.mjs";
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
import "./assets/css/default.scss";
import "./assets/css/reset.scss";
import { createPinia } from "pinia";
import routers from "./routers/index";
import { userInfor } from "./store/user/user";
import { globalState } from "./store";
import globalComponents from "@/util/globalComponents";
import JsonExcel from "vue-json-excel3";
import print from "vue3-print-nb";
let app = createApp(App);
app.use(print);
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}
app.component("downloadExcel", JsonExcel);
app
.use(createPinia())
.use(routers)
.use(ElementPlus, { locale: zhCn })
.use(globalComponents)
.mount("#app");
// 得挂载后才能使用
let userInforState = userInfor();
let globalStateState = globalState();
let closeLoadingShow = () => globalStateState.setLoadingShow(false);
routers.afterEach((to) => {
globalStateState.setLoadingShow(true, "正在前往");
if (!userInforState.token && to.path !== "/login") {
let tempUserInfor = JSON.parse(localStorage.getItem("userInfor") as string);
let tempToken = localStorage.getItem("token");
let temoRefreshToken = localStorage.getItem("refreshToken");
let tempHistoryRouter = localStorage.getItem("historyRouterPath");
if (tempUserInfor && tempToken && temoRefreshToken) {
userInforState.setLoginState(tempUserInfor);
userInforState.setToken(tempToken);
userInforState.setRefToken(temoRefreshToken);
// 如果有历史路由,保持历史路由路径,无感刷新页面
if (tempHistoryRouter) {
routers.push({ path: tempHistoryRouter as string });
}
closeLoadingShow();
} else {
routers.replace({ path: "/login" });
closeLoadingShow();
}
return;
}
globalStateState.setHistoryRouterPath(to.path);
closeLoadingShow();
});

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>

View File

@@ -0,0 +1,266 @@
import {
createRouter,
createWebHashHistory,
createWebHistory,
RouteRecordRaw,
} from "vue-router";
const routes: Array<RouteRecordRaw> = [
{
path: "/login",
name: "登陆",
component: () => import("@/pages/login/login.vue"),
},
{
path: "/:cathAll(.*)",
name: "未找到",
component: () => import("@/pages/404/404.vue"),
},
{
path: "/",
name: "主页",
redirect: "main",
component: () => import("@/layout/index.vue"),
children: [
{
path: "/main",
name: "首页",
meta: {
icon: {
type: "elem",
value: "House",
},
},
component: () => import("@/pages/main/index.vue"),
},
{
path: "/system",
name: "系统设置",
meta: {
icon: {
type: "elem",
value: "Setting",
},
},
children: [
{
path: "/account",
name: "用户管理",
meta: {
icon: {
type: "elem",
value: "User",
},
},
component: () => import("@/pages/system/user/index.vue"),
},
{
path: "/role",
name: "角色管理",
meta: {
icon: {
type: "elem",
value: "Avatar",
},
},
component: () => import("@/pages/system/role/index.vue"),
},
{
path: "/menue",
name: "菜单管理",
meta: {
icon: {
type: "elem",
value: "Menu",
},
},
component: () => import("@/pages/system/menue/index.vue"),
},
{
path: "/personal",
name: "个人中心",
meta: {
icon: {
type: "elem",
value: "UserFilled",
},
},
component: () => import("@/pages/system/user/personal.vue"),
},
],
},
{
path: "/serviceList",
name: "服务项目",
meta: {
icon: {
type: "elem",
},
},
component: () => import("@/pages/serviceList/serviceList.vue"),
},
{
path: "/funeralRetail",
name: "殡仪零售",
meta: {
icon: {
type: "svg",
value: "零售",
},
},
component: () => import("@/pages/funeralRetail/funeralRetail.vue"),
},
{
path: "/funeralServices",
name: "殡仪服务",
meta: {
icon: {
type: "svg",
value: "服务.svg",
},
},
component: () => import("@/pages/funeralServices/funeralServices.vue"),
},
{
path: "/checkout",
name: "结账处理",
meta: {
icon: {
type: "svg",
value: "结账.svg",
},
},
component: () => import("@/pages/checkout/checkout.vue"),
},
{
path: "/invalidReview",
name: "作废审核",
meta: {
icon: {
type: "svg",
value: "作废.svg",
},
},
component: () => import("@/pages/invalidReview/invalidReview.vue"),
},
{
path: "/funeralRetail",
name: "零售管理",
meta: {
icon: {
type: "svg",
value: "零售.svg",
},
},
component: () => import("@/pages/funeralRetail/funeralRetail.vue"),
children: [
{
path: "/departedSaint",
name: "逝者零售管理",
meta: {
icon: {
type: "svg",
value: "零售管理.svg",
},
},
component: () =>
import("@/pages/funeralRetail/departedSaint/departedSaint.vue"),
},
{
path: "/noDepartedSaint",
name: "无逝者零售登记",
meta: {
icon: {
type: "svg",
value: "登记.svg",
},
},
component: () =>
import(
"@/pages/funeralRetail/noDepartedSaint/noDepartedSaint.vue"
),
},
{
path: "/saintCheckout",
name: "零售结算",
meta: {
icon: {
type: "svg",
value: "结算.svg",
},
},
component: () =>
import("@/pages/funeralRetail/saintCheckout/saintCheckout.vue"),
},
],
},
{
path: "/statistics",
name: "后台统计",
meta: {
icon: {
type: "svg",
value: "统计.svg",
},
},
children: [
{
path: "/sales",
name: "销售统计报表",
meta: {
icon: {
type: "svg",
value: "销售统计.svg",
},
},
component: () => import("@/pages/statistics/sales/sales.vue"),
},
{
path: "/saleDetail",
name: "公司销售明细",
meta: {
icon: {
type: "svg",
value: "销售明细.svg",
},
},
component: () =>
import("@/pages/statistics/saleDetail/saleDetail.vue"),
},
// {
// path: "/guideDetail",
// name: "引导员销售统计表",
// meta: {
// icon: {
// type: "elem",
// value: "Menu",
// },
// },
// component: () =>
// import("@/pages/statistics/guideDetail/guideDetail.vue"),
// },
{
path: "/dayIncome",
name: "公司日收入统计表",
meta: {
icon: {
type: "svg",
value: "日收入.svg",
},
},
component: () =>
import("@/pages/statistics/dayIncome/dayIncome.vue"),
},
],
},
],
},
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
export default router;

View File

@@ -0,0 +1,46 @@
import { defineStore } from "pinia"
import { RouteRecordRaw } from "vue-router";
interface globalState {
loadingShow: boolean;
globalLodingShow: boolean;
globalLodingShowText: string;
currentMenue: RouteRecordRaw | null;
historyRouterPath: string;
loadingText: string
}
export const globalState = defineStore('globalState', {
state(): globalState{
return {
loadingShow: false,
loadingText: '',
currentMenue: null,
historyRouterPath: '/',
globalLodingShow: false,
globalLodingShowText: ""
}
},
actions: {
setLoadingShow(state: boolean, showText = '请稍候...' ) {
this.loadingShow = state;
this.loadingText = showText;
},
toggleLoadingShow() {
this.loadingShow = !this.loadingShow;
},
setSelectMenue(menue: RouteRecordRaw) {
this.currentMenue = menue;
},
setHistoryRouterPath(path: string) {
this.historyRouterPath = path;
localStorage.setItem('historyRouterPath', path);
},
setLoadingText(text:string) {
this.loadingText = text;
},
setGlobalLoadingShow(state: boolean, showText = '请稍候...') {
this.globalLodingShow = state;
this.globalLodingShowText = showText;
}
}
})

View File

@@ -0,0 +1,44 @@
import { defineStore } from "pinia";
import { userType } from "@/types/user";
interface user {
userInfor: userType;
token: string | undefined;
refreshToken: string | undefined;
}
export const userInfor = defineStore("userInfor", {
state(): user {
return {
userInfor: {
name: "",
},
token: "",
refreshToken: "",
};
},
actions: {
setLoginState(userData: userType) {
this.userInfor = userData;
localStorage.setItem("userInfor", JSON.stringify(userData));
},
removeLoginState() {
localStorage.removeItem("userInfor");
},
setToken(token: string) {
this.token = token;
localStorage.setItem("token", token);
},
setRefToken(toekn: string) {
this.refreshToken = toekn;
localStorage.setItem("refreshToken", toekn);
},
removeToken() {
this.token = undefined;
this.refreshToken = undefined;
localStorage.removeItem("token");
localStorage.removeItem("refreshToken");
localStorage.removeItem("historyRouterPath");
},
},
});

82
frontEnd/src/style.css Normal file
View File

@@ -0,0 +1,82 @@
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
* {
padding: 0;
margin: 0;
box-sizing: border-box;
font-size: 0.85rem;
}
ul {
list-style: none;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.card {
padding: 2em;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

View File

@@ -0,0 +1,101 @@
declare global {
interface Product {
id?: number; // 商品ID
name: string; // 商品名称
parentId?: string; // 商品分类
group?: string; // 商品分组
price: number; // 商品价格
unit: string; // 计量单位
image?: string; // 商品图片路径
quantity: number; // 商品数量
remark?: string; // 标注
}
interface RegisForm {
id?: number;
deceasedId?: number;
name: string; // 逝者姓名
idNumber: string; // 证件号码
gender: string; // 性别
age: number; // 年龄
buyer: string; // 购买人
purchaseDate: string; // 购买日期 (格式为 YYYY-MM-DD)
handler: string; // 经办人 (数据库字段为 handler不是 handler)
salesAmount: number; // 销售金额
guide: string; // 引导员
familyName: string;
familyPhone: string;
services?: ServiceItemType[];
retailState?: number;
province?: string; // 所在省
city?: string; // 所在市
area?: string; // 所在区域
address?: string; // 详细地址
serviceItems: ServiceItemType[] | string; // 服务项目列表 (数据库字段为 service_items)
type: number;
retailId?: number;
deceasedName?: string;
}
type RegistrationType =
| "服务登记"
| "零售登记"
| "修改登记"
| "零售结账"
| "服务修改";
interface PaymentForm {
checkoutDate: string; // 结账日期
handler: string; // 经办人
settlementDate: string; // 结算日期
cashAmount: number; // 现金金额
unionPayAmount: number; // 银联支付金额
cardAmount: number; // 刷卡金额
publicTransferAmount: number; // 对公转账金额
workshopPayment: NumberConstructor; // 车间支付
}
interface ServiceItemType {
name: string; // 服务项目名称
quantity: number; // 数量
unit: string; // 单位
price: number; // 售价
remark: string; // 备注
category: number; // 关联的分类信息
createDate?: string; // 创建时间
updateDate?: string; // 更新时间
}
export interface DeceasedRetail {
/** 逝者ID */
deceased: number;
/** 购买人 */
buyer: string;
/** 购买日期(格式化为 YYYY-MM-DD HH:mm:ss */
purchaseDate: string;
/** 经办人 */
handler: string;
/** 销售金额(精确到小数点后两位) */
salesAmount: number;
/** 引导员 */
guide: string;
/** 服务项目列表 */
serviceItems: string[];
/** 结账状态0未结账1已结账 */
retailState: number;
}
interface guideOption {
value: number;
label: string;
}
}
export {};

View File

@@ -0,0 +1,7 @@
export type roleType = {
name?: string;
roleState?: number;
values?: string;
createDate?:string;
id?:number;
}

View File

@@ -0,0 +1,9 @@
export type systemMenueType = {
name?: string;
path?: string;
parentId?: number;
icon?: string;
show?: boolean;
id?:number;
children?: systemMenueType[];
}

View File

@@ -0,0 +1,17 @@
import { userType } from "./user";
export type tableDataType = {
data: Array<userType | any>;
total: number;
pageNumber: number;
pageSize: number;
}
export type tableOptionType = {
url: string;
showPagination?: boolean;
searchParams: object;
searchUrl: string;
resizeTable?: boolean;
executeType?: "reset" | "list" | "search";
};

View File

@@ -0,0 +1,18 @@
import { systemMenueType } from "./systemMenue";
export type userType = {
id?: number;
name?: string; // 姓名
sex?: string | number | undefined;
phone?: string;
createDate?: string;
userState?: boolean | number; // 账户状态
role?: number | string; // 角色
birthday?: string; // 生日
age?: number | string; // 年龄
province?: string; // 所在省
city?: string; // 所在市
area?: string; // 所在区域
address?: string; // 详细地址
routerMenue?: systemMenueType[];
pwd?: string;
};

View File

@@ -0,0 +1,11 @@
import { App, defineAsyncComponent, defineComponent } from "vue"
const modulesComponent = import.meta.glob('../components/**/**.vue');
const install = (app: App) => {
for(const [key , value] of Object.entries(modulesComponent)) {
const name = key.slice(key.lastIndexOf('/') + 1, key.lastIndexOf('.'));
app.component(name, defineAsyncComponent(value))
}
}
export default install

View File

@@ -0,0 +1,15 @@
//重置对象属性
export function resetParams(params: { [key: string]: any }) {
Object.keys(params).forEach((key: string) => {
let keyVal = params[key];
let type = typeof keyVal;
if (type === "undefined") params[key] = undefined;
if (type === "string") params[key] = "";
if (type === "number") params[key] = 0;
if (type === "object") {
if (Array.isArray(keyVal)) params[key] = [];
if (Object.prototype.toString.call(keyVal) === "[object Object]")
params[key] = {};
}
});
}

7
frontEnd/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}