addOrEditForm.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. <template>
  2. <el-card shadow="always">
  3. <el-button type="primary" plain size="large" @click="goBack">返回</el-button>
  4. <span style="margin-left: 20px;">新增耗材</span>
  5. </el-card>
  6. <div class="add-or-edit-supplies-manage">
  7. <el-form ref="SuppliesManageFormRef" :model="form" :rules="rules" label-width="160px" style="padding: 20px;">
  8. <el-row :gutter="20">
  9. <el-col :span="12">
  10. <el-form-item label="耗材名称:" prop="suppliesName">
  11. <el-input v-model="form.suppliesName" placeholder="请输入" />
  12. </el-form-item>
  13. </el-col>
  14. <el-col :span="12">
  15. <el-form-item label="商品资质:" prop="productQualification">
  16. <el-select v-model="form.productQualification" placeholder="请选择" clearable>
  17. <el-option v-for="dict in product_qualification" :key="dict.value" :label="dict.label" :value="dict.value" />
  18. </el-select>
  19. </el-form-item>
  20. </el-col>
  21. </el-row>
  22. <el-row :gutter="20">
  23. <el-col :span="12">
  24. <el-form-item label="商品编码:" prop="suppliesCode">
  25. <el-input v-model="form.suppliesCode" placeholder="请输入" />
  26. </el-form-item>
  27. </el-col>
  28. <el-col :span="12">
  29. <el-form-item label="批准文号:" prop="approvalNumber">
  30. <el-input v-model="form.approvalNumber" placeholder="请输入" />
  31. </el-form-item>
  32. </el-col>
  33. </el-row>
  34. <el-row :gutter="20">
  35. <el-col :span="12">
  36. <el-form-item label="院方系统编码:" prop="hospitalSystemCode">
  37. <el-input v-model="form.hospitalSystemCode" placeholder="请输入" />
  38. </el-form-item>
  39. </el-col>
  40. <el-col :span="12">
  41. <el-form-item label="所属分类:" prop="suppliesCategoryList">
  42. <el-cascader v-model="form.suppliesCategoryList" :options="categoryOptions" :props="{
  43. checkStrictly: true,
  44. emitPath: true,
  45. value: 'value',
  46. label: 'label',
  47. children: 'children'
  48. }" placeholder="请选择" clearable filterable style="width: 100%" @change="handleCategoryChange" />
  49. </el-form-item>
  50. </el-col>
  51. </el-row>
  52. <el-row :gutter="20">
  53. <el-col :span="12">
  54. <el-form-item label="生产厂商:" prop="manufacturer">
  55. <el-select v-model="form.manufacturer" placeholder="请选择" clearable>
  56. <el-option v-for="dict in product_manufacturer" :key="dict.value" :label="dict.label" :value="dict.value" />
  57. </el-select>
  58. </el-form-item>
  59. </el-col>
  60. <el-col :span="12">
  61. <el-form-item label="保质期临期提醒:" prop="shelfLifeReminder">
  62. <el-input v-model="form.shelfLifeReminder" placeholder="请输入">
  63. <template #append>个月</template>
  64. </el-input>
  65. </el-form-item>
  66. </el-col>
  67. </el-row>
  68. <el-row :gutter="20">
  69. <el-col :span="12">
  70. <el-form-item label="供应商:" prop="supplier">
  71. <el-select v-model="form.supplier" placeholder="请选择" clearable>
  72. <el-option v-for="dict in product_supplier" :key="dict.value" :label="dict.label" :value="dict.value" />
  73. </el-select>
  74. </el-form-item>
  75. </el-col>
  76. <el-col :span="12">
  77. <el-form-item label="品牌:" prop="brand">
  78. <el-input v-model="form.brand" placeholder="请输入" />
  79. </el-form-item>
  80. </el-col>
  81. </el-row>
  82. <el-row :gutter="20">
  83. <el-col :span="12">
  84. <el-form-item label="许可证有效期提醒:" prop="licenseExpiryReminder">
  85. <el-input v-model="form.licenseExpiryReminder" placeholder="请输入">
  86. <template #append>个月</template>
  87. </el-input>
  88. </el-form-item>
  89. </el-col>
  90. <el-col :span="12">
  91. <el-form-item label="产品所属标签:" prop="productLabel">
  92. <el-select v-model="form.productLabelList" multiple placeholder="请选择" style="width: 100%;" :disabled="true" @click="labelDialogVisible = true" class="custom-label-select" value-key="labelId">
  93. <el-option v-for="item in form.productLabelList" :key="item.labelId" :label="item.labelName" :value="item">
  94. <el-tag type="info" size="small">{{ item.labelName }}</el-tag>
  95. </el-option>
  96. </el-select>
  97. </el-form-item>
  98. </el-col>
  99. </el-row>
  100. <el-row :gutter="20">
  101. <el-col :span="12">
  102. <el-form-item label="商品许可证有效期至:" prop="productLicenseExpiry">
  103. <el-date-picker v-model="form.productLicenseExpiry" type="date" placeholder="选择一天" value-format="YYYY-MM-DD" />
  104. </el-form-item>
  105. </el-col>
  106. <el-col :span="12">
  107. <el-form-item label="产品适用科室:" prop="applicableDepartment">
  108. <el-cascader v-model="form.applicableDepartmentList" :options="deptList" :props="{
  109. multiple: true,checkStrictly: true, // 允许任意层级选择emitPath: false, // 只返回选中节点的 value
  110. value: 'deptId',label: 'deptName',children: 'children'
  111. }" placeholder="请选择" clearable style="width: 100%" />
  112. </el-form-item>
  113. </el-col>
  114. </el-row>
  115. <el-row :gutter="20">
  116. <el-col :span="12">
  117. <el-form-item label="保质期:" prop="shelfLife">
  118. <el-input v-model="form.shelfLife" placeholder="请输入">
  119. <template #append>个月</template>
  120. </el-input>
  121. </el-form-item>
  122. </el-col>
  123. </el-row>
  124. <el-divider content-position="left">入货信息:</el-divider>
  125. <el-row :gutter="20">
  126. <el-col :span="12">
  127. <el-form-item label="入货价格:" prop="purchasePrice" required>
  128. <el-input v-model="form.purchasePrice" placeholder="请输入">
  129. <template #append>元</template>
  130. </el-input>
  131. </el-form-item>
  132. </el-col>
  133. <el-col :span="12">
  134. <el-form-item label="销售价格:" prop="sellPrice" required>
  135. <el-input v-model="form.sellPrice" placeholder="请输入">
  136. <template #append>元/{{getDictLabel(product_package_unit ,form.suppliesUnit )|| '--' }}</template>
  137. </el-input>
  138. </el-form-item>
  139. </el-col>
  140. </el-row>
  141. <el-row :gutter="20">
  142. <el-col :span="12">
  143. <el-form-item label="耗材单位:" prop="suppliesUnit" required>
  144. <el-select v-model="form.suppliesUnit" placeholder="请选择" clearable>
  145. <el-option v-for="dict in product_package_unit" :key="dict.value" :label="dict.label" :value="dict.value" />
  146. </el-select>
  147. </el-form-item>
  148. </el-col>
  149. <el-col :span="12">
  150. <el-form-item label="耗材规格:" prop="suppliesSpec" required>
  151. <div class="spec-input-group">
  152. <el-input v-model="form.suppliesSpec" placeholder="请输入" style="width: 320px;" :maxlength="10" />
  153. <el-select v-model="form.suppliesSpecUnit" class="spec-unit-select">
  154. <el-option v-for="dict in product_spec_unit" :key="dict.value" :label="dict.label" :value="dict.value" />
  155. </el-select>
  156. <span class="spec-unit-text">/{{getDictLabel(product_package_unit ,form.suppliesUnit )|| '--' }}</span>
  157. </div>
  158. </el-form-item>
  159. </el-col>
  160. </el-row>
  161. <div class="dialog-footer" style="text-align:center;margin-top:32px;">
  162. <el-button type="primary" @click="submitForm">保存</el-button>
  163. <el-button type="primary" @click="submitFormAndPutaway">保存并上架</el-button>
  164. </div>
  165. </el-form>
  166. <LabelDialog v-model="labelDialogVisible" :initial-selected-labels="form.productLabelList || []" @confirm="onLabelConfirm" />
  167. </div>
  168. </template>
  169. <script setup lang="ts">
  170. import { ref, reactive, onMounted, getCurrentInstance, type ComponentInternalInstance } from 'vue';
  171. import { useRouter, useRoute } from 'vue-router';
  172. import { addSuppliesManage, updateSuppliesManage, getSuppliesManage } from '@/api/warehouse/suppliesManage/index';
  173. import { SuppliesManageForm } from '@/api/warehouse/suppliesManage/types';
  174. import { listSuppliesCategory } from '@/api/warehouse/suppliesCategory/index';
  175. import { listDept } from '@/api/system/dept';
  176. import { toRefs } from 'vue';
  177. import LabelDialog from '@/views/warehouse/nutriProduct/labelDialog.vue';
  178. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  179. const { product_qualification, product_package_unit, product_supplier, put_flag, product_manufacturer, product_spec_unit } = toRefs < any > (proxy ?.useDict('product_qualification', 'product_package_unit', 'product_supplier', 'put_flag', 'product_manufacturer', 'product_spec_unit'));
  180. const loading = ref(true);
  181. const router = useRouter();
  182. const route = useRoute();
  183. const buttonLoading = ref(false);
  184. const SuppliesManageFormRef = ref();
  185. const labelDialogVisible = ref(false);
  186. // 选项数据
  187. const categoryOptions = ref < any[] > ([]);
  188. const deptList = ref < any[] > ([]);
  189. const unit_options = ref < any[] > ([{ label: '个', value: '1' }]); // TODO: 替换为实际数据
  190. const rules = {
  191. suppliesName: [{ required: true, message: '耗材名称不能为空', trigger: 'blur' }],
  192. hospitalSystemCode: [{ required: true, message: "院方系统编码不能为空", trigger: "blur" }],
  193. shelfLifeReminder: [{ required: true, message: "保质期临期提醒不能为空", trigger: "blur" }],
  194. productQualification: [{ required: true, message: "商品资质不能为空", trigger: "blur" }],
  195. suppliesCode: [{ required: true, message: '商品编码不能为空', trigger: 'blur' }],
  196. shelfLife: [{ required: true, message: '保质期不能为空', trigger: 'blur' }],
  197. suppliesCategoryList: [{ required: true, message: '所属分类不能为空', trigger: 'change' }],
  198. purchasePrice: [{ required: true, message: '入货价格不能为空', trigger: 'blur' }],
  199. suppliesUnit: [{ required: true, message: '耗材单位不能为空', trigger: 'change' }],
  200. suppliesSpec: [{ required: true, message: '耗材规格不能为空', trigger: 'blur' }],
  201. sellPrice: [{ required: true, message: '销售价格不能为空', trigger: 'blur' }]
  202. };
  203. const form = reactive < SuppliesManageForm > ({
  204. id: undefined,
  205. suppliesName: undefined,
  206. hospitalSystemCode: undefined,
  207. suppliesCode: undefined,
  208. manufacturer: undefined,
  209. supplier: undefined,
  210. licenseExpiryReminder: undefined,
  211. shelfLife: undefined,
  212. productQualification: undefined,
  213. approvalNumber: undefined,
  214. suppliesCategoryList: [],
  215. shelfLifeReminder: undefined,
  216. brand: undefined,
  217. productLabel: undefined,
  218. productLabelList: [],
  219. applicableDepartment: undefined,
  220. applicableDepartmentList: [],
  221. purchasePrice: undefined,
  222. suppliesUnit: '2',
  223. sellPrice: undefined,
  224. suppliesSpec: undefined,
  225. suppliesSpecUnit: '1',
  226. putFlag: undefined
  227. });
  228. const getCategoryName = (categoryIds) => {
  229. if (!categoryIds) return '--';
  230. // 处理多个分类ID的情况
  231. const ids = categoryIds.toString().split(',');
  232. const categoryNames = ids.map(id => {
  233. const findCategory = (categories, targetId) => {
  234. for (const category of categories) {
  235. if (category.value.toString() === targetId.toString()) {
  236. return category.label;
  237. }
  238. if (category.children) {
  239. const found = findCategory(category.children, targetId);
  240. if (found) return found;
  241. }
  242. }
  243. return null;
  244. };
  245. return findCategory(categoryOptions.value, id);
  246. }).filter(name => name !== null);
  247. return categoryNames.length > 0 ? categoryNames.join('/') : '--';
  248. };
  249. const getDeptList = async () => {
  250. loading.value = true;
  251. try {
  252. const res = await listDept({
  253. pageNum: 1,
  254. pageSize: 999
  255. });
  256. if (!res.data) {
  257. console.warn("部门数据为空");
  258. deptList.value = [];
  259. return;
  260. }
  261. // 处理树形数据
  262. const processedData = proxy ?.handleTree(res.data, 'deptId');
  263. if (!processedData) {
  264. console.warn("树形数据处理失败");
  265. deptList.value = [];
  266. return;
  267. }
  268. deptList.value = processedData;
  269. } catch (error) {
  270. console.error('获取部门列表失败:', error);
  271. deptList.value = [];
  272. } finally {
  273. loading.value = false;
  274. }
  275. };
  276. // 构建分类树
  277. function buildCategoryTree(flatList: any[]) {
  278. const idMap: Record < string, any > = {};
  279. const tree = [];
  280. flatList.forEach(item => {
  281. idMap[item.categoryId] = {
  282. label: item.categoryName,
  283. value: item.categoryId,
  284. children: []
  285. };
  286. });
  287. flatList.forEach(item => {
  288. if (item.parentId && item.parentId !== 0 && idMap[item.parentId]) {
  289. idMap[item.parentId].children.push(idMap[item.categoryId]);
  290. } else if (item.parentId === 0) {
  291. tree.push(idMap[item.categoryId]);
  292. }
  293. });
  294. // 去除没有children的children字段
  295. function clean(node: any) {
  296. if (node.children && node.children.length === 0) {
  297. delete node.children;
  298. } else if (node.children) {
  299. node.children.forEach(clean);
  300. }
  301. }
  302. tree.forEach(clean);
  303. return tree;
  304. }
  305. // 标签确认回调
  306. function onLabelConfirm(selectedLabels: Array < { labelId: string | number;labelName: string } > ) {
  307. form.productLabelList = selectedLabels;
  308. }
  309. // 字典label工具
  310. function getDictLabel(dictList: any[], value: string) {
  311. if (!dictList || !Array.isArray(dictList)) return value || '--';
  312. const found = dictList.find(item => item.value === value);
  313. return found ? found.label : value || '--';
  314. }
  315. // 判断是新增还是编辑
  316. const id = route.params.id;
  317. if (id) {
  318. // 编辑,获取详情
  319. getSuppliesManage(id.toString()).then(res => {
  320. Object.assign(form, res.data);
  321. // 如果有分类ID,需要设置级联选择器的值
  322. if (res.data.suppliesCategoryId) {
  323. // 这里需要根据后端返回的数据结构调整
  324. form.suppliesCategoryList = [res.data.suppliesCategoryId];
  325. }
  326. });
  327. }
  328. // 初始化数据
  329. onMounted(async () => {
  330. getDeptList(); // 初始化时加载部门数据
  331. const res = await listSuppliesCategory();
  332. categoryOptions.value = buildCategoryTree(res.rows || []);
  333. });
  334. // 添加分类选择处理函数
  335. const handleCategoryChange = (value: any[]) => {
  336. if (value && value.length > 0) {
  337. form.suppliesCategoryId = value[value.length - 1];
  338. } else {
  339. form.suppliesCategoryId = undefined;
  340. }
  341. };
  342. // 修改提交函数
  343. function submitForm() {
  344. SuppliesManageFormRef.value ?.validate(async (valid: boolean) => {
  345. if (valid) {
  346. buttonLoading.value = true;
  347. try {
  348. // 确保设置了 suppliesCategoryId
  349. if (form.suppliesCategoryList && form.suppliesCategoryList.length > 0) {
  350. form.suppliesCategoryId = form.suppliesCategoryList[form.suppliesCategoryList.length - 1];
  351. }
  352. if (form.id) {
  353. await updateSuppliesManage(form);
  354. } else {
  355. await addSuppliesManage(form);
  356. }
  357. proxy ?.$modal.msgSuccess('操作成功');
  358. goToList();
  359. } catch (error) {
  360. console.error('提交失败:', error);
  361. proxy ?.$modal.msgError('操作失败');
  362. } finally {
  363. buttonLoading.value = false;
  364. }
  365. }
  366. });
  367. }
  368. /** 保存并上架按钮操作 */
  369. const submitFormAndPutaway = () => {
  370. SuppliesManageFormRef.value ?.validate(async (valid: boolean) => {
  371. if (valid) {
  372. buttonLoading.value = true;
  373. try {
  374. form.putFlag = '1';
  375. if (form.id) {
  376. await updateSuppliesManage(form);
  377. } else {
  378. await addSuppliesManage(form);
  379. }
  380. proxy ?.$modal.msgSuccess('操作成功');
  381. goToList();
  382. } catch (error) {
  383. console.error('提交失败:', error);
  384. proxy ?.$modal.msgError('操作失败');
  385. } finally {
  386. buttonLoading.value = false;
  387. }
  388. }
  389. });
  390. };
  391. // 返回按钮
  392. const goBack = () => {
  393. router.back();
  394. };
  395. // 统一的列表页跳转函数
  396. const goToList = () => {
  397. router.push('/warehouse/suppliesManage');
  398. };
  399. </script>
  400. <style scoped>
  401. .add-or-edit-supplies-manage {
  402. padding: 20px;
  403. background-color: #fff;
  404. }
  405. .el-form {
  406. max-width: 1200px;
  407. margin: 0 auto;
  408. }
  409. .el-row {
  410. margin-bottom: 20px;
  411. }
  412. :deep(.el-form-item__label) {
  413. font-weight: 500;
  414. }
  415. :deep(.el-input-group__append) {
  416. padding: 0 15px;
  417. }
  418. .custom-label-select :deep(.el-select__tags) {
  419. max-width: calc(100% - 30px);
  420. }
  421. .spec-input-group {
  422. display: flex;
  423. align-items: center;
  424. gap: 8px;
  425. }
  426. .spec-unit-select {
  427. width: 80px;
  428. }
  429. .spec-unit-text {
  430. color: #606266;
  431. font-size: 14px;
  432. white-space: nowrap;
  433. }
  434. </style>