This commit is contained in:
ChenQiuYu 2025-04-24 09:36:59 +08:00
commit 5115886b2f
208 changed files with 31610 additions and 0 deletions

30
.gitignore vendored Normal file
View File

@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

33
README.md Normal file
View File

@ -0,0 +1,33 @@
# web
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
## Customize configuration
See [Vite Configuration Reference](https://vite.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Type-Check, Compile and Minify for Production
```sh
npm run build
```

1
env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

4211
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

33
package.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "web",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build"
},
"dependencies": {
"axios": "^1.8.4",
"echarts": "^5.6.0",
"element-plus": "^2.9.7",
"pinia": "^3.0.1",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
},
"devDependencies": {
"@tsconfig/node22": "^22.0.1",
"@types/node": "^22.14.0",
"@vitejs/plugin-vue": "^5.2.3",
"@vue/tsconfig": "^0.7.0",
"npm-run-all2": "^7.0.2",
"sass": "^1.86.3",
"typescript": "~5.8.0",
"vite": "^6.2.4",
"vite-plugin-vue-devtools": "^7.7.2",
"vue-tsc": "^2.2.8"
}
}

1
public/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
config.json

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 987 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1015 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 941 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

10
src/App.vue Normal file
View File

@ -0,0 +1,10 @@
<template>
<RouterView />
</template>
<style scoped lang="scss">
</style>
<script setup lang="ts">
</script>

View File

@ -0,0 +1,58 @@
{
"01": "汉族",
"02": "蒙古族",
"03": "回族",
"04": "藏族",
"05": "维吾尔族",
"06": "苗族",
"07": "彝族",
"08": "壮族",
"09": "布依族",
"10": "朝鲜族",
"11": "满族",
"12": "侗族",
"13": "瑶族",
"14": "白族",
"15": "土家族",
"16": "哈尼族",
"17": "哈萨克族",
"18": "傣族",
"19": "黎族",
"20": "傈僳族",
"21": "佤族",
"22": "畲族",
"23": "高山族",
"24": "拉祜族",
"25": "水族",
"26": "东乡族",
"27": "纳西族",
"28": "景颇族",
"29": "柯尔克孜族",
"30": "土族",
"31": "达斡尔族",
"32": "仫佬族",
"33": "羌族",
"34": "布朗族",
"35": "撒拉族",
"36": "毛南族",
"37": "仡佬族",
"38": "锡伯族",
"39": "阿昌族",
"40": "普米族",
"41": "塔吉克族",
"42": "怒族",
"43": "乌孜别克族",
"44": "俄罗斯族",
"45": "鄂温克族",
"46": "德昂族",
"47": "保安族",
"48": "裕固族",
"49": "京族",
"50": "塔塔尔族",
"51": "独龙族",
"52": "鄂伦春族",
"53": "赫哲族",
"54": "门巴族",
"55": "珞巴族",
"56": "基诺族"
}

5730
src/assets/config/area.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,64 @@
export const priceBtnList = [
{name: '医保', type: 1, img: '/src/assets/images/price/yiBaoKa.png', color: '#39b035'},
{name: '微信', type: 2, img: '/src/assets/images/price/weiXin.png', color: '#39b035'},
{name: '支付宝', type: 3, img: '/src/assets/images/price/zhiFuBao.png', color: '#409eff'},
{name: '现金', type: 4, img: '/src/assets/images/price/xianJin.png', color: '#faf205'},
{name: '其他', type: 5, img: '/src/assets/images/price/qiTa.png', color: '#05c5ff'},
]
export const priceBtnListNoSocial = [
{name: '微信', type: 2, img: '/src/assets/images/price/weiXin.png', color: '#39b035'},
{name: '支付宝', type: 3, img: '/src/assets/images/price/zhiFuBao.png', color: '#409eff'},
{name: '现金', type: 4, img: '/src/assets/images/price/xianJin.png', color: '#faf205'},
{name: '其他', type: 5, img: '/src/assets/images/price/qiTa.png', color: '#05c5ff'},
]
export const reconciliationResult =
{
"0": "平",
"1": "不平",
"101": "中心多",
"102": "医药机构多",
"103": "数据不一致"
}
export const medTypeJson ={
"41": "定点药店购药",
"11": "普通门诊",
"140201": "门诊特病",
"140104": "门诊慢病",
"1301": "急诊抢救",
"9107": "体检",
"110104": "门诊统筹",
"21": "普通住院",
"990301": "统筹区内转院",
"990901": "特殊病住院",
"71": "家庭病床",
"210303": "精神病住院",
"28": "日间手术",
"15": "门诊特药",
"51": "生育门诊",
"52": "生育住院",
"530102": "计划生育门诊",
"530202": "计划生育住院",
"990503": "日间病床(日间治疗)",
"990101": "门诊单病种(门诊治疗病种)",
"12": "门诊挂号",
"1102": "新冠门诊",
"2110": "新冠住院",
"23": "转外诊治住院",
"990502": "特殊情况门诊(用于家庭医生签约)",
"110105": "门诊统筹手术病种",
"990504": "特殊日间病床",
"14": "门诊慢特病",
"1404": "城乡两病门诊",
"510102": "产前检查",
"108": "辅助生殖门诊"
}
export const tempList = [
["咳嗽", "干咳", "咳痰", "夜咳", "晨咳", "咽干", "咽痒", "咽痛", "痰中带血", "声音嘶哑", "咽部异物感", "反复感冒", "发热", "喷嚏", "流涕", "鼻塞", "头痛", "头晕", "耳鸣", "汗多", "盗汗", "自汗", "出汗", "易汗出"],
["胃胀", "胃痛", "胃不适", "腹胀", "腹痛", "腹泻", "恶心", "呕吐", "反酸", "嗳气", "烧心", "纳差", "便秘", "便溏", "便血", "黑便", "大便干", "大便黏", "五更泻", "腹痛欲便", "里急后重", "排便不爽", "溏结不调"],
["胸闷", "胸痛", "心悸", "气短", "气喘", "气促", "眠差", "眠浅", "多梦", "易醒", "早醒", "入睡困难", "嗜睡", "尿频", "尿急", "尿痛", "尿不尽", "尿灼热", "尿分叉", "夜尿多", "尿浊", "尿血", "水肿", "阳痿", "早泄"],
["胁痛", "颈椎痛", "关节痛", "关节僵硬", "四肢麻木", "半身麻木", "四肢无力", "偏瘫", "拘挛", "肩痛", "背痛", "腰痛", "眼干", "0千", "口苦", "牙痛", "齿衄", "口疮", "皮疹", "斑疹", "丘疹", "风团", "皮肤红斑", "皮肤瘙痒"],
["闭经", "崩漏", "月经量多", "月经量少", "经期错乱", "带下量多", "带下量少", "带下异味", "黄带", "痛经", "月经提前", "月经延后"],
["偶尔1天", "2天", "3天", "4天", "5天", "1个月", "2个月", "3个月", "半年", "1年", "1周", "2周", "3周"]
]

View File

@ -0,0 +1,292 @@
{
"A01": "预防保健科",
"A02": "全科医疗科",
"A03": {
"name": "内科",
"children": {
"A03.01": "呼吸内科专业",
"A03.02": "消化内科专业",
"A03.03": "神经内科专业",
"A03.04": "心血管内科专业",
"A03.05": "血液内科专业",
"A03.06": "肾病学专业",
"A03.07": "内分泌专业",
"A03.08": "免疫学专业",
"A03.09": "变态反应专业",
"A03.10": "老年病专业",
"A03.11": "其他"
}
},
"A04": {
"name": "外科",
"children": {
"A04.01": "普通外科专业",
"A04.01.01": "肝脏移植项目",
"A04.01.02": "胰腺移植项目",
"A04.01.03": "小肠移植项目",
"A04.02": "神经外科专业",
"A04.03": "骨科专业",
"A04.04": "泌尿外科专业",
"A04.04.01": "肾脏移植项目",
"A04.05": "胸外科专业",
"A04.05.01": "肺脏移植项目",
"A04.06": "心脏大血管外科专业",
"A04.06.01": "心脏移植项目",
"A04.07": "烧伤科专业",
"A04.08": "整形外科专业",
"A04.09": "其他"
}
},
"A05": {
"name": "妇产科",
"children": {
"A05.01": "妇科专业",
"A05.02": "产科专业",
"A05.03": "计划生育专业",
"A05.04": "优生学专业",
"A05.05": "生殖健康与不孕症专业",
"A05.06": "其他"
}
},
"A06": {
"name": "妇女保健科",
"children": {
"A06.01": "青春期保健专业",
"A06.02": "围产期保健专业",
"A06.03": "更年期保健专业",
"A06.04": "妇女心理卫生专业",
"A06.05": "妇女营养专业",
"A06.06": "其他"
}
},
"A07": {
"name": "儿科",
"children": {
"A07.01": "新生儿专业",
"A07.02": "小儿传染病专业",
"A07.03": "小儿消化专业",
"A07.04": "小儿呼吸专业",
"A07.05": "小儿心脏病专业",
"A07.06": "小儿肾病专业",
"A07.07": "小儿血液病专业",
"A07.08": "小儿神经病学专业",
"A07.09": "小儿内分泌专业",
"A07.10": "小儿遗传病专业",
"A07.11": "小儿免疫专业",
"A07.12": "其他"
}
},
"A08": {
"name": "小儿外科",
"children": {
"A08.01": "小儿普通外科专业",
"A08.02": "小儿骨科专业",
"A08.03": "小儿泌尿外科专业",
"A08.04": "小儿胸心外科专业",
"A08.05": "小儿神经外科专业",
"A08.06": "其他"
}
},
"A09": {
"name": "儿童保健科",
"children": {
"A09.01": "儿童生长发育专业",
"A09.02": "儿童营养专业",
"A09.03": "儿童心理卫生专业",
"A09.04": "儿童五官保健专业",
"A09.05": "儿童康复专业",
"A09.06": "其他"
}
},
"A10": "眼科",
"A11": {
"name": "耳鼻咽喉科",
"children": {
"A11.01": "耳科专业",
"A11.02": "鼻科专业",
"A11.03": "咽喉科专业",
"A11.04": "其他"
}
},
"A12": {
"name": "口腔科",
"children": {
"A12.01": "牙体牙髓病专业",
"A12.02": "牙周病专业",
"A12.03": "口腔黏膜病专业",
"A12.04": "儿童口腔专业",
"A12.05": "口腔颌骨外科专业",
"A12.06": "口腔修复专业",
"A12.07": "口腔正畸专业",
"A12.08": "口腔种植专业",
"A12.09": "口腔麻醉专业",
"A12.10": "口腔颌面医学影像专业"
}
},
"A13": {
"name": "皮肤科",
"children": {
"A13.01": "皮肤病专业",
"A13.02": "性传播疾病专业",
"A13.03": "其他"
}
},
"A14": "医疗美容科",
"A15": {
"name": "精神科",
"children": {
"A15.01": "精神病专业",
"A15.02": "精神卫生专业",
"A15.03": "药物依赖专业",
"A15.04": "精神康复专业",
"A15.05": "社区防治专业",
"A15.06": "临床心理专业",
"A15.07": "司法精神专业",
"A15.08": "其他"
}
},
"A16": {
"name": "传染科",
"children": {
"A16.01": "肠道传染病专业",
"A16.02": "呼吸道传染病专业",
"A16.03": "肝炎专业",
"A16.04": "虫媒传染病专业",
"A16.05": "动物源性传染病专业",
"A16.06": "蠕虫病专业",
"A16.07": "其它"
}
},
"A17": "结核病科",
"A18": "地方病科",
"A19": "肿瘤科",
"A20": "急诊医学科",
"A21": "康复医学科",
"A22": "运动医学科",
"A23": {
"name": "职业病科",
"children": {
"A23.01": "职业中毒专业",
"A23.02": "尘肺专业",
"A23.03": "放射病专业",
"A23.04": "物理因素损伤专业",
"A23.05": "职业健康监护专业",
"A23.06": "其他"
}
},
"A24": "临终关怀科",
"A25": "特种医学与军事医学科",
"A26": "麻醉科",
"A27": "疼痛科",
"A28": "重症医学科",
"A30": {
"name": "医学检验科",
"children": {
"A30.01": "临床体液、血液专业",
"A30.02": "临床微生物学专业",
"A30.03": "临床生化检验专业",
"A30.04": "临床免疫、血清学专业",
"A30.05": "临床细胞分子遗传学专业",
"A30.06": "其他"
}
},
"A31": "病理科",
"A32": {
"name": "医学影像科",
"children": {
"A32.01": "X线诊断专业",
"A32.02": "CT诊断专业",
"A32.03": "磁共振成像诊断专业",
"A32.04": "核医学专业",
"A32.05": "超声诊断专业",
"A32.06": "心电诊断专业",
"A32.07": "脑电及脑血流图诊断专业",
"A32.08": "神经肌肉电图专业",
"A32.09": "介入放射学专业",
"A32.10": "放射治疗专业",
"A32.11": "其他"
}
},
"A50": {
"name": "中医科",
"children": {
"A50.01": "内科专业",
"A50.02": "外科专业",
"A50.03": "妇产科专业",
"A50.04": "儿科专业",
"A50.05": "皮肤科专业",
"A50.06": "眼科专业",
"A50.07": "耳鼻咽喉科专业",
"A50.08": "口腔科专业",
"A50.09": "肿瘤科专业",
"A50.10": "骨伤科专业",
"A50.11": "肛肠科专业",
"A50.12": "老年病科专业",
"A50.13": "针灸科专业",
"A50.14": "推拿科专业",
"A50.15": "康复医学专业",
"A50.16": "急诊科专业",
"A50.17": "预防保健科专业",
"A50.18": "其他"
}
},
"A51": {
"name": "民族医学科",
"children": {
"A51.01": "维吾尔医学",
"A51.02": "藏医学",
"A51.03": "蒙医学",
"A51.04": "彝医学",
"A51.05": "傣医学",
"A51.06": "其他"
}
},
"A52": "中西医结合科",
"A69": "其他业务科室",
"B01": "传染病预防控制科(中心)",
"B02": "性病艾滋病预防控制科(中心)",
"B03": "结核病预防控制科(中心)",
"B04": "血吸虫预防控制科(中心)",
"B05": "慢性非传染性疾病预防控制科(中心)",
"B06": "寄生虫病预防控制科(中心)",
"B07": "地方病控制科(中心)",
"B08": "精神卫生科(中心)",
"B09": "妇幼保健科",
"B10": "免疫规划科(中心)",
"B11": "农村改水技术指导科(中心)",
"B12": "疾病控制与应急处理办公室",
"B13": "食品卫生科",
"B14": "环境卫生所",
"B15": "职业卫生科",
"B16": "放射卫生科",
"B17": "学校卫生科",
"B18": "健康教育科(中心)",
"B19": "预防医学门诊",
"B69": "其他业务科室",
"C01": "综合卫生监督科",
"C02": "产品卫生监督科",
"C03": "职业卫生监督科",
"C04": "环境卫生监督科",
"C05": "传染病执法监督科",
"C06": "医疗服务监督科",
"C07": "稽查科(大队)",
"C08": "许可受理科",
"C09": "放射卫生监督科",
"C10": "学校卫生监督科",
"C11": "食品安全监督科",
"C69": "其他",
"D71": "护理部",
"D72": "药剂科(药房)",
"D73": "感染科",
"D74": "输血科(血库)",
"D81": "办公室",
"D82": "人事科",
"D83": "财务科",
"D84": "设备科",
"D85": "信息科(中心)",
"D86": "医政科",
"D87": "教育培训科",
"D88": "总务科",
"D89": "新农合管理办公室",
"D99": "其他科室"
}

