transferItems.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <template>
  2. <vxe-modal v-model="dialogVisible" :title="title" show-zoom resize show-footer destroy-on-close transfer @hide="close" :width="width">
  3. <template #default>
  4. <div style="height: 80vh" class="d-flex flex-cln">
  5. <div class="pd-16 border-bottom d-flex j-sb pb-4">
  6. <div>
  7. <searchTabs v-model="tabActive" :list="[{ label: '按检测项目', value: '1' }, { label: '按执行标准', value: '2' }]" :is-num="false"></searchTabs>
  8. </div>
  9. <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="auto">
  10. <el-form-item label="检测项名称:" prop="name">
  11. <el-input v-model="queryParams.name" placeholder="请输入检测项名称关键字" clearable style="width: 180px" @keyup.enter="handleQuery" />
  12. </el-form-item>
  13. <el-form-item>
  14. <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
  15. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  16. </el-form-item>
  17. </el-form>
  18. </div>
  19. <div class="flex1 ov-hd d-flex content-border">
  20. <div class="tree-wrap">
  21. <div v-show="tabActive === '1'">
  22. <el-tree ref="treeItemsRef" class="base-tree-tabs" auto-expand-parent default-expand-all node-key="id" :data="itemsData" :props="{ label: 'name' }" highlight-current @node-click="itemsClick">
  23. <template #default="{ node, data }">
  24. <span>
  25. {{ node.label }}
  26. <template v-if="!data?.children">({{ data?.itemCount || '0' }})</template>
  27. </span>
  28. </template>
  29. </el-tree>
  30. </div>
  31. <div v-show="tabActive === '2'">
  32. <el-tree ref="treeStandardsRef" class="base-tree-tabs" auto-expand-parent :data="standards" node-key="id" :props="{ label: 'name' }" highlight-current @node-click="standardsClick">
  33. <template #default="{ node, data }">
  34. <span>
  35. {{ node.label }}
  36. <template v-if="!data?.children">({{ data?.itemCount || '0' }})</template>
  37. </span>
  38. </template>
  39. </el-tree>
  40. </div>
  41. </div>
  42. <div class="flex1 ov-hd d-flex flex-cln">
  43. <div class="flex1 ov-hd">
  44. <vxe-table ref="tableLeftRef" :loading="loading" border :data="list" height="100%" :column-config="{ resizable: true }" :row-config="{keyField: 'id',isCurrent: true, isHover: true}" :checkbox-config="{ highlight: true, range: true, trigger: 'row', reserve: true }">
  45. <vxe-column type="checkbox" width="60"></vxe-column>
  46. <!-- 序号 -->
  47. <vxe-column type="seq" width="60" title="序号" align="center" />
  48. <vxe-column title="检测项目" align="center" field="name" min-width="100" :formatter="colNoData" />
  49. <vxe-column title="单价(元、批次)" align="center" field="price" min-width="100" :formatter="colNoData" />
  50. </vxe-table>
  51. </div>
  52. </div>
  53. <div class="d-flex j-c a-c flex-cln pd-10">
  54. <el-button type="primary" @click="transferRight">
  55. <el-icon>
  56. <DArrowRight />
  57. </el-icon>
  58. </el-button>
  59. <div class="pd-10"></div>
  60. <el-button type="primary" @click="transferLeft">
  61. <el-icon>
  62. <DArrowLeft />
  63. </el-icon>
  64. </el-button>
  65. </div>
  66. <div class="flex1 ov-hd d-flex flex-cln">
  67. <div class="flex1 ov-hd">
  68. <vxe-table ref="tableRightRef" :loading="loading" border :data="targetList" height="100%" :column-config="{ resizable: true }" :row-config="{ keyField: 'id', isCurrent: true, isHover: true }" :checkbox-config="{ highlight: true, range: true, trigger: 'row', reserve: true }">
  69. <vxe-column type="checkbox" width="60"></vxe-column>
  70. <vxe-column type="seq" width="60" title="序号" align="center" />
  71. <vxe-column title="检测项目" align="center" field="name" min-width="100" :formatter="colNoData" />
  72. <vxe-column title="单价(元、批次)" align="center" field="price" min-width="100" :formatter="colNoData" />
  73. </vxe-table>
  74. </div>
  75. </div>
  76. </div>
  77. </div>
  78. </template>
  79. <template #footer>
  80. <el-button @click="close">取消</el-button>
  81. <el-button type="primary" @click="submitForm">确认添加</el-button>
  82. </template>
  83. </vxe-modal>
  84. </template>
  85. <script setup name="transferItems" lang="ts">
  86. import { itemsList, itemsListByTree, itemsStandardList } from '@/api/cdt/items';
  87. import { colNoData } from '@/utils/noData';
  88. import { propTypes } from '@/utils/propTypes';
  89. import { searchTabs } from '@/views/models';
  90. import { FormInstance } from 'element-plus';
  91. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  92. const dialogVisible = ref(false);
  93. const emit = defineEmits(['update:show', 'close', 'change']);
  94. const props = defineProps({
  95. show: propTypes.bool.def(false),
  96. title: propTypes.string.def('选择检测项目'),
  97. width: propTypes.string.def('80vw'),
  98. info: propTypes.any.def(null),
  99. items: propTypes.array.def([]) // 已选检测项目
  100. });
  101. const treeItemsRef = ref<any>();
  102. const treeStandardsRef = ref<any>();
  103. const tabActive = ref('1');
  104. const queryParams = ref<any>({
  105. pageNum: 1,
  106. pageSize: 300,
  107. name: '',
  108. itemTypeId: '',
  109. packageId: ''
  110. });
  111. const loading = ref(false);
  112. const total = ref(0);
  113. const list = ref<any>([]);
  114. const targetList = ref<any>([]);
  115. const itemsData = ref<any>([]);
  116. // 是否打开过刚才id
  117. const mapIdsOpened = ref<any>({});
  118. const getList = async () => {
  119. loading.value = true;
  120. const res = await itemsList(queryParams.value);
  121. if (!res || res.code !== 200) return;
  122. res.rows.forEach(element => {
  123. mapIdsOpened.value[element.id] = true;
  124. });
  125. list.value = res.rows
  126. total.value = res.total;
  127. loading.value = false;
  128. tableLeftRef.value?.setCheckboxRow(targetList.value, true);
  129. };
  130. const handleQuery = () => {
  131. queryParams.value.pageNum = 1;
  132. getList();
  133. };
  134. const resetQuery = () => {
  135. queryParams.value = {
  136. pageNum: 1,
  137. pageSize: 10,
  138. name: '',
  139. itemTypeId: '',
  140. packageId: ''
  141. };
  142. // 重置树高亮
  143. treeItemsRef.value?.setCurrentKey(null, true);
  144. treeStandardsRef.value?.setCurrentKey(null, true);
  145. handleQuery();
  146. };
  147. const getTabsTree = async () => {
  148. const res = await itemsListByTree();
  149. if (!res || res.code !== 200) return;
  150. itemsData.value = res.data;
  151. };
  152. const itemsClick = (data: any) => {
  153. if (!data.children) {
  154. queryParams.value.itemTypeId = data.id;
  155. queryParams.value.packageId = '';
  156. }
  157. handleQuery();
  158. };
  159. const standardsClick = (data: any) => {
  160. if (!data.children) {
  161. queryParams.value.packageId = data.id;
  162. queryParams.value.itemTypeId = '';
  163. }
  164. handleQuery();
  165. };
  166. // 查询执行标准列表
  167. const standards = ref<any>([]);
  168. const getStandardList = async () => {
  169. const res = await itemsStandardList({ pageNum: 1, pageSize: 10000 });
  170. if (!res || res.code !== 200) return;
  171. standards.value = res.rows;
  172. };
  173. const formRef = ref<FormInstance>();
  174. const close = () => {
  175. emit('update:show', false);
  176. emit('close', false);
  177. };
  178. const submitForm = async () => {
  179. try {
  180. if (!targetList.value.length) {
  181. // 抛出错误
  182. return proxy.$modal.msgWarning('请选择检测项目');
  183. }
  184. emit('change', targetList.value);
  185. close();
  186. } catch (error) {
  187. proxy.$modal.msgWarning(error);
  188. }
  189. };
  190. const tableLeftRef = ref<any>();
  191. const tableRightRef = ref<any>();
  192. const transferRight = () => {
  193. // 去重
  194. const newsList = tableLeftRef.value?.getCheckboxReserveRecords(true).concat(tableLeftRef.value?.getCheckboxRecords());
  195. // 过滤没打开过的
  196. const noOpenList = targetList.value.filter((item: any) => !mapIdsOpened.value[item.id]);
  197. // 合并newsList和noOpenList
  198. targetList.value = newsList
  199. };
  200. const transferLeft = () => {
  201. targetList.value = targetList.value.filter((item: any) => !tableRightRef.value?.getCheckboxRecords().includes(item));
  202. tableLeftRef.value?.setCheckboxRow(tableRightRef.value?.getCheckboxRecords(), false);
  203. tableRightRef.value.setCheckboxRow(tableRightRef.value?.getCheckboxRecords(), false);
  204. };
  205. const checkRowKeys = ref<any>([]);
  206. watch(
  207. () => props.show,
  208. (val) => {
  209. dialogVisible.value = val;
  210. if (val) {
  211. getTabsTree();
  212. getStandardList();
  213. }
  214. },
  215. { immediate: true }
  216. );
  217. watch(
  218. () => props.items,
  219. (val) => {
  220. console.log(val);
  221. if (val && val.length) {
  222. targetList.value = val;
  223. } else {
  224. targetList.value = [];
  225. checkRowKeys.value = [];
  226. }
  227. },
  228. { immediate: true }
  229. );
  230. </script>
  231. <style lang="scss" scoped>
  232. .tree-wrap {
  233. width: 310px;
  234. box-sizing: border-box;
  235. border-right: 1px solid #ebeef5;
  236. padding: 16px;
  237. }
  238. .content-border {
  239. border: 1px solid var(--border-color);
  240. }
  241. </style>