crud.tsx 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788
  1. import { AddReq, compute,useCrud ,useExpose, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, uiContext, UserPageQuery, } from '@fast-crud/fast-crud';
  2. import dayjs from 'dayjs';
  3. import * as api from './api';
  4. import { uploadFile } from './api';
  5. import { auth } from '/@/utils/authFunction';
  6. import {request} from '/@/utils/service';
  7. import {getBaseURL} from '/@/utils/baseUrl';
  8. import Cookies from 'js-cookie';
  9. import { successMessage } from '/@/utils/message';
  10. import { dictionary } from '/@/utils/dictionary';
  11. import { APIResponseData } from '../columns/types';
  12. import { computed, h,defineComponent, ref, onMounted, watch } from 'vue';
  13. import { ElTable, ElTableColumn, ElLoading } from 'element-plus';
  14. const BorrowRecords = defineComponent({
  15. props: { form: Object },
  16. setup(props) {
  17. try {
  18. const records = ref([]);
  19. const loading = ref(false);
  20. const filteredRecords = computed(() =>
  21. (records.value || []).filter((item: any) => item?.record_type === 0)
  22. );
  23. const loadData = async () => {
  24. if (!props.form?.id) return;
  25. loading.value = true;
  26. try {
  27. const res = await api.getBorrowRecords(props.form.id);
  28. records.value = res.data || [];
  29. } finally {
  30. loading.value = false;
  31. }
  32. };
  33. watch(() => props.form?.id, loadData, { immediate: true });
  34. return () => (
  35. <ElTable data={filteredRecords.value} v-loading={loading.value} style="width: 100%">
  36. <ElTableColumn prop="application_no" label="借用编号" />
  37. <ElTableColumn prop="borrower_name" label="借用人" />
  38. <ElTableColumn prop="record_type_label" label="状态" />
  39. <ElTableColumn prop="borrower_type" label="借用类型" />
  40. </ElTable>
  41. );
  42. } catch (e) {
  43. console.error("BorrowRecords setup error:", e);
  44. return () => <div>组件加载失败</div>;
  45. }
  46. }
  47. });
  48. const MaintenanceHistory = defineComponent({
  49. props: { form: Object },
  50. setup(props) {
  51. try {
  52. const records = ref([]);
  53. const loading = ref(false);
  54. const loadData = async () => {
  55. if (!props.form?.id) return;
  56. loading.value = true;
  57. try {
  58. const res = await api.getMaintenanceHistory(props.form.id);
  59. records.value = res.data || [];
  60. } finally {
  61. loading.value = false;
  62. }
  63. };
  64. watch(() => props.form?.id, loadData, { immediate: true });
  65. return () => (
  66. <ElTable data={records.value} v-loading={loading.value} style="width: 100%">
  67. <ElTableColumn prop="damage_no" label="维修编号" />
  68. <ElTableColumn prop="device_name" label="设备名称" />
  69. <ElTableColumn prop="damage_reason" label="损坏原因" />
  70. <ElTableColumn prop="estimated_loss" label="损失价格" />
  71. </ElTable>
  72. );
  73. } catch (e) {
  74. console.error("MaintenanceHistory setup error:", e);
  75. return () => <div>组件加载失败</div>;
  76. }
  77. }
  78. });
  79. export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
  80. const pageRequest = async (query: UserPageQuery) => {
  81. return await api.GetList(query);
  82. };
  83. const editRequest = async ({ form, row }: EditReq) => {
  84. form.id = row.id;
  85. return await api.UpdateObj(form);
  86. };
  87. const delRequest = async ({ row }: DelReq) => {
  88. return await api.DelObj(row.id);
  89. };
  90. const addRequest = async ({ form }: AddReq) => {
  91. return await api.AddObj(form);
  92. };
  93. const selectedIds = ref([]);
  94. const onSelectionChange = (changed) => {
  95. console.log("selection", changed);
  96. selectedIds.value = changed.map((item) => item.id);
  97. };
  98. crudExpose.selectedIds = selectedIds;
  99. return {
  100. // selectedIds,
  101. crudOptions: {
  102. request: {
  103. pageRequest,
  104. addRequest,
  105. editRequest,
  106. delRequest,
  107. },
  108. table: {
  109. rowKey: "id",
  110. onSelectionChange
  111. },
  112. toolbar:{
  113. show:true,
  114. },
  115. rowHandle: {
  116. fixed: 'right',
  117. width: 220,
  118. view: {
  119. show: true,
  120. },
  121. edit: {
  122. show: true,
  123. },
  124. remove: {
  125. show: true,
  126. },
  127. },
  128. actionbar: {
  129. buttons: {
  130. add: {
  131. show: auth('user:Create')
  132. },
  133. // batchimport:{
  134. // text: "批量导入",
  135. // title: "导入",
  136. // // show: auth('user:batchimport'),
  137. // },
  138. // export: {
  139. // text: "导出",
  140. // title: "导出",
  141. // show: auth('user:Export'),
  142. // },
  143. // downloadtemplate: {
  144. // text: "下载模板",
  145. // title: "下载模板",
  146. // // show: auth('user:Export'),
  147. // },
  148. // batchdelete: {
  149. // text: "批量删除",
  150. // title: "批量删除",
  151. // // show: auth('user:Export'),
  152. // },
  153. }
  154. },
  155. columns: {
  156. $checked: {
  157. title: "选择",
  158. form: { show: false },
  159. column: {
  160. type: "selection",
  161. align: "center",
  162. width: "55px",
  163. columnSetDisabled: true, //禁止在列设置中选择
  164. selectable(row, index) {
  165. // return row.id !== 1; //设置第一行不允许选择
  166. return row.id;
  167. }
  168. }
  169. },
  170. _index: {
  171. title: '序号',
  172. form: { show: false },
  173. column: {
  174. align: 'center',
  175. width: '70px',
  176. columnSetDisabled: true,
  177. formatter: (context) => {
  178. let index = context.index ?? 1;
  179. let pagination = crudExpose!.crudBinding.value.pagination;
  180. return ((pagination!.currentPage ?? 1) - 1) * pagination!.pageSize + index + 1;
  181. },
  182. },
  183. },
  184. search:{
  185. title: '关键字搜索',
  186. search: { show: true,type: 'input', },
  187. type: 'input',
  188. form: {
  189. component: { placeholder: '请输入' },
  190. show:false},
  191. column: {show:false}
  192. },
  193. code: {
  194. title: '设备编号',
  195. search: { show: true },
  196. type: 'input',
  197. column: { minWidth: 120 },
  198. form: {
  199. component: { placeholder: '请输入设备编号' },
  200. rules: [{ required: true, message: '请输入设备编号' }],
  201. },
  202. viewForm:{
  203. component: { placeholder: '' },
  204. rules: [{ required: true, message: '' }],
  205. },
  206. },
  207. category_name: {
  208. title: '设备分类',
  209. search: { show: true },
  210. type: 'dict-select',
  211. dict: dict({
  212. url: '/api/system/device/category/?page=1&limit=100',
  213. value: 'id',
  214. label: 'name',
  215. }),
  216. column: { minWidth: 100 },
  217. form: {
  218. show:false,
  219. component: { placeholder: '请选择设备分类' },
  220. rules: [{ required: true, message: '请选择设备分类' }],
  221. },
  222. viewForm:{
  223. component: { placeholder: '' },
  224. rules: [{ required: true, message: '' }],
  225. }
  226. },
  227. name: {
  228. title: '设备名称',
  229. search: { show: false },
  230. type: 'input',
  231. column: { minWidth: 120 },
  232. form: {
  233. component: { placeholder: '请输入设备名称' },
  234. rules: [{ required: true, message: '请输入设备名称' }]
  235. },
  236. viewForm:{
  237. component: { placeholder: '' },
  238. rules: [{ required: true, message: '' }],
  239. }
  240. },
  241. "inventory.borrowed_quantity":{
  242. title: '设备状态',
  243. type: 'text',
  244. search: { show: false },
  245. column: {
  246. show: true,
  247. minWidth: 100,
  248. formatter: ({ row }) => {
  249. const isBorrowed = row.borrowed_quantity > 0;
  250. const label = isBorrowed ? row.total_quantity-row.borrowed_quantity==0?'在借':'在借' : row.total_quantity==0?'在借':'空闲';
  251. const color = isBorrowed ? '#FFA500' : '#4CAF50';
  252. return h(
  253. 'span',
  254. {
  255. style: {
  256. display: 'inline-block',
  257. padding: '2px 8px',
  258. border: `1px solid ${color}`,
  259. borderRadius: '4px',
  260. backgroundColor: color,
  261. color: 'white',
  262. fontWeight: 'bold',
  263. }
  264. },
  265. label
  266. );
  267. }
  268. },
  269. form: {
  270. show:false,
  271. component: { placeholder: '请选择设备状态' },
  272. rules: [{ required: true, message: '请选择设备状态' }],
  273. },
  274. },
  275. borrowed_quantity:{
  276. title:"已借出",
  277. type:'input',
  278. search:{show:false},
  279. column: { minWidth: 100 },
  280. form: {
  281. show:false,
  282. component: { placeholder: '请输入已借出' },
  283. rules: [{ required: true, message: '请输入已借出' }],
  284. },
  285. viewForm:{
  286. component: { placeholder: '' },
  287. rules: [{ required: true, message: '' }],
  288. }
  289. },
  290. brand:{
  291. title:'品牌',
  292. type:"input",
  293. column: { minWidth: 100 },
  294. form: {
  295. component: { placeholder: '请输入品牌' }
  296. },
  297. viewForm:{
  298. component: { placeholder: '' },
  299. rules: [{ required: true, message: '' }],
  300. },
  301. },
  302. specification: {
  303. title: '规格型号',
  304. search: { show: false },
  305. type: 'input',
  306. column: { minWidth: 100 },
  307. form: {
  308. component: { placeholder: '请输入规格型号' }
  309. },
  310. viewForm:{
  311. component: { placeholder: '' },
  312. rules: [{ required: true, message: '' }],
  313. },
  314. },
  315. serial_number:{
  316. title:'序列号',
  317. type:"input",
  318. column: { minWidth: 100 },
  319. form: {
  320. component: { placeholder: '请输入序列号' }
  321. },
  322. viewForm:{
  323. component: { placeholder: '' },
  324. rules: [{ required: true, message: '' }],
  325. },
  326. },
  327. total_quantity:{
  328. title:'库存数量',
  329. type:"input",
  330. column: { minWidth: 100 },
  331. addForm:{
  332. show:false,
  333. },
  334. viewForm:{
  335. show:false,
  336. },
  337. editForm:{show:false},
  338. from:{
  339. show:false,
  340. component: { placeholder: '请输入库存数量' },
  341. rules: [{ required: false, message: '请输入库存数量' }],
  342. }
  343. },
  344. /* warehouse:{
  345. title:'存放仓库',
  346. search: { show: true },
  347. type:"dict-select",
  348. column: { minWidth: 150 },
  349. dict: dict({
  350. url: '/api/system/warehouse/',
  351. value: 'id',
  352. label: 'name'
  353. }),
  354. form: {
  355. component: { placeholder: '请选择存放仓库' },
  356. rules: [{ required: true, message: '请选择存放仓库' }],
  357. }
  358. }, */
  359. status:{
  360. title:'是否可见',
  361. type: 'dict-switch',
  362. column: {
  363. show:false,
  364. minWidth: 90,
  365. component: {
  366. name: 'fs-dict-switch',
  367. activeText: '',
  368. inactiveText: '',
  369. style: '--el-switch-on-color: var(--el-color-primary); --el-switch-off-color: #dcdfe6',
  370. onChange: compute((context) => {
  371. return () => {
  372. api.UpdateObjStatus(context.row).then((res:APIResponseData) => {
  373. successMessage(res.msg as string);
  374. });
  375. };
  376. }),
  377. },
  378. },
  379. dict: dict({
  380. data: dictionary('device_button_status_bool'),
  381. }),
  382. form: {
  383. show:false,
  384. rules: [{ required: true, message: '请选择是否可见' }],
  385. },
  386. viewForm:{
  387. component: { placeholder: '' },
  388. rules: [{ required: true, message: '' }],
  389. }
  390. },
  391. status_label:{
  392. title: '当前状态',
  393. search: { show: false },
  394. form: {
  395. show:false,
  396. }
  397. },
  398. department: {
  399. title: '所属部门',
  400. search: { show: false },
  401. type: 'input',
  402. column: { minWidth: 120 },
  403. form: {
  404. component: { placeholder: '请输入所属部门' }
  405. },
  406. viewForm:{
  407. component: { placeholder: '' },
  408. rules: [{ required: true, message: '' }],
  409. }
  410. },
  411. purchase_date: {
  412. title: '采购时间',
  413. type: 'date',
  414. search: { show: false },
  415. column: {
  416. minWidth: 120,
  417. formatter: ({ value }) => (value ? dayjs(value).format('YYYY-MM-DD') : ''),
  418. },
  419. form: {
  420. component: { placeholder: '请选择采购时间' },
  421. rules: [{ required: false, message: '请选择采购时间' }],
  422. },
  423. viewForm:{
  424. component: { placeholder: '' },
  425. rules: [{ required: true, message: '' }],
  426. },
  427. valueResolve({ form, value }) {
  428. form.purchase_date = value ? dayjs(value).format('YYYY-MM-DD') : null;
  429. },
  430. valueBuilder({ form, value }) {
  431. form.purchase_date = value;
  432. },
  433. },
  434. supplier: {
  435. title: '供应商',
  436. type: 'dict-select',
  437. search: { show: true },
  438. column: {
  439. minWidth: 120,
  440. },
  441. dict: dict({
  442. url: '/api/system/supplier/',
  443. value: 'id',
  444. label: 'name'
  445. }),
  446. form: {
  447. component: { placeholder: '请输入供应商' },
  448. },
  449. viewForm:{
  450. component: { placeholder: '' },
  451. rules: [{ required: true, message: '' }],
  452. }
  453. },
  454. price: {
  455. title: '价格',
  456. type: 'number',
  457. search: { show: false },
  458. column: { minWidth: 100 },
  459. form: {
  460. component: { placeholder: '请输入设备购入价格' },
  461. rules: [{ required: false, message: '请输入设备购入价格' }],
  462. },
  463. viewForm:{
  464. component: { placeholder: '' },
  465. rules: [{ required: true, message: '' }],
  466. }
  467. },
  468. warranty_expiration: {
  469. title: '质保到期',
  470. type: 'date',
  471. search: { show: false },
  472. column: { minWidth: 120 ,
  473. formatter: ({ value }) => (value ? dayjs(value).format('YYYY-MM-DD') : ''),
  474. },
  475. viewForm: {
  476. title: '质保到期',
  477. component: {
  478. render({ value }) {
  479. // eslint-disable-next-line no-console
  480. console.log("valuevaluevalue:::",value);
  481. if (!value) return '';
  482. const formatted = dayjs(value).format('YYYY-MM-DD');
  483. const isExpired = dayjs(value).isBefore(dayjs(), 'day');
  484. return h(
  485. 'span',
  486. {
  487. style: {
  488. color: isExpired ? 'red' : 'inherit',
  489. fontWeight: isExpired ? 'bold' : 'normal'
  490. }
  491. },
  492. isExpired ? `${formatted}(已过期)` : formatted
  493. );
  494. },
  495. showHtml: true,
  496. }
  497. },
  498. form: {
  499. component: { placeholder: '请选择质保到期' },
  500. rules: [{ required: false, message: '请选择质保到期' }],
  501. },
  502. valueResolve({ form, value }) {
  503. form.warranty_expiration = value ? dayjs(value).format('YYYY-MM-DD') : null;
  504. },
  505. valueBuilder({ form, value }) {
  506. form.warranty_expiration = value;
  507. },
  508. },
  509. tenant_id:{
  510. title: '租户id',
  511. type: 'input',
  512. value: 1,
  513. column: {
  514. show:false,
  515. minWidth: 120,
  516. },
  517. // dict: dict({
  518. // url: '/api/system/tenant/list/',
  519. // value: 'id',
  520. // label: 'name'
  521. // }),
  522. form: {
  523. value:1,
  524. show:false,
  525. component: { placeholder: '请填租户id' },
  526. rules: [{ required: false, message: '请填租户id' }],
  527. },
  528. viewForm:{
  529. component: { placeholder: '' },
  530. rules: [{ required: true, message: '' }],
  531. }
  532. },
  533. category:{
  534. show: false,
  535. title: '设备类别',
  536. search: { show: false },
  537. type: 'dict-select',
  538. column: { show: false,minWidth: 120 },
  539. dict: dict({
  540. url: '/api/system/device/category/?page=1&limit=100',
  541. value: 'id',
  542. label: 'name'
  543. }),
  544. form: {
  545. component: {
  546. placeholder: '请输入设备类别',
  547. filterable: true
  548. },
  549. rules: [{ required: true, message: '请输入设备类别' }],
  550. },
  551. viewForm:{
  552. component: { placeholder: '' },
  553. rules: [{ required: true, message: '' }],
  554. }
  555. },
  556. quantity:{
  557. show: false,
  558. title: '库存数量',
  559. search: { show: false },
  560. type: 'input',
  561. column: { show: false,minWidth: 120 },
  562. viewForm:{
  563. component:{placeholder:""}
  564. },
  565. form: {
  566. component: { placeholder: '请输入库存数量' },
  567. rules: [{ required: true, message: '请输入库存数量' }],
  568. valueResolve({ form, value }) {
  569. form.quantity = Number(value ?? 0);
  570. },
  571. valueBuilder({ form, value }) {
  572. // form.tags =Array.from(String(value), Number);
  573. form.quantity = form.total_quantity;
  574. }
  575. }
  576. },
  577. tags:{
  578. show: false,
  579. title: '设备标签',
  580. search: { show: false },
  581. type: 'dict-select',
  582. column: { show: false,minWidth: 120 },
  583. dict: dict({
  584. url: '/api/system/device_tags/?page=1&limit=100',
  585. value: 'id',
  586. label: 'name'
  587. }),
  588. viewForm:{
  589. component:{placeholder:""}
  590. },
  591. form: {
  592. component: { placeholder: '请选择设备标签',
  593. filterable: true
  594. },
  595. rules: [{ required: true, message: '请选择设备标签' }],
  596. valueResolve({ form, value }) {
  597. form.tags = Array.from(String(value), Number);
  598. },
  599. valueBuilder({ form, value }) {
  600. if(value){
  601. form.tags = Array.isArray(value)? value.length > 0 ? Number(value[0]) : '': Number(value);
  602. }
  603. },
  604. }
  605. },
  606. image:{
  607. title: '设备图片',
  608. type: 'image-uploader',
  609. column: {
  610. minWidth: 120,
  611. show:false,
  612. },
  613. form: {
  614. show: auth('image:upload'),
  615. component: {
  616. uploader: {
  617. type: 'form',
  618. limit: 1,
  619. action: '/api/system/device/upload-image/',
  620. accept: ".jpg,.png",
  621. uploadRequest: async ({ action, file, onProgress }) => {
  622. const token = Cookies.get('token');
  623. const data = new FormData();
  624. data.append("image", file);
  625. return await request({
  626. url: action,
  627. method: "post",
  628. timeout: 60000,
  629. headers: {
  630. "Content-Type": "multipart/form-data",
  631. "Authorization": token ? `JWT ${token}` : ''
  632. },
  633. data,
  634. onUploadProgress: (p) => {
  635. onProgress({ percent: Math.round((p.loaded / p.total) * 100) });
  636. }
  637. });
  638. },
  639. successHandle(ret) {
  640. // eslint-disable-next-line no-console
  641. // console.log("ret.data.image_url:::",ret.data.image_url);
  642. return {
  643. url: getBaseURL(ret.data.image_url),
  644. key: ret.data.id,
  645. ...ret.data
  646. };
  647. },
  648. }
  649. },
  650. },
  651. valueBuilder({ row, key }) {
  652. return row[key] ? [row[key]] : [];
  653. },
  654. valueResolve({ form, key }) {
  655. // eslint-disable-next-line no-console
  656. console.log("form[key]:::",form[key]);
  657. form[key] = Array.isArray(form[key]) ? form[key][0] : form[key];
  658. }
  659. },
  660. id: {
  661. title: 'deviceid',
  662. search: { show: false },
  663. type: 'input',
  664. column: { show:false,minWidth: 120 },
  665. form: {
  666. show:false,
  667. component: { placeholder: '请输入维修' }
  668. },
  669. viewForm:{
  670. show:true,
  671. component: { placeholder: '' },
  672. rules: [{ required: true, message: '' }],
  673. }
  674. },
  675. },
  676. viewForm: {
  677. wrapper: {
  678. buttons: {
  679. ok:{
  680. text:'提交',
  681. show:false
  682. }
  683. }
  684. },
  685. row: { gutter: 20 },
  686. group: {
  687. groupType: "tabs",
  688. groups: {
  689. base: {
  690. slots: {
  691. label: (scope) => {
  692. return (
  693. <span style={{ color: scope.hasError ? "red" : "green" }}>
  694. <fs-icon icon={"ion:checkmark-circle"} />
  695. 基础信息
  696. </span>
  697. );
  698. }
  699. },
  700. icon: "el-icon-goods",
  701. columns: ["code","category_name", "name", "status","borrowed_quantity","brand","specification","serial_number","warehouse","is_visible","department","purchase_date","supplier","price","warranty_expiration","tenant_id","category","quantity","tags","image"]
  702. },
  703. borrow: {
  704. show:true,
  705. label: "借用记录",
  706. icon: "el-icon-price-tag",
  707. columns: ["id"],
  708. slots: {
  709. default: (scope) => {
  710. const currentRow = crudExpose.getFormData();
  711. console.log("currentRow::",currentRow);
  712. return <BorrowRecords form={currentRow || {}}/>;
  713. }
  714. }
  715. },
  716. maintenance: {
  717. show:true,
  718. label: "维修记录",
  719. icon: "el-icon-warning-outline",
  720. columns: ["id"],
  721. slots: {
  722. default: (scope) => {
  723. const currentRowwx = crudExpose.getFormData();
  724. console.log("currentRowwx::",currentRowwx);
  725. return <MaintenanceHistory form={currentRowwx || {}} />
  726. }
  727. }
  728. },
  729. maintain: {
  730. label: "保养记录",
  731. collapsed: true,
  732. icon: "el-icon-warning-outline",
  733. columns: []
  734. },
  735. datastatis: {
  736. label: "数据统计",
  737. collapsed: true,
  738. icon: "el-icon-warning-outline",
  739. columns: []
  740. },
  741. }
  742. }
  743. },
  744. },
  745. };
  746. };