View File

@ -0,0 +1,6 @@
{
"0": "未知",
"1": "男",
"2": "女",
"9": "未说明"
}

View File

@ -0,0 +1,28 @@
{
"310": "职工基本医疗保险",
"31003": "医疗保险个人账户(用人单位)",
"312": "农民工住院医疗",
"320": "公务员医疗补助",
"321": "公务员医疗补助(市直统发)",
"323": "公务员医疗补助(市直非统发)",
"330": "大额医疗费用补助",
"331": "二次补助",
"340": "离休人员医疗保障",
"350": "一至六级残废军人医疗补助",
"360": "老红军医疗保障",
"370": "企业补充医疗保险",
"380": "新型农村合作医疗",
"390": "城乡居民基本医疗保险",
"391": "城镇居民基本医疗保险",
"392": "城乡居民大病医疗保险",
"399": "其他特殊人员医疗保障",
"39901": "劳模医疗保障",
"39902": "补充百分之10医疗",
"39903": "城乡居民补充医疗保险",
"39904": "建国前老工人医疗保险",
"39905": "二乙医疗保险",
"39906": "意外伤害医疗保险",
"410": "长期照护保险",
"510": "生育保险",
"520": "公务员生育"
}

143
src/assets/config/menu.json Normal file
View File

@ -0,0 +1,143 @@
[
{
"name": "首页",
"icon": "icon-shouye",
"path": "/home/index",
"children": [
{
"name": "首页",
"path": "/home/index"
}
]
},
{
"name": "零售",
"icon": "icon-renminbi1688",
"path": "/retail/retail",
"children": [
{
"name": "零售",
"path": "/retail/retail"
},
{
"name": "零售单",
"path": "/retail/sales"
},
{
"name": "对账",
"path": "/retail/flows"
}
]
},
{
"name": "库存",
"icon": "icon-cangku",
"path": "/inventory/goods",
"children": [
{
"name": "商品",
"path": "/inventory/goods"
},
{
"name": "采购",
"path": "/inventory/purchase"
},
{
"name": "领用",
"path": "/inventory/use"
},
{
"name": "供应商",
"path": "/inventory/supplier"
},
{
"name": "盘点",
"path": "/inventory/check"
}
]
},
{
"name": "会员",
"icon": "icon-huiyuan",
"path": "/member/index",
"children": [
{
"name": "首页",
"path": "/member/index"
}
]
},
{
"name": "医保",
"icon": "icon-yibao",
"path": "/social/directory",
"children": [
{
"name": "医保目录",
"path": "/social/directory"
},
{
"name": "数据更新",
"path": "/social/update"
},
{
"name": "进销存上报",
"path": "/social/inventoryUp"
},
{
"name": "结算",
"path": "/social/costRecord"
},
{
"name": "对账",
"path": "/social/accountRecords"
},
{
"name": "自费病人",
"path": "/social/selfPerson"
}
]
},
{
"name": "统计",
"icon": "icon-tongjifenxi-xiangmubiaogetongji",
"path": "/statistics/overview",
"children": [
{
"name": "营收统计",
"path": "/statistics/overView"
}
]
},
{
"name": "设置",
"icon": "icon-shezhi",
"path": "/settings/baseinfo",
"children": [
{
"name": "基本设置",
"path": "/settings/baseinfo"
},
{
"name": "授权设置",
"path": "/settings/auth"
},
{
"name": "用户管理",
"path": "/settings/userManage"
},
{
"name": "打印管理",
"path": "/settings/print"
},
{
"name": "模版管理",
"path": "/settings/template"
},
{
"name": "操作日志",
"path": "/settings/operationLog"
}
]
}
]

View File

@ -0,0 +1,58 @@
{
"01": "汉族",
"02": "蒙古族",
"03": "回族",
"04": "藏族",
"05": "维吾尔族",
"06": "苗族",
"07": "彝族",
"08": "壮族",
"09": "布依族",
"10": "朝鲜族",
"11": "满族",
"12": "侗族",
"13": "瑶族",
"14": "白族",
"15": "土家族",
"16": "哈尼族",
"17": "哈萨克族",
"18": "傣族",
"19": "黎族",
"20": "傈僳族",
"21": "佤族",
"22": "畲族",
"23": "高山族",
"24": "拉祜族",
"25": "水族",
"26": "东乡族",
"27": "纳西族",
"28": "景颇族",
"29": "柯尔克孜族",
"30": "土族",
"31": "达斡尔族",
"32": "仫佬族",
"33": "羌族",
"34": "布朗族",
"35": "撒拉族",
"36": "毛南族",
"37": "仡佬族",
"38": "锡伯族",
"39": "阿昌族",
"40": "普米族",
"41": "塔吉克族",
"42": "怒族",
"43": "乌孜别克族",
"44": "俄罗斯族",
"45": "鄂温克族",
"46": "德昂族",
"47": "保安族",
"48": "裕固族",
"49": "京族",
"50": "塔塔尔族",
"51": "独龙族",
"52": "鄂伦春族",
"53": "赫哲族",
"54": "门巴族",
"55": "珞巴族",
"56": "基诺族"
}

View File

@ -0,0 +1,23 @@
{
"01": "居民身份证(户口簿)",
"02": "中国人民解放军军官证",
"03": "中国人民武装警察警官证",
"04": "香港特区护照/港澳居民来往内地通行证",
"05": "澳门特区护照/港澳居民来往内地通行证",
"06": "台湾居民来往大陆通行证",
"07": "外国人永久居留证",
"08": "外国人护照",
"09": "残疾人证",
"10": "军烈属证明",
"11": "外国人就业证",
"12": "外国专家证",
"13": "外国人常驻记者证",
"14": "台港澳人员就业证",
"15": "回国(来华)定居专家证",
"16": "中国护照",
"17": "港澳台居民居住证",
"90": "社会保障卡",
"99": "其他身份证件",
"990102": "扶贫人口编码",
"990201": "医学出生证明"
}

View File

@ -0,0 +1,96 @@
{
"11": "在职",
"1101": "职工在职",
"1102": "公务员在职",
"11021": "转制并轨在职",
"1103": "灵活就业人员在职",
"11031": "保健对象在职",
"1104": "减员职工",
"1105": "农民工",
"1111": "伤残军人在职",
"1112": "伤残军人退休",
"1113": "职工在职(农垦)",
"1114": "公务员退休(特)",
"116003": "失业人员",
"116004": "子女",
"116006": "企业高管(在职)",
"116011": "非财政划拨在职保健对象",
"116012": "国务院特殊津贴在职人员",
"116013": "在职副厅以上干部",
"116014": "在职残疾军人",
"116015": "二等乙级在职职工",
"116016": "下岗职工",
"116017": "在职二级保健对象",
"12": "退休人员",
"1201": "职工退休",
"1202": "公务员退休",
"1203": "灵活就业人员退休",
"12031": "保健对象退休",
"1204": "退职职工",
"1205": "转制并轨退休",
"126000": "老工人",
"126001": "退休新人",
"126002": "退休非新人",
"126003": "提前退休",
"126004": "退休二级保健对象",
"126009": "非财政划拨退休保健对象",
"126010": "国务院特殊津贴退休人员",
"126013": "退休副厅以上干部",
"126014": "省直代管512退休干部",
"126015": "退休残疾军人",
"126016": "二等乙级退休职工",
"126017": "退休(满足年限)",
"126018": "退休保健对象",
"126019": "退休(不满足年限)",
"126022": "企业高管(退休)",
"13": "离休",
"1300": "离休人员",
"136001": "行政离休",
"136002": "财政供养离休人员",
"136003": "缴专项基金的建国前工人",
"136004": "地震截瘫(市属离休)",
"136005": "离休省属有级别",
"136006": "离休省属无级别",
"136007": "离休市属有级别",
"136008": "离休市属无级别",
"136009": "市直离休",
"136010": "建国前老工人(市属离休)",
"136011": "二等乙(市属离休)",
"136012": "企业离休",
"136013": "已剥离专项基金的建国前工人",
"136014": "普通离休(非地市)",
"136015": "省属离休人员",
"136016": "市直机关事业单位离休",
"136017": "企业自筹离休人员",
"136018": "普通离休(地市级)",
"136019": "市属离休人员",
"136020": "市直企业离休",
"136021": "特殊离休",
"136022": "垦区离休人员",
"136023": "省直代管离休",
"136024": "享受副省部级待遇离休",
"14": "居民(未成年)",
"1401": "新生儿",
"1402": "学龄前儿童",
"1403": "中小学生",
"1404": "大学生",
"1405": "未成年(未入学)",
"1406": "低保学生儿童",
"146001": "市直财政供养人员",
"146002": "残疾人员",
"146004": "低保人员",
"146005": "重度残疾学生",
"1461": "积分入学(百佳学子)",
"149901": "学生儿童",
"15": "居民(成年)",
"1501": "普通居民(成年)",
"151": "居民",
"156001": "普通居民",
"16": "居民(老年)",
"17": "农牧民少",
"18": "农牧民中",
"20": "农牧民老",
"33": "一至六级残疾军人",
"34": "建国前老工人 ",
"99": "其他"
}

123
src/assets/scss/base.scss Normal file
View File

@ -0,0 +1,123 @@
// 主题色
$primary-color: #409EFF;
$success-color: #67C23A;
$warning-color: #E6A23C;
$danger-color: #F56C6C;
$info-color: #909399;
// 文字颜色
$text-primary: #303133;
// 次级文字颜色
$text-color-secondary: #909399;
// 边框颜色
$border-color-base: #DCDFE6;
$border-color-light: #E4E7ED;
$border-color-lighter: #EBEEF5;
$border-color-extra-light: #F2F6FC;
// 背景颜色
$background-color-base: #eee;
$background-color-main: #5078c8;
$background-color-panel: #FFFFFF;
$background-color-b1:#E2E2E2;
// 字体大小
$font-size-extra-large: 20px;
$font-size-large: 18px;
$font-size-medium: 16px;
$font-size-base: 14px;
$font-size-small: 13px;
$font-size-extra-small: 12px;
// 边框圆角
$border-radius-base: 4px;
$border-radius-small: 2px;
$border-radius-large: 8px;
$border-radius-circle: 50%;
// 间距
$spacing-base: 8px;
$spacing-small: 4px;
$spacing-large: 16px;
$spacing-extra-large: 24px;
// 尺寸
$padding-base: 18px;
$margin-base: 8px;
// 阴影
$box-shadow-base: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
$box-shadow-light: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
// 过渡
$transition-duration: 0.3s;
$transition-timing-function: ease-in-out;
// 布局
$header-height: 60px;
$sidebar-width: 200px;
$sidebar-collapse-width: 64px;
@mixin space{
margin: $spacing-base;
}
// 圆角混合器
@mixin border-radius {
border-radius: $border-radius-base;
}
@mixin center-wrapper{
max-width: 1920px;
min-width: 1280px;
margin: 0 auto;
}
// 内间距混合器
@mixin padding {
padding: $padding-base;
}
// 外边距混合器
@mixin margin{
margin: $margin-base;
}
// 阴影混合器
@mixin box-shadow {
box-shadow: $box-shadow-base;
}
// 背景颜色混合器
@mixin background-color {
background-color: $background-color-base;
}
@mixin background-color-panel {
background-color: $background-color-panel;
}
// 文字颜色混合器
@mixin text-color {
color: $text-primary;
}
// 边框颜色混合器
@mixin border{
border:1px solid $border-color-base;
}
// 字体大小混合器
@mixin font-size {
font-size: $font-size-base;
}
// 间距混合器
@mixin spacing{
margin: $spacing-base;
padding: $spacing-base;
}
@mixin main-background {
background-color: $background-color-main;
}

View File

@ -0,0 +1,18 @@
.layout-container{
.header{
height: 80px;
}
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
.layout-main{
flex: 1;
min-height: 0;
}
.footer{
height: 50px;
}
}

98
src/assets/scss/main.scss Normal file
View File

@ -0,0 +1,98 @@
@use "base";
ul, li {
list-style: none; /* 移除项目符号(如圆点、数字等) */
margin: 0; /* 清除默认外边距 */
padding: 0; /* 清除默认内边距 */
}
// 重置样式
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
font-family: PingFangSC, PingFang SC, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
font-size: base.$font-size-base;
color: base.$text-primary;
background-color: base.$background-color-base;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#app {
height: 100vh;
width: 100vw;
overflow: hidden;
}
html {
}
body {
background-color: base.$background-color-base;
//font-family: Helvetica Neue, Helvetica, Arial, PingFang SC, MyHeiTi, Hiragino Sans GB, Heiti SC, WenQuanYi Micro Hei, sans-serif !important;
font-family: Source Han Sans, serif;
}
.center-wrapper {
@include base.center-wrapper;
}
.container-wrapper {
height: 100%;
//background-color: base.$background-color-panel;
@include base.center-wrapper;
//@include base.padding;
@include base.border-radius;
@include base.box-shadow;
}
.space {
@include base.space;
}
.container-wrapper_flex {
@include base.center-wrapper;
display: flex;
flex-direction: column;
min-height: 0;
height: 100%;
}
.content-wrapper {
flex: 1;
overflow: auto;
width: 100%;
@include base.padding;
@include base.border-radius;
@include base.box-shadow;
@include base.background-color-panel;
margin-top: 10px;
min-height: 0;
}
// 清除圆角
.clear-border-radius {
border-radius: 0;
}
// 清除背景色
.clear-background-color {
background-color: transparent;
}
// 清除内边距
.clear-padding {
padding: 0;
}
// 清除外边距
.clear-margin {
margin: 0;
}

View File

@ -0,0 +1,21 @@
<script setup lang="ts">
import {CloseBold} from "@element-plus/icons-vue";
</script>
<template>
<div class="close"><el-icon><CloseBold /></el-icon></div>
</template>
<style scoped lang="scss">
.close{
cursor: pointer;
position: absolute;
top: 5px;
right: 10px;
color: #6e6e6e;
font-size: 24px;
&:hover{
color: #409eff;
}
}
</style>

View File

@ -0,0 +1,15 @@
<script setup lang="ts">
</script>
<template>
<div class="divider"></div>
</template>
<style scoped lang="scss">
.divider{
display: block;
height: 0;
width: 100%;
border-bottom: 1px solid #ddd;
}
</style>

54
src/components/Mask.vue Normal file
View File

@ -0,0 +1,54 @@
<script setup lang="ts">
import { ref, watch } from "vue";
let _width = ref(0);
let _height = ref(0);
let _isShow = ref(false);
const { width, height, isShow } = defineProps(['width', 'height', 'isShow']);
_isShow.value = isShow == null ? false : isShow;
_width.value = width == null ? 1200 : width;
_height.value = height == null ? 800 : height;
watch(() => isShow, (newVal) => {
_isShow.value = newVal == null ? false : newVal;
});
</script>
<template>
<transition name="el-fade-in">
<div class="mask" v-if="_isShow">
<div class="content-wrapper" :style="{ width: _width + 'px', height: _height + 'px' }" v-if="_isShow">
<el-scrollbar height="100%">
<slot></slot>
</el-scrollbar>
</div>
</div>
</transition>
</template>
<style scoped lang="scss">
.mask {
position: fixed;
background-color: rgba(0, 0, 0, 0.5);
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 20px;
z-index: 999;
display: flex;
justify-content: center;
align-items: center;
.content-wrapper {
position: relative;
background-color: #FFF;
border-radius: 6px;
padding: 20px;
overflow: hidden;
max-height: 90vh;
z-index: 1999;
}
}
</style>

36
src/components/Picker.vue Normal file
View File

@ -0,0 +1,36 @@
<script setup lang="ts">
import {ref, watchEffect} from 'vue';
const value1 = ref<any>('');
watchEffect(() => {
value1.value = new Date()
})
</script>
<template>
<div class="my-picker">
<el-date-picker :teleported="false" v-model="value1" type="date" placeholder="Pick a day"/>
</div>
</template>
<style>
.my-picker .el-input {
width: 0;
height: 0;
display: none;
}
.my-picker .el-popper {
display: block !important;
transition: none !important;
position: initial !important;
}
.el-picker__popper.el-popper .el-popper__arrow:before {
display: none;
}
</style>

View File

@ -0,0 +1,60 @@
<template>
<el-popover placement="bottom-start" trigger="click" :width="props.width" >
<template #reference>
<el-input v-model="input" :style="{'width': props.width+'px'}" clearable></el-input>
</template>
<div class="code-popo" v-if="props.list.length > 0">
<div class="code-item" v-for="item in props.list" >
<div class="code-item-name" v-for="subItem in item" @click="inputStr(subItem)">
{{subItem}}
</div>
</div>
</div>
</el-popover>
</template>
<script setup lang="ts">
import {ref} from "vue";
const input = defineModel<string | null>();
const props = defineProps({
list: {
type: Array,
default: []
},
width: {
type: Number,
default: 1000
}
})
const inputStr = (str: string) => {
let strList = input.value?input.value.split(","):[];
strList.push(str);
input.value = strList.join(",");
}
</script>
<style scoped lang="scss">
@use "@/assets/scss/base.scss";
.code-popo{
width: 100%;
.code-item{
display: flex;
flex-wrap: wrap;
padding: 5px 0;
border-bottom: 1px solid base.$border-color-base;
.code-item-name{
flex: 0 0 calc(100% / 12);
font-size: base.$font-size-small;
box-sizing: border-box;
padding: 5px;
text-align: center;
cursor: pointer;
&:hover{
color: base.$background-color-main;
}
}
}
}
</style>

View File

@ -0,0 +1,108 @@
<template>
<el-popover placement="bottom-start" trigger="click" :width="props.width" ref="popoverRef">
<template #reference>
<el-input style="width: 100%;"
v-model="keyword"
:prefix-icon="Plus"
:placeholder="props.placeholder"
:style="{'width': props.width+'px'}"
clearable
@input="changeInput"
class="no-border-input"
>
</el-input>
</template>
<div class="container">
<el-table
:data="searchList" style="width: 100%"
@row-click="clickRow"
:show-header="props.showHeader"
max-height="200px"
>
<el-table-column
v-for="item in showConfig"
:prop="item.prop"
:label="item.label"
show-overflow-tooltip
></el-table-column>
</el-table>
</div>
</el-popover>
</template>
<script setup lang="ts">import { ref } from "vue";
import { post } from "@/utils/request.ts";
import { Plus } from "@element-plus/icons-vue";
const keyword = ref("");
const popoverRef = ref();
interface showConfig {
prop: string;
label: string;
}
const props = defineProps({
requestApi: {
type: String,
default: ""
},
width: {
type: Number,
default: 1000
},
showConfig: {
type: Array as () => showConfig[],
default: () => []
},
showHeader: {
type: Boolean,
default: true
},
placeholder: {
type: String,
default: ""
}
});
const searchList = ref([]);
const changeInput = (inputStr: string) => {
if (!props.requestApi || props.requestApi === "") {
return;
}
post(props.requestApi, { keyword: keyword.value }).then((res: any) => {
searchList.value = res;
});
};
const emit = defineEmits(['selectedCallBack']);
const clickRow = (row: any) => {
emit('selectedCallBack', row);
popoverRef.value.hide();
};
const beforeShow = () => {
if (searchList.value.length === 0) {
popoverRef.value.hide();
}
};
</script>
<style scoped lang="scss">
.no-border-input {
:deep(.el-input__wrapper) {
border: none !important;
box-shadow: none !important;
}
:deep(.el-input__inner) {
border: none !important;
box-shadow: none !important;
}
}
</style>

View File

@ -0,0 +1,59 @@
<template>
<el-cascader
v-model="selectedArea"
:options="areaOptions"
:props="cascaderProps"
placeholder="请选择省市区"
clearable
/>
</template>
<script lang="ts" setup>
import {ref} from 'vue'
import areaData from '@/assets/config/area.json'
interface Area {
name: string
code: string
city?: Area[] // city
area?: Area[] // area
}
//
const formatAreas = (areas: Area[]):any[] => {
return areas.map(area => ({
value: area.code,
label: area.name,
children: getChildren(area) //
}))
}
// cityarea
const getChildren = (area: Area) => {
if (area.city && area.city.length > 0) {
return formatAreas(area.city)
}
if (area.area && area.area.length > 0) {
return formatAreas(area.area)
}
return null
}
const areaOptions = formatAreas(areaData)
const selectedArea = ref<string[]>([])
const cascaderProps = {
value: 'value',
label: 'label',
children: 'children',
checkStrictly: true,
emitPath: true //
}
interface CascaderOption {
value: string
label: string
children?: CascaderOption[]
}
</script>

67
src/components/UpLoad.vue Normal file
View File

@ -0,0 +1,67 @@
<template>
<el-upload
class="avatar-uploader"
:action="url"
:show-file-list="false"
:on-success="handleSuccess"
:before-upload="beforeUpload"
:on-error="handleError"
>
<img v-if="imageUrl" :src="imageUrl" class="avatar" alt=""/>
<el-icon v-else><Plus /></el-icon>
</el-upload>
</template>
<script setup lang="ts">
import {ElMessage} from 'element-plus'
import {Plus} from '@element-plus/icons-vue'
import {defineModel,ref} from 'vue'
import {loadConfig} from "@/utils/config.ts";
const url = defineModel()
const imageUrl = ref<any>()
const getImageUrl = (url: any) => {
loadConfig().then(res=>{
imageUrl.value = res.base_url +'file/getImage/'+ url
})
}
const emit = defineEmits(['uploadSuccess'])
const handleSuccess = (response:any) => {
ElMessage.success('文件上传成功!'); //
getImageUrl(response.data)
emit('uploadSuccess',response.data)
}
const handleError = () => {
ElMessage.error('文件上传失败');
}
const beforeUpload = (file:any) => {
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
ElMessage.error('上传文件大小不能超过 2MB!');
}
return isLt2M;
}
defineExpose({getImageUrl})
</script>
<style scoped>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
</style>

View File

@ -0,0 +1,197 @@
<script setup lang="ts">
import {getKey} from "@/utils/discrotyUtil.ts";
import insutypes from "@/assets/config/insutypes.json";
import {onMounted, onUnmounted, ref} from "vue";
import {ElMessage} from "element-plus";
import {post} from "@/utils/request.ts";
import type {Request, Response} from "@/utils/ws.ts";
import {useWsStore} from "@/stores/wsStore.ts";
const wsStore = useWsStore();
const isReading = ref(false)
const socialCard:any = defineModel();
const ReadSocialCard = async (readType: string) => {
socialCard.value.lastUse="cardPay"
isReading.value = true;
let config_db: any = await post('common/config/getall');
let config: any = {}
config.ACCESS_KEY = config_db.social_ACCESS_KEY;
config.SECRETKEY = config_db.social_SECRETKEY;
config.IP = config_db.social_IP;
config.PORT = config_db.social_PORT;
config.ORGID = config_db.social_fixmedinsCode;
config.EC_URL = config_db.social_EC_URL;
let data: any = {}
data.officeId = "1"
data.officeName = "内科";
data.operatorId = "1";
data.operatorName = "陈庭荣";
data.readType = readType;
let request: Request = {
type: "ReadCard",
config: config,
data: data
}
wsStore.sendMessage(request);
};
const reciceMessage = (response: Response) => {
if(socialCard.value.lastUse!="cardPay"){
return;
}
if (response.Code == 301) {
let msg = response.Message;
ElMessage({
message: msg,
type: 'warning',
});
isReading.value = false;
return;
}
let readType = response.Data.readType;
if (!readType) {
return
}
const params = {
mdtrtCertType: readType,
mdtrtCertNo: response.Data.mdtrt_cert_no,
certno: response.Data.certno,
psnName: response.Data.psn_name,
psnCertType: "01",
cardSn: response.Data.card_sn ? response.Data.card_sn : "",
}
getInfoFor1101(params)
}
const getInfoFor1101 = (params: any) => {
post("social/person/getCustomSocialInfo", {data: params}).then((res: any) => {
socialCard.value.data = res;
socialCard.value.mdtrtCertType = params.mdtrtCertType;
socialCard.value.mdtrtCertNo = params.mdtrtCertNo;
socialCard.value.payInfo.selfpay_prop_type = res.insuinfo[0].insutype;
}).finally(() => {
isReading.value = false;
})
}
onMounted(async () => {
wsStore.setMessageCallback(reciceMessage)
});
onUnmounted(()=>{
wsStore.removeAllMessageCallback()
})
</script>
<template>
<div class="card-pay">
<div class="left">医保<br/>信息</div>
<div class="right">
<div class="insuinfo" v-if="socialCard.data">
<div class="line">
<div class="label">姓名</div>
<div class="info">{{ socialCard.data.baseinfo.psn_name }}</div>
<div class="btn" @click="socialCard.data=null">退出</div>
</div>
<div class="line">
<div class="label">险种</div>
<div class="info">
<el-select v-model="socialCard.payInfo.selfpay_prop_type" placeholder="请选择" style="width: 100%">
<el-option
v-for="(item,index) in socialCard.data.insuinfo"
:key="item.insutype"
:label="'账户余额'+getKey(insutypes, item.insutype)+'('+item.balc.toFixed(2)+')'"
:value="item.insutype"
>
</el-option>
</el-select>
</div>
</div>
</div>
<div class="card-empty" v-else>
<div class="tip" v-loading="isReading">暂无医保信息</div>
<div class="btn-wrapper"> <div class="btn" @click="ReadSocialCard('03')" tabindex="-1">医保卡</div>
<div class="btn" @click="ReadSocialCard('01')" tabindex="-1">电子码</div>
<div class="btn" @click="ReadSocialCard('02')" tabindex="-1">身份证</div>
<div class="btn" @click="ReadSocialCard('04')" tabindex="-1">人脸识别</div></div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.card-pay {
position: relative;
display: flex;
.left {
width: 100px;
height: 100px;
background: #DDD;
text-align: center;
line-height: 30px;
font-size: 24px;
color: #666;
padding: 20px;
box-sizing: border-box;
border-radius: 10px 0 0 10px;
}
.right {
position: relative;
flex: 1;
background: #EEE;
box-sizing: border-box;
padding: 10px;
border-radius: 0 10px 10px 0;
.line{
position: relative;
display: flex;
height: 40px;
box-sizing: border-box;
padding-left: 10px;
padding-right: 10px;
line-height: 40px;
.label{
position: relative;
width: 40px;
}
.info{
flex: 1;
}
}
.card-empty{
width: 100%;
height: 100%;
overflow: hidden;
.tip{
position: relative;
width: 100%;
height: 50px;
text-align: center;
line-height: 40px;
}
.btn-wrapper{
position: relative;
height: 30px;
display: flex;
.btn{
flex: 1;
margin-left: 5px;
margin-right: 5px;
}
}
}
.btn{
text-align: center;
line-height: 30px;
outline: none;
color: #409EFF;
cursor: pointer;
}
}
}
</style>

View File

@ -0,0 +1,185 @@
<template>
<div class="container">
<div class="search">
<el-input v-model="query.keyword" placeholder="根据姓名搜索"/>
<el-button type="success" @click="addChargeOrder" size="small">+收费</el-button>
</div>
<div class="list">
<el-scrollbar>
<ul>
<li class="list-item" :class="curItem.id == item.id ? 'active' : ''" v-for="(item, index) in orderList"
:key="index" @click="clickItem(item)">
<span>
<img v-if="item.patinetGender==''" class="avatar" src="/static/images/outpatient/man.png"
alt="头像"/>
<img v-if="item.patinetGender==''" class="avatar" src="/static/images/outpatient/women.png"
alt="头像"/>
</span>
<span class="item_name">{{ item.patientName }}</span>
<el-tooltip
class="box-item"
effect="dark"
:content="formatTime(item.createDatetime)||'-'"
placement="bottom-start"
>
<span class="item_time">
{{ formatTime(item.createDatetime) || '-' }}
</span>
</el-tooltip>
<span>01-01</span>
</li>
</ul>
</el-scrollbar>
</div>
</div>
</template>
<script setup lang="ts">
import {onMounted, ref} from "vue";
import {post} from "@/utils/request.ts";
import Panel from "@/components/common/Panel.vue";
import {formatTime} from "@/utils/dateUtils.ts";
const curItem = ref<any>({});
const emit = defineEmits(['clickItem'])
const clickItem = (item: any) => {
curItem.value = item
emit('clickItem', item);
}
const orderList = ref<any>([]);
const query = ref({
pageSize: 20,
pageNum: 1,
keyword: ""
})
const addChargeOrder = () => {
const newOrder = {
id:-1,
patientName :"匿名患者",
payType : -1,
}
if (orderList.value[0]?.id == -1){
return
}
orderList.value.unshift(newOrder)
clickFirst()
}
const clickFirst = () => {
clickItem(orderList.value[0])
}
const getOrderList = () => {
post("charge/list", {query: query.value}).then(
(res: any) => {
orderList.value = res.list
clickFirst()
}
)
}
const delDraft = () =>{
orderList.value.shift();
clickFirst()
}
defineExpose({delDraft})
onMounted(()=>{
getOrderList()
})
</script>
<style scoped lang="scss">
.container{
display: flex;
flex-direction: column; //
width: 100%;
.search{
height: 25px;
width: 100%;
display: flex;
margin: 0;
padding: 0;
}
.list {
display: flex;
min-height: 0;
flex-direction: column;
width: 100%;
.list-title {
height: 48px;
background: #F5FAFF;
padding: 0 27px;
display: flex;
align-items: center;
font-weight: 500;
font-size: 14px;
color: rgba(34, 42, 57, 0.8);
font-style: normal;
span {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
}
ul {
flex: 1;
min-height: 0;
.list-item {
height: 48px;
display: flex;
align-items: center;
padding: 0 18px;
font-weight: 400;
font-size: 14px;
color: rgba(34, 42, 57, 0.7);
font-style: normal;
cursor: pointer;
span {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
.avatar {
width: 26px;
height: 26px;
}
}
&:hover {
background: rgba(#4D6DE4, 0.1);
}
.item_name {
flex: 1.5;
white-space: nowrap; //
overflow: hidden; //
text-overflow: ellipsis; //
}
.item_time {
flex: 2;
white-space: nowrap; //
overflow: hidden; //
text-overflow: ellipsis; //
}
}
.active {
color: #fff;
background: #4D6DE4;
&:hover {
background: #4D6DE4;
}
}
}
}
}
</style>

View File

@ -0,0 +1,77 @@
<template>
<el-table :data="list" max-height="150">
<el-table-column prop="name" label="名称"></el-table-column>
<el-table-column prop="selectedPrice" label="单价"></el-table-column>
<el-table-column prop="number" label="数量">
<template #default="scope">
<el-input-number v-model="scope.row.selectedNum" min="0" @change="handleNumChange"></el-input-number>
<el-dropdown>
<span style="line-height: 30px;margin-left: 10px">{{ scope.row.selectedUnit }}</span>
<template #dropdown>
<el-dropdown-menu v-if="scope.row.trdnFlag == 1">
<el-dropdown-item @click="selectUnit(scope.row,scope.row.packagingUnit)">{{ scope.row.packagingUnit }}
</el-dropdown-item>
<el-dropdown-item @click="selectUnit(scope.row,scope.row.minPackagingUnit)">{{ scope.row.minPackagingUnit }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button type="danger" link @click="delGoods(scope.row)">X</el-button>
</template>
</el-table-column>
</el-table>
<SearchInput :request-api="goodsSearchApi" :show-config="goodsShowConfig" @selectedCallBack="goodsSelect"></SearchInput>
</template>
<script setup lang="ts">
import SearchInput from "@/components/SearchInput.vue";
const list = defineModel<any>();
const delGoods = (item: any) => {
list.value = list.value.filter((i: any) => i.id != item.id)
}
const goodsSearchApi = "goods/goods/search";
const goodsShowConfig = [
{
label: "项目名称",
prop: "name",
},
{
label: "项目类型",
prop: "type",
},
{
label: "售价",
prop: "unitPrice",
},
]
const goodsSelect = (row: any) => {
row.selectedNum = 1
row.selectedUnit = row.packagingUnit
row.selectedPrice = row.unitPrice
list.value.push(row)
emit('totalPriceChange')
}
const selectUnit = (item: any, unit: any) => {
item.selectedUnit = unit;
if (unit == item.packagingUnit) {
item.selectedPrice = item.unitPrice
}else if (unit == item.minPackagingUnit) {
item.selectedPrice = item.disassemblyPrice
}
emit('totalPriceChange')
}
const emit = defineEmits(["totalPriceChange"])
const handleNumChange = ()=>{
emit('totalPriceChange')
}
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,50 @@
<template>
<el-table :data="list" max-height="150">
<el-table-column prop="name" label="名称"></el-table-column>
<el-table-column prop="unit" label="单位"></el-table-column>
<el-table-column prop="unitPrice" label="单价"></el-table-column>
<el-table-column prop="number" label="数量"></el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button type="danger" link @click="delService(scope.row)">X</el-button>
</template>
</el-table-column>
</el-table>
<SearchInput :request-api="serviceSearchApi" :show-config="serviceShowConfig" @selectedCallBack="serviceSelect"></SearchInput>
</template>
<script setup lang="ts">
import SearchInput from "@/components/SearchInput.vue";
const list =defineModel<any>();
const delService = (item: any) => {
list.value = list.value.filter((i: any) => i.id != item.id)
}
const serviceSearchApi = "item/search";
const serviceShowConfig = [
{
label: "服务名称",
prop: "itemName",
},
{
label: "服务医保码",
prop: "itemSocialCode",
},
{
label: "售价",
prop: "unitPrice",
},
]
const emit = defineEmits(['selectedCallBack'])
const serviceSelect = (row: any) => {
row.name = row.itemName
row.number = 1
list.value.push(row)
emit('selectedCallBack',row)
}
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,195 @@
<template>
<Mask :width="300" :height="438" :is-show="show">
<el-card>
<template #header>
<div class="header">
<span>收费</span>
<CloseBtn @click="show = false" style="margin-top: 12px"></CloseBtn>
</div>
</template>
<div class="panel">
<div class="price">{{ prop.money }}</div>
<div class="social" >
<CardPay v-model="socialCard"/>
</div>
<div class="price-type">
<div
class="price-type-item"
:class="['btn',payType==item.type?'active':'']"
v-for="(item,index) in priceBtnList"
@click="changePriceType(item.type)"
>
<div class="image" :style="{'background-color':item.color}">
<img style="width: 16px;height: 16px;" :src="item.img" alt=""/>
</div>
<span>{{item.name}}</span>
</div>
</div>
</div>
<template #footer>
<div class="footer">
<el-checkbox v-model="printReceipt">同时打印凭证</el-checkbox>
<el-button @click="completeSettlement()" type="primary">完成收费</el-button>
</div>
</template>
</el-card>
</Mask>
</template>
<script setup lang="ts">
import {nextTick, onMounted, onUnmounted, ref} from "vue";
import CloseBtn from "@/components/CloseBtn.vue";
import {post} from "@/utils/request.ts";
import Mask from "@/components/Mask.vue";
import CardPay from "@/components/charge/CardPay.vue";
import {medTypeJson, priceBtnList} from "@/assets/config/constants.ts"
import {useWsStore} from "@/stores/wsStore.ts";
const wsStore = useWsStore();
const socialCard =defineModel<any>();
const prop=defineProps({
money: {
type: String,
default: ''
}
})
const show = ref(false)
const printReceipt = ref(false);
const payType = ref(null);
const retailOrder = ref<any>(null);
const init = (order: any) => {
retailOrder.value = order;
console.log(order)
show.value = true;
}
defineExpose({init})
const changePriceType = (type: any) => {
payType.value = type;
}
const emit = defineEmits(['orderComplete','orderCanceled'])
const completeSettlement = ()=>{
if (!retailOrder.value){
return;
}
if (payType.value == null){
return
}
if(payType.value == 1){
//
console.log('医保结算',socialCard.value)
socialSettlement();
}else {
debugger
//
post('charge/completeOrder',{id:retailOrder.value.id,payType:payType.value}).then((res:any)=>{
orderCompleted()
})
}
}
const orderCompleted = ()=>{
show.value = false;
emit('orderComplete',printReceipt.value)
}
const orderCanceled = ()=>{
show.value = false;
emit('orderCanceled')
}
const getBalcByInsutype = (type:any) =>{
let balc =0;
for (const item of socialCard.value.data.insuinfo) {
if (item.insutype == type) {
balc = item.balc;
break;
}
}
return balc
}
const socialSettlement = ()=>{
const params = {
mdtrtCertType: socialCard.value.mdtrtCertType,
mdtrtCertNo: socialCard.value.mdtrtCertNo,
psnNo: socialCard.value.data.baseinfo.psn_no,
orderId:retailOrder.value.id,
insutype: socialCard.value.payInfo.selfpay_prop_type,
payType: payType.value,
curBalc:getBalcByInsutype(socialCard.value.payInfo.selfpay_prop_type)
}
//
post("retail/socialPrePay",{orderInfo:params}).then((res:any)=>{
openPsnPayment(res,params)
})
}
const psnPaymentRef = ref();
const openPsnPayment = (payInfo:any,orderInfo:any)=>{
nextTick(()=>{
psnPaymentRef.value.open(payInfo,orderInfo);
})
}
</script>
<style scoped lang="scss">
.panel {
.price{
height: 50px;
text-align: center;
font-size: 25px;
font-weight: 600;
color: rgba(237, 120, 23, 0.8);
}
.price-type {
display: flex;
margin-top: 20px;
justify-content: space-between;
flex-wrap: wrap;
align-items: center;
.btn {
height: 45px;
width: 95px;
font-size: 14px;
border-radius: 10px;
display: flex;
background: #efecec;
margin-bottom: 10px;
justify-content: center;
align-items: center;
padding: 10px;
cursor: pointer;
.image{
width: 16px;
height: 16px;
margin-right: 3px;
border-radius: 23px;
background-color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
}
&:hover {
//color: #fff;
//background-color: #409EFF;
border: 1px solid #409EFF;
}
}
.active {
color: #fff;
background-color: #409EFF;
}
}
}
.footer{
display: flex;
justify-content: space-between;
}
</style>

View File

@ -0,0 +1,21 @@
<template>
<footer>
@版权所有:沈阳嘉尔网络科技有限公司
</footer>
</template>
<style scoped lang="scss">
@use "@/assets/scss/base";
footer{
position: relative;
width: 100%;
height: 40px;
background-color: base.$background-color-main;
line-height: 40px;
color: #FFF;
text-align: center;
}
</style>
<script setup lang="ts">
</script>

View File

@ -0,0 +1,145 @@
<template>
<header>
<div class="center-wrapper">
<div class="menu">
<router-link to="/home" class="menu-item" active-class="active active1">首页</router-link>
<router-link to="/registration" class="menu-item" active-class="active active2">挂号</router-link>
<router-link to="/outpatient" class="menu-item" active-class="active active3">接诊</router-link>
<router-link to="/charge" class="menu-item" active-class="active active4">收费</router-link>
<router-link to="/inventory" class="menu-item" active-class="active active5">库存</router-link>
<router-link to="/member" class="menu-item" active-class="active active6">会员</router-link>
<router-link to="/social" class="menu-item" active-class="active active7">医保</router-link>
<router-link to="/statistics" class="menu-item" active-class="active active8">统计</router-link>
<router-link to="/settings" class="menu-item" active-class="active active9">设置</router-link>
</div>
</div>
</header>
</template>
<style scoped lang="scss">
@use "@/assets/scss/base";
.menu {
position: relative;
text-align: center;
line-height: 50px;
.menu-item {
display: inline-block;
color: #FFF;
outline: none;
text-decoration: none;
font-size: 18px;
width: 140px;
height: 50px;
position: relative;
span {
display: inline-block;
height: 50px;
}
&::before {
content: '';
display: inline-block;
width: 26px;
height: 50px;
vertical-align: middle;
border-radius: 12px;
background-repeat: no-repeat;
background-size: 100%;
background-position:0 10px;
}
&:nth-child(1)::before {
background-image: url('/static/images/menu_icon/1.png'); //
}
&:nth-child(2)::before {
background-image: url('/static/images/menu_icon/2.png'); //
}
&:nth-child(3)::before {
background-image: url('/static/images/menu_icon/3.png'); //
}
&:nth-child(4)::before {
background-image: url('/static/images/menu_icon/4.png'); //
}
&:nth-child(5)::before {
background-image: url('/static/images/menu_icon/5.png'); //
}
&:nth-child(6)::before {
background-image: url('/static/images/menu_icon/6.png'); //
}
&:nth-child(7)::before {
background-image: url('/static/images/menu_icon/7.png'); //
}
&:nth-child(8)::before {
background-image: url('/static/images/menu_icon/8.png'); //
}
&:nth-child(9)::before {
background-image: url('/static/images/menu_icon/9.png'); //
}
}
.active {
background: #fff;
color: base.$background-color-main;
border-radius: 12px;
}
.active1:before {
background-image: url('/static/images/menu_icon/1-active.png') !important;
}
.active2:before {
background-image: url('/static/images/menu_icon/2-active.png') !important;
}
.active3:before {
background-image: url('/static/images/menu_icon/3-active.png') !important;
}
.active4:before {
background-image: url('/static/images/menu_icon/4-active.png') !important;
}
.active5:before {
background-image: url('/static/images/menu_icon/5-active.png') !important;
}
.active6:before {
background-image: url('/static/images/menu_icon/6-actvie.png') !important;
}
.active7:before {
background-image: url('/static/images/menu_icon/7-active.png') !important;
}
.active8:before {
background-image: url('/static/images/menu_icon/8-active.png') !important;
}
.active9:before {
background-image: url('/static/images/menu_icon/9-active.png') !important;
}
}
header {
position: relative;
width: 100%;
height: 80px;
background-color: base.$background-color-main;
padding: 15px 0;
}
</style>
<script setup lang="ts">
</script>

View File

@ -0,0 +1,92 @@
<script setup lang="ts">
import { ref, watch,defineEmits } from "vue";
import { CloseBold } from '@element-plus/icons-vue'
let _width = ref(0);
let _height = ref(0);
let _isShow = ref(false);
const { width, height, isShow,title } = defineProps(['width', 'height', 'isShow','title']);
_isShow.value = isShow == null ? false : isShow;
_width.value = width == null ? 1200 : width;
_height.value = height == null ? 800 : height;
console.log(_width, _height, _isShow);
watch(() => isShow, (newVal) => {
_isShow.value = newVal == null ? false : newVal;
});
const emit=defineEmits(['close']);
const close = () => {
emit('close',false);
};
</script>
<template>
<teleport to="body">
<transition name="el-fade-in">
<div class="mask" v-if="_isShow">
<div class="mask-wrapper" :style="{ width: _width + 'px', height: _height + 'px' }" v-if="_isShow">
<div class="header">
<div class="title">{{title}}</div>
<div class="close" @click="close"><el-icon><CloseBold/></el-icon></div>
</div>
<el-scrollbar height="100%">
<slot></slot>
</el-scrollbar>
</div>
</div>
</transition>
</teleport>
</template>
<style scoped lang="scss">
.mask {
position: fixed;
background-color: rgba(0, 0, 0, 0.5);
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 20px;
z-index: 999;
display: flex;
justify-content: center;
align-items: center;
.mask-wrapper {
position: relative;
background-color: #FFF;
border-radius: 6px;
padding:0 20px 20px 20px;
overflow: hidden;
max-height: 90vh;
z-index: 1999;
.header{
position: relative;
height: 72px;
padding: 0 4px;
.title{
position: absolute;
top: 50%;
left:0;
font-weight: 800;
font-size: 20px;
color: #333333;
font-style: normal;
transform: translateY(-50%);
}
.close{
position: absolute;
top: 50%;
right: 0;
color: #6e6e6e;
font-size: 32px;
transform: translateY(-50%);
&:hover{
color: #409eff;
}
}
}
}
}
</style>

View File

@ -0,0 +1,88 @@
<template>
<div class="panel" :class="props.isFullHeight ? 'panel--flex' : ''">
<div class="panel-header" v-if="props.showHeader">
<div class="panel-header-title">{{props.title}}</div>
<div class="panel-header-tools" v-if="props.showTools">
<slot name="tools"></slot>
</div>
</div>
<div class="panel-content" :style="!props.showHeader ? 'padding-top: 0' : ''">
<slot></slot>
</div>
</div>
</template>
<style scoped lang="scss">
.panel {
background: #fff;
border-radius: 6px;
&-header {
position: relative;
height: 58px;
padding-right: 18px;
display: flex;
justify-content: space-between;
align-items: center;
&:before {
content: '';
position: absolute;
left: 0;
top: 17px;
width: 7px;
height: 24px;
background: #4D6DE4;
border-radius: 3px;
}
&-title {
width: 150px;
font-weight: 800;
font-size: 20px;
color: #333333;
font-style: normal;
text-indent: 17px;
line-height: 48px;
}
&-tools {
}
}
&-content {
}
}
.panel--flex{
display: flex;
flex-direction: column;
min-height: 0;
height: 100%;
.panel-content{
flex: 1;
min-height: 0;
}
}
</style>
<script setup lang="ts">
const props = defineProps({
title: {
type: String,
default: ''
},
isFullHeight: {
type: Boolean,
default: true
},
showTools: {
type: Boolean,
default: true
},
showHeader: {
type: Boolean,
default: true
}
})
</script>

View File

@ -0,0 +1,54 @@
<template xmlns="http://www.w3.org/1999/html">
<Mask :width="1100" :height="600" :is-show="show" :top="100" title="过期商品详情" @close="show=false">
<el-table :data="tableData" style="width: 100%" class="table" max-height="1000px">
<el-table-column label="名称" prop="name"></el-table-column>
<el-table-column label="剩余天数">
<template #default="scope">
<span class="remaining-date">
{{scope.row.remaining_days <0?'已过期':scope.row.remaining_days+"天后到期"}}
</span>
</template>
</el-table-column>
<el-table-column label="商品数量" prop="">
<template #default="scope">
{{
scope.row.whole_number
}}{{ scope.row.packaging_unit }}
<span v-if="scope.row.fragment_number>0">
{{ scope.row.fragment_number }}{{ scope.row.min_packaging_unit }}
</span>
</template>
</el-table-column>
<el-table-column label="过期时间" prop="expiry_date"></el-table-column>
<el-table-column label="采购单号" prop="inventory_purchase_code"></el-table-column>
<el-table-column label="批次号" prop="production_batch_code"></el-table-column>
<el-table-column label="生产日期" prop="production_date"></el-table-column>
</el-table>
</Mask>
</template>
<script setup lang="ts">
import Mask from "@/components/common/Mask.vue";
import {ref} from "vue";
import CloseBtn from "@/components/CloseBtn.vue";
const tableData = ref<any>([]);
const show = ref(false);
const init = (list:any) => {
tableData.value = list;
show.value = true;
};
defineExpose({init})
const close = () => {
show.value = false;
};
</script>
<style scoped lang="scss">
.table{
margin-top: 30px;
.remaining-date{
color: red;
}
}
</style>

View File

@ -0,0 +1,53 @@
<template>
<Mask :width="1100" :height="600" :is-show="show" :top="100" title="库存预警详情" @close="close">
<el-table :data="tableData" style="width: 100%" class="table" max-height="1000px">
<el-table-column label="名称" prop="name"></el-table-column>
<el-table-column label="剩余库存">
<template #default="scope">
{{
scope.row.inventoryWholeNumber
}}{{ scope.row.packagingUnit }}
<span v-if="scope.row.inventoryFragmentNumber>0">
{{ scope.row.inventoryFragmentNumber }}{{ scope.row.minPackagingUnit }}
</span>
</template>
</el-table-column>
<el-table-column label="库存预警线">
<template #default="scope">
小于等于{{scope.row.inventoryWarnNumber}}{{scope.row.packagingUnit}}
</template>
</el-table-column>
<el-table-column label="医保码" prop="hilistCode" show-overflow-tooltip></el-table-column>
<el-table-column label="生产企业" prop="producer" show-overflow-tooltip></el-table-column>
<el-table-column label="商品备注" prop="remark" show-overflow-tooltip></el-table-column>
<el-table-column label="是否允许拆零">
<template #default="scope">
{{scope.row.trdnFlag?"允许":"不允许"}}
</template>
</el-table-column>
</el-table>
</Mask>
</template>
<script setup lang="ts">
import Mask from "@/components/common/Mask.vue";
import {ref} from "vue";
import CloseBtn from "@/components/CloseBtn.vue";
const tableData = ref<any>([]);
const show = ref(false);
const init = (list:any) => {
tableData.value = list;
show.value = true;
};
defineExpose({init})
const close = () => {
show.value = false;
};
</script>
<style scoped lang="scss">
.table{
margin-top: 30px;
}
</style>

View File

@ -0,0 +1,120 @@
<script setup lang="ts">
import {ref} from "vue";
import {post} from "@/utils/request.ts";
const state = ref([])
const options = ref([])
const selectRef = ref();
const querySearchAsync = (queryString: string, cb: (arg: any) => void) => {
post("goods/goods/search", {keyword: queryString}).then((res: any) => {
options.value = res;
let list = res;
for (let i = 0; i < list.length; i++) {
list[i].value = list[i].name;
}
// cb(res)
})
}
const emit = defineEmits(['selectCallBack'])
const handleSelect = (item: any) => {
console.log(item,'item')
let goods = JSON.parse(JSON.stringify(item));
console.log("goods",goods)
let inventory = {
hilistCode: goods.hilistCode,
unitPrice: goods.unitPrice,
packagingUnit: goods.packagingUnit,
minPackagingUnit: goods.minPackagingUnit,
goodId: goods.id,
name: goods.name,
wholeNumber: 0,
purchaseUnitPrice: goods.purchaseUnitPrice,
disassemblyPrice : goods.disassemblyPrice
}
if (selectRef.value) {
(selectRef.value as HTMLElement).blur();
}
emit('selectCallBack', inventory);
state.value = []
}
</script>
<template>
<div class="search_box">
<el-select-v2
v-model="state"
style="width: 100%"
filterable
remote
ref="selectRef"
:remote-method="querySearchAsync"
:options="options"
placeholder="输入关键字搜索药品"
:fit-input-width="800"
>
<template #header>
<div class="header">
<div class="text">药品</div>
<div class="text">规格</div>
<div class="text">库存</div>
<div class="text">厂家</div>
<div class="text">最近供应商</div>
</div>
</template>
<template #default="{ item }">
<div class="row" @click="handleSelect(item)">
<div class="text" style="margin-left: 0px">{{ item.name }}</div>
<div class="text">{{item.minPackagingNumber}}*{{item.minPackagingUnit}}/{{ item.packagingUnit }}</div>
<div class="text">
{{ item.inventoryWholeNumber }}{{ item.packagingUnit }}
<template v-if="item.inventoryFragmentNumber > 0">
{{ item.inventoryFragmentNumber }}{{ item.minPackagingUnit }}
</template>
</div>
<div class="text">{{ item.producer }}</div>
<div class="text" style="margin-left: 15px"></div>
</div>
</template>
<template #footer>
</template>
</el-select-v2>
</div>
</template>
<style scoped lang="scss">
.search_box {
//background-color: rgb(148.6, 212.3, 117.1);
background-color: #fff;
padding: 10px;
border-radius: 10px;
box-sizing: border-box;
margin-top: 10px;
margin-bottom: 10px;
}
.header {
display: flex;
.text {
font-size: 16px;
width: 200px;
color: #6a6a6a;
}
}
.row {
display: flex;
max-height: 500px;
.text {
font-size: 12px;
width: 150px;
margin-left: 10px;
overflow: hidden; /* 隐藏超出部分 */
text-overflow: ellipsis; /* 显示省略号 */
white-space: nowrap; /* 防止换行 */
}
}
</style>

View File

@ -0,0 +1,69 @@
<template>
<div class="box">
<ul class="item-list">
<div v-for="(item, index) in units" :class="['item',(index+1)%4 == 0 ? 'last-item' : '',item==curItem?'active':'']" @click="selectItem(item)">{{item}}</div>
</ul>
</div>
</template>
<script setup lang="ts">
const curItem = defineModel();
const emit = defineEmits(['selected'])
const props = defineProps({
units: {
type: Array,
default: () => []
}
})
const selectItem = (item:any) => {
if(curItem.value == item){
curItem.value = null;
return
}
curItem.value = item
}
</script>
<style scoped lang="scss">
.box{
margin: 0px;
padding: 0px;
position:absolute;
background: #fff;
top: 0;
left: 0;
width: 322px;
height: auto;
border: 1px solid #ddd;
z-index: 999;
}
.item-list {
display: flex;
flex-wrap: wrap;
}
.item{
display: flex;
width: 80px;
height: 36px;
background: #fff;
color: #000;
border: 1px solid #ededed;
align-items: center;
justify-content: center;
&:hover{
color: #000;
background: #eff3f6;
}
&.last-item {
margin-right: 0; /* 最后一个元素不加右边距 */
}
}
.active{
background: #00ace9 !important;
color: #fff !important;
}
</style>

View File

@ -0,0 +1,285 @@
<template>
<CloseBtn @click="exit"></CloseBtn>
<el-descriptions title="新增领用" border label-width="100">
<el-descriptions-item label="领用人" width="200">
<el-input v-model="formData.name" disabled></el-input>
</el-descriptions-item>
<el-descriptions-item label="备注">
<el-input v-model="formData.remark"></el-input>
</el-descriptions-item>
</el-descriptions>
<GoodsSearch @selectCallBack="goodsSelectCallBack"></GoodsSearch>
<table class="simple-table" style="margin-top: 15px;">
<thead>
<tr>
<th>名称</th>
<th>批次</th>
<th>生产批号</th>
<th>有效期</th>
<th>当前库存</th>
<th>出库数量</th>
</tr>
</thead>
<tbody>
<template v-for="(item,index) in tableList">
<tr>
<td>{{ item.name }}</td>
<td>
<el-select
v-model="item.selectList"
multiple
@change="addLine(item)"
:teleported="false"
style="width: 100px;"
collapse-tags
popper-class="table-select">
<div class="select-header">
<span>批次ID</span>
<span>生产批号</span>
<span>入库数据</span>
<span>有效期</span>
<span>进价</span>
</div>
<el-option
v-for="(subItem,subIndex) in item.batchList"
:key="subItem.id"
:label="subItem.id"
:value="subItem.id">
<div class="option-row">
<span>{{ subItem.id }}</span>
<span>{{ subItem.productionBatchCode }}</span>
<span>{{ subItem.productionDate }}</span>
<span>{{ subItem.expiryDate }}</span>
<span>{{ subItem.purchaseUnitPrice }}</span>
</div>
</el-option>
</el-select>
</td>
<td></td>
<td>
</td>
<td>
{{ item.wholeNumber }}{{ item.packagingUnit }}
<template v-if="item.trdnFlag == 1">
{{ item.fragmentNumber }}{{ item.minPackagingUnit }}
</template>
</td>
<td>
{{ item.totalOutWholeNumber }}{{ item.packagingUnit }}
<template v-if="item.trdnFlag == 1">
{{ item.totalFragmentNumber }}{{ item.minPackagingUnit }}
</template>
</td>
</tr>
<template v-for="subItem in item.children">
<tr>
<td>-</td>
<td>-</td>
<td>{{ subItem.productionBatchCode }}</td>
<td>{{ subItem.expiryDate }}</td>
<td>
{{ subItem.wholeNumber }}{{ subItem.packagingUnit }}
<template v-if="item.trdnFlag == 1">
{{ subItem.fragmentNumber }}{{ subItem.minPackagingUnit }}
</template>
</td>
<td>
<el-input v-model.number="subItem.outWholeNumber" size="small" style="width: 50px" @change="changeOutNumber(item)">
<template #suffix>
{{ item.packagingUnit }}
</template>
</el-input>
<el-input v-model.number="subItem.outFragmentNumber" size="small" style="width: 50px"
@change="changeOutNumber(item)" v-if="item.trdnFlag == 1">
<template #suffix>
{{ item.minPackagingUnit }}
</template>
</el-input>
</td>
</tr>
</template>
</template>
</tbody>
</table>
<div class="bottom">
<el-button type="primary" @click="save">保存</el-button>
<el-button type="primary" @click="exit">关闭</el-button>
</div>
</template>
<script setup lang="ts">
import {onMounted, ref, unref} from "vue";
import GoodsSearch from "@/components/inventory/GoodsSearch.vue";
import {post} from "@/utils/request.ts";
import CloseBtn from "@/components/CloseBtn.vue";
const emit = defineEmits(['close'])
const exit = () => {
emit('close')
}
const formData = ref({
useUserId: null,
name: '',
username: "",
remark: ''
})
interface Inventory {
goodId: string | number;
batchList?: any[];// batchList
children: childCheck[];
packagingUnit?: string;
minPackagingUnit?: string;
totalOutWholeNumber: number;
totalFragmentNumber: number;
wholeNumber?: number;
fragmentNumber?: number;
selectList?: number[]; // selectList
name?: string;
trdnFlag?: number;
}
const tableList = ref<Inventory[]>([]);
const findIndexForTableList = (goodId: any) => {
return tableList.value.findIndex((item: any) => item.goodId === goodId);
}
const goodsSelectCallBack = (inventory: any) => {
if (findIndexForTableList(inventory.goodId) != -1) {
return
}
post("inventory/goods/getByGoodsId", {goodsId: inventory.goodId}).then((res: any) => {
inventory.batchList = res.inventoryGoodsList
inventory.minPackagingUnit = res.minPackagingUnit
inventory.trdnFlag = res.trdnFlag
inventory.totalOutWholeNumber = 0;
inventory.totalFragmentNumber = 0;
inventory.wholeNumber = res.inventoryWholeNumber;
inventory.fragmentNumber = res.inventoryFragmentNumber;
inventory.packagingUnit = res.packagingUnit
tableList.value.push(inventory)
})
console.log(inventory)
}
interface childCheck {
id?: number,
goodsId: number,
name: string,
productionBatchCode: string,
minPackagingNumber: number,
productionDate: string
expiryDate: string,
purchaseUnitPrice: number,
wholeNumber: number,
packagingUnit: string,
fragmentNumber: number,
fragmentPrice: string,
minPackagingUnit: string;
outFragmentNumber: number;
outWholeNumber: number;
}
const addLine = (row: any) => {
post('inventory/goods/getListByIds', {idList: row.selectList}).then((list: any) => {
let children = [];
for (let i = 0; i < list.length; i++) {
let inventoryGoods = list[i];
let index = row.children ? row.children.findIndex((item: any) => item.id === inventoryGoods.id) : -1;
if (index > -1) {
children.push(row.children[index]);
} else {
let childCheck: childCheck = {
name: inventoryGoods.name,
id: inventoryGoods.id,
goodsId: inventoryGoods.goodId,
minPackagingNumber: row.minPackagingNumber,
productionBatchCode: inventoryGoods.productionBatchCode,
productionDate: inventoryGoods.productionDate,
expiryDate: inventoryGoods.expiryDate,
purchaseUnitPrice: inventoryGoods.purchaseUnitPrice,
wholeNumber: inventoryGoods.wholeNumber,
packagingUnit: inventoryGoods.packagingUnit,
fragmentNumber: inventoryGoods.fragmentNumber,
fragmentPrice: inventoryGoods.fragmentPrice,
minPackagingUnit: row.minPackagingUnit,
outFragmentNumber: inventoryGoods.outFragmentNumber,
outWholeNumber: inventoryGoods.outWholeNumber,
}
children.push(childCheck);
}
}
row.children = children;
console.log("row", row)
})
}
const changeOutNumber = (row: any) => {
let totalOutWholeNumber = 0;
let totalOutFragmentNumber = 0;
for (let i = 0; i < row.children.length; i++) {
let child = row.children[i];
totalOutWholeNumber += child.outWholeNumber ? child.outWholeNumber : 0;
totalOutFragmentNumber += child.outFragmentNumber ? child.outFragmentNumber : 0;
}
row.totalOutWholeNumber = totalOutWholeNumber;
row.totalFragmentNumber = totalOutFragmentNumber;
console.log(row)
}
const save = () => {
let children = [];
console.log(tableList.value)
// for (let i = 0; i < tableList.value.length; i++) {
// let item = tableList.value[i];
// for (let j = 0; j < item.children.length; j++) {
// let child = item.children[j];
// if (child && (child.outWholeNumber > 0 || child.outFragmentNumber > 0)) {
// let childrenItem = {
// id: child.id,
// outWholeNumber: child.outWholeNumber,
// outFragmentNumber: child.outFragmentNumber,
// }
// children.push(childrenItem);
// }
// }
//
//
// }
// let dataJson = JSON.stringify(tableList.value);
post("inventory/apply/create", {useInfo: formData.value, data: tableList.value}).then((res: any) => {
exit()
})
}
const getUserInfo = () => {
post("manager/user/verify", null).then((res: any) => {
formData.value.useUserId = res.id;
formData.value.name = res.name;
formData.value.username = res.username;
})
}
onMounted(() => {
getUserInfo()
})
</script>
<style scoped lang="scss">
.btn {
margin-top: 5px;
}
.bottom {
margin-top: 10px;
position: absolute;
right: 10px;
bottom: 10px;
}
</style>

View File

@ -0,0 +1,224 @@
<template>
<CloseBtn @click="exit"></CloseBtn>
<el-descriptions title="详情领用" border label-width="100">
<el-descriptions-item label="领用人" width="200">
<el-input v-model="formData.name" disabled></el-input>
</el-descriptions-item>
<el-descriptions-item label="备注">
<el-input v-model="formData.remark" disabled></el-input>
</el-descriptions-item>
</el-descriptions>
<table class="simple-table" style="margin-top: 15px;">
<thead>
<tr>
<th>名称</th>
<th>批次</th>
<th>生产批号</th>
<th>有效期</th>
<th>当前库存</th>
<th>出库数量</th>
</tr>
</thead>
<tbody>
<template v-for="(item,index) in tableList" :key="index">
<tr>
<td>{{ item.name }}</td>
<td>
<el-select
v-model="item.selectList"
multiple
@change="addLine(item)"
:teleported="false"
style="width: 100px;"
collapse-tags
popper-class="table-select">
<div class="select-header">
<span>批次ID</span>
<span>生产批号</span>
<span>入库数据</span>
<span>有效期</span>
<span>进价</span>
</div>
<el-option
v-for="(subItem) in item.batchList"
:key="subItem.id"
:label="subItem.id"
:value="subItem.id"
:disabled="true"
>
<div class="option-row">
<span>{{ subItem.id }}</span>
<span>{{ subItem.productionBatchCode }}</span>
<span>{{ subItem.productionDate }}</span>
<span>{{ subItem.expiryDate }}</span>
<span>{{ subItem.purchaseUnitPrice }}</span>
</div>
</el-option>
</el-select>
</td>
<td></td>
<td>
</td>
<td>
{{ item.wholeNumber }}{{ item.packagingUnit }}
<template v-if="item.trdnFlag == 1">
{{ item.fragmentNumber }}{{ item.minPackagingUnit }}
</template>
</td>
<td>
{{ item.totalOutWholeNumber }}{{ item.packagingUnit }}
<template v-if="item.trdnFlag == 1">
{{ item.totalFragmentNumber }}{{ item.minPackagingUnit }}
</template>
</td>
</tr>
<template v-for="subItem in item.children">
<tr>
<td>-</td>
<td>-</td>
<td>{{ subItem.productionBatchCode }}</td>
<td>{{ subItem.expiryDate }}</td>
<td>
{{ subItem.wholeNumber }}{{ subItem.packagingUnit }}
<template v-if="item.trdnFlag == 1">
{{ subItem.fragmentNumber }}{{ subItem.minPackagingUnit }}
</template>
</td>
<td>
{{ subItem.outWholeNumber }}{{ subItem.packagingUnit }}
<template v-if="item.trdnFlag == 1">
{{ subItem.outFragmentNumber }}{{ subItem.minPackagingUnit }}
</template>
</td>
</tr>
</template>
</template>
</tbody>
</table>
<div class="bottom">
<el-button type="primary" @click="exit">关闭</el-button>
</div>
</template>
<script setup lang="ts">
import {onMounted, ref, defineProps} from "vue";
import {post} from "@/utils/request.ts";
import CloseBtn from "@/components/CloseBtn.vue";
const props = defineProps({
id: {
type: String,
default: null
}
})
const emit = defineEmits(['close'])
const exit = () => {
emit('close')
}
const formData = ref({
useUserId: null,
name: '',
username: "",
remark: ''
})
interface Inventory {
goodId: string | number;
batchList?: any[];// batchList
children: childCheck[];
packagingUnit?: string;
minPackagingUnit?: string;
totalOutWholeNumber: number;
totalFragmentNumber: number;
wholeNumber?: number;
fragmentNumber?: number;
selectList?: number[]; // selectList
name?: string;
trdnFlag?: number;
}
const tableList = ref<Inventory[]>([]);
interface childCheck {
id?: number,
goodsId: number,
name: string,
productionBatchCode: string,
minPackagingNumber: number,
productionDate: string
expiryDate: string,
purchaseUnitPrice: number,
wholeNumber: number,
packagingUnit: string,
fragmentNumber: number,
fragmentPrice: string,
minPackagingUnit: string;
outFragmentNumber: number;
outWholeNumber: number;
}
const addLine = (row: any) => {
post('inventory/goods/getListByIds', {idList: row.selectList}).then((list: any) => {
let children = [];
for (let i = 0; i < list.length; i++) {
let inventoryGoods = list[i];
let index = row.children ? row.children.findIndex((item: any) => item.id === inventoryGoods.id) : -1;
if (index > -1) {
children.push(row.children[index]);
} else {
let childCheck: childCheck = {
name: inventoryGoods.name,
id: inventoryGoods.id,
goodsId: inventoryGoods.goodId,
minPackagingNumber: row.minPackagingNumber,
productionBatchCode: inventoryGoods.productionBatchCode,
productionDate: inventoryGoods.productionDate,
expiryDate: inventoryGoods.expiryDate,
purchaseUnitPrice: inventoryGoods.purchaseUnitPrice,
wholeNumber: inventoryGoods.wholeNumber,
packagingUnit: inventoryGoods.packagingUnit,
fragmentNumber: inventoryGoods.fragmentNumber,
fragmentPrice: inventoryGoods.fragmentPrice,
minPackagingUnit: row.minPackagingUnit,
outFragmentNumber: inventoryGoods.outFragmentNumber,
outWholeNumber: inventoryGoods.outWholeNumber,
}
children.push(childCheck);
}
}
row.children = children;
})
}
const getUserInfo = () => {
post("manager/user/verify", null).then((res: any) => {
formData.value.useUserId = res.id;
formData.value.name = res.name;
formData.value.username = res.username;
})
}
const detail = () => {
post("inventory/apply/getApplyDetail", {id: props.id}).then((res: any) => {
formData.value = res.useInfo;
tableList.value = JSON.parse(res);
})
}
onMounted(() => {
getUserInfo()
detail()
})
</script>
<style scoped lang="scss">
.btn {
margin-top: 5px;
}
.bottom {
margin-top: 10px;
position: absolute;
right: 10px;
bottom: 10px;
}
</style>

View File

@ -0,0 +1,457 @@
<template>
<div class="body_wrapper">
<div class="top">
<el-descriptions
title="盘点单"
direction="vertical"
:column="1"
/>
<el-input class="remark" v-model="remark">
<template #prepend>备注</template>
</el-input>
</div>
<div class="content">
<GoodsSearch @selectCallBack="goodsSelectCallBack"></GoodsSearch>
<table class="simple-table" style="margin-top: 15px;">
<thead>
<tr>
<th>名称</th>
<th>批次</th>
<th>最小包装数量</th>
<th>库存</th>
<th>改后库存</th>
<th>变化量</th>
</tr>
</thead>
<tbody>
<template v-for="(item,index) in list">
<tr>
<td>{{ item.name }}</td>
<td>
<!--<el-checkbox-group v-model="item.childIdList" @change="addLine(item)">
<el-checkbox v-for="(subItem,subIndex) in item.selectList" :label="subItem.id" :value="subItem.id" />
</el-checkbox-group>-->
<el-select
v-model="item.childIdList"
@change="addLine(item)"
multiple
:teleported="false"
style="width: 150px;"
collapse-tags
popper-class="table-select">
<div class="select-header">
<span>批次ID</span>
<span>生产批号</span>
<span>入库时间</span>
<span>有效期</span>
<span>进价</span>
</div>
<el-option
v-for="(subItem,subIndex) in item.selectList"
:key="subItem.id"
:label="subItem.id"
:value="subItem.id">
<div class="option-row">
<span>{{ subItem.id }}</span>
<span>{{ subItem.productionBatchCode }}</span>
<span>{{ subItem.productionDate }}</span>
<span>{{ subItem.expiryDate }}</span>
<span>{{ subItem.purchaseUnitPrice }}</span>
</div>
</el-option>
</el-select>
</td>
<td>{{ item.minPackagingNumber }}</td>
<td>
{{ item.before.wholeNumber }}{{ item.packagingUnit }}
<template v-if="item.trdnFlag ==1">
{{ item.before.fragmentNumber }}{{ item.minPackagingUnit }}
</template>
</td>
<td>
<el-form v-show="item.childIdList.length===0">
<el-input v-model.number="item.after.wholeNumber" style="width: 60px" @change="setChange(item)">
<template #suffix>{{ item.packagingUnit }}</template>
</el-input>
<el-input v-model.number="item.after.fragmentNumber" style="width: 60px;margin-left: 5px"
@change="setChange(item)" v-if="item.trdnFlag ==1">
<template #suffix>{{ item.minPackagingUnit }}</template>
</el-input>
</el-form>
</td>
<td>
{{ item.change.wholeNumber }}{{ item.packagingUnit }}
<template v-if="item.trdnFlag ==1">
{{ item.change.fragmentNumber }}{{ item.minPackagingUnit }}
</template>
</td>
</tr>
<template v-for="(subItem,subIndex) in item.children">
<tr>
<td>-</td>
<td>-</td>
<td>-</td>
<td> {{ subItem.before.wholeNumber }}{{ item.packagingUnit }}
<template v-if="item.trdnFlag ==1">
{{ subItem.before.fragmentNumber }}{{ item.minPackagingUnit }}
</template>
</td>
<td>
<el-input v-model.number="subItem.after.wholeNumber" style="width: 60px" @change="setChange(subItem)">
<template #suffix>{{ item.packagingUnit }}</template>
</el-input>
<el-input v-model.number="subItem.after.fragmentNumber" style="width: 60px;margin-left: 5px"
@change="setChange(subItem)" v-if="item.trdnFlag ==1">
<template #suffix>{{ item.minPackagingUnit }}</template>
</el-input>
</td>
<td>
{{ subItem.change.wholeNumber }}{{ item.packagingUnit }}
<template v-if="item.trdnFlag ==1">
{{ item.change.fragmentNumber }}{{ item.minPackagingUnit }}
</template>
</td>
</tr>
</template>
</template>
</tbody>
</table>
</div>
</div>
<div class="bottom">
<el-button @click="save" type="primary">保存</el-button>
<el-button @click="exit" type="primary">关闭</el-button>
</div>
</template>
<style scoped lang="scss">
.body_wrapper {
position: relative;
width: 100%;
height: 100%;
display: flex;
min-height: 0;
flex-direction: column;
.top {
position: relative;
width: 100%;
height: 60px;
}
.content {
display: block;
width: 100%;
flex: 1;
//min-height: 0;
table {
//border-collapse: collapse;
width: 100%;
}
th,
td {
//border: 1px solid #ccc;
padding: 8px;
text-align: left;
}
th {
//background-color: #F9F9F9;
}
/* 可选:为可点击的行添加鼠标悬停效果 */
tr:hover {
//background-color: #f5f5f5;
}
}
.bottom {
height: 60px;
position: absolute;
right: 10px;
bottom: 10px;
}
}
.header {
display: flex;
.text {
font-size: 16px;
width: 200px;
color: #6a6a6a;
}
}
/* 表格容器 */
.simple-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
border: none;
color: #606266;
thead {
background-color: #f5f7fa;
th {
padding: 12px 0;
text-align: left;
font-weight: bold; /* 表头加粗 */
font-size: 14px; /* 表头字号 */
border-bottom: 1px solid #ebeef5;
}
}
tbody {
tr:hover {
background-color: #f5f7fa; /* 悬停效果 */
}
td {
padding: 12px 0;
text-align: left;
font-size: 14px; /* 单元格字号 */
border-bottom: 1px solid #ebeef5;
}
}
}
.table-select {
.el-select-dropdown__item {
padding: 0 !important;
height: auto;
}
//
.select-header {
display: flex;
padding: 8px 16px;
background: #f5f7fa;
border-bottom: 1px solid #ebeef5;
span {
flex: 1;
min-width: 110px;
font-weight: bold;
}
}
//
.option-row {
display: flex;
padding: 8px 16px;
span {
flex: 1;
min-width: 110px;
}
}
}
.remark {
//
:deep(.el-input-group__prepend) {
background-color: #fffdec;
}
//
:deep(.el-input__wrapper) {
background-color: #fffdec;
}
}
.bottom {
margin-top: 10px;
position: absolute;
right: 10px;
bottom: 10px;
}
</style>
<script setup lang="ts">
import {onMounted} from "vue";
import {post} from "@/utils/request.ts";
import {ref} from "vue";
import GoodsSearch from "@/components/inventory/GoodsSearch.vue";
const remark = ref("");
const state = ref([])
const options = ref([])
const goodsSelectCallBack = (item: any) => {
if (findIndexForTableList(item.goodId) != -1) {
return
}
addRow(item.goodId);
state.value = []
}
const findIndexForTableList = (goodId: any) => {
return list.value.findIndex((item: any) => item.goodsId === goodId);
}
interface Check {
id?: number,
goodsId: number,
name: string,
minPackagingUnit: string,
packagingUnit: string,
minPackagingNumber: number,
listSize: number,
trdnFlag: number,
childIdList: number[],
before: {
wholeNumber: number,
fragmentNumber: number,
},
after: {
wholeNumber: number,
fragmentNumber: number,
},
change: {
wholeNumber: number,
fragmentNumber: number,
}
children: childCheck[],
selectList: seletcType[],
}
interface seletcType {
id: number,
wholeNumber: number,
fragmentNumber: number,
productionBatchCode: string,
productionDate: string,
expiryDate: string,
purchaseUnitPrice: string
}
interface childCheck {
id?: number,
goodsId: number,
name: string,
minPackagingNumber: number,
before: {
wholeNumber: number,
fragmentNumber: number,
},
after: {
wholeNumber: number,
fragmentNumber: number,
},
change: {
wholeNumber: number,
fragmentNumber: number,
}
}
const list = ref<Check[]>([])
const emit = defineEmits(['close'])
onMounted(() => {
//addRow(1);
})
let setChange = function (row: any) {
let totalBefore = row.before.wholeNumber * row.minPackagingNumber + row.before.fragmentNumber; // 使
let totalAfter = row.after.wholeNumber * row.minPackagingNumber + row.after.fragmentNumber; // 使
console.log(totalBefore, totalAfter)
let diff = totalAfter - totalBefore;
row.change.wholeNumber = Math.trunc(diff / row.minPackagingNumber);
row.change.fragmentNumber = diff % row.minPackagingNumber
}
let addLine = (row: any) => {
post("inventory/goods/getListByIds", {idList: row.childIdList}).then((list: any) => {
let children = [];
for (let i = 0; i < list.length; i++) {
let inventoryGoods = list[i];
let index = row.children.findIndex((item: any) => item.id === inventoryGoods.id);
if (index > -1) {
children.push(row.children[index]);
} else {
let childCheck: childCheck = {
name: inventoryGoods.name,
id: inventoryGoods.id,
goodsId: inventoryGoods.goodId,
minPackagingNumber: row.minPackagingNumber,
before: {
wholeNumber: inventoryGoods.wholeNumber,
fragmentNumber: inventoryGoods.fragmentNumber,
},
after: {
wholeNumber: inventoryGoods.wholeNumber,
fragmentNumber: inventoryGoods.fragmentNumber,
},
change: {
wholeNumber: 0,
fragmentNumber: 0,
}
}
children.push(childCheck);
}
}
row.children = children;
})
}
let addRow = (goodsId: number) => {
post("inventory/goods/getByGoodsId", {goodsId}).then((res: any) => {
let check: Check = {
goodsId: res.id,
name: res.name,
listSize: res.listSize,
childIdList: [],
minPackagingNumber: res.minPackagingNumber,
packagingUnit: res.packagingUnit,
minPackagingUnit: res.minPackagingUnit,
trdnFlag: res.trdnFlag,
before: {
wholeNumber: res.wholeNumber,
fragmentNumber: res.fragmentNumber,
},
after: {
wholeNumber: res.wholeNumber,
fragmentNumber: res.fragmentNumber,
},
change: {
wholeNumber: 0,
fragmentNumber: 0,
},
children: [],
selectList: [],
}
let inventoryGoodsList = res.inventoryGoodsList
for (let i = 0; i < inventoryGoodsList.length; i++) {
let inventoryGoods = inventoryGoodsList[i];
let childCheck: seletcType = {
id: inventoryGoods.id,
wholeNumber: inventoryGoods.wholeNumber,
fragmentNumber: inventoryGoods.fragmentNumber,
productionBatchCode: inventoryGoods.productionBatchCode,
productionDate: inventoryGoods.productionDate,
expiryDate: inventoryGoods.expiryDate,
purchaseUnitPrice: inventoryGoods.purchaseUnitPrice,
}
check.selectList.push(childCheck)
}
list.value.push(check)
})
}
let save = () => {
post("inventory/check/save", {list: list.value, remark: remark.value}).then((res: any) => {
emit('close');
})
// emit('close');
}
let exit = () => {
emit('close');
}
</script>

View File

@ -0,0 +1,350 @@
<template>
<div class="body_wrapper">
<div class="top">
<el-descriptions
title="盘点详情"
direction="vertical"
:column="1"
/>
<el-input class="remark" v-model="remark">
<template #prepend>备注</template>
</el-input>
</div>
<div class="content">
<table class="simple-table" style="margin-top: 15px;">
<thead>
<tr>
<th>名称</th>
<th>批次</th>
<th>最小包装数量</th>
<th>库存</th>
<th>改后库存</th>
<th>变化量</th>
</tr>
</thead>
<tbody>
<template v-for="(item,index) in list">
<tr>
<td>{{ item.name }}</td>
<td v-if="item.childIdList.length=0"></td>
<td v-else>
<el-select
v-model="item.childIdList"
multiple
:teleported="false"
style="width: 150px;"
collapse-tags
popper-class="table-select"
:collapse-tags-tooltip="true"
>
<div class="select-header">
<span>批次ID</span>
<span>生产批号</span>
<span>入库时间</span>
<span>有效期</span>
<span>进价</span>
</div>
<el-option
v-for="(subItem,subIndex) in item.selectList"
:key="subItem.id"
:label="subItem.id"
:value="subItem.id"
disabled
>
<div class="option-row">
<span>{{ subItem.id }}</span>
<span>{{ subItem.productionBatchCode }}</span>
<span>{{ subItem.productionDate }}</span>
<span>{{ subItem.expiryDate }}</span>
<span>{{ subItem.purchaseUnitPrice }}</span>
</div>
</el-option>
</el-select>
</td>
<td>{{ item.minPackagingNumber }}</td>
<td>
{{ item.before.wholeNumber }}{{ item.packagingUnit }}
<template v-if="item.trdnFlag ==1">
{{ item.before.fragmentNumber }}{{ item.minPackagingUnit }}
</template>
</td>
<td>
{{ item.after.wholeNumber }}{{ item.packagingUnit }}
<template v-if="item.trdnFlag ==1">
{{ item.after.fragmentNumber }}{{ item.minPackagingUnit }}
</template>
</td>
<td>
{{ item.change.wholeNumber }}{{ item.packagingUnit }}
<template v-if="item.trdnFlag ==1">
{{ item.change.fragmentNumber }}{{ item.minPackagingUnit }}
</template>
</td>
</tr>
<template v-for="(subItem,subIndex) in item.children">
<tr>
<td>-</td>
<td>-</td>
<td>-</td>
<td>
{{ subItem.before.wholeNumber }}{{ item.packagingUnit }}
<template v-if="item.trdnFlag ==1">
{{ subItem.before.fragmentNumber }}{{ item.minPackagingUnit }}
</template>
</td>
<td>
{{ subItem.after.wholeNumber }}{{ item.packagingUnit }}
<template v-if="item.trdnFlag ==1">
{{ subItem.after.fragmentNumber }}{{ item.minPackagingUnit }}
</template>
</td>
<td>
{{ subItem.change.wholeNumber }}{{ item.packagingUnit }}
<template v-if="item.trdnFlag ==1">
{{ item.change.fragmentNumber }}{{ item.minPackagingUnit }}
</template>
</td>
</tr>
</template>
</template>
</tbody>
</table>
</div>
</div>
<div class="bottom">
<el-button @click="exit" type="primary">关闭</el-button>
</div>
</template>
<style scoped lang="scss">
.body_wrapper {
position: relative;
width: 100%;
height: 100%;
display: flex;
min-height: 0;
flex-direction: column;
.top {
position: relative;
width: 100%;
height: 60px;
}
.content {
display: block;
width: 100%;
flex: 1;
//min-height: 0;
table {
//border-collapse: collapse;
width: 100%;
}
th,
td {
//border: 1px solid #ccc;
padding: 8px;
text-align: left;
}
th {
//background-color: #F9F9F9;
}
/* 可选:为可点击的行添加鼠标悬停效果 */
tr:hover {
//background-color: #f5f5f5;
}
}
.bottom {
height: 60px;
position: absolute;
right: 10px;
bottom: 10px;
}
}
.header {
display: flex;
.text {
font-size: 16px;
width: 200px;
color: #6a6a6a;
}
}
/* 表格容器 */
.simple-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
border: none;
color: #606266;
thead {
background-color: #f5f7fa;
th {
padding: 12px 0;
text-align: left;
font-weight: bold; /* 表头加粗 */
font-size: 14px; /* 表头字号 */
border-bottom: 1px solid #ebeef5;
}
}
tbody {
tr:hover {
background-color: #f5f7fa; /* 悬停效果 */
}
td {
padding: 12px 0;
text-align: left;
font-size: 14px; /* 单元格字号 */
border-bottom: 1px solid #ebeef5;
}
}
}
.table-select {
.el-select-dropdown__item {
padding: 0 !important;
height: auto;
}
//
.select-header {
display: flex;
padding: 8px 16px;
background: #f5f7fa;
border-bottom: 1px solid #ebeef5;
span {
flex: 1;
min-width: 110px;
font-weight: bold;
}
}
//
.option-row {
display: flex;
padding: 8px 16px;
span {
flex: 1;
min-width: 110px;
}
}
}
.remark {
//
:deep(.el-input-group__prepend) {
background-color: #fffdec;
}
//
:deep(.el-input__wrapper) {
background-color: #fffdec;
}
}
.bottom {
margin-top: 10px;
position: absolute;
right: 10px;
bottom: 10px;
}
</style>
<script setup lang="ts">
import {onMounted,defineProps} from "vue";
import {post} from "@/utils/request.ts";
import {ref} from "vue";
const props = defineProps({
id: {
type: String,
default: null
}
})
const remark = ref("");
interface Check {
id?: number,
goodsId: number,
name: string,
minPackagingUnit: string,
packagingUnit: string,
minPackagingNumber: number,
listSize: number,
trdnFlag: number,
childIdList: number[],
before: {
wholeNumber: number,
fragmentNumber: number,
},
after: {
wholeNumber: number,
fragmentNumber: number,
},
change: {
wholeNumber: number,
fragmentNumber: number,
}
children: childCheck[],
selectList: seletcType[],
}
interface seletcType {
id: number,
wholeNumber: number,
fragmentNumber: number,
productionBatchCode: string,
productionDate: string,
expiryDate: string,
purchaseUnitPrice: string
}
interface childCheck {
id?: number,
goodsId: number,
name: string,
minPackagingNumber: number,
before: {
wholeNumber: number,
fragmentNumber: number,
},
after: {
wholeNumber: number,
fragmentNumber: number,
},
change: {
wholeNumber: number,
fragmentNumber: number,
}
}
const list = ref<Check[]>([])
const emit = defineEmits(['close'])
onMounted(() => {
detail()
})
let exit = () => {
emit('close');
}
const detail = () => {
post("inventory/check/getCheckDetail",{id: props.id}).then((res: any) => {
list.value = JSON.parse(res);
})
}
defineExpose({detail})
</script>

View File

@ -0,0 +1,172 @@
<template>
<CloseBtn @click="close"></CloseBtn>
<!-- <div class="close" @click="close"><el-icon><Close /></el-icon></div>-->
<el-table
:data="cateList"
style="width: 100%"
>
<el-table-column
width="200">
<template #default="scope">
<el-input v-model="scope.row.name" v-if="scope.row.isEdit"></el-input>
<span v-else>{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column
width="120">
<template #default="scope">
<div >
<div v-if="scope.row.isAdd" style="display: flex;">
<el-button type="primary" size="small" @click="saveDo" class="btn">确定</el-button>
<el-button size="small" @click="cancelAdd" class="btn">取消</el-button>
</div>
<div v-else>
<span @click="move(-1,scope.row)" class="btn" ><el-icon><ArrowUpBold /></el-icon></span>
<span @click="move(1,scope.row)" class="btn"><el-icon><ArrowDownBold /></el-icon></span>
<span @click="scope.row.isEdit=true" class="btn" id="edit"><el-icon><Edit /></el-icon></span>
<span @click="getCountByCateId(scope.row.id)" class="btn"> <el-icon><Delete /></el-icon></span>
</div>
</div>
</template>
</el-table-column>
</el-table>
<el-divider/>
<el-button type="primary" @click="add" id="add">添加</el-button>
<el-button type="primary" @click="save">保存</el-button>
</template>
<script setup lang="ts">
import {onMounted, onUnmounted, ref} from "vue";
import {post} from "@/utils/request.ts";
import {ElMessageBox} from "element-plus";
import CloseBtn from "@/components/CloseBtn.vue";
const props = defineProps({
type: {
type: Number,
},
})
interface CateItem {
id?: number;
name: string;
type: number|undefined;
isEdit: boolean;
isAdd: boolean;
sort: number;
}
const cateList = ref<CateItem[]>([]);
const getCateList = () => {
post("goods/cate/list",{type:props.type}).then((res:any)=>{
cateList.value=res
})
}
onMounted(()=>{
getCateList()
document.addEventListener("click", handleClickOutside);
})
onUnmounted(()=>{
document.removeEventListener("click", handleClickOutside);
})
const handleClickOutside = (event: MouseEvent) => {
const target = event.target as HTMLElement;
if (!target.closest('.el-input')&&!target.closest('#edit')&&!target.closest('#add')) {
cateList.value.forEach(item => {
item.isEdit = false;
});
}
};
const emit = defineEmits(['close'])
const close = () => {
emit('close')
}
const getCountByCateId = (cateId:any)=>{
let count =0;
post("goods/goods/getByCateId",{cateId:cateId}).
then((res:any)=>{
count=res
ElMessageBox.confirm(
`${count}个西药属于该二级分类,删除后将一同清空西药的分类。是否确定删除?`,
'Warning',
{
confirmButtonText: '确认删除',
cancelButtonText: '取消',
type: 'warning',
}
).then(() => {
del(cateId)
})
})
}
const del = async (id:any) => {
await post("goods/cate/del",{id})
getCateList()
}
const saveDo = async ()=>{
cateList.value.forEach((item,index)=>{
item.sort=index+1
if(item.name === ''){
removeItemByIndex(index)
}
})
await post("goods/cate/save",{cateList:cateList.value})
getCateList()
}
const save = ()=>{
saveDo()
close()
}
const removeItemByIndex = (index: number) => {
if (index >= 0 && index < cateList.value.length) {
cateList.value.splice(index, 1);
}
}
// const save = async ()=>{
// await post("goods/cate/save",cateList.value)
// getCateList()
// }
const add = () => {
const newCate: CateItem = {
name: '',
type: props.type,
isEdit: true,
isAdd: true,
sort: cateList.value.length+1
}
cateList.value.push(newCate)
}
// -1 1
const move = (direction:number,row :any)=>{
const index = cateList.value.findIndex(item => item.id === row.id);
if(index === -1)return;
const targetIndex = index + direction;
if(targetIndex<0||targetIndex>=cateList.value.length)return;
//
[cateList.value[index], cateList.value[targetIndex]] = [cateList.value[targetIndex], cateList.value[index]];
console.log(cateList)
}
const cancelAdd = ()=>{
cateList.value.pop()
}
</script>
<style scoped lang="scss">
.close{
cursor: pointer;
position: absolute;
top: 10px;
right: 10px;
font-size: 24px;
z-index: 1999;
}
.btn {
margin-left: 10px;
cursor: pointer;
}
</style>

Some files were not shown because too many files have changed in this diff Show More