feat: 111

This commit is contained in:
fuenmao
2025-09-29 14:26:13 +08:00
parent 39f70340a0
commit 0e34840773
32 changed files with 2051 additions and 1328 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# title # title
VITE_GLOB_APP_TITLE = SimpleAdmin VITE_GLOB_APP_TITLE = 未病局管理系统
# 本地运行端口号 # 本地运行端口号
VITE_PORT = 8848 VITE_PORT = 8848
+2 -2
View File
@@ -25,10 +25,10 @@ VITE_PWA = true
VITE_HTTP_PROXY = true VITE_HTTP_PROXY = true
# 线上环境接口地址 # 线上环境接口地址
VITE_API_URL = http://127.0.0.1:5566 VITE_API_URL = http://medical.23544.com:7106/
# 开发环境跨域代理,支持配置多个 # 开发环境跨域代理,支持配置多个
VITE_PROXY = [["/api","http://127.0.0.1:5566"]] VITE_PROXY = [["/api","http://medical.23544.com:7106/"]]
#是否启用mqtt #是否启用mqtt
VITE_MQTT = false VITE_MQTT = false
+29 -31
View File
@@ -20,42 +20,40 @@ module.exports = {
} }
}, },
// 继承某些已有的规则 // 继承某些已有的规则
extends: ["plugin:vue/vue3-recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"], extends: ["plugin:vue/vue3-recommended", "plugin:@typescript-eslint/recommended"],
/** /**
* "off" 或 0 ==> 关闭规则 * "off" 或 0 ==> 关闭规则
* "warn" 或 1 ==> 打开的规则作为警告(不影响代码执行) * "warn" 或 1 ==> 打开的规则作为警告(不影响代码执行)
* "error" 或 2 ==> 规则作为一个错误(代码不能执行,界面报错) * "error" 或 2 ==> 规则作为一个错误(代码不能执行,界面报错)
*/ */
rules: { rules: {
// eslint (http://eslint.cn/docs/rules) // // eslint (http://eslint.cn/docs/rules)
"no-var": "error", // 要求使用 let 或 const 而不是 var // "no-var": "error", // 要求使用 let 或 const 而不是 var
"no-multiple-empty-lines": ["error", { max: 1 }], // 不允许多个空行 // "no-multiple-empty-lines": ["error", { max: 1 }], // 不允许多个空行
"prefer-const": "off", // 使用 let 关键字声明但在初始分配后从未重新分配的变量,要求使用 const // "prefer-const": "off", // 使用 let 关键字声明但在初始分配后从未重新分配的变量,要求使用 const
"no-use-before-define": "off", // 禁止在 函数/类/变量 定义之前使用它们 // "no-use-before-define": "off", // 禁止在 函数/类/变量 定义之前使用它们
// // typeScript (https://typescript-eslint.io/rules)
// typeScript (https://typescript-eslint.io/rules) // "@typescript-eslint/no-unused-vars": "error", // 禁止定义未使用的变量
"@typescript-eslint/no-unused-vars": "error", // 禁止定义未使用的变量 // "@typescript-eslint/prefer-ts-expect-error": "error", // 禁止使用 @ts-ignore
"@typescript-eslint/prefer-ts-expect-error": "error", // 禁止使用 @ts-ignore // "@typescript-eslint/no-empty-function": "error", // 禁止空函数
"@typescript-eslint/no-empty-function": "error", // 禁止空函数 // "@typescript-eslint/ban-ts-comment": "error", // 禁止 @ts-<directive> 使用注释或要求在指令后进行描述
"@typescript-eslint/ban-ts-comment": "error", // 禁止 @ts-<directive> 使用注释或要求在指令后进行描述 // "@typescript-eslint/no-inferrable-types": "off", // 可以轻松推断的显式类型可能会增加不必要的冗长
"@typescript-eslint/no-inferrable-types": "off", // 可以轻松推断的显式类型可能会增加不必要的冗长 // "@typescript-eslint/no-namespace": "off", // 禁止使用自定义 TypeScript 模块和命名空间
"@typescript-eslint/no-namespace": "off", // 禁止使用自定义 TypeScript 模块和命名空间 "@typescript-eslint/no-explicit-any": "off" // 禁止使用 any 类型
"@typescript-eslint/no-explicit-any": "off", // 禁止使用 any 类型 // "@typescript-eslint/ban-types": "off", // 禁止使用特定类型
"@typescript-eslint/ban-types": "off", // 禁止使用特定类型 // "@typescript-eslint/no-var-requires": "off", // 允许使用 require() 函数导入模块
"@typescript-eslint/no-var-requires": "off", // 允许使用 require() 函数导入模块 // "@typescript-eslint/no-non-null-assertion": "off", // 不允许使用后缀运算符的非空断言(!)
"@typescript-eslint/no-non-null-assertion": "off", // 不允许使用后缀运算符的非空断言(!) // // vue (https://eslint.vuejs.org/rules)
// "vue/script-setup-uses-vars": "error", // 防止<script setup>使用的变量<template>被标记为未使用,此规则仅在启用该no-unused-vars规则时有效
// vue (https://eslint.vuejs.org/rules) // "vue/v-slot-style": "error", // 强制执行 v-slot 指令样式
"vue/script-setup-uses-vars": "error", // 防止<script setup>使用的变量<template>被标记为未使用,此规则仅在启用该no-unused-vars规则时有效 // "vue/no-mutating-props": "error", // 不允许改变组件 prop
"vue/v-slot-style": "error", // 强制执行 v-slot 指令样式 // "vue/custom-event-name-casing": "error", // 为自定义事件名称强制使用特定大小写
"vue/no-mutating-props": "error", // 不允许改变组件 prop // "vue/html-closing-bracket-newline": "error", // 在标签的右括号之前要求或禁止换行
"vue/custom-event-name-casing": "error", // 为自定义事件名称强制使用特定大小写 // "vue/attribute-hyphenation": "error", // 对模板中的自定义组件强制执行属性命名样式:my-prop="prop"
"vue/html-closing-bracket-newline": "error", // 在标签的右括号之前要求或禁止换行 // "vue/attributes-order": "off", // vue api使用顺序,强制执行属性顺序
"vue/attribute-hyphenation": "error", // 对模板中的自定义组件强制执行属性命名样式:my-prop="prop" // "vue/no-v-html": "off", // 禁止使用 v-html
"vue/attributes-order": "off", // vue api使用顺序,强制执行属性顺序 // "vue/require-default-prop": "off", // 此规则要求为每个 prop 为必填时,必须提供默认值
"vue/no-v-html": "off", // 禁止使用 v-html // "vue/multi-word-component-names": "off", // 要求组件名称始终为 “-” 链接的单词
"vue/require-default-prop": "off", // 此规则要求为每个 prop 为必填时,必须提供默认值 // "vue/no-setup-props-destructure": "off" // 禁止解构 props 传递给 setup
"vue/multi-word-component-names": "off", // 要求组件名称始终为 “-” 链接的单词
"vue/no-setup-props-destructure": "off" // 禁止解构 props 传递给 setup
} }
}; };
-4
View File
@@ -1,4 +0,0 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no-install commitlint --edit $1
-4
View File
@@ -1,4 +0,0 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run lint:lint-staged
+1 -1
View File
@@ -13,7 +13,7 @@
"files.eol": "\n", "files.eol": "\n",
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
"[vue]": { "[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "Vue.volar"
}, },
"[typescript]": { "[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
+2 -2
View File
@@ -6,7 +6,7 @@ import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
import { createHtmlPlugin } from "vite-plugin-html"; import { createHtmlPlugin } from "vite-plugin-html";
import vue from "@vitejs/plugin-vue"; import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx"; import vueJsx from "@vitejs/plugin-vue-jsx";
import eslintPlugin from "vite-plugin-eslint"; // import eslintPlugin from "vite-plugin-eslint";
import viteCompression from "vite-plugin-compression"; import viteCompression from "vite-plugin-compression";
import vueSetupExtend from "unplugin-vue-setup-extend-plus/vite"; import vueSetupExtend from "unplugin-vue-setup-extend-plus/vite";
import UnoCSS from "unocss/vite"; import UnoCSS from "unocss/vite";
@@ -31,7 +31,7 @@ export const createVitePlugins = (viteEnv: ViteEnv): (PluginOption | PluginOptio
// vue 可以使用 jsx/tsx 语法 // vue 可以使用 jsx/tsx 语法
vueJsx(), vueJsx(),
// esLint 报错信息显示在浏览器界面上 // esLint 报错信息显示在浏览器界面上
eslintPlugin(), // eslintPlugin(),
// name 可以写在 script 标签上 // name 可以写在 script 标签上
vueSetupExtend({}), vueSetupExtend({}),
// 创建打包压缩配置 // 创建打包压缩配置
-48
View File
@@ -18,67 +18,22 @@ declare module 'vue' {
CropUpload: typeof import('./src/components/CropUpload/index.vue')['default'] CropUpload: typeof import('./src/components/CropUpload/index.vue')['default']
DatePicker: typeof import('./src/components/Form/DatePicker/index.vue')['default'] DatePicker: typeof import('./src/components/Form/DatePicker/index.vue')['default']
ECharts: typeof import('./src/components/ECharts/index.vue')['default'] ECharts: typeof import('./src/components/ECharts/index.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert']
ElAside: typeof import('element-plus/es')['ElAside']
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBadge: typeof import('element-plus/es')['ElBadge']
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCascader: typeof import('element-plus/es')['ElCascader']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCol: typeof import('element-plus/es')['ElCol']
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog'] ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElEmpty: typeof import('element-plus/es')['ElEmpty'] ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElFooter: typeof import('element-plus/es')['ElFooter']
ElForm: typeof import('element-plus/es')['ElForm'] ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon'] ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElLink: typeof import('element-plus/es')['ElLink']
ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption'] ElOption: typeof import('element-plus/es')['ElOption']
ElPageHeader: typeof import('element-plus/es')['ElPageHeader']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover'] ElPopover: typeof import('element-plus/es')['ElPopover']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect'] ElSelect: typeof import('element-plus/es')['ElSelect']
ElSlider: typeof import('element-plus/es')['ElSlider']
ElSpace: typeof import('element-plus/es')['ElSpace'] ElSpace: typeof import('element-plus/es')['ElSpace']
ElStep: typeof import('element-plus/es')['ElStep']
ElSteps: typeof import('element-plus/es')['ElSteps']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs'] ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
ElUpload: typeof import('element-plus/es')['ElUpload']
ElWatermark: typeof import('element-plus/es')['ElWatermark']
ESign: typeof import('./src/components/ESign/index.vue')['default'] ESign: typeof import('./src/components/ESign/index.vue')['default']
FormContainer: typeof import('./src/components/Form/FormContainer/index.vue')['default'] FormContainer: typeof import('./src/components/Form/FormContainer/index.vue')['default']
Grid: typeof import('./src/components/Grid/index.vue')['default'] Grid: typeof import('./src/components/Grid/index.vue')['default']
@@ -112,7 +67,4 @@ declare module 'vue' {
UploadImgs: typeof import('./src/components/Upload/UploadImgs.vue')['default'] UploadImgs: typeof import('./src/components/Upload/UploadImgs.vue')['default']
UserSelector: typeof import('./src/components/Selectors/UserSelector/index.vue')['default'] UserSelector: typeof import('./src/components/Selectors/UserSelector/index.vue')['default']
} }
export interface ComponentCustomProperties {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
} }
+1 -1
View File
@@ -23,7 +23,7 @@
"serve": "vite", "serve": "vite",
"build:dev": "vue-tsc && vite build --mode development", "build:dev": "vue-tsc && vite build --mode development",
"build:test": "vue-tsc && vite build --mode test", "build:test": "vue-tsc && vite build --mode test",
"build:pro": "vue-tsc && vite build --mode production", "build:pro": "vite build --mode production",
"type:check": "vue-tsc --noEmit --skipLibCheck", "type:check": "vue-tsc --noEmit --skipLibCheck",
"preview": "pnpm run build:dev && vite preview", "preview": "pnpm run build:dev && vite preview",
"lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src", "lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
+12 -11
View File
@@ -23,14 +23,14 @@ const medicinalApi = {
}, },
/** 获取药材消耗记录 */ /** 获取药材消耗记录 */
getConsumptionRecords(id: number) { getConsumptionRecords(params: { id: number; pageNum?: number; pageSize?: number }) {
return http.get<any>(`${id}/consumption-records`); console.log("获取添加记录,请求参数=", params);
return http.get<ResPage<any>>(`consumption-records`, params); // 透传分页参数
}, },
/** 获取药材添加记录 */ /** 获取药材添加记录 */
getAdditionRecords(id: number, params?: { pageNum?: number; pageSize?: number }) { getAdditionRecords(params: { id: number; pageNum?: number; pageSize?: number }) {
// 注意:这里把 id 放到路径里,分页走 params return http.get<ResPage<any>>(`addition-records`, params);
return http.get<any>(`/${id}/addition-records`, { params });
}, },
/** 添加药材 */ /** 添加药材 */
@@ -39,13 +39,13 @@ const medicinalApi = {
}, },
/** 修改药材添加记录 */ /** 修改药材添加记录 */
updateAdditionRecord(id: number, params: UpdateMedicinalAddLogInput) { updateAdditionRecord(params: UpdateMedicinalAddLogInput) {
return http.put(`addition-records/${id}`, params); return http.post(`addition-records`, params);
}, },
/** 删除药材添加记录 */ /** 删除药材添加记录 */
deleteAdditionRecord(id: number) { deleteAdditionRecord(params: { id: number }) {
return http.delete(`addition-records/${id}`); return http.post(`addition-delete`, params);
} }
}; };
@@ -62,14 +62,15 @@ export interface MedicinalInfo {
export interface AddMedicinalInput { export interface AddMedicinalInput {
medicinalTypeId: number; medicinalTypeId: number;
addWeight: number; addWeight: number;
unitPrice: number; totalPrice: number;
medicinalName: string; medicinalName: string;
} }
/** 修改添加记录 */ /** 修改添加记录 */
export interface UpdateMedicinalAddLogInput { export interface UpdateMedicinalAddLogInput {
id: number;
addWeight: number; addWeight: number;
unitPrice: number; totalPrice: number;
} }
export { medicinalApi }; export { medicinalApi };
+127 -19
View File
@@ -1,41 +1,149 @@
import { moduleRequest } from "@/api/request"; import { moduleRequest } from "@/api/request";
import { ResPage } from "@/api/interface";
const http = moduleRequest("/biz/weibingju/prescription/"); const http = moduleRequest("/biz/weibingju/prescription/");
// -----------------------------
// 类型定义(对应后端 DTO)
// -----------------------------
/** 人员类型 */
export enum PersonType {
Employee = 1,
Relative = 2
}
/** 处方列表 DTO */
export interface PrescriptionDto {
id: number;
personId: number;
personName: string;
personType: PersonType;
employeeName?: string;
visitDate?: string;
canEdit: boolean;
}
/** 处方药材 DTO */
export interface PrescriptionHerbDto {
Id: number;
medicinalName: string;
amountG: number;
note?: string;
}
/** 人员详情 DTO */
export interface PersonDetailDto {
id?: number;
name: string;
age?: number;
weight?: number;
height?: number;
isEmployee: boolean;
employeeId?: number;
employeeName?: string;
relationShip?: number;
relationShipName?: string;
}
/** 处方详情 DTO */
export interface PrescriptionDetailDto {
id: number;
person: PersonDetailDto;
diagnosisResult: string;
userMedicinaAdvice: string;
visitDate?: string;
herbs: PrescriptionHerbDto[];
canEdit: boolean;
}
/** 保存处方输入 DTO */
export interface SavePrescriptionInput {
id?: number;
person: PersonInputDto;
diagnosisResult: string;
userMedicinaAdvice: string;
visitDate?: string;
herbs: PrescriptionHerbInputDto[];
}
/** 人员输入 DTO */
export interface PersonInputDto {
id?: number;
name: string;
age?: number;
weight?: number;
height?: number;
isEmployee: boolean;
employeeId?: number;
relationShip?: number;
}
/** 处方药材输入 DTO */
export interface PrescriptionHerbInputDto {
Id: number;
amountG: number;
note?: string;
}
/** 人员 DTO(列表用) */
export interface PersonDto {
id: number;
name: string;
age?: number;
employeeId?: number;
}
/** 添加人员输入 DTO */
export interface AddPersonInput {
name: string;
age?: number;
weight?: number;
height?: number;
isEmployee: boolean;
employeeId?: number;
relationShip?: number;
}
/** 关系类型 DTO */
export interface RelationTypeDto {
id: number;
name: string;
}
// -----------------------------
// API 方法(对应后端接口)
// -----------------------------
const prescriptionApi = { const prescriptionApi = {
/** 获取处方列表(分页) */ /** 获取处方列表 */
getList(params: any) { getList(params: any) {
return http.get<any>("list", params); return http.get<ResPage<PrescriptionDto>>("list", params);
}, },
/** 获取处方详情 */ /** 获取处方详情 */
getDetail(params: any) { getDetail(id: number) {
return http.get<any>("detail", params); return http.get<PrescriptionDetailDto>(`getprescriptiondetile?id=${id}`);
}, },
/** 保存处方(新增/编辑) */ /** 保存处方(新增/编辑) */
save(params: any) { save(params: SavePrescriptionInput) {
const url = params.id ? "edit" : "add"; return http.post<number>("add", params);
return http.post<any>(url, params);
}, },
/** 获取人员列表 */ /** 获取人员列表 */
getPersonList(params: any) { getPersonList(params: { type?: PersonType; employeeId?: number }) {
return http.get<any>("persons", params, { loading: false }); return http.get<PersonDto[]>("persons", params);
}, },
/** 新增人员 */ /** 添加人员 */
addPerson(params: any) { addPerson(params: AddPersonInput) {
return http.post<any>("addpersons", params); return http.post<number>("addpersons", params);
}, },
/** 获取亲属关系列表 */ /** 获取关系类型 */
getRelationTypes() { getRelationTypes() {
return http.get<any>("relation-types", {}, { loading: false }); return http.get<RelationTypeDto[]>("relation-types");
},
/** 搜索药材 */
searchMedicinals(params: any) {
return http.get<any>("medicinals/search", params, { loading: false });
} }
}; };
+2 -2
View File
@@ -15,5 +15,5 @@
export * from "./organization"; export * from "./organization";
export * from "./ops"; export * from "./ops";
export * from "./WeiBingJu/medicinal"; export * from "./weibingju/medicinal";
export * from "./WeiBingJu/prescription"; export * from "./weibingju/prescription";
+3 -1
View File
@@ -134,10 +134,12 @@ export default class RequestHttp {
this.apiNameArray.forEach(apiName => { this.apiNameArray.forEach(apiName => {
let responseApiArray = responseUrl.split("/"); //分割 let responseApiArray = responseUrl.split("/"); //分割
let method = responseApiArray[responseApiArray.length - 1]; //取最后一个 let method = responseApiArray[responseApiArray.length - 1]; //取最后一个
console.log("method=", method);
console.log("noMessageApiNameArray=", this.noMessageApiNameArray);
let result = this.noMessageApiNameArray.includes(method); //判断是否在不提示的数组中 let result = this.noMessageApiNameArray.includes(method); //判断是否在不提示的数组中
if (!result && responseUrl.includes(apiName)) { if (!result && responseUrl.includes(apiName)) {
//如果不在不提示的数组中并且请求地址包含指定的方法 //如果不在不提示的数组中并且请求地址包含指定的方法
ElMessage.success(data.msg); // ElMessage.success(data.msg);
} }
}); });
} }
+65 -80
View File
@@ -6,88 +6,73 @@
// biome-ignore lint: disable // biome-ignore lint: disable
export {} export {}
declare global { declare global {
const EffectScope: (typeof import("vue"))["EffectScope"]; const EffectScope: typeof import('vue')['EffectScope']
const ElMessage: (typeof import("element-plus/es"))["ElMessage"]; const ElMessage: typeof import('element-plus/es')['ElMessage']
const ElMessageBox: (typeof import("element-plus/es"))["ElMessageBox"]; const ElMessageBox: (typeof import("element-plus/es"))["ElMessageBox"]
const computed: (typeof import("vue"))["computed"]; const computed: typeof import('vue')['computed']
const createApp: (typeof import("vue"))["createApp"]; const createApp: typeof import('vue')['createApp']
const customRef: (typeof import("vue"))["customRef"]; const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: (typeof import("vue"))["defineAsyncComponent"]; const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: (typeof import("vue"))["defineComponent"]; const defineComponent: typeof import('vue')['defineComponent']
const effectScope: (typeof import("vue"))["effectScope"]; const effectScope: typeof import('vue')['effectScope']
const getCurrentInstance: (typeof import("vue"))["getCurrentInstance"]; const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: (typeof import("vue"))["getCurrentScope"]; const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: (typeof import("vue"))["h"]; const h: typeof import('vue')['h']
const inject: (typeof import("vue"))["inject"]; const inject: typeof import('vue')['inject']
const isProxy: (typeof import("vue"))["isProxy"]; const isProxy: typeof import('vue')['isProxy']
const isReactive: (typeof import("vue"))["isReactive"]; const isReactive: typeof import('vue')['isReactive']
const isReadonly: (typeof import("vue"))["isReadonly"]; const isReadonly: typeof import('vue')['isReadonly']
const isRef: (typeof import("vue"))["isRef"]; const isRef: typeof import('vue')['isRef']
const markRaw: (typeof import("vue"))["markRaw"]; const markRaw: typeof import('vue')['markRaw']
const nextTick: (typeof import("vue"))["nextTick"]; const nextTick: typeof import('vue')['nextTick']
const onActivated: (typeof import("vue"))["onActivated"]; const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: (typeof import("vue"))["onBeforeMount"]; const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeRouteLeave: (typeof import("vue-router"))["onBeforeRouteLeave"]; const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
const onBeforeRouteUpdate: (typeof import("vue-router"))["onBeforeRouteUpdate"]; const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
const onBeforeUnmount: (typeof import("vue"))["onBeforeUnmount"]; const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: (typeof import("vue"))["onBeforeUpdate"]; const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: (typeof import("vue"))["onDeactivated"]; const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: (typeof import("vue"))["onErrorCaptured"]; const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onMounted: (typeof import("vue"))["onMounted"]; const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: (typeof import("vue"))["onRenderTracked"]; const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: (typeof import("vue"))["onRenderTriggered"]; const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: (typeof import("vue"))["onScopeDispose"]; const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: (typeof import("vue"))["onServerPrefetch"]; const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onUnmounted: (typeof import("vue"))["onUnmounted"]; const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: (typeof import("vue"))["onUpdated"]; const onUpdated: typeof import('vue')['onUpdated']
const onWatcherCleanup: (typeof import("vue"))["onWatcherCleanup"]; const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
const provide: (typeof import("vue"))["provide"]; const provide: typeof import('vue')['provide']
const reactive: (typeof import("vue"))["reactive"]; const reactive: typeof import('vue')['reactive']
const readonly: (typeof import("vue"))["readonly"]; const readonly: typeof import('vue')['readonly']
const ref: (typeof import("vue"))["ref"]; const ref: typeof import('vue')['ref']
const resolveComponent: (typeof import("vue"))["resolveComponent"]; const resolveComponent: typeof import('vue')['resolveComponent']
const shallowReactive: (typeof import("vue"))["shallowReactive"]; const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: (typeof import("vue"))["shallowReadonly"]; const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: (typeof import("vue"))["shallowRef"]; const shallowRef: typeof import('vue')['shallowRef']
const toRaw: (typeof import("vue"))["toRaw"]; const toRaw: typeof import('vue')['toRaw']
const toRef: (typeof import("vue"))["toRef"]; const toRef: typeof import('vue')['toRef']
const toRefs: (typeof import("vue"))["toRefs"]; const toRefs: typeof import('vue')['toRefs']
const toValue: (typeof import("vue"))["toValue"]; const toValue: typeof import('vue')['toValue']
const triggerRef: (typeof import("vue"))["triggerRef"]; const triggerRef: typeof import('vue')['triggerRef']
const unref: (typeof import("vue"))["unref"]; const unref: typeof import('vue')['unref']
const useAttrs: (typeof import("vue"))["useAttrs"]; const useAttrs: typeof import('vue')['useAttrs']
const useCssModule: (typeof import("vue"))["useCssModule"]; const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: (typeof import("vue"))["useCssVars"]; const useCssVars: typeof import('vue')['useCssVars']
const useId: (typeof import("vue"))["useId"]; const useId: typeof import('vue')['useId']
const useLink: (typeof import("vue-router"))["useLink"]; const useLink: typeof import('vue-router')['useLink']
const useModel: (typeof import("vue"))["useModel"]; const useModel: typeof import('vue')['useModel']
const useRoute: (typeof import("vue-router"))["useRoute"]; const useRoute: typeof import('vue-router')['useRoute']
const useRouter: (typeof import("vue-router"))["useRouter"]; const useRouter: typeof import('vue-router')['useRouter']
const useSlots: (typeof import("vue"))["useSlots"]; const useSlots: typeof import('vue')['useSlots']
const useTemplateRef: (typeof import("vue"))["useTemplateRef"]; const useTemplateRef: typeof import('vue')['useTemplateRef']
const watch: (typeof import("vue"))["watch"]; const watch: typeof import('vue')['watch']
const watchEffect: (typeof import("vue"))["watchEffect"]; const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: (typeof import("vue"))["watchPostEffect"]; const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: (typeof import("vue"))["watchSyncEffect"]; const watchSyncEffect: typeof import('vue')['watchSyncEffect']
} }
// for type re-export // for type re-export
declare global { declare global {
// @ts-ignore // @ts-ignore
export type { export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
Component, import('vue')
ComponentPublicInstance,
ComputedRef,
DirectiveBinding,
ExtractDefaultPropTypes,
ExtractPropTypes,
ExtractPublicPropTypes,
InjectionKey,
PropType,
Ref,
MaybeRef,
MaybeRefOrGetter,
VNode,
WritableComputedRef
} from "vue";
import("vue");
} }
+17 -27
View File
@@ -2,43 +2,36 @@
<template> <template>
<!-- 查询表单 --> <!-- 查询表单 -->
<SearchForm v-show="isShowSearch" :search="_search" :reset="_reset" :columns="searchColumns" :search-param="searchParam" :search-col="searchCol" /> <SearchForm v-show="isShowSearch" :search="_search" :reset="_reset" :columns="searchColumns"
:search-param="searchParam" :search-col="searchCol" />
<!-- 表格主体 --> <!-- 表格主体 -->
<div class="card table-main"> <div class="card table-main">
<!-- 表格头部 操作按钮 --> <!-- 表格头部 操作按钮 -->
<div class="table-header"> <div class="table-header">
<div class="header-button-lf"> <div class="header-button-lf">
<slot name="tableHeader" :selected-list="selectedList" :selected-list-ids="selectedListIds" :is-selected="isSelected" /> <slot name="tableHeader" :selected-list="selectedList" :selected-list-ids="selectedListIds"
:is-selected="isSelected" />
</div> </div>
<div v-if="toolButton" class="header-button-ri"> <div v-if="toolButton" class="header-button-ri">
<slot name="toolButton"> <slot name="toolButton">
<el-button v-if="showToolButton('refresh')" :icon="Refresh" circle @click="getTableList" /> <el-button v-if="showToolButton('refresh')" :icon="Refresh" circle @click="getTableList" />
<el-button v-if="showToolButton('setting') && columns.length" :icon="Operation" circle @click="openColSetting" /> <el-button v-if="showToolButton('setting') && columns.length" :icon="Operation" circle
<el-button v-if="showToolButton('search') && searchColumns?.length" :icon="Search" circle @click="isShowSearch = !isShowSearch" /> @click="openColSetting" />
<el-button v-if="showToolButton('search') && searchColumns?.length" :icon="Search" circle
@click="isShowSearch = !isShowSearch" />
</slot> </slot>
</div> </div>
</div> </div>
<!-- 表格主体 --> <!-- 表格主体 -->
<el-table <el-table ref="tableRef" v-bind="$attrs" :id="uuid" :data="processTableData" :border="border" :row-key="rowKey"
ref="tableRef" @selection-change="selectionChange">
v-bind="$attrs"
:id="uuid"
:data="processTableData"
:border="border"
:row-key="rowKey"
@selection-change="selectionChange"
>
<!-- 默认插槽 --> <!-- 默认插槽 -->
<slot /> <slot />
<template v-for="item in tableColumns" :key="item"> <template v-for="item in tableColumns" :key="item">
<!-- selection || radio || index || expand || sort --> <!-- selection || radio || index || expand || sort -->
<el-table-column <el-table-column v-if="item.type && columnTypes.includes(item.type)" v-bind="item"
v-if="item.type && columnTypes.includes(item.type)" :align="item.align ?? 'center'" :reserve-selection="item.type == 'selection'">
v-bind="item"
:align="item.align ?? 'center'"
:reserve-selection="item.type == 'selection'"
>
<template #default="scope"> <template #default="scope">
<!-- expand --> <!-- expand -->
<template v-if="item.type == 'expand'"> <template v-if="item.type == 'expand'">
@@ -51,7 +44,9 @@
</el-radio> </el-radio>
<!-- sort --> <!-- sort -->
<el-tag v-if="item.type == 'sort'" class="move"> <el-tag v-if="item.type == 'sort'" class="move">
<el-icon> <DCaret /></el-icon> <el-icon>
<DCaret />
</el-icon>
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
@@ -78,13 +73,8 @@
</el-table> </el-table>
<!-- 分页组件 --> <!-- 分页组件 -->
<slot name="pagination"> <slot name="pagination">
<Pagination <Pagination v-if="pagination" :pageable="pageable" :pager-count="pagerCount"
v-if="pagination" :handle-size-change="handleSizeChange" :handle-current-change="handleCurrentChange" />
:pageable="pageable"
:pager-count="pagerCount"
:handle-size-change="handleSizeChange"
:handle-current-change="handleCurrentChange"
/>
</slot> </slot>
</div> </div>
<!-- 列设置 --> <!-- 列设置 -->
@@ -4,7 +4,7 @@
<AssemblySize id="assemblySize" /> <AssemblySize id="assemblySize" />
<SearchMenu id="searchMenu" /> <SearchMenu id="searchMenu" />
<ThemeSetting id="themeSetting" /> <ThemeSetting id="themeSetting" />
<Message id="message" /> <!-- <Message id="message" /> -->
<Fullscreen id="fullscreen" /> <Fullscreen id="fullscreen" />
<ChangeModule id="changeModule" /> <ChangeModule id="changeModule" />
</div> </div>
@@ -18,7 +18,7 @@ import { computed } from "vue";
import { useUserStore } from "@/stores/modules/user"; import { useUserStore } from "@/stores/modules/user";
import AssemblySize from "./components/AssemblySize.vue"; import AssemblySize from "./components/AssemblySize.vue";
import ThemeSetting from "./components/ThemeSetting.vue"; import ThemeSetting from "./components/ThemeSetting.vue";
import Message from "./components/Message.vue"; // import Message from "./components/Message.vue";
import Fullscreen from "./components/Fullscreen.vue"; import Fullscreen from "./components/Fullscreen.vue";
import Avatar from "./components/Avatar.vue"; import Avatar from "./components/Avatar.vue";
import ChangeModule from "./components/ChangeModule.vue"; import ChangeModule from "./components/ChangeModule.vue";
@@ -34,14 +34,17 @@ const username = computed(() => userStore.userInfo?.name);
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding-right: 25px; padding-right: 25px;
.header-icon { .header-icon {
display: flex; display: flex;
align-items: center; align-items: center;
&>* { &>* {
margin-left: 21px; margin-left: 21px;
color: var(--el-header-text-color); color: var(--el-header-text-color);
} }
} }
.username { .username {
margin: 0 20px; margin: 0 20px;
font-size: 15px; font-size: 15px;
@@ -14,20 +14,19 @@
</div> </div>
</template> </template>
<el-tabs v-model="activeName"> <el-tabs v-model="activeName">
<el-tab-pane <el-tab-pane v-for="(tab, index) in messageTypeOptions" :key="index"
v-for="(tab, index) in messageTypeOptions" :label="tab.label + `(${messageStore.getUnReadCount(tab.value)})`" :name="tab.value">
:key="index"
:label="tab.label + `(${messageStore.getUnReadCount(tab.value)})`"
:name="tab.value"
>
<div class="message-list"> <div class="message-list">
<div class="message-item" v-for="(msg, index2) in getNewUnRead(tab.value)" :key="index2"> <div class="message-item" v-for="(msg, index2) in getNewUnRead(tab.value)" :key="index2">
<img v-if="tab.value === MessageTypeDictEnum.INFORM" src="@/assets/images/msg01.png" alt="" class="message-icon" /> <img v-if="tab.value === MessageTypeDictEnum.INFORM" src="@/assets/images/msg01.png" alt=""
<img v-else-if="tab.value === MessageTypeDictEnum.NOTICE" src="@/assets/images/msg03.png" alt="" class="message-icon" /> class="message-icon" />
<img v-else-if="tab.value === MessageTypeDictEnum.NOTICE" src="@/assets/images/msg03.png" alt=""
class="message-icon" />
<img v-else src="@/assets/images/msg02.png" alt="" class="message-icon" /> <img v-else src="@/assets/images/msg02.png" alt="" class="message-icon" />
<div class="message-content"> <div class="message-content">
<!-- 如果未读class用message-title-black --> <!-- 如果未读class用message-title-black -->
<span :class="{ 'message-title': msg.read, 'message-title-black': !msg.read }">{{ msg.subject }}</span> <span :class="{ 'message-title': msg.read, 'message-title-black': !msg.read }">{{ msg.subject
}}</span>
<span class="message-date">{{ msg.sendTimeFormat }}</span> <span class="message-date">{{ msg.sendTimeFormat }}</span>
</div> </div>
</div> </div>
@@ -95,6 +94,7 @@ function toMessage() {
.el-link { .el-link {
margin-right: 8px; margin-right: 8px;
} }
.message-empty { .message-empty {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -103,32 +103,40 @@ function toMessage() {
height: 260px; height: 260px;
line-height: 45px; line-height: 45px;
} }
.message-list { .message-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.message-item { .message-item {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 15px 0; padding: 15px 0;
border-bottom: 1px solid var(--el-border-color-light); border-bottom: 1px solid var(--el-border-color-light);
&:last-child { &:last-child {
border: none; border: none;
} }
.message-icon { .message-icon {
width: 40px; width: 40px;
height: 40px; height: 40px;
margin: 0 20px 0 5px; margin: 0 20px 0 5px;
} }
.message-content { .message-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.message-title { .message-title {
margin-bottom: 2px; margin-bottom: 2px;
} }
.message-title-black { .message-title-black {
margin-bottom: 2px; margin-bottom: 2px;
font-weight: bold; font-weight: bold;
} }
.message-date { .message-date {
font-size: 12px; font-size: 12px;
color: var(--el-text-color-secondary); color: var(--el-text-color-secondary);
@@ -1,14 +1,14 @@
<template> <template>
<el-dialog :model-value="visible" @update:model-value="handleUpdateVisible" title="添加药材" width="600px" destroy-on-close> <el-dialog :model-value="visible" @update:model-value="handleUpdateVisible" title="添加药材" width="600px"
destroy-on-close :close-on-click-modal="false">
<el-form :model="form" label-width="120px" ref="formRef" class="mt-2" :rules="formRules"> <el-form :model="form" label-width="120px" ref="formRef" class="mt-2" :rules="formRules">
<!-- 药材下拉 + 未找到按钮 --> <!-- 药材下拉 + 未找到按钮 -->
<el-form-item v-if="!isAddingNew" label="药材名称" prop="medicinalTypeId" style="display: flex; align-items: center"> <el-form-item v-if="!isAddingNew" label="药材名称" prop="Id" style="display: flex; align-items: center">
<el-select v-model="form.medicinalTypeId" placeholder="请选择药材" filterable style="flex: 1; margin-right: 8px"> <el-select v-model="form.Id" placeholder="请选择药材" filterable style="flex: 1; margin-right: 8px">
<el-option v-for="item in medicinalList" :key="item.id" :label="item.dictLabel" :value="item.id" /> <el-option v-for="item in medicinalList" :key="item.id" :label="item.medicinalName" :value="item.id" />
</el-select> </el-select>
<el-button type="text" @click="handleNotFound">未找到</el-button> <el-button type="text" @click="handleNotFound">未找到</el-button>
</el-form-item> </el-form-item>
<!-- 新药材输入框 --> <!-- 新药材输入框 -->
<el-form-item label="新药材名称" v-if="isAddingNew" prop="medicinalName"> <el-form-item label="新药材名称" v-if="isAddingNew" prop="medicinalName">
<el-input v-model="form.medicinalName" placeholder="请输入新药材名称" /> <el-input v-model="form.medicinalName" placeholder="请输入新药材名称" />
@@ -33,7 +33,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, watch, computed } from "vue"; import { ref, reactive, watch, computed } from "vue";
import { medicinalApi } from "@/api/modules/biz/WeiBingJu/medicinal"; import { medicinalApi } from "@/api/modules/biz/weibingju/medicinal";
import { ElMessage, ElForm, FormRules } from "element-plus"; import { ElMessage, ElForm, FormRules } from "element-plus";
const props = defineProps({ const props = defineProps({
@@ -46,7 +46,7 @@ const isAddingNew = ref(false);
const formRef = ref<InstanceType<typeof ElForm>>(); const formRef = ref<InstanceType<typeof ElForm>>();
const form = reactive({ const form = reactive({
medicinalTypeId: null as number | null, Id: null as number | null,
addWeight: 0, addWeight: 0,
// 前端改为 totalPrice // 前端改为 totalPrice
totalPrice: 0, totalPrice: 0,
@@ -55,7 +55,7 @@ const form = reactive({
// 动态表单校验(注意 totalPrice 字段) // 动态表单校验(注意 totalPrice 字段)
const formRules = computed<FormRules>(() => ({ const formRules = computed<FormRules>(() => ({
medicinalTypeId: [{ required: !isAddingNew.value, message: "请选择药材", trigger: "change" }], Id: [{ required: !isAddingNew.value, message: "请选择药材", trigger: "change" }],
medicinalName: [{ required: isAddingNew.value, message: "请输入新药材名称", trigger: "blur" }], medicinalName: [{ required: isAddingNew.value, message: "请输入新药材名称", trigger: "blur" }],
addWeight: [ addWeight: [
{ required: true, message: "请输入添加量", trigger: "blur" }, { required: true, message: "请输入添加量", trigger: "blur" },
@@ -72,7 +72,6 @@ const fetchMedicinalList = async () => {
const res = await medicinalApi.getDictList(); const res = await medicinalApi.getDictList();
medicinalList.value = res.data || []; medicinalList.value = res.data || [];
} catch (err) { } catch (err) {
ElMessage.error("加载药材列表失败");
medicinalList.value = []; medicinalList.value = [];
} }
}; };
@@ -96,7 +95,7 @@ const handleUpdateVisible = (newVisible: boolean) => {
}; };
const handleCancel = () => emit("update:visible", false); const handleCancel = () => emit("update:visible", false);
const handleResetForm = () => { const handleResetForm = () => {
form.medicinalTypeId = null; form.Id = null;
form.addWeight = 0; form.addWeight = 0;
form.totalPrice = 0; form.totalPrice = 0;
form.medicinalName = ""; form.medicinalName = "";
@@ -106,7 +105,7 @@ const handleResetForm = () => {
const handleNotFound = () => { const handleNotFound = () => {
isAddingNew.value = true; isAddingNew.value = true;
form.medicinalTypeId = null; form.Id = null;
}; };
const handleSubmit = async () => { const handleSubmit = async () => {
@@ -123,15 +122,10 @@ const handleSubmit = async () => {
ElMessage.warning("添加量需大于 0"); ElMessage.warning("添加量需大于 0");
return; return;
} }
// unitPrice = totalPrice / addWeight,保留两位
const unitPrice = parseFloat((form.totalPrice / form.addWeight).toFixed(2));
// 构造后端期望的 payload(注意把 totalPrice 改为 unitPrice)
const payload: any = { const payload: any = {
medicinalTypeId: form.medicinalTypeId, Id: form.Id,
addWeight: form.addWeight, addWeight: form.addWeight,
unitPrice: unitPrice, totalPrice: form.totalPrice,
medicinalName: form.medicinalName medicinalName: form.medicinalName
}; };
@@ -141,7 +135,6 @@ const handleSubmit = async () => {
emit("update:visible", false); emit("update:visible", false);
emit("refreshList"); emit("refreshList");
} catch (err: any) { } catch (err: any) {
ElMessage.error(err.message || "添加失败");
} }
}; };
</script> </script>
@@ -1,17 +1,14 @@
<template> <template>
<el-dialog <el-dialog :model-value="props.visible" :title="`${props.medicinalName} - 添加记录`" width="1200px"
:model-value="props.visible" @close="handleDialogClose" @update:model-value="handleUpdateVisible" append-to-body
:title="`${props.medicinalName} - 添加记录`" :body-class="'el-dialog__body-new'" :close-on-click-modal="false">
width="1200px" <ProTable ref="proTableRef" title="添加记录" :columns="columns" :request-api="getAdditionRecordsApi" :row-key="rowKey"
@close="handleDialogClose" :border="true" :show-selection="false">
@update:model-value="handleUpdateVisible"
append-to-body
>
<ProTable ref="proTableRef" title="添加记录" :columns="columns" :request-api="getAdditionRecordsApi" :row-key="rowKey" :show-selection="false">
<!-- 行内列 slot:addWeight --> <!-- 行内列 slot:addWeight -->
<template #addWeight="{ row }"> <template #addWeight="{ row }">
<template v-if="editingId === row.id"> <template v-if="editingId === row.id">
<el-input-number v-model.number="editModel.addWeight" :min="0.01" :step="0.01" controls-position="right" size="small" /> <el-input-number v-model.number="editModel.addWeight" :min="0.01" :step="0.01" controls-position="right"
size="small" />
</template> </template>
<template v-else> <template v-else>
{{ row.addWeight }} {{ row.addWeight }}
@@ -26,17 +23,11 @@
<!-- totalPrice 列:查看时显示合计,编辑时可以修改 --> <!-- totalPrice 列:查看时显示合计,编辑时可以修改 -->
<template #totalPrice="{ row }"> <template #totalPrice="{ row }">
<template v-if="editingId === row.id"> <template v-if="editingId === row.id">
<el-input-number <el-input-number v-model.number="editModel.totalPrice" :min="0" :step="0.01" controls-position="right"
v-model.number="editModel.totalPrice" size="small" placeholder="输入总价(元)" />
:min="0"
:step="0.01"
controls-position="right"
size="small"
placeholder="输入总价(元)"
/>
</template> </template>
<template v-else> <template v-else>
{{ ((row.addWeight ?? 0) * (row.unitPrice ?? 0)).toFixed(2) }} {{ row.totalPrice }}
</template> </template>
</template> </template>
@@ -52,8 +43,11 @@
<el-button size="small" @click="cancelRowEdit">取消</el-button> <el-button size="small" @click="cancelRowEdit">取消</el-button>
</template> </template>
<template v-else> <template v-else>
<el-button type="text" size="small" @click="startRowEdit(row)"> 编辑 </el-button> <el-button v-if="row.canEdit" type="text" size="small" @click="startRowEdit(row)">编辑</el-button>
<el-button type="text" size="small" text-color="#ff4d4f" @click="handleDeleteRecord(row.id)"> 删除 </el-button> <el-button v-if="row.canEdit" type="text" size="small" style="color: #ff4d4f"
@click="handleDeleteRecord(row.id)">
删除
</el-button>
</template> </template>
</template> </template>
</ProTable> </ProTable>
@@ -64,7 +58,6 @@
/** /**
* 单行行内编辑版本(总价 totalPrice 输入): * 单行行内编辑版本(总价 totalPrice 输入):
* - 点击「编辑」:将该行复制到 editModel(editModel.addWeight & editModel.totalPrice),记录 editingId(只允许单行编辑) * - 点击「编辑」:将该行复制到 editModel(editModel.addWeight & editModel.totalPrice),记录 editingId(只允许单行编辑)
* - 保存:计算 unitPrice = totalPrice / addWeight,发送 unitPrice 到后端
* - 取消:不调用后端,直接清除 editingId(恢复显示) * - 取消:不调用后端,直接清除 editingId(恢复显示)
*/ */
@@ -75,7 +68,7 @@ import { ElMessage, ElMessageBox } from "element-plus";
const props = defineProps({ const props = defineProps({
visible: { type: Boolean, default: false }, visible: { type: Boolean, default: false },
medicinalId: { type: Number, required: true }, id: { type: Number, required: true },
medicinalName: { type: String, default: "药材" } medicinalName: { type: String, default: "药材" }
}); });
const emit = defineEmits(["update:visible", "close", "refreshList"]); const emit = defineEmits(["update:visible", "close", "refreshList"]);
@@ -86,7 +79,7 @@ const proTableRef = ref<any | null>(null);
// 当前正在编辑的行 id(单行) // 当前正在编辑的行 id(单行)
const editingId = ref<number | null>(null); const editingId = ref<number | null>(null);
// 编辑模型(只存一行):注意 totalPrice 而不是 unitPrice // 编辑模型(只存一行):注意 totalPrice
const editModel = reactive({ const editModel = reactive({
addWeight: 0, addWeight: 0,
totalPrice: 0 totalPrice: 0
@@ -111,7 +104,8 @@ const rowKey = (row: any) => row.id;
watch( watch(
() => props.visible, () => props.visible,
async val => { async val => {
if (val && props.medicinalId) { if (val && props.id) {
console.log("打开添加记录弹窗,药材ID=", props.id);
await nextTick(); await nextTick();
proTableRef.value?.refresh?.(); proTableRef.value?.refresh?.();
} }
@@ -121,7 +115,10 @@ watch(
// 将 ProTable 的请求透传到后端(ProTable 会带上 pageNum/pageSize 等) // 将 ProTable 的请求透传到后端(ProTable 会带上 pageNum/pageSize 等)
const getAdditionRecordsApi = (params: any) => { const getAdditionRecordsApi = (params: any) => {
return medicinalApi.getAdditionRecords(props.medicinalId, params); return medicinalApi.getAdditionRecords({
id: props.id,
...params
});
}; };
// 开始编辑:把当前行复制到 editModel(addWeight 和 totalPrice) // 开始编辑:把当前行复制到 editModel(addWeight 和 totalPrice)
@@ -133,7 +130,7 @@ const startRowEdit = (row: any) => {
editingId.value = row.id; editingId.value = row.id;
editModel.addWeight = row.addWeight ?? 0; editModel.addWeight = row.addWeight ?? 0;
// 后端返回的是 unitPrice,前端要显示/编辑 totalPrice = unitPrice * addWeight // 后端返回的是 unitPrice,前端要显示/编辑 totalPrice = unitPrice * addWeight
editModel.totalPrice = Number(((row.unitPrice ?? 0) * (row.addWeight ?? 1)).toFixed(2)); editModel.totalPrice = row.totalPrice;
}; };
// 取消编辑:清空 editingId(恢复显示) // 取消编辑:清空 editingId(恢复显示)
@@ -157,12 +154,11 @@ const saveRowEdit = async (row: any) => {
try { try {
saving.value = true; saving.value = true;
// 计算 unitPrice(保留两位)
const unitPrice = parseFloat((editModel.totalPrice / editModel.addWeight).toFixed(2));
await medicinalApi.updateAdditionRecord(row.id, { await medicinalApi.updateAdditionRecord({
id: row.id,
addWeight: editModel.addWeight, addWeight: editModel.addWeight,
unitPrice: unitPrice totalPrice: editModel.totalPrice
}); });
ElMessage.success("保存成功"); ElMessage.success("保存成功");
@@ -171,7 +167,6 @@ const saveRowEdit = async (row: any) => {
emit("refreshList"); emit("refreshList");
cancelRowEdit(); cancelRowEdit();
} catch (err: any) { } catch (err: any) {
ElMessage.error(err?.message || "保存失败");
} finally { } finally {
saving.value = false; saving.value = false;
} }
@@ -186,12 +181,11 @@ const handleDeleteRecord = async (id: number) => {
}) })
.then(async () => { .then(async () => {
try { try {
await medicinalApi.deleteAdditionRecord(id); await medicinalApi.deleteAdditionRecord({ id: id });
ElMessage.success("删除成功"); ElMessage.success("删除成功");
await proTableRef.value?.refresh?.(); await proTableRef.value?.refresh?.();
emit("refreshList"); emit("refreshList");
} catch (err: any) { } catch (err: any) {
ElMessage.error(err?.message || "删除失败");
} }
}) })
.catch(() => { .catch(() => {
@@ -218,8 +212,8 @@ const handleUpdateVisible = (newVal: boolean) => {
}; };
</script> </script>
<style scoped lang="scss"> <style lang="scss">
.mb-4 { .el-dialog__body-new {
margin-bottom: 16px; height: 550px !important;
} }
</style> </style>
@@ -0,0 +1,120 @@
<template>
<el-dialog :model-value="props.visible" :title="`${props.medicinalName} - 消耗记录`" width="800px"
@close="handleDialogClose" @update:model-value="handleUpdateVisible" append-to-body
:body-class="'el-dialog__body-new'" :close-on-click-modal="false">
<!-- 统一使用ProTable实现列表(带分页),与AdditionRecordDialog风格保持一致 -->
<ProTable ref="proTableRef" title="消耗记录列表" :columns="columns" :request-api="getConsumptionRecordsApi"
:row-key="rowKey" :show-selection="false" :border="true">
<!-- 人员列:包含家属标记的自定义渲染 -->
<template #personName="{ row }">
<span>{{ row.personName }}</span>
<el-tag size="small" type="info" v-if="row.isRelative" style="margin-left: 8px">
家属({{ row.employeeName || "未知员工" }})
</el-tag>
</template>
<!-- 使用时间:格式化渲染 -->
<template #useTime="{ row }">
{{ row.useTime ? formatDate(row.useTime) : "-" }}
</template>
</ProTable>
</el-dialog>
</template>
<script setup lang="ts">
import { defineProps, defineEmits, ref, watch, nextTick } from "vue";
import ProTable from "@/components/ProTable/index.vue";
import { medicinalApi } from "@/api";
import { ElMessage, ElMessageBox } from "element-plus";
/** 1. 定义Props(与父组件传参匹配,参考AdditionRecordDialog) */
const props = defineProps({
visible: {
type: Boolean,
default: false
},
id: { // 对应父组件传的「currentMedicinalId」,与AdditionRecordDialog的id参数统一
type: Number,
required: true
},
medicinalName: {
type: String,
default: "药材"
}
});
/** 2. 定义Emits(与AdditionRecordDialog事件机制统一) */
const emit = defineEmits(["update:visible", "close", "refreshList"]);
/** 3. 状态管理(ProTable引用 + 无编辑状态,仅列表) */
const proTableRef = ref<any | null>(null); // ProTable实例引用,用于刷新列表
/** 4. 列配置(与消耗记录字段匹配,仅保留展示列) */
const columns = [
{
prop: "amountG",
label: "使用量(g)",
minWidth: 100
},
{
prop: "personName",
label: "使用人员",
minWidth: 250,
slotName: "personName" // 关联自定义模板
},
{
prop: "useTime",
label: "使用时间",
minWidth: 150,
slotName: "useTime" // 关联时间格式化模板
}
];
/** 5. 行唯一标识(与后端返回的主键匹配) */
const rowKey = (row: any) => row.id;
/** 6. 接口请求(透传ProTable分页参数,与AdditionRecordDialog逻辑统一) */
const getConsumptionRecordsApi = (params: any) => {
// 调用药材消耗记录接口,携带「药材ID」和「分页参数」
return medicinalApi.getConsumptionRecords({
id: props.id, // 药材ID(必传)
...params // 分页参数(pageNum、pageSize等,从ProTable传入)
});
};
/** 7. 时间格式化工具(与AdditionRecordDialog格式统一) */
const formatDate = (dateStr: string) => {
const d = new Date(String(dateStr).replace(" ", "T"));
return isNaN(d.getTime()) ? dateStr : d.toLocaleString();
};
/** 8. 弹窗显示/隐藏监听(弹窗显示时刷新列表,与AdditionRecordDialog逻辑统一) */
watch(
() => props.visible,
async (isVisible) => {
if (isVisible && props.id) {
await nextTick(); // 等待ProTable渲染完成
proTableRef.value?.refresh?.(); // 刷新列表数据
}
},
{ immediate: true } // 初始加载时触发
);
/** 9. 弹窗关闭逻辑(与AdditionRecordDialog统一,清空状态+通知父组件) */
const handleDialogClose = () => {
emit("update:visible", false); // 通知父组件更新弹窗状态
emit("close"); // 触发关闭事件(可选)
};
/** 10. 弹窗状态更新回调(与Element Plus Dialog事件匹配) */
const handleUpdateVisible = (newVal: boolean) => {
if (!newVal) { // 仅当弹窗关闭时执行清理逻辑
handleDialogClose();
}
};
</script>
<style lang="scss">
.el-dialog__body-new {
height: 550px !important;
}
</style>
@@ -1,6 +1,5 @@
<template> <template>
<div class="table-box"> <div class="table-box">
<el-card class="mt-4">
<ProTable ref="proTable" title="药材列表" :columns="columns" :request-api="medicinalApi.page"> <ProTable ref="proTable" title="药材列表" :columns="columns" :request-api="medicinalApi.page">
<template #tableHeader> <template #tableHeader>
<!-- 新增按钮:保持框架一致的 s-button 写法 --> <!-- 新增按钮:保持框架一致的 s-button 写法 -->
@@ -8,23 +7,20 @@
</template> </template>
<template #operation="scope"> <template #operation="scope">
<s-button link :opt="FormOptEnum.VIEW" size="small" @click="handleViewConsumption(scope.row)"> 查看消耗记录 </s-button> <s-button link :opt="FormOptEnum.VIEW" size="small" @click="handleViewConsumption(scope.row)"> 查看消耗记录
<s-button link :opt="FormOptEnum.VIEW" size="small" class="ml-2" @click="handleViewAddition(scope.row)"> 查看添加记录 </s-button> </s-button>
<s-button link :opt="FormOptEnum.VIEW" size="small" class="ml-2" @click="handleViewAddition(scope.row)">
查看添加记录 </s-button>
</template> </template>
</ProTable> </ProTable>
</el-card>
<!-- 消耗记录弹窗 --> <!-- 消耗记录弹窗 -->
<consumption-record-dialog v-model:visible="showConsumptionDialog" :medicinal-id="currentMedicinalId" :medicinal-name="currentMedicinalName" /> <consumption-record-dialog v-model:visible="showConsumptionDialog" :id="currentMedicinalId"
:medicinal-name="currentMedicinalName" />
<!-- 添加记录弹窗 --> <!-- 添加记录弹窗 -->
<addition-record-dialog <addition-record-dialog :visible="showAdditionDialog" :id="currentMedicinalId"
:visible="showAdditionDialog" :medicinal-name="currentMedicinalName" @update:visible="val => (showAdditionDialog = val)"
:medicinal-id="currentMedicinalId" @refresh-list="handleRefresh" />
:medicinal-name="currentMedicinalName"
@update:visible="val => (showAdditionDialog = val)"
@refresh-list="handleRefresh"
/>
<!-- 新增药材弹窗 --> <!-- 新增药材弹窗 -->
<add-medicinal-dialog v-model:visible="showAddMedicinalDialog" @refresh-list="handleRefresh" /> <add-medicinal-dialog v-model:visible="showAddMedicinalDialog" @refresh-list="handleRefresh" />
@@ -40,7 +36,7 @@ import type { ColumnProps, ProTableInstance } from "@/components/ProTable/interf
import { FormOptEnum } from "@/enums"; import { FormOptEnum } from "@/enums";
// 子组件 // 子组件
//import ConsumptionRecordDialog from "./components/ConsumptionRecordDialog.vue"; import ConsumptionRecordDialog from "./components/ConsumptionRecordDialog.vue";
import AdditionRecordDialog from "./components/AdditionRecordDialog.vue"; import AdditionRecordDialog from "./components/AdditionRecordDialog.vue";
import AddMedicinalDialog from "./components/AddMedicinalDialog.vue"; import AddMedicinalDialog from "./components/AddMedicinalDialog.vue";
@@ -56,9 +52,10 @@ const currentMedicinalName = ref<string>("");
const columns: ColumnProps<MedicinalInfo>[] = [ const columns: ColumnProps<MedicinalInfo>[] = [
{ type: "selection", fixed: "left", width: 80 }, { type: "selection", fixed: "left", width: 80 },
{ prop: "medicinalName", label: "药材名称", search: { el: "input" } }, { prop: "medicinalName", label: "药材名称", search: { el: "input" } },
{ prop: "totalUsedWeight", label: "使用量(g)" },
{ prop: "stock", label: "药材库存(g)" }, { prop: "stock", label: "药材库存(g)" },
{ prop: "usedCost", label: "使用成本" }, { prop: "usedCost", label: "预估使用成本" },
{ prop: "remainingValue", label: "剩余价值" }, { prop: "remainingValue", label: "预估剩余价值" },
{ prop: "operation", label: "操作", width: 240, fixed: "right" } { prop: "operation", label: "操作", width: 240, fixed: "right" }
]; ];
@@ -69,7 +66,7 @@ function onOpenAdd() {
// 查看消耗记录 // 查看消耗记录
function handleViewConsumption(row: MedicinalInfo) { function handleViewConsumption(row: MedicinalInfo) {
currentMedicinalId.value = row.medicinalId ?? (row as any).id ?? null; currentMedicinalId.value = row.Id ?? (row as any).id ?? null;
currentMedicinalName.value = row.medicinalName ?? ""; currentMedicinalName.value = row.medicinalName ?? "";
showConsumptionDialog.value = true; showConsumptionDialog.value = true;
} }
@@ -78,7 +75,7 @@ function handleViewConsumption(row: MedicinalInfo) {
function handleViewAddition(row: MedicinalInfo) { function handleViewAddition(row: MedicinalInfo) {
console.log("查看添加记录", row); console.log("查看添加记录", row);
// 直接设置正确的ID和名称,然后显示弹窗 // 直接设置正确的ID和名称,然后显示弹窗
currentMedicinalId.value = row.medicinalId ?? (row as any).id ?? null; currentMedicinalId.value = row.id ?? (row as any).id ?? null;
currentMedicinalName.value = row.medicinalName ?? ""; currentMedicinalName.value = row.medicinalName ?? "";
showAdditionDialog.value = true; showAdditionDialog.value = true;
} }
@@ -88,15 +85,3 @@ function handleRefresh() {
proTable.value?.refresh?.(); proTable.value?.refresh?.();
} }
</script> </script>
<style scoped lang="scss">
.table-box {
padding-bottom: 24px;
}
.mt-4 {
margin-top: 16px;
}
.ml-2 {
margin-left: 8px;
}
</style>
@@ -0,0 +1,959 @@
<template>
<el-dialog :model-value="visible" :title="dialogTitle" width="1000px" destroy-on-close @close="handleClose"
:body-class="'modal-class'" @update:model-value="handleVisibleChange" :close-on-click-modal="false">
<!-- 表单根节点:查看模式禁用,新增/编辑模式可交互 -->
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px" class="prescription-form"
:disabled="isViewMode">
<!-- 1. 人员信息 -->
<el-card class="form-section" shadow="never">
<template #header>
<div class="section-title">人员信息</div>
</template>
<!-- 人员类型 -->
<el-form-item label="人员类型" prop="personType">
<el-radio-group v-model="formData.personType" @change="handlePersonTypeChange" :disabled="isViewMode">
<el-radio :label="1">公司员工</el-radio>
<el-radio :label="2">员工家属</el-radio>
</el-radio-group>
</el-form-item>
<!-- 公司员工模式 -->
<template v-if="formData.personType === 1">
<!-- 员工姓名 -->
<el-form-item label="员工姓名" prop="employeeInfo">
<div class="flex items-center gap-2">
<el-select v-if="showEmployeeSelect" v-model="formData.employeeId" placeholder="请选择员工" filterable
:loading="employeeLoading" class="flex-1" @change="handleEmployeeChange" @clear="handleEmployeeClear"
:disabled="isViewMode">
<el-option v-for="item in employeeOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<!-- 新增/编辑模式显示"新增员工"按钮 -->
<el-button v-if="!isViewMode" type="primary" size="small" @click="handleOpenAddEmployeeDialog">
新增员工
</el-button>
</div>
<el-input v-if="!showEmployeeSelect && !formData.employeeId" v-model="formData.newEmployeeName"
placeholder="请输入员工姓名" class="mt-2" :disabled="isViewMode" />
</el-form-item>
</template>
<!-- 员工家属模式 -->
<template v-if="formData.personType === 2">
<!-- 关联员工 -->
<el-form-item label="关联员工" prop="relatedEmployeeInfo">
<div class="flex items-center gap-2">
<el-select v-if="showRelatedEmployeeSelect" v-model="formData.relatedEmployeeId" placeholder="请选择关联员工"
filterable :loading="employeeLoading" class="flex-1" @change="handleRelatedEmployeeChange"
@clear="handleRelatedEmployeeClear" :disabled="isViewMode">
<el-option v-for="item in employeeOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<!-- 新增/编辑模式显示"新增关联员工"按钮 -->
<el-button v-if="!isViewMode" type="primary" size="small" @click="handleOpenAddRelatedEmployeeDialog">
新增员工
</el-button>
</div>
<el-input v-if="!showRelatedEmployeeSelect && !formData.relatedEmployeeId"
v-model="formData.newRelatedEmployeeName" placeholder="请输入关联员工姓名" class="mt-2" :disabled="isViewMode" />
</el-form-item>
<!-- 家属姓名 -->
<el-form-item label="家属姓名" prop="relativeInfo">
<div class="flex items-center gap-2">
<el-select v-if="showRelativeSelect && (formData.relatedEmployeeId || formData.newRelatedEmployeeName)"
v-model="formData.relativeId" placeholder="请选择家属" filterable :loading="relativeLoading" class="flex-1"
@change="handleRelativeChange" @clear="handleRelativeClear" :disabled="isViewMode">
<el-option v-for="item in relativeOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-input v-else v-model="formData.newRelativeName" placeholder="请输入家属姓名" class="flex-1"
:disabled="!formData.relatedEmployeeId && !formData.newRelatedEmployeeName || isViewMode" />
<!-- 新增/编辑模式显示"新增家属"按钮 -->
<el-button
v-if="!isViewMode && showRelativeSelect && formData.relatedEmployeeId && !formData.newRelatedEmployeeName"
type="primary" size="small" @click="handleOpenAddRelativeDialog">
新增家属
</el-button>
</div>
</el-form-item>
<!-- 亲属关系 -->
<el-form-item label="亲属关系" prop="relationShip">
<el-select v-model="formData.relationShip" placeholder="请选择亲属关系" :disabled="isViewMode">
<el-option v-for="item in relationTypes" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</template>
<!-- 出生日期、体重、身高 -->
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="出生日期" prop="birthday">
<el-date-picker v-model="formData.birthday" type="date" placeholder="请选择出生日期" format="YYYY-MM-DD"
value-format="YYYY-MM-DD" class="w-full" :disabled="isViewMode" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="当前体重(kg)" prop="weight">
<el-input-number v-model="formData.weight" :min="0" :max="500" :precision="1" placeholder="请输入体重"
class="w-full" :disabled="isViewMode" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="身高(cm)" prop="height">
<el-input-number v-model="formData.height" :min="0" :max="300" :precision="1" placeholder="请输入身高"
class="w-full" :disabled="isViewMode" />
</el-form-item>
</el-col>
</el-row>
</el-card>
<!-- 2. 药材消耗 -->
<el-card class="form-section" shadow="never">
<template #header>
<div class="section-title">
药材消耗
<!-- 新增/编辑模式显示"添加药材"按钮 -->
<el-button v-if="!isViewMode" type="primary" size="small" class="ml-4" @click="addHerb">
添加药材
</el-button>
</div>
</template>
<el-table :data="formData.herbs" border>
<el-table-column label="药材名称--剩余量" width="200">
<template #default="{ row, $index }">
<el-select v-model="row.id" placeholder="请选择药材" filterable :loading="medicinalLoading"
@change="handleMedicinalChange($index)" :disabled="isViewMode">
<el-option v-for="item in medicinalOptions" :key="item.id"
:label="`${item.medicinalName}--${item.stock}g`" :value="item.id" />
</el-select>
</template>
</el-table-column>
<el-table-column label="消耗(g)" width="200">
<template #default="{ row }">
<el-input-number v-model="row.amountG" :min="0" :precision="2" placeholder="消耗量" class="w-full"
:disabled="isViewMode" />
</template>
</el-table-column>
<el-table-column label="备注" min-width="80">
<template #default="{ row }">
<el-input v-model="row.note" placeholder="请输入备注" :disabled="isViewMode" />
</template>
</el-table-column>
<el-table-column label="操作" width="80">
<template #default="{ $index }">
<!-- 新增/编辑模式显示"移除"按钮 -->
<el-button v-if="!isViewMode" type="danger" size="small" @click="removeHerb($index)">
移除
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 3. 诊断信息 -->
<el-card class="form-section" shadow="never">
<template #header>
<div class="section-title">诊断信息</div>
</template>
<el-form-item label="诊断结果" prop="diagnosisResult">
<el-input v-model="formData.diagnosisResult" type="textarea" :rows="3" placeholder="请输入诊断结果"
:disabled="isViewMode" />
</el-form-item>
<el-form-item label="用药建议" prop="userMedicinaAdvice">
<el-input v-model="formData.userMedicinaAdvice" type="textarea" :rows="3" placeholder="请输入用药建议"
:disabled="isViewMode" />
</el-form-item>
</el-card>
<!-- 4. 就诊信息 -->
<el-card class="form-section" shadow="never">
<template #header>
<div class="section-title">就诊信息</div>
</template>
<el-form-item label="就诊时间" prop="visitDate">
<el-date-picker v-model="formData.visitDate" type="datetime" placeholder="请选择就诊时间" format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm:ss" class="w-full" :disabled="isViewMode" />
</el-form-item>
</el-card>
</el-form>
<!-- 底部按钮 -->
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<!-- 新增/编辑模式显示提交按钮 -->
<el-button v-if="!isViewMode" type="primary" :loading="submitLoading" @click="handleSubmit">
{{ isEdit ? '更新' : '保存' }}
</el-button>
</div>
</template>
<!-- 新增员工弹窗(仅在新增/编辑模式显示) -->
<el-dialog v-if="!isViewMode" v-model="showAddEmployeeDialog" title="新增员工" width="400px">
<el-form :model="newEmployeeForm" label-width="80px" ref="newEmployeeFormRef">
<el-form-item label="员工姓名" prop="name">
<el-input v-model="newEmployeeForm.name" placeholder="请输入员工姓名" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showAddEmployeeDialog = false">取消</el-button>
<el-button type="primary" @click="confirmAddEmployee">确定</el-button>
</template>
</el-dialog>
<!-- 新增关联员工弹窗(仅在新增/编辑模式显示) -->
<el-dialog v-if="!isViewMode" v-model="showAddRelatedEmployeeDialog" title="新增关联员工" width="400px">
<el-form :model="newRelatedEmployeeForm" label-width="80px" ref="newRelatedEmployeeFormRef">
<el-form-item label="员工姓名" prop="name">
<el-input v-model="newRelatedEmployeeForm.name" placeholder="请输入员工姓名" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showAddRelatedEmployeeDialog = false">取消</el-button>
<el-button type="primary" @click="confirmAddRelatedEmployee">确定</el-button>
</template>
</el-dialog>
<!-- 新增家属弹窗(仅在新增/编辑模式显示) -->
<el-dialog v-if="!isViewMode" v-model="showAddRelativeDialog" title="新增家属" width="400px">
<el-form :model="newRelativeForm" label-width="80px" ref="newRelativeFormRef">
<el-form-item label="家属姓名" prop="name">
<el-input v-model="newRelativeForm.name" placeholder="请输入家属姓名" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showAddRelativeDialog = false">取消</el-button>
<el-button type="primary" @click="confirmAddRelative">确定</el-button>
</template>
</el-dialog>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { prescriptionApi } from '@/api'
import { medicinalApi } from '@/api/modules/biz/weibingju/medicinal'
// 接口定义
interface Employee {
id: string | number;
name: string;
birthday?: string;
height?: number;
}
interface Relative {
id: string | number;
name: string;
employeeId?: string | number;
birthday?: string;
height?: number;
relationShip?: number;
}
interface Medicinal { id: number | string; medicinalName: string; stock: number }
interface Herb { id?: number | string; medicinalName: string; amountG: number; note: string }
// Props定义:区分新增(无id)、编辑(有id且isEdit=true)、查看(有id且isEdit=false)
interface Props {
visible: boolean;
prescriptionId?: number;
isEdit: boolean
}
const props = withDefaults(defineProps<Props>(), {
visible: false,
prescriptionId: undefined,
isEdit: false
})
const emit = defineEmits<{
"update:visible": [value: boolean];
"refresh-list": [];
}>()
// 核心模式判断:查看模式 = 有处方ID且非编辑
const isViewMode = computed(() => !!props.prescriptionId && !props.isEdit)
// 弹窗标题:根据模式动态显示
const dialogTitle = computed(() => {
if (isViewMode.value) return '查看就诊处方'
return props.isEdit ? '编辑就诊处方' : '新增就诊处方'
})
// 表单引用 & 状态
const formRef = ref<FormInstance>()
const newEmployeeFormRef = ref<FormInstance>()
const newRelatedEmployeeFormRef = ref<FormInstance>()
const newRelativeFormRef = ref<FormInstance>()
const submitLoading = ref(false)
const isEdit = computed(() => props.isEdit)
// 表单数据
const formData = ref({
personType: 1,
employeeId: undefined as string | number | undefined,
newEmployeeName: '',
relatedEmployeeId: undefined as string | number | undefined,
newRelatedEmployeeName: '',
relativeId: undefined as string | number | undefined,
newRelativeName: '',
relationShip: undefined as number | undefined,
birthday: undefined as string | undefined,
weight: undefined as number | undefined,
height: undefined as number | undefined,
herbs: [] as Herb[],
diagnosisResult: '',
userMedicinaAdvice: '',
visitDate: ''
})
// 表单校验规则
const formRules: FormRules = {
personType: [{ required: true, message: '请选择人员类型', trigger: 'change' }],
employeeInfo: [{
required: true,
validator: (rule, value, callback) => {
if (formData.value.personType === 1) {
if (!formData.value.employeeId && !formData.value.newEmployeeName) {
callback(new Error('请选择或输入员工姓名'))
} else {
callback()
}
} else {
callback()
}
},
trigger: ['change', 'blur']
}],
relatedEmployeeInfo: [{
required: true,
validator: (rule, value, callback) => {
if (formData.value.personType === 2) {
if (!formData.value.relatedEmployeeId && !formData.value.newRelatedEmployeeName) {
callback(new Error('请选择或输入关联员工姓名'))
} else {
callback()
}
} else {
callback()
}
},
trigger: ['change', 'blur']
}],
relativeInfo: [{
required: true,
validator: (rule, value, callback) => {
if (formData.value.personType === 2) {
if (!formData.value.relativeId && !formData.value.newRelativeName) {
callback(new Error('请选择或输入家属姓名'))
} else {
callback()
}
} else {
callback()
}
},
trigger: ['change', 'blur']
}],
relationShip: [{
required: formData.value.personType === 2,
message: '请选择亲属关系',
trigger: 'change'
}],
visitDate: [{ required: true, message: '请选择就诊时间', trigger: 'change' }],
diagnosisResult: [{ required: true, message: '请输入诊断结果', trigger: 'blur' }]
}
// 数据源 & 弹窗状态
const employeeOptions = ref<Employee[]>([])
const employeeLoading = ref(false)
const showEmployeeSelect = ref(true)
const showRelatedEmployeeSelect = ref(true)
const relativeOptions = ref<Relative[]>([])
const relativeLoading = ref(false)
const showRelativeSelect = ref(true)
const relationTypes = ref<{ id: number; name: string }[]>([])
const medicinalOptions = ref<Medicinal[]>([])
const medicinalLoading = ref(false)
const showAddEmployeeDialog = ref(false)
const showAddRelatedEmployeeDialog = ref(false)
const showAddRelativeDialog = ref(false)
const newEmployeeForm = ref({ name: '' })
const newRelatedEmployeeForm = ref({ name: '' })
const newRelativeForm = ref({ name: '' })
// 本地临时人员集合
const localCreatedPersons = ref<Array<{
tempId: string
name: string
isEmployee: boolean
employeeTempId?: string
employeeId?: number
relationShip?: number
}>>([])
// 生成临时ID
const genTempId = (prefix = 'tmp') => `${prefix}_${Date.now()}_${Math.floor(Math.random() * 1000)}`
// 检查名称重复
const checkNameDuplicate = (name: string, isEmployee: boolean) => {
const normalizedName = name.trim()
if (isEmployee) {
return employeeOptions.value.some(item =>
(item.name || '').trim() === normalizedName ||
localCreatedPersons.value.some(p => p.isEmployee && p.name.trim() === normalizedName)
)
} else {
return relativeOptions.value.some(item =>
(item.name || '').trim() === normalizedName ||
localCreatedPersons.value.some(p => !p.isEmployee && p.name.trim() === normalizedName)
)
}
}
// 加载数据接口
const fetchEmployees = async (keyword?: string) => {
try {
employeeLoading.value = true
const params: any = { type: 1 }
if (keyword) params.keyword = keyword
const res: any = await prescriptionApi.getPersonList(params)
employeeOptions.value = (res && res.data) ? res.data : []
} catch (err) {
console.error('获取员工列表失败', err)
employeeOptions.value = []
} finally {
employeeLoading.value = false
}
}
const loadRelatives = async (employeeId: number | string) => {
try {
relativeLoading.value = true
if (typeof employeeId === 'string' && employeeId.startsWith('tmp_')) {
relativeOptions.value = []
showRelativeSelect.value = false
ElMessage.info('该员工为本地新增,暂无家属数据,请手动输入家属姓名')
} else {
const res: any = await prescriptionApi.getPersonList({ type: 2, employeeId })
relativeOptions.value = (res && res.data) ? res.data : []
showRelativeSelect.value = relativeOptions.value.length > 0
}
} catch (err) {
console.error('加载家属失败', err)
relativeOptions.value = []
showRelativeSelect.value = false
} finally {
relativeLoading.value = false
}
}
const fetchRelationTypes = async () => {
try {
const res: any = await prescriptionApi.getRelationTypes()
relationTypes.value = (res && res.data) ? res.data : []
} catch (err) {
relationTypes.value = [
{ id: 1, name: '配偶' },
{ id: 2, name: '子女' },
{ id: 3, name: '父母' },
{ id: 4, name: '兄弟姐妹' },
{ id: 5, name: '其他' }
]
console.error('获取关系类型失败', err)
}
}
const loadAllMedicinals = async () => {
try {
medicinalLoading.value = true
const res: any = await medicinalApi.getDictList()
medicinalOptions.value = (res && res.data) ? res.data : []
} catch (err) {
console.error('加载药材失败', err)
medicinalOptions.value = []
} finally {
medicinalLoading.value = false
}
}
// 选择回调
const handleEmployeeChange = (selectedId: string | number | undefined) => {
if (selectedId && !isViewMode.value) { // 仅在非查看模式执行
formData.value.newEmployeeName = ''
showEmployeeSelect.value = true
const selectedEmployee = employeeOptions.value.find(item => item.id === selectedId)
if (selectedEmployee) {
formData.value.birthday = selectedEmployee.birthday
formData.value.height = selectedEmployee.height
}
}
}
const handleEmployeeClear = () => {
if (!isViewMode.value) { // 仅在非查看模式执行
formData.value.newEmployeeName = ''
formData.value.birthday = undefined
formData.value.height = undefined
}
}
const handleRelatedEmployeeChange = (selectedId: string | number | undefined) => {
if (selectedId && !isViewMode.value) { // 仅在非查看模式执行
formData.value.newRelatedEmployeeName = ''
showRelatedEmployeeSelect.value = true
formData.value.relativeId = undefined
formData.value.newRelativeName = ''
loadRelatives(selectedId)
}
}
const handleRelatedEmployeeClear = () => {
if (!isViewMode.value) { // 仅在非查看模式执行
formData.value.newRelatedEmployeeName = ''
formData.value.relativeId = undefined
formData.value.newRelativeName = ''
formData.value.birthday = undefined
formData.value.height = undefined
formData.value.relationShip = undefined
relativeOptions.value = []
}
}
const handleRelativeChange = (selectedId: string | number | undefined) => {
if (selectedId && !isViewMode.value) { // 仅在非查看模式执行
formData.value.newRelativeName = ''
showRelativeSelect.value = true
const selectedRelative = relativeOptions.value.find(item => item.id === selectedId)
if (selectedRelative) {
formData.value.birthday = selectedRelative.birthday
formData.value.height = selectedRelative.height
formData.value.relationShip = selectedRelative.relationShip
}
}
}
const handleRelativeClear = () => {
if (!isViewMode.value) { // 仅在非查看模式执行
formData.value.newRelativeName = ''
formData.value.birthday = undefined
formData.value.height = undefined
formData.value.relationShip = undefined
}
}
const handleMedicinalChange = (index: number) => {
if (!isViewMode.value) { // 仅在非查看模式执行
const selectedMedicinalId = formData.value.herbs[index].id;
if (selectedMedicinalId) {
const sel = medicinalOptions.value.find(m => m.id === selectedMedicinalId);
formData.value.herbs[index].medicinalName = sel ? sel.medicinalName : '';
} else {
formData.value.herbs[index].medicinalName = '';
}
}
};
const handlePersonTypeChange = () => {
if (!isViewMode.value) { // 仅在非查看模式执行
formData.value.employeeId = undefined
formData.value.newEmployeeName = ''
formData.value.relatedEmployeeId = undefined
formData.value.newRelatedEmployeeName = ''
formData.value.relativeId = undefined
formData.value.newRelativeName = ''
formData.value.relationShip = undefined
formData.value.birthday = undefined
formData.value.height = undefined
showEmployeeSelect.value = true
showRelatedEmployeeSelect.value = true
showRelativeSelect.value = true
}
}
const addHerb = () => {
if (!isViewMode.value) { // 仅在非查看模式执行
formData.value.herbs.push({ id: undefined, medicinalName: '', amountG: 0, note: '' })
}
}
const removeHerb = (index: number) => {
if (!isViewMode.value) { // 仅在非查看模式执行
formData.value.herbs.splice(index, 1)
}
}
// 新增员工相关
const handleOpenAddEmployeeDialog = () => {
if (!isViewMode.value) {
formData.value.employeeId = undefined
formData.value.newEmployeeName = ''
formData.value.birthday = undefined
formData.value.height = undefined
newEmployeeForm.value.name = ''
showAddEmployeeDialog.value = true
}
}
const confirmAddEmployee = async () => {
if (!newEmployeeFormRef.value || isViewMode.value) return
try {
await newEmployeeFormRef.value.validate()
const name = newEmployeeForm.value.name.trim()
if (checkNameDuplicate(name, true)) {
ElMessage.warning('该员工姓名已存在,请更换姓名')
return
}
const tempId = genTempId('tmp_emp')
employeeOptions.value.unshift({ id: tempId, name })
localCreatedPersons.value.push({ tempId, name, isEmployee: true })
formData.value.employeeId = tempId
formData.value.newEmployeeName = ''
formData.value.birthday = undefined
formData.value.height = undefined
showEmployeeSelect.value = true
showAddEmployeeDialog.value = false
newEmployeeForm.value.name = ''
ElMessage.success('员工已添加')
} catch (err) { }
}
// 新增关联员工相关
const handleOpenAddRelatedEmployeeDialog = () => {
if (!isViewMode.value) {
formData.value.relatedEmployeeId = undefined
formData.value.newRelatedEmployeeName = ''
formData.value.relativeId = undefined
formData.value.newRelativeName = ''
formData.value.birthday = undefined
formData.value.height = undefined
formData.value.relationShip = undefined
newRelatedEmployeeForm.value.name = ''
showAddRelatedEmployeeDialog.value = true
}
}
const confirmAddRelatedEmployee = async () => {
if (!newRelatedEmployeeFormRef.value || isViewMode.value) return
try {
await newRelatedEmployeeFormRef.value.validate()
const name = newRelatedEmployeeForm.value.name.trim()
if (checkNameDuplicate(name, true)) {
ElMessage.warning('该员工姓名已存在,请更换姓名')
return
}
const tempId = genTempId('tmp_emp')
employeeOptions.value.unshift({ id: tempId, name })
localCreatedPersons.value.push({ tempId, name, isEmployee: true })
formData.value.relatedEmployeeId = tempId
formData.value.newRelatedEmployeeName = ''
formData.value.relativeId = undefined
formData.value.newRelativeName = ''
formData.value.birthday = undefined
formData.value.height = undefined
formData.value.relationShip = undefined
showRelatedEmployeeSelect.value = true
showAddRelatedEmployeeDialog.value = false
newRelatedEmployeeForm.value.name = ''
loadRelatives(tempId)
ElMessage.success('关联员工已添加')
} catch (err) { }
}
// 新增家属相关
const handleOpenAddRelativeDialog = () => {
if (!isViewMode.value) {
formData.value.relativeId = undefined
formData.value.newRelativeName = ''
formData.value.birthday = undefined
formData.value.height = undefined
formData.value.relationShip = undefined
newRelativeForm.value.name = ''
showAddRelativeDialog.value = true
}
}
const confirmAddRelative = async () => {
if (!newRelativeFormRef.value || isViewMode.value) return
try {
await newRelativeFormRef.value.validate()
const name = newRelativeForm.value.name.trim()
const employeeId = formData.value.relatedEmployeeId
if (!employeeId) {
ElMessage.warning('请先选择关联员工')
return
}
if (checkNameDuplicate(name, false)) {
ElMessage.warning('该家属姓名已存在,请更换姓名')
return
}
const tempId = genTempId('tmp_rel')
relativeOptions.value.unshift({ id: tempId, name, employeeId })
localCreatedPersons.value.push({
tempId,
name,
isEmployee: false,
employeeTempId: typeof employeeId === 'string' && employeeId.startsWith('tmp_') ? employeeId : undefined,
employeeId: typeof employeeId === 'number' ? employeeId : undefined,
relationShip: formData.value.relationShip
})
formData.value.relativeId = tempId
formData.value.newRelativeName = ''
formData.value.birthday = undefined
formData.value.height = undefined
formData.value.relationShip = undefined
showRelativeSelect.value = true
showAddRelativeDialog.value = false
newRelativeForm.value.name = ''
ElMessage.success('家属已添加')
} catch (err) { }
}
// 重置/关闭/提交
const handleClose = () => {
emit('update:visible', false)
resetForm()
}
const handleVisibleChange = (value: boolean) => {
emit('update:visible', value)
if (!value) resetForm()
}
const resetForm = () => {
formData.value = {
personType: 1,
employeeId: undefined,
newEmployeeName: '',
relatedEmployeeId: undefined,
newRelatedEmployeeName: '',
relativeId: undefined,
newRelativeName: '',
relationShip: undefined,
birthday: undefined,
weight: undefined,
height: undefined,
herbs: [],
diagnosisResult: '',
userMedicinaAdvice: '',
visitDate: ''
}
localCreatedPersons.value = []
showEmployeeSelect.value = true
showRelatedEmployeeSelect.value = true
showRelativeSelect.value = true
newEmployeeForm.value.name = ''
newRelatedEmployeeForm.value.name = ''
newRelativeForm.value.name = ''
showAddEmployeeDialog.value = false
showAddRelatedEmployeeDialog.value = false
showAddRelativeDialog.value = false
}
const handleSubmit = async () => {
if (!formRef.value || isViewMode.value) return
try {
await formRef.value.validate()
} catch (err) {
return
}
submitLoading.value = true
try {
const personObj: any = {}
const isPatientEmployee = formData.value.personType === 1
personObj.isEmployee = isPatientEmployee
if (isPatientEmployee) {
if (formData.value.employeeId) {
if (typeof formData.value.employeeId === 'string' && String(formData.value.employeeId).startsWith('tmp_')) {
const tmpEmp = localCreatedPersons.value.find(p => p.tempId === formData.value.employeeId && p.isEmployee)
const name = tmpEmp ? tmpEmp.name : undefined
if (name) {
personObj.name = name
personObj.employeeIsTemp = true
personObj.employeeName = name
} else {
personObj.name = employeeOptions.value.find(e => e.id === formData.value.employeeId)?.name
personObj.employeeIsTemp = true
personObj.employeeName = personObj.name
}
} else {
personObj.id = Number(formData.value.employeeId)
personObj.name = employeeOptions.value.find(e => e.id === formData.value.employeeId)?.name
}
} else if (formData.value.newEmployeeName) {
personObj.name = formData.value.newEmployeeName.trim()
personObj.employeeIsTemp = true
personObj.employeeName = personObj.name
}
} else {
if (formData.value.relativeId) {
if (typeof formData.value.relativeId === 'string' && String(formData.value.relativeId).startsWith('tmp_')) {
const tmpRel = localCreatedPersons.value.find(p => p.tempId === formData.value.relativeId && !p.isEmployee)
personObj.name = tmpRel ? tmpRel.name : relativeOptions.value.find(r => r.id === formData.value.relativeId)?.name
personObj.tempId = formData.value.relativeId
} else {
personObj.id = Number(formData.value.relativeId)
personObj.name = relativeOptions.value.find(r => r.id === formData.value.relativeId)?.name
}
} else if (formData.value.newRelativeName) {
personObj.name = formData.value.newRelativeName.trim()
}
if (formData.value.relatedEmployeeId) {
if (typeof formData.value.relatedEmployeeId === 'string' && String(formData.value.relatedEmployeeId).startsWith('tmp_')) {
const tmpEmp = localCreatedPersons.value.find(p => p.tempId === formData.value.relatedEmployeeId && p.isEmployee)
const name = tmpEmp ? tmpEmp.name : employeeOptions.value.find(e => e.id === formData.value.relatedEmployeeId)?.name
if (name) {
personObj.employeeIsTemp = true
personObj.employeeName = name
} else {
personObj.employeeId = formData.value.relatedEmployeeId
}
} else {
personObj.employeeId = Number(formData.value.relatedEmployeeId)
}
} else if (formData.value.newRelatedEmployeeName) {
personObj.employeeIsTemp = true
personObj.employeeName = formData.value.newRelatedEmployeeName.trim()
}
personObj.relationShip = formData.value.relationShip
}
if ((!personObj.name || personObj.name === undefined) && personObj.id && typeof personObj.id === 'string' && String(personObj.id).startsWith('tmp_')) {
const tmp = localCreatedPersons.value.find(p => p.tempId === personObj.id)
if (tmp) personObj.name = tmp.name
}
personObj.birthday = formData.value.birthday
personObj.weight = formData.value.weight
personObj.height = formData.value.height
const herbsPayload = formData.value.herbs.map(h => ({
id: h.id || 0,
amountG: h.amountG,
note: h.note
}))
const payload: any = {
id: props.prescriptionId ?? undefined,
person: personObj,
diagnosisResult: formData.value.diagnosisResult,
userMedicinaAdvice: formData.value.userMedicinaAdvice,
visitDate: formData.value.visitDate,
herbs: herbsPayload
}
const res: any = await prescriptionApi.save(payload)
if (res && (res.code === 0 || res.data !== undefined)) {
ElMessage.success(props.isEdit ? '处方更新成功' : '处方保存成功')
emit('refresh-list')
handleClose()
} else {
ElMessage.success('保存成功')
emit('refresh-list')
handleClose()
}
} catch (err) {
console.error('保存处方失败', err)
} finally {
submitLoading.value = false
}
}
// 监听弹窗显示
watch(() => props.visible, async (newVal) => {
if (newVal) {
resetForm()
await Promise.all([
fetchEmployees(),
fetchRelationTypes(),
loadAllMedicinals()
])
// 编辑/查看模式:加载详情
if ((props.isEdit || isViewMode.value) && props.prescriptionId) {
try {
const res: any = await prescriptionApi.getDetail(props.prescriptionId)
const detail = res?.data ?? null
if (detail) {
const person = detail.person || {}
formData.value.personType = person.isEmployee ? 1 : 2
if (formData.value.personType === 1) {
formData.value.employeeId = person.id ?? undefined
formData.value.birthday = person.birthday ?? undefined
formData.value.height = person.height ?? undefined
} else {
formData.value.relatedEmployeeId = person.employeeId ?? undefined
formData.value.relativeId = person.id ?? undefined
formData.value.birthday = person.birthday ?? undefined
formData.value.height = person.height ?? undefined
formData.value.relationShip = person.relationShip ?? undefined
if (formData.value.relatedEmployeeId) {
loadRelatives(formData.value.relatedEmployeeId)
}
}
formData.value.weight = person.weight ?? undefined
formData.value.diagnosisResult = detail.diagnosisResult ?? ''
formData.value.userMedicinaAdvice = detail.userMedicinaAdvice ?? ''
formData.value.visitDate = detail.visitDate ?? ''
formData.value.herbs = (detail.herbs || []).map((h: any) => ({
id: h.medicinalId,
medicinalName: h.medicinalName,
amountG: h.amountG,
note: h.note || ''
}))
}
} catch (err) {
}
}
}
})
</script>
<style lang="scss">
.modal-class {
max-height: 60vh !important;
overflow-y: auto !important;
}
</style>
<style scoped lang="scss">
.prescription-form {
.form-section {
margin-bottom: 20px;
.section-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
}
}
.dialog-footer {
text-align: right;
}
.flex {
display: flex;
}
.items-center {
align-items: center;
}
.gap-2 {
gap: 8px;
}
.flex-1 {
flex: 1;
}
.ml-4 {
margin-left: 16px;
}
.w-full {
width: 100%;
}
.mt-2 {
margin-top: 8px;
}
</style>
@@ -0,0 +1,375 @@
<template>
<el-dialog v-model="visibleRef" title="处方报告" width="1500px" :body-class="'report-modal-body'" :show-close="false"
:top="'0'" append-to-body>
<!-- 报告核心内容 -->
<div class="report-content">
<!-- 3.1 就诊人基本信息 -->
<div class="report-section">
<h3 class="section-heading">一、就诊人基本信息</h3>
<div class="info-grid">
<div class="info-item">
<span class="info-label">就诊人姓名:</span>
<span class="info-value">{{ reportData.patientName || "暂无数据" }}</span>
</div>
<div class="info-item">
<span class="info-label">出生日期:</span>
<span class="info-value">{{ reportData.birthday || "暂无数据" }}</span>
</div>
<div class="info-item">
<span class="info-label">身高:</span>
<span class="info-value">{{ reportData.height ? `${reportData.height} cm` : "暂无数据" }}</span>
</div>
<div class="info-item">
<span class="info-label">体重:</span>
<span class="info-value">{{ reportData.weight ? `${reportData.weight} kg` : "暂无数据" }}</span>
</div>
<div class="info-item">
<span class="info-label">就诊时间:</span>
<span class="info-value">{{ reportData.visitDate || "暂无数据" }}</span>
</div>
</div>
</div>
<!-- 3.2 药材消耗明细(超紧凑卡片布局) -->
<div class="report-section mt-16">
<h3 class="section-heading">二、药材消耗明细</h3>
<div class="herbs-grid-container">
<!-- 单个药材卡片(更小尺寸) -->
<div class="herb-card" v-for="(herb, index) in reportData.herbs" :key="index">
<div class="herb-name">
<span class="herb-label">名称:</span>
{{ herb.medicinalName || "未命名药材" }}
</div>
<div class="herb-amount">
<span class="herb-label">用量:</span>
{{ herb.amountG !== undefined ? `${herb.amountG} g` : "0 g" }}
</div>
</div>
<!-- 无药材空状态 -->
<div v-if="reportData.herbs.length === 0" class="empty-herbs">
<el-icon size="24" color="#9ca3af" class="empty-icon">
<DocumentEmpty />
</el-icon>
<p class="empty-text">当前处方暂无药材消耗记录</p>
</div>
</div>
</div>
<!-- 3.3 诊断与用药建议 -->
<div class="report-section mt-16">
<h3 class="section-heading">三、诊断与用药建议</h3>
<div class="advice-container">
<div class="advice-item">
<span class="advice-label">诊断结果:</span>
<div class="advice-content">
{{ reportData.diagnosisResult || "暂无诊断结果描述" }}
</div>
</div>
<div class="advice-item mt-8">
<span class="advice-label">用药建议:</span>
<div class="advice-content">
{{ reportData.userMedicinaAdvice || "暂无用药建议描述" }}
</div>
</div>
</div>
</div>
</div>
<template #footer>
<el-button @click="handleClose">关闭报告</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
import { ElMessage } from "element-plus";
import { prescriptionApi } from "@/api";
// Props定义
interface Props {
modelValue: boolean;
prescriptionId?: number;
}
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
prescriptionId: undefined,
});
// 弹窗显隐控制
const visibleRef = ref(props.modelValue);
const emit = defineEmits<{
"update:modelValue": [value: boolean];
}>();
// 监听父组件传值,同步弹窗状态
watch(
() => props.modelValue,
(newVal) => {
visibleRef.value = newVal;
},
{ immediate: true }
);
// 数据状态
const loading = ref(false);
const error = ref(false);
const errorMsg = ref("");
const reportData = ref({
patientName: "",
birthday: "",
height: undefined,
weight: undefined,
visitDate: "",
herbs: [] as Array<{ medicinalName: string; amountG: number; note: string }>,
diagnosisResult: "",
userMedicinaAdvice: "",
});
// 加载报告数据
const fetchReportData = async () => {
if (!props.prescriptionId) return;
try {
loading.value = true;
error.value = false;
const res: any = await prescriptionApi.getDetail(props.prescriptionId);
const detail = res?.data || {};
const person = detail.person || {};
reportData.value = {
patientName: person.name || "",
birthday: person.birthday || "",
height: person.height,
weight: person.weight,
visitDate: detail.visitDate || "",
herbs: (detail.herbs || []).map((h: any) => ({
medicinalName: h.medicinalName || "未命名药材",
amountG: h.amountG || 0,
note: h.note || "",
})),
diagnosisResult: detail.diagnosisResult || "",
userMedicinaAdvice: detail.userMedicinaAdvice || "",
};
} catch (err: any) {
error.value = true;
errorMsg.value = err.message || "加载报告失败,请检查网络";
ElMessage.error(errorMsg.value);
} finally {
loading.value = false;
}
};
// 关闭弹窗
const handleClose = () => {
emit("update:modelValue", false);
};
// 弹窗关闭前重置数据
const handleBeforeClose = () => {
reportData.value = {
patientName: "",
birthday: "",
height: undefined,
weight: undefined,
visitDate: "",
herbs: [],
diagnosisResult: "",
userMedicinaAdvice: "",
};
error.value = false;
errorMsg.value = "";
};
// 监听处方ID变化,重新加载数据
watch(
() => props.prescriptionId,
(newId) => {
if (props.modelValue && newId) {
fetchReportData();
}
}
);
</script>
<style lang="scss">
.report-modal-body {
// padding: 20px !important;
font-size: 14px;
color: #303133;
background: #fff;
overflow-y: auto !important;
max-height: 100vh !important;
}
</style>
<style lang="scss" scoped>
/* 报告通用样式 */
.report-content {
width: 100%;
}
.report-section {
padding: 15px;
border: 1px solid #e6e6e6;
border-radius: 8px;
margin-bottom: 16px;
background: #fff;
}
.section-heading {
font-size: 16px;
font-weight: 700;
color: #1f2937;
margin: 0 0 12px 0;
padding-bottom: 8px;
border-bottom: 2px solid #409eff;
}
/* 就诊人基本信息样式 */
.info-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.info-item {
display: flex;
align-items: center;
line-height: 24px;
}
.info-label {
font-weight: 600;
color: #4b5563;
min-width: 100px;
}
.info-value {
flex: 1;
word-break: break-all;
color: #303133;
}
/* 药材消耗明细样式(超紧凑设计) */
.herbs-grid-container {
// display: grid;
// /* 核心:每列最小宽度缩小到150px,可根据需要进一步减小到120px */
// grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
// gap: 8px;
/* 卡片间距缩小 */
padding: 8px 0;
display: flex;
flex-wrap: wrap;
gap: 15px;
}
/* 单个药材卡片(更小尺寸) */
.herb-card {
padding: 8px;
/* 内边距大幅缩小 */
border: 1px solid #e6e6e6;
border-radius: 4px;
background: #f9fafb;
line-height: 1.3;
/* 行高压缩 */
font-size: 12px;
/* 字体缩小 */
min-width: 70px;
}
/* 卡片内标签样式 */
.herb-label {
font-weight: 600;
color: #4b5563;
font-size: 13px;
/* 标签字体更小 */
}
/* 药材空状态样式 */
.empty-herbs {
grid-column: 1 / -1;
text-align: center;
padding: 30px 0;
color: #9ca3af;
background: #f9fafb;
border-radius: 4px;
font-size: 14px;
}
.empty-icon {
margin-bottom: 8px;
}
/* 诊断与用药建议样式 */
.advice-container {
padding: 8px 0;
}
.advice-item {
display: flex;
flex-direction: column;
}
.advice-label {
font-weight: 600;
color: #4b5563;
margin-bottom: 4px;
}
.advice-content {
padding: 8px 12px;
border: 1px solid #e6e6e6;
border-radius: 4px;
min-height: 60px;
line-height: 1.5;
background: #f9fafb;
word-break: break-all;
color: #303133;
}
/* 间距工具类 */
.mt-8 {
margin-top: 8px;
}
.mt-16 {
margin-top: 16px;
}
/* 加载/错误样式 */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 300px;
}
.loading-text {
margin-top: 16px;
color: #606266;
}
.error-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 300px;
color: #f56c6c;
}
.error-icon {
margin-bottom: 12px;
}
.error-text {
margin-bottom: 16px;
font-size: 14px;
}
.retry-btn {
min-width: 100px;
}
</style>
@@ -0,0 +1,122 @@
<template>
<div class="table-box">
<!-- 原有:处方列表(仅操作列新增“查看报告”按钮) -->
<ProTable ref="proTable" title="处方列表" :columns="columns" :request-api="getPrescriptionListApi" :row-key="rowKey">
<!-- 原有:表格头部按钮(不变) -->
<template #tableHeader>
<s-button suffix="处方" type="success" @click="handleAddPrescription" />
</template>
<!-- 原有:就诊人类型自定义插槽(不变) -->
<template #personType="scope">
<el-space>
<span v-if="scope.row.personType === 1">公司员工</span>
<span v-else-if="scope.row.personType === 2">
员工家属,所属员工:{{ scope.row.employeeName || "未知" }}
</span>
<span v-else>未知类型</span>
</el-space>
</template>
<!-- 关键改动:操作列新增“查看报告”按钮 -->
<template #operation="{ row }">
<s-button link :opt="FormOptEnum.VIEW" size="small" @click="handleViewDetail(row.id)">
查看详情
</s-button>
<s-button v-if="row.canEdit" link :opt="FormOptEnum.EDIT" size="small" class="ml-2" @click="handleEdit(row.id)">
编辑
</s-button>
<!-- 新增:触发报告弹窗 -->
<s-button link :opt="FormOptEnum.VIEW" size="small" class="ml-2" @click="handleOpenReport(row.id)">
查看报告
</s-button>
</template>
</ProTable>
<!-- 原有:处方详情/编辑弹窗(不变) -->
<PrescriptionDetailDialog v-model:visible="showDetailDialog" :prescription-id="currentPrescriptionId"
:is-edit="isEdit" @refresh-list="handleRefresh" />
<!-- 关键改动:引入独立的报告弹窗组件 -->
<PrescriptionReportDialog v-model="showReportDialog" :prescription-id="currentReportId" />
</div>
</template>
<script setup lang="ts" name="PrescriptionModule">
import { ref } from "vue";
import ProTable from "@/components/ProTable/index.vue";
import type { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
import { prescriptionApi } from "@/api";
// 原有:引入详情弹窗组件(不变)
import PrescriptionDetailDialog from "./components/PrescriptionDetailDialog.vue";
// 关键改动:引入独立的报告弹窗组件
import PrescriptionReportDialog from "./components/PrescriptionReportDialog.vue";
import { FormOptEnum } from "@/enums";
// 原有:表格ref、详情弹窗状态(不变)
const proTable = ref<ProTableInstance | null>(null);
const showDetailDialog = ref(false);
const currentPrescriptionId = ref<number | undefined>(undefined);
const isEdit = ref(false);
// 关键改动:新增报告弹窗相关状态
const showReportDialog = ref(false); // 报告弹窗显隐
const currentReportId = ref<number | undefined>(undefined); // 当前报告对应的处方ID
// 原有:就诊人类型选项、表格列配置(不变)
const personTypeOptions = [
{ label: "公司员工", value: 1 },
{ label: "员工家属", value: 2 }
];
const columns: ColumnProps<any>[] = [
{ type: "selection", fixed: "left", width: 80 },
{ prop: "personName", label: "就诊人", search: { el: "input" } },
{
prop: "personType",
label: "就诊人类型",
sortable: false,
enum: personTypeOptions,
search: { el: "select" }
},
{ prop: "visitDate", label: "就诊时间" },
{ prop: "totalMedicineCost", label: "预估处方药材成本(元)" },
{ prop: "operation", label: "操作", width: 280, fixed: "right" }
];
const rowKey = "id";
const getPrescriptionListApi = (params: any) => {
return prescriptionApi.getList(params);
};
// 原有:新增、查看详情、编辑、刷新逻辑(不变)
const handleAddPrescription = () => {
currentPrescriptionId.value = undefined;
isEdit.value = false;
showDetailDialog.value = true;
};
const handleViewDetail = (id: number) => {
currentPrescriptionId.value = id;
isEdit.value = false;
showDetailDialog.value = true;
};
const handleEdit = (id: number) => {
currentPrescriptionId.value = id;
isEdit.value = true;
showDetailDialog.value = true;
};
const handleRefresh = () => {
proTable.value?.refresh?.();
};
// 关键改动:新增“打开报告弹窗”方法
const handleOpenReport = (id: number) => {
currentReportId.value = id; // 传递当前处方ID给报告组件
showReportDialog.value = true; // 显示报告弹窗
};
</script>
<style lang="scss" scoped>
/* 原有样式(不变):确保操作列按钮不换行 */
.table-box {
padding: 16px;
}
</style>
+6 -5
View File
@@ -19,17 +19,17 @@
<el-tab-pane label="账号登录" name="pwd"> <el-tab-pane label="账号登录" name="pwd">
<LoginForm :tenant-option="props.sysTenantOption" :tenant-options="props.tenantOptions" /> <LoginForm :tenant-option="props.sysTenantOption" :tenant-options="props.tenantOptions" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="手机号登录" name="phone"> <!-- <el-tab-pane label="手机号登录" name="phone">
<LoginPhoneForm :tenant-option="props.sysTenantOption" :tenant-options="props.tenantOptions" /> <LoginPhoneForm :tenant-option="props.sysTenantOption" :tenant-options="props.tenantOptions" />
</el-tab-pane> </el-tab-pane> -->
</el-tabs> </el-tabs>
<div class="fixed-corner"> <div class="fixed-corner">
<span>{{ props.sysVersion }}</span> <!-- <span>{{ props.sysVersion }}</span> -->
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<Footer /> <!-- <Footer /> -->
<choose-module v-if="auth.showChooseModule" :show-modal="true" /> <choose-module v-if="auth.showChooseModule" :show-modal="true" />
</template> </template>
@@ -62,7 +62,7 @@ interface LoginProps {
//默认值 //默认值
const props = reactive<LoginProps>({ const props = reactive<LoginProps>({
sysName: "SimpleAdmin", sysName: "未病局管理系统",
sysVersion: "", sysVersion: "",
sysLogo: "", sysLogo: "",
sysTenantOption: TenantEnum.CLOSE, sysTenantOption: TenantEnum.CLOSE,
@@ -107,6 +107,7 @@ function updateSysConfig(props: LoginProps, config: ReturnType<typeof useConfigS
<style scoped lang="scss"> <style scoped lang="scss">
@use "./index"; @use "./index";
.demo-tabs>.el-tabs__content { .demo-tabs>.el-tabs__content {
padding: 32px; padding: 32px;
font-size: 32px; font-size: 32px;
@@ -1,85 +0,0 @@
<template>
<!-- 关键修改:v-model → :visible + @update:visible -->
<el-dialog :visible="visible" @update:visible="handleUpdateVisible" :title="`${medicinalName} - 消耗记录`" width="800px">
<el-table v-loading="loading" :data="consumptionRecords" border style="width: 100%" empty-text="暂无消耗记录">
<el-table-column prop="amountG" label="使用量(g)" width="120"></el-table-column>
<el-table-column label="使用人员" width="200">
<template #default="scope">
<span>{{ scope.row.personName }}</span>
<el-tag size="small" type="info" v-if="scope.row.isRelative" style="margin-left: 8px">
家属({{ scope.row.employeeName || "未知员工" }})
</el-tag>
</template>
</el-table-column>
<el-table-column prop="useTime" label="使用时间" width="200">
<template #default="scope">
{{ scope.row.useTime ? new Date(scope.row.useTime).toLocaleString() : "-" }}
</template>
</el-table-column>
</el-table>
</el-dialog>
</template>
<script setup lang="ts">
import { defineProps, defineEmits, ref, watch } from "vue";
import { getConsumptionRecords, Medicinal } from "@/api/modules/biz/WeiBingJu/medicinal";
import { ElMessage } from "element-plus";
// 1. 接收父组件prop
const props = defineProps({
visible: {
type: Boolean,
default: false
},
medicinalId: {
type: Number,
required: true
},
medicinalName: {
type: String,
default: "药材"
}
});
// 2. 定义发射事件:update:visible + 关闭通知
const emit = defineEmits(["update:visible", "close"]);
// 状态数据
const loading = ref(false);
const consumptionRecords = ref<Medicinal.ConsumptionRecord[]>([]);
// 3. 监听弹窗状态变化
const handleUpdateVisible = (newVisible: boolean) => {
emit("update:visible", newVisible);
// 弹窗关闭时清空数据
if (!newVisible) {
consumptionRecords.value = [];
emit("close");
}
};
// 监听visible变化:弹窗显示时加载消耗记录
watch(
() => props.visible,
async newVal => {
if (newVal && props.medicinalId) {
await fetchConsumptionRecords();
}
},
{ immediate: true }
);
// 加载消耗记录
const fetchConsumptionRecords = async () => {
loading.value = true;
try {
const res = await getConsumptionRecords({ medicinalId: props.medicinalId });
consumptionRecords.value = res;
} catch (err: any) {
ElMessage.error(err.message || "加载消耗记录失败");
consumptionRecords.value = [];
} finally {
loading.value = false;
}
};
</script>
@@ -1,714 +0,0 @@
<template>
<el-dialog :model-value="visible" @update:model-value="handleUpdateVisible" :title="dialogTitle" width="900px" destroy-on-close>
<!-- 人员信息 -->
<el-card shadow="hover" class="mb-4">
<template #header><span>人员信息</span></template>
<el-form :model="personForm" label-width="120px" :disabled="!isEdit" ref="personFormRef" :rules="personFormRules">
<!-- 1. 人员类型 -->
<el-form-item label="人员类型" required prop="isEmployee">
<el-radio-group v-model="personForm.isEmployee" @change="handlePersonTypeChange">
<el-radio :label="true">公司员工</el-radio>
<el-radio :label="false">员工家属</el-radio>
</el-radio-group>
</el-form-item>
<!-- 2. 员工场景:员工选择/新增 -->
<template v-if="personForm.isEmployee">
<!-- 员工下拉选择(默认显示) -->
<el-form-item v-if="showEmployeeSelect" label="员工姓名" required prop="employeeId">
<el-select
v-model="personForm.employeeId"
placeholder="搜索或选择员工"
filterable
remote
:remote-method="handleEmployeeSearch"
@change="handleEmployeeChange"
clearable
>
<!-- 正常员工选项 -->
<el-option v-for="emp in employeeOptions" :key="emp.id" :label="emp.name" :value="emp.id" />
<!-- 未找到选项(仅搜索后无结果时显示) -->
<el-option v-if="employeeOptions.length === 0 && employeeKeyword" :key="0" :label="`未找到,新增员工:${employeeKeyword}`" :value="0" />
</el-select>
</el-form-item>
<!-- 员工新增文本框(点击"未找到"后显示) -->
<el-form-item v-if="!showEmployeeSelect" label="员工姓名" required prop="newEmployeeName">
<el-input
v-model="personForm.newEmployeeName"
placeholder="输入新员工姓名"
@blur="() => (personForm.name = personForm.newEmployeeName)"
/>
</el-form-item>
</template>
<!-- 3. 家属场景:关联员工选择 + 家属选择/新增 -->
<template v-else>
<!-- 3.1 关联员工选择(第一步) -->
<el-form-item v-if="showRelatedEmployeeSelect" label="关联员工" required prop="relatedEmployeeId">
<el-select
v-model="personForm.relatedEmployeeId"
placeholder="搜索或选择关联员工"
filterable
remote
:remote-method="handleRelatedEmployeeSearch"
@change="handleRelatedEmployeeChange"
clearable
>
<!-- 正常关联员工选项 -->
<el-option v-for="emp in relatedEmployeeOptions" :key="emp.id" :label="emp.name" :value="emp.id" />
<!-- 未找到选项 -->
<el-option
v-if="relatedEmployeeOptions.length === 0 && relatedEmployeeKeyword"
:key="0"
:label="`未找到,新增关联员工:${relatedEmployeeKeyword}`"
:value="0"
/>
</el-select>
</el-form-item>
<!-- 3.2 关联员工新增(点击"未找到"后显示) -->
<el-form-item v-if="!showRelatedEmployeeSelect" label="关联员工姓名" required prop="newRelatedEmployeeName">
<el-input v-model="personForm.newRelatedEmployeeName" placeholder="输入新关联员工姓名" />
</el-form-item>
<!-- 3.3 家属选择(选择关联员工后显示) -->
<el-form-item v-if="showRelativeSelect && personForm.relatedEmployeeId" label="家属姓名" required prop="relativeId">
<el-select
v-model="personForm.relativeId"
placeholder="搜索或选择家属"
filterable
remote
:remote-method="handleRelativeSearch"
@change="handleRelativeChange"
clearable
>
<!-- 正常家属选项 -->
<el-option v-for="rel in relativeOptions" :key="rel.id" :label="rel.name" :value="rel.id" />
<!-- 未找到选项 -->
<el-option v-if="relativeOptions.length === 0 && relativeKeyword" :key="0" :label="`未找到,新增家属:${relativeKeyword}`" :value="0" />
</el-select>
</el-form-item>
<!-- 3.4 家属新增(点击"未找到"后显示 / 关联员工新增时同步显示) -->
<el-form-item v-if="!showRelativeSelect || !showRelatedEmployeeSelect" label="家属姓名" required prop="newRelativeName">
<el-input
v-model="personForm.newRelativeName"
placeholder="输入新家属姓名"
@blur="() => (personForm.name = personForm.newRelativeName)"
/>
</el-form-item>
<!-- 3.5 亲属关系(家属场景必选) -->
<el-form-item label="亲属关系" required prop="relationShip" v-if="personForm.relatedEmployeeId || !showRelatedEmployeeSelect">
<el-select v-model="personForm.relationShip" clearable>
<el-option v-for="item in relationTypes" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</template>
<!-- 4. 公共信息:年龄/体重/身高(复用原有逻辑) -->
<el-form-item label="年龄" prop="age">
<el-input v-model.number="personForm.age" placeholder="输入年龄" />
</el-form-item>
<el-form-item label="体重(kg)" prop="weight">
<el-input v-model.number="personForm.weight" placeholder="输入体重" />
</el-form-item>
<el-form-item label="身高(cm)" prop="height">
<el-input v-model.number="personForm.height" placeholder="输入身高" />
</el-form-item>
</el-form>
</el-card>
<!-- 药材消耗、诊断建议等原有模块(保持不变) -->
<el-card shadow="hover" class="mb-4">
<template #header>
<div class="flex justify-between items-center">
<span>药材消耗</span>
<el-button v-if="isEdit" type="primary" size="small" @click="handleAddHerb"> 添加药材 </el-button>
</div>
</template>
<el-table :data="herbList" border :disabled="!isEdit" empty-text="暂无药材消耗记录">
<el-table-column label="药材名称" required>
<template #default="scope">
<el-select
v-model="scope.row.medicinalId"
placeholder="搜索或选择药材"
filterable
remote
:remote-method="kw => handleHerbSearch(kw, scope.$index)"
@change="val => handleHerbChange(val, scope.$index)"
:disabled="!isEdit"
clearable
>
<el-option v-for="item in scope.row.medicinalOptions" :key="item.id" :label="item.name" :value="item.id" />
<el-option
v-if="scope.row.medicinalOptions.length === 0 && scope.row.keyword"
:key="0"
:label="`新增药材:${scope.row.keyword}`"
:value="0"
/>
</el-select>
<el-input v-if="scope.row.showNewInput" v-model="scope.row.newName" placeholder="输入新药材名称" class="mt-2" :disabled="!isEdit" />
</template>
</el-table-column>
<el-table-column label="消耗(g)" required>
<template #default="scope">
<el-input v-model.number="scope.row.amountG" type="number" min="0.1" placeholder="输入消耗克数" :disabled="!isEdit" />
</template>
</el-table-column>
<el-table-column label="备注">
<template #default="scope">
<el-input v-model="scope.row.note" placeholder="输入备注" :disabled="!isEdit" />
</template>
</el-table-column>
<el-table-column label="操作" v-if="isEdit">
<template #default="scope">
<el-button type="text" text-color="#ff4d4f" @click="handleDeleteHerb(scope.$index)"> 删除 </el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<div class="flex gap-4 mb-4">
<el-card shadow="hover" class="flex-1">
<template #header><span>诊断结果</span></template>
<el-input v-model="prescriptionForm.diagnosisResult" type="textarea" rows="5" placeholder="输入诊断结果" :disabled="!isEdit" />
</el-card>
<el-card shadow="hover" class="flex-1">
<template #header><span>用药建议</span></template>
<el-input v-model="prescriptionForm.userMedicinaAdvice" type="textarea" rows="5" placeholder="输入用药建议" :disabled="!isEdit" />
</el-card>
</div>
<el-card shadow="hover" class="mb-4">
<el-form-item label="就诊时间" required>
<el-date-picker
v-model="prescriptionForm.visitDate"
type="datetime"
placeholder="选择就诊时间"
value-format="YYYY-MM-DD HH:mm:ss"
:disabled="!isEdit"
/>
</el-form-item>
</el-card>
<template #footer>
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" @click="handleSave" v-if="isEdit">
{{ prescriptionId ? "保存修改" : "新增处方" }}
</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, watch, computed, nextTick } from "vue";
import { ElMessage, ElMessageBox, ElForm, FormItemRule } from "element-plus";
import { prescriptionApi } from "@/api/modules/biz/WeiBingJu/prescription";
// 1. props/emit定义
const props = defineProps({
visible: { type: Boolean, default: false },
prescriptionId: { type: Number, default: 0 },
isEdit: { type: Boolean, default: false }
});
const emit = defineEmits(["update:visible", "close", "refreshList"]);
// 2. 基础变量
const dialogTitle = computed(() => {
if (props.prescriptionId === 0) return "新增就诊处方";
return props.isEdit ? "编辑处方" : "处方详情";
});
const loading = ref(false);
const personFormRef = ref<InstanceType<typeof ElForm>>();
const relationTypes = ref<any[]>([]); // 亲属关系列表
const herbList = ref<any[]>([]); // 药材列表(原有逻辑)
// 3. 处方表单(原有逻辑)
const prescriptionForm = reactive({
diagnosisResult: "",
userMedicinaAdvice: "",
visitDate: new Date().toISOString().slice(0, 16) // 默认当前时间
});
// 4. 人员表单(核心重构:区分员工/家属场景)
const personForm = reactive({
// 基础字段
id: undefined, // 人员ID(编辑时用)
name: "", // 人员姓名(最终提交用)
isEmployee: true, // 是否员工(true=员工,false=家属)
// 员工场景字段
employeeId: undefined, // 已选员工ID
newEmployeeName: "", // 新增员工姓名
// 家属场景字段
relatedEmployeeId: undefined, // 关联员工ID
newRelatedEmployeeName: "", // 新增关联员工姓名
relativeId: undefined, // 已选家属ID
newRelativeName: "", // 新增家属姓名
relationShip: undefined, // 亲属关系ID
// 公共字段
age: undefined,
weight: undefined,
height: undefined
});
// 5. 下拉/新增状态控制(核心:控制显示隐藏)
const state = reactive({
// 员工场景
showEmployeeSelect: true, // 是否显示员工下拉(默认显示)
employeeOptions: [] as any[], // 员工下拉选项
employeeKeyword: "", // 员工搜索关键词
// 家属场景
showRelatedEmployeeSelect: true, // 是否显示关联员工下拉(默认显示)
relatedEmployeeOptions: [] as any[], // 关联员工下拉选项
relatedEmployeeKeyword: "", // 关联员工搜索关键词
showRelativeSelect: true, // 是否显示家属下拉(默认显示)
relativeOptions: [] as any[], // 家属下拉选项
relativeKeyword: "" // 家属搜索关键词
});
// 6. 表单验证规则
const personFormRules = reactive<Record<string, FormItemRule[]>>({
isEmployee: [{ required: true, message: "请选择人员类型", trigger: "change" }],
employeeId: [{ required: true, message: "请选择员工", trigger: "change" }],
newEmployeeName: [{ required: true, message: "请输入员工姓名", trigger: "blur" }],
relatedEmployeeId: [{ required: true, message: "请选择关联员工", trigger: "change" }],
newRelatedEmployeeName: [{ required: true, message: "请输入关联员工姓名", trigger: "blur" }],
relativeId: [{ required: true, message: "请选择家属", trigger: "change" }],
newRelativeName: [{ required: true, message: "请输入家属姓名", trigger: "blur" }],
relationShip: [{ required: true, message: "请选择亲属关系", trigger: "change" }],
age: [{ type: "number", message: "请输入合法年龄", trigger: "blur" }],
weight: [{ type: "number", message: "请输入合法体重", trigger: "blur" }],
height: [{ type: "number", message: "请输入合法身高", trigger: "blur" }]
});
// 7. 初始化逻辑(监听弹窗显示/隐藏)
watch(
() => props.visible,
async val => {
if (val) {
await fetchRelationTypes(); // 加载亲属关系
if (props.prescriptionId) {
await fetchPrescriptionDetail(); // 编辑/详情:加载已有数据
} else {
resetForm(); // 新增:重置表单
}
} else {
resetForm(); // 弹窗关闭:重置表单
emit("close");
}
},
{ immediate: true }
);
// 8. 基础接口请求(原有逻辑优化)
// 加载亲属关系
const fetchRelationTypes = async () => {
try {
const res = await prescriptionApi.getRelationTypes();
relationTypes.value = res || [];
} catch (err) {
relationTypes.value = [];
ElMessage.error("加载亲属关系失败");
}
};
// 加载处方详情(编辑/详情场景)
const fetchPrescriptionDetail = async () => {
loading.value = true;
try {
const res = await prescriptionApi.getDetail({ id: props.prescriptionId });
if (!res) return;
// 填充处方信息
prescriptionForm.diagnosisResult = res.diagnosisResult || "";
prescriptionForm.userMedicinaAdvice = res.userMedicinaAdvice || "";
prescriptionForm.visitDate = res.visitDate || new Date().toISOString().slice(0, 16);
// 填充人员信息
const { person } = res;
if (person) {
personForm.id = person.id;
personForm.name = person.name;
personForm.isEmployee = person.EmployeeId === undefined; // 员工:EmployeeId为空
personForm.age = person.age;
personForm.weight = person.weight;
personForm.height = person.height;
personForm.relationShip = person.RelationShip;
// 员工场景:赋值员工ID
if (personForm.isEmployee) {
personForm.employeeId = person.id;
state.employeeOptions = [{ id: person.id, name: person.name }];
}
// 家属场景:赋值关联员工ID和家属ID
else {
personForm.relatedEmployeeId = person.EmployeeId;
personForm.relativeId = person.id;
// 加载关联员工选项
state.relatedEmployeeOptions = [{ id: person.EmployeeId, name: res.employeeName || "" }];
// 加载家属选项
state.relativeOptions = [{ id: person.id, name: person.name }];
}
}
// 填充药材信息(原有逻辑)
herbList.value = (res.herbs || []).map((h: any) => ({
medicinalId: h.medicinalId,
medicinalOptions: [{ id: h.medicinalId, name: h.medicinalName }],
keyword: h.medicinalName,
showNewInput: false,
newName: "",
amountG: h.amountG,
note: h.note || ""
}));
} catch (err) {
ElMessage.error("加载处方详情失败");
} finally {
loading.value = false;
}
};
// 9. 员工场景交互逻辑
// 员工下拉搜索
const handleEmployeeSearch = async (keyword: string) => {
state.employeeKeyword = keyword.trim();
if (!keyword) {
state.employeeOptions = [];
return;
}
// 调用接口查询员工(personType=1表示员工)
const res = await prescriptionApi.getPersonList({ personType: 1, keyword });
state.employeeOptions = res || [];
};
// 员工下拉选择(含"未找到"逻辑)
const handleEmployeeChange = async (val: number) => {
if (val === 0) {
// 点击"未找到":隐藏下拉,显示新增文本框
state.showEmployeeSelect = false;
personForm.employeeId = undefined; // 清空下拉值
personForm.newEmployeeName = state.employeeKeyword; // 预填搜索关键词
} else {
// 选择已有员工:显示下拉,清空新增文本框
state.showEmployeeSelect = true;
personForm.newEmployeeName = "";
// 赋值人员姓名
const selectedEmp = state.employeeOptions.find(emp => emp.id === val);
if (selectedEmp) personForm.name = selectedEmp.name;
}
};
// 10. 家属场景交互逻辑
// 关联员工下拉搜索
const handleRelatedEmployeeSearch = async (keyword: string) => {
state.relatedEmployeeKeyword = keyword.trim();
if (!keyword) {
state.relatedEmployeeOptions = [];
return;
}
// 调用接口查询员工(personType=1表示员工)
const res = await prescriptionApi.getPersonList({ personType: 1, keyword });
state.relatedEmployeeOptions = res || [];
};
// 关联员工下拉选择(含"未找到"逻辑)
const handleRelatedEmployeeChange = async (val: number) => {
if (val === 0) {
// 点击"未找到":隐藏关联员工下拉,显示新增文本框+家属新增文本框
state.showRelatedEmployeeSelect = false;
state.showRelativeSelect = false; // 隐藏家属下拉
personForm.relatedEmployeeId = undefined;
personForm.newRelatedEmployeeName = state.relatedEmployeeKeyword;
personForm.relativeId = undefined;
} else {
// 选择已有关联员工:显示关联员工下拉,加载家属列表
state.showRelatedEmployeeSelect = true;
state.showRelativeSelect = true; // 显示家属下拉
personForm.newRelatedEmployeeName = "";
personForm.newRelativeName = "";
// 加载该员工的家属(personType=2表示家属,传employeeId)
await handleLoadRelatives(val);
}
};
// 加载关联员工的家属列表
const handleLoadRelatives = async (employeeId: number) => {
try {
const res = await prescriptionApi.getPersonList({
personType: 2, // 假设personType=2表示家属
employeeId
});
state.relativeOptions = res || [];
} catch (err) {
state.relativeOptions = [];
ElMessage.error("加载家属列表失败");
}
};
// 家属下拉搜索
const handleRelativeSearch = async (keyword: string) => {
state.relativeKeyword = keyword.trim();
if (!keyword || !personForm.relatedEmployeeId) {
state.relativeOptions = [];
return;
}
// 调用接口查询家属(传关联员工ID)
const res = await prescriptionApi.getPersonList({
personType: 2,
employeeId: personForm.relatedEmployeeId,
keyword
});
state.relativeOptions = res || [];
};
// 家属下拉选择(含"未找到"逻辑)
const handleRelativeChange = async (val: number) => {
if (val === 0) {
// 点击"未找到":隐藏家属下拉,显示家属新增文本框
state.showRelativeSelect = false;
personForm.relativeId = undefined;
personForm.newRelativeName = state.relativeKeyword;
} else {
// 选择已有家属:显示家属下拉,清空新增文本框
state.showRelativeSelect = true;
personForm.newRelativeName = "";
// 赋值家属姓名
const selectedRel = state.relativeOptions.find(rel => rel.id === val);
if (selectedRel) personForm.name = selectedRel.name;
}
};
// 11. 人员类型切换逻辑(重置状态)
const handlePersonTypeChange = () => {
// 重置所有状态
state.showEmployeeSelect = true;
state.showRelatedEmployeeSelect = true;
state.showRelativeSelect = true;
state.employeeOptions = [];
state.relatedEmployeeOptions = [];
state.relativeOptions = [];
state.employeeKeyword = "";
state.relatedEmployeeKeyword = "";
state.relativeKeyword = "";
// 重置表单字段
personForm.employeeId = undefined;
personForm.newEmployeeName = "";
personForm.relatedEmployeeId = undefined;
personForm.newRelatedEmployeeName = "";
personForm.relativeId = undefined;
personForm.newRelativeName = "";
personForm.relationShip = undefined;
personForm.name = "";
// 重置表单验证
nextTick(() => {
personFormRef.value?.clearValidate();
});
};
// 12. 药材相关逻辑(保持原有)
const handleAddHerb = () => {
herbList.value.push({
medicinalId: undefined,
medicinalOptions: [],
keyword: "",
showNewInput: false,
newName: "",
amountG: undefined,
note: ""
});
};
const handleDeleteHerb = (index: number) => {
herbList.value.splice(index, 1);
};
const handleHerbSearch = async (keyword: string, index: number) => {
const res = await prescriptionApi.searchMedicinals({ keyword });
herbList.value[index].keyword = keyword;
herbList.value[index].medicinalOptions = res || [];
};
const handleHerbChange = (val: number | 0, index: number) => {
if (val === 0) {
herbList.value[index].showNewInput = true;
herbList.value[index].newName = herbList.value[index].keyword;
} else {
herbList.value[index].showNewInput = false;
herbList.value[index].newName = "";
}
};
// 13. 表单提交/取消逻辑(优化参数组装)
const handleSave = async () => {
// 表单验证
if (!personFormRef.value?.validate()) return;
// 药材验证
if (herbList.value.length === 0) {
return ElMessage.warning("请至少添加一条药材消耗记录");
}
// 诊断结果验证
if (!prescriptionForm.diagnosisResult.trim()) {
return ElMessage.warning("请输入诊断结果");
}
loading.value = true;
try {
// 处理新增员工/家属的情况
let newPersonId: number | undefined = personForm.id;
if (!newPersonId) {
if (personForm.isEmployee) {
// 新增员工
if (state.showEmployeeSelect) {
newPersonId = personForm.employeeId; // 选择已有员工
} else {
// 新增员工:调用addPerson接口
newPersonId = await prescriptionApi.addPerson({
name: personForm.newEmployeeName.trim(),
isEmployee: true
});
}
} else {
// 新增家属:先处理关联员工(如有新增)
let relatedEmpId = personForm.relatedEmployeeId;
if (!state.showRelatedEmployeeSelect) {
// 新增关联员工
relatedEmpId = await prescriptionApi.addPerson({
name: personForm.newRelatedEmployeeName.trim(),
isEmployee: true
});
}
// 新增家属
if (state.showRelativeSelect) {
newPersonId = personForm.relativeId; // 选择已有家属
} else {
// 新增家属:调用addPerson接口
newPersonId = await prescriptionApi.addPerson({
name: personForm.newRelativeName.trim(),
isEmployee: false,
EmployeeId: relatedEmpId,
RelationShip: personForm.relationShip
});
}
}
}
// 组装药材参数
const herbParams = herbList.value.map(h => ({
medicinalId: h.medicinalId,
amountG: h.amountG,
note: h.note?.trim()
}));
// 最终提交参数
const submitParams = {
id: props.prescriptionId || undefined,
PersonId: newPersonId, // 人员ID(新增/编辑后的值)
diagnosisResult: prescriptionForm.diagnosisResult.trim(),
userMedicinaAdvice: prescriptionForm.userMedicinaAdvice.trim(),
visitDate: prescriptionForm.visitDate,
herbs: herbParams
};
// 提交保存
await prescriptionApi.save(submitParams);
ElMessage.success(props.prescriptionId ? "修改成功" : "新增成功");
emit("update:visible", false);
emit("refreshList");
} catch (err: any) {
ElMessage.error(err.message || "保存失败");
} finally {
loading.value = false;
}
};
const handleCancel = () => {
if (props.isEdit) {
ElMessageBox.confirm("是否放弃未保存的修改?", "确认取消", { type: "warning" })
.then(() => emit("update:visible", false))
.catch(); // 无逻辑时可省略函数体
} else {
emit("update:visible", false);
}
};
const handleUpdateVisible = (newVisible: boolean) => {
emit("update:visible", newVisible);
};
// 重置表单
const resetForm = () => {
// 重置处方表单
prescriptionForm.diagnosisResult = "";
prescriptionForm.userMedicinaAdvice = "";
prescriptionForm.visitDate = new Date().toISOString().slice(0, 16);
// 重置人员表单
Object.assign(personForm, {
id: undefined,
name: "",
isEmployee: true,
employeeId: undefined,
newEmployeeName: "",
relatedEmployeeId: undefined,
newRelatedEmployeeName: "",
relativeId: undefined,
newRelativeName: "",
relationShip: undefined,
age: undefined,
weight: undefined,
height: undefined
});
// 重置状态
Object.assign(state, {
showEmployeeSelect: true,
employeeOptions: [],
employeeKeyword: "",
showRelatedEmployeeSelect: true,
relatedEmployeeOptions: [],
relatedEmployeeKeyword: "",
showRelativeSelect: true,
relativeOptions: [],
relativeKeyword: ""
});
// 重置药材列表
herbList.value = [];
// 重置表单验证
nextTick(() => {
personFormRef.value?.clearValidate();
});
};
</script>
<style scoped>
.flex {
display: flex;
}
.justify-between {
justify-content: space-between;
}
.items-center {
align-items: center;
}
.gap-4 {
gap: 16px;
}
.flex-1 {
flex: 1;
}
.el-form-item {
margin-bottom: 20px;
}
</style>
@@ -1,103 +0,0 @@
<template>
<div class="table-box">
<el-card class="mt-4">
<ProTable ref="proTable" title="处方列表" :columns="columns" :request-api="getPrescriptionListApi" :row-key="rowKey">
<template #tableHeader>
<s-button suffix="处方" type="success" @click="handleAddPrescription" />
</template>
<template #operation="scope">
<s-button link :opt="FormOptEnum.VIEW" size="small" @click="handleViewDetail(scope.row.id)"> 查看详情 </s-button>
<s-button link :opt="FormOptEnum.EDIT" size="small" class="ml-2" @click="handleEdit(scope.row.id)" v-if="scope.row.canEdit">
编辑
</s-button>
</template>
</ProTable>
</el-card>
<!-- 弹窗:查看/编辑处方 -->
<prescription-detail-dialog
:visible="showDetailDialog"
:prescription-id="currentPrescriptionId"
:is-edit="isEdit"
@update:visible="val => (showDetailDialog = val)"
@refresh-list="handleRefresh"
/>
</div>
</template>
<script setup lang="ts" name="prescriptionModule">
import { ref } from "vue";
import ProTable from "@/components/ProTable/index.vue";
import type { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
import { prescriptionApi } from "@/api";
import PrescriptionDetailDialog from "./components/PrescriptionDetailDialog.vue";
import { FormOptEnum } from "@/enums";
// ProTable ref
const proTable = ref<ProTableInstance | null>(null);
// 弹窗状态
const showDetailDialog = ref(false);
const currentPrescriptionId = ref<number | null>(null);
const isEdit = ref(false);
// 表格列配置
const columns: ColumnProps<any>[] = [
{ type: "selection", fixed: "left", width: 80 },
{ prop: "personName", label: "就诊人", search: { el: "input" } },
{
prop: "personType",
label: "就诊人类型",
formatter: (row: any) => (row.personType === 1 ? "公司员工" : `员工家属(${row.employeeName || "未知"})`)
},
{ prop: "visitDate", label: "就诊时间" },
{ prop: "operation", label: "操作", width: 180, fixed: "right" }
];
// rowKey定义
const rowKey = (row: any) => row.id;
// 请求接口
const getPrescriptionListApi = (params: any) => {
return prescriptionApi.getList(params);
};
// 新增处方
const handleAddPrescription = () => {
currentPrescriptionId.value = null;
isEdit.value = true;
showDetailDialog.value = true;
};
// 查看详情
const handleViewDetail = (id: number) => {
currentPrescriptionId.value = id;
isEdit.value = false;
showDetailDialog.value = true;
};
// 编辑
const handleEdit = (id: number) => {
currentPrescriptionId.value = id;
isEdit.value = true;
showDetailDialog.value = true;
};
// 刷新表格
const handleRefresh = () => {
proTable.value?.refresh?.();
};
</script>
<style scoped lang="scss">
.table-box {
padding-bottom: 24px;
}
.mt-4 {
margin-top: 16px;
}
.ml-2 {
margin-left: 8px;
}
</style>
+19 -35
View File
@@ -5,34 +5,23 @@
!--> !-->
<template> <template>
<div class="main-box"> <div class="main-box">
<TreeFilter <TreeFilter ref="treeFilter" label="name" title="组织列表" :request-api="sysOrgApi.tree"
ref="treeFilter" :default-value="initParam.orgId" @change="changeTreeFilter" />
label="name"
title="组织列表"
:request-api="sysOrgApi.tree"
:default-value="initParam.orgId"
@change="changeTreeFilter"
/>
<div class="table-box"> <div class="table-box">
<ProTable ref="proTable" :columns="columns" :request-api="sysRoleApi.page" :init-param="initParam"> <ProTable ref="proTable" :columns="columns" :request-api="sysRoleApi.page" :init-param="initParam">
<!-- 表格 header 按钮 --> <!-- 表格 header 按钮 -->
<template #tableHeader="scope"> <template #tableHeader="scope">
<s-button suffix="角色" @click="onOpen(FormOptEnum.ADD)" /> <s-button suffix="角色" @click="onOpen(FormOptEnum.ADD)" />
<s-button <s-button type="danger" plain suffix="角色" :opt="FormOptEnum.DELETE" :disabled="!scope.isSelected"
type="danger" @click="onDelete(scope.selectedListIds, '删除所选角色')" />
plain
suffix="角色"
:opt="FormOptEnum.DELETE"
:disabled="!scope.isSelected"
@click="onDelete(scope.selectedListIds, '删除所选角色')"
/>
</template> </template>
<!-- 状态 --> <!-- 状态 -->
<template #status="scope"> <template #status="scope">
<el-tag v-if="scope.row.status === CommonStatusEnum.ENABLE" type="success">{{ <el-tag v-if="scope.row.status === CommonStatusEnum.ENABLE" type="success">{{
dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status) dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status)
}}</el-tag> }}</el-tag>
<el-tag v-else type="danger">{{ dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status) }}</el-tag> <el-tag v-else type="danger">{{ dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status)
}}</el-tag>
</template> </template>
<!-- 操作 --> <!-- 操作 -->
<template #operation="scope"> <template #operation="scope">
@@ -43,7 +32,8 @@
<el-link type="primary" :underline="false" :icon="ArrowDown"> 授权 </el-link> <el-link type="primary" :underline="false" :icon="ArrowDown"> 授权 </el-link>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item v-for="(item, index) in cmdEnum" :key="index" :command="command(scope.row, item)">{{ item }}</el-dropdown-item> <el-dropdown-item v-for="(item, index) in cmdEnum" :key="index" :command="command(scope.row, item)">{{
item }}</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
@@ -59,15 +49,9 @@
<!-- 授权权限组件 --> <!-- 授权权限组件 -->
<GrantPermission ref="grantPermissionRef" /> <GrantPermission ref="grantPermissionRef" />
<!-- 用户选择器 --> <!-- 用户选择器 -->
<user-selector <user-selector ref="userSelectorRef" :org-tree-api="sysOrgApi.tree" :position-tree-api="sysPositionApi.tree"
ref="userSelectorRef" :role-tree-api="sysRoleApi.tree" :user-selector-api="sysUserApi.selector" multiple
:org-tree-api="sysOrgApi.tree" @successful="handleChooseUser" />
:position-tree-api="sysPositionApi.tree"
:role-tree-api="sysRoleApi.tree"
:user-selector-api="sysUserApi.selector"
multiple
@successful="handleChooseUser"
/>
</div> </div>
</div> </div>
</template> </template>
@@ -209,14 +193,14 @@ function handleCommand(command: Command) {
successful: RefreshTable successful: RefreshTable
}); });
break; break;
case cmdEnum.GrantMobileResource: // case cmdEnum.GrantMobileResource:
// 打开授权移动端资源组件 // // 打开授权移动端资源组件
grantMobileResourceRef.value?.onOpen({ // grantMobileResourceRef.value?.onOpen({
opt: FormOptEnum.EDIT, // opt: FormOptEnum.EDIT,
record: command.row, // record: command.row,
successful: RefreshTable // successful: RefreshTable
}); // });
break; // break;
case cmdEnum.GrantPermission: case cmdEnum.GrantPermission:
// 打开授权权限组件 // 打开授权权限组件
grantPermissionRef.value?.onOpen({ grantPermissionRef.value?.onOpen({
+12 -6
View File
@@ -6,11 +6,17 @@
<template> <template>
<div class="card"> <div class="card">
<el-tabs v-model="activeName" class="-mt-4"> <el-tabs v-model="activeName" class="-mt-4">
<el-tab-pane label="系统配置" name="sysConfig"><SysBaseConfig :sys-configs="sysBase" /></el-tab-pane> <el-tab-pane label="系统配置" name="sysConfig">
<el-tab-pane label="安全配置" name="safety"><SafetyConfig :sys-configs="safetyConfig" /></el-tab-pane> <SysBaseConfig :sys-configs="sysBase" />
<el-tab-pane label="MQTT配置" name="mqtt"><MqttConfig :sys-configs="mqttConfig" /></el-tab-pane> </el-tab-pane>
<el-tab-pane label="文件配置" name="file">开发中......</el-tab-pane> <el-tab-pane label="安全配置" name="safety">
<el-tab-pane label="其他配置" name="other"><OtherConfig v-if="activeName === 'other'" /></el-tab-pane> <SafetyConfig :sys-configs="safetyConfig" />
</el-tab-pane>
<!-- <el-tab-pane label="MQTT配置" name="mqtt"><MqttConfig :sys-configs="mqttConfig" /></el-tab-pane>
<el-tab-pane label="文件配置" name="file">开发中......</el-tab-pane> -->
<el-tab-pane label="其他配置" name="other">
<OtherConfig v-if="activeName === 'other'" />
</el-tab-pane>
</el-tabs> </el-tabs>
</div> </div>
</template> </template>
@@ -19,7 +25,7 @@
import SysBaseConfig from "./components/sysBaseConfig.vue"; import SysBaseConfig from "./components/sysBaseConfig.vue";
import SafetyConfig from "./components/safetyConfig/index.vue"; import SafetyConfig from "./components/safetyConfig/index.vue";
import OtherConfig from "./components/otherConfig/index.vue"; import OtherConfig from "./components/otherConfig/index.vue";
import MqttConfig from "./components/mqttConfig/index.vue"; // import MqttConfig from "./components/mqttConfig/index.vue";
import { SysConfig, sysConfigApi } from "@/api"; import { SysConfig, sysConfigApi } from "@/api";
import { SysConfigTypeEnum } from "@/enums"; import { SysConfigTypeEnum } from "@/enums";
+48 -27
View File
@@ -60,26 +60,26 @@ const data: BindData[] = [
/*{ title: '密保手机', description: '已绑定手机', value: '138****8293', type: 'phone', bindStatus: 1 }, /*{ title: '密保手机', description: '已绑定手机', value: '138****8293', type: 'phone', bindStatus: 1 },
{ title: '密保邮箱', description: '未绑定邮箱', value: '', type: 'email', bindStatus: 0 }, { title: '密保邮箱', description: '未绑定邮箱', value: '', type: 'email', bindStatus: 0 },
{ title: '实名状态', description: '未实名', value: '', type: 'userReal', bindStatus: 0 },*/ { title: '实名状态', description: '未实名', value: '', type: 'userReal', bindStatus: 0 },*/
{ avatar: "ant-design:qq-outlined", title: "绑定QQ", description: "未绑定", value: "", bindType: BindType.QQ, bindStatus: 0, color: "1890FF" }, // { avatar: "ant-design:qq-outlined", title: "绑定QQ", description: "未绑定", value: "", bindType: BindType.QQ, bindStatus: 0, color: "1890FF" },
{ // {
avatar: "ant-design:wechat-outlined", // avatar: "ant-design:wechat-outlined",
title: "绑定微信", // title: "绑定微信",
description: "未绑定", // description: "未绑定",
value: "", // value: "",
bindType: BindType.WeChat, // bindType: BindType.WeChat,
bindStatus: 0, // bindStatus: 0,
color: "1AAD19" // color: "1AAD19"
}, // },
{ // {
avatar: "ant-design:alipay-circle-outlined", // avatar: "ant-design:alipay-circle-outlined",
title: "绑定支付宝", // title: "绑定支付宝",
description: "未绑定", // description: "未绑定",
value: "", // value: "",
bindType: BindType.AliPay, // bindType: BindType.AliPay,
bindStatus: 0, // bindStatus: 0,
color: "178bf5" // color: "178bf5"
}, // },
{ avatar: "simple-icons:gitee", title: "绑定Gitee", description: "未绑定", value: "", bindType: BindType.Gitee, bindStatus: 0, color: "d84040" } // { avatar: "simple-icons:gitee", title: "绑定Gitee", description: "未绑定", value: "", bindType: BindType.Gitee, bindStatus: 0, color: "d84040" }
]; ];
/** /**
@@ -102,34 +102,55 @@ function bindAction(bindType: BindType) {
padding: 0; padding: 0;
margin: 0; margin: 0;
} }
.list-item { .list-item {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 10px; padding: 10px;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0;
} }
.item-content { .item-content {
flex-grow: 1; flex-grow: 1;
color: #585252; /* 调整字体颜色为深灰色 */ color: #585252;
/* 调整字体颜色为深灰色 */
} }
.item-content h3 { .item-content h3 {
margin: 0 0 10px; /* 添加一些间距 */ margin: 0 0 10px;
/* 添加一些间距 */
font-size: 16px; font-size: 16px;
font-weight: normal; /* 调整标题字体为正常粗细 */ font-weight: normal;
color: #242425; /* 标题字体颜色稍深一点 */
/* 调整标题字体为正常粗细 */
color: #242425;
/* 标题字体颜色稍深一点 */
} }
.item-content p { .item-content p {
margin: 0; /* 移除段落默认的上下外边距 */ margin: 0;
/* 移除段落默认的上下外边距 */
font-size: 14px; font-size: 14px;
color: #8f8f94; /* 标题字体颜色稍深一点 */ color: #8f8f94;
/* 标题字体颜色稍深一点 */
} }
.item-action { .item-action {
margin-left: auto; margin-left: auto;
} }
.item-action .el-button { .item-action .el-button {
font-weight: normal; font-weight: normal;
color: #409eff; /* 为按钮文字设置主题颜色 */ color: #409eff;
/* 为按钮文字设置主题颜色 */
} }
.bind-icon { .bind-icon {
padding-right: 10px; padding-right: 10px;
padding-left: 10px; padding-left: 10px;
+32 -6
View File
@@ -13,7 +13,9 @@
<img :src="userInfo?.avatar" /> <img :src="userInfo?.avatar" />
<a style="cursor: pointer" @click="uploadAvatar"> <a style="cursor: pointer" @click="uploadAvatar">
<div :class="userInfo?.avatar ? 'mask' : 'mask-notImg'"> <div :class="userInfo?.avatar ? 'mask' : 'mask-notImg'">
<el-icon><Upload /></el-icon> <el-icon>
<Upload />
</el-icon>
</div> </div>
</a> </a>
</div> </div>
@@ -31,7 +33,8 @@
<el-divider /> <el-divider />
<div class="account-center-team"> <div class="account-center-team">
<div v-if="userInfo?.signature" class="mb-2" style="width: 100%"> <div v-if="userInfo?.signature" class="mb-2" style="width: 100%">
<el-image :src="userInfo?.signature" style="height: 120px; border: 1px solid rgb(236 236 236)" width="100%" /> <el-image :src="userInfo?.signature" style="height: 120px; border: 1px solid rgb(236 236 236)"
width="100%" />
</div> </div>
<el-button @click="openSignName">打开签名板</el-button> <el-button @click="openSignName">打开签名板</el-button>
</div> </div>
@@ -41,10 +44,18 @@
<el-col :lg="17" :md="24"> <el-col :lg="17" :md="24">
<div class="card"> <div class="card">
<el-tabs v-model="activeName" class="-mt-4" @tab-click="tabClick"> <el-tabs v-model="activeName" class="-mt-4" @tab-click="tabClick">
<el-tab-pane label="基本信息" :name="centerStore.getAccountBasic"><AccountBasic /></el-tab-pane> <el-tab-pane label="基本信息" :name="centerStore.getAccountBasic">
<el-tab-pane label="账号相关" :name="centerStore.getFile"><AccountBind /></el-tab-pane> <AccountBasic />
<el-tab-pane label="快捷方式" :name="centerStore.getShort"><ShortcutSetting /></el-tab-pane> </el-tab-pane>
<el-tab-pane label="我的消息" :name="centerStore.getMessage"><MyMessage /></el-tab-pane> <el-tab-pane label="账号相关" :name="centerStore.getFile">
<AccountBind />
</el-tab-pane>
<!-- <el-tab-pane label="快捷方式" :name="centerStore.getShort">
<ShortcutSetting />
</el-tab-pane>
<el-tab-pane label="我的消息" :name="centerStore.getMessage">
<MyMessage />
</el-tab-pane> -->
</el-tabs> </el-tabs>
</div> </div>
</el-col> </el-col>
@@ -135,6 +146,7 @@ centerStore.$subscribe((mutations, state) => {
.account-center-avatarHolder { .account-center-avatarHolder {
margin-bottom: 24px; margin-bottom: 24px;
text-align: center; text-align: center;
&>.avatar { &>.avatar {
width: 104px; width: 104px;
height: 104px; height: 104px;
@@ -142,11 +154,13 @@ centerStore.$subscribe((mutations, state) => {
margin-bottom: 20px; margin-bottom: 20px;
overflow: hidden; overflow: hidden;
border-radius: 50%; border-radius: 50%;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
.mask { .mask {
position: absolute; position: absolute;
display: flex; display: flex;
@@ -161,6 +175,7 @@ centerStore.$subscribe((mutations, state) => {
border-radius: 50%; border-radius: 50%;
opacity: 0; opacity: 0;
} }
.mask-notImg { .mask-notImg {
position: absolute; position: absolute;
display: flex; display: flex;
@@ -175,12 +190,15 @@ centerStore.$subscribe((mutations, state) => {
border-radius: 50%; border-radius: 50%;
opacity: 0; opacity: 0;
} }
.avatar a:hover .mask { .avatar a:hover .mask {
opacity: 1; opacity: 1;
} }
.avatar a:hover .mask-notImg { .avatar a:hover .mask-notImg {
opacity: 1; opacity: 1;
} }
.username { .username {
margin-bottom: 4px; margin-bottom: 4px;
font-size: 20px; font-size: 20px;
@@ -188,12 +206,14 @@ centerStore.$subscribe((mutations, state) => {
line-height: 28px; line-height: 28px;
} }
} }
.account-center-detail { .account-center-detail {
p { p {
position: relative; position: relative;
padding-left: 26px; padding-left: 26px;
margin-bottom: 8px; margin-bottom: 8px;
} }
i { i {
position: absolute; position: absolute;
top: 4px; top: 4px;
@@ -202,16 +222,20 @@ centerStore.$subscribe((mutations, state) => {
height: 14px; height: 14px;
background: url("/src/assets/icons/userDetail.svg"); background: url("/src/assets/icons/userDetail.svg");
} }
.title { .title {
background-position: 0 0; background-position: 0 0;
} }
.group { .group {
background-position: 0 -22px; background-position: 0 -22px;
} }
.address { .address {
background-position: 0 -44px; background-position: 0 -44px;
} }
} }
.account-center-team { .account-center-team {
.members { .members {
a { a {
@@ -219,6 +243,7 @@ centerStore.$subscribe((mutations, state) => {
height: 24px; height: 24px;
margin: 12px 0; margin: 12px 0;
line-height: 24px; line-height: 24px;
.member { .member {
display: inline-block; display: inline-block;
max-width: 100px; max-width: 100px;
@@ -231,6 +256,7 @@ centerStore.$subscribe((mutations, state) => {
} }
} }
} }
:deep(.el-tabs__item) { :deep(.el-tabs__item) {
font-size: large; font-size: large;
} }