sd-treedata-dictionary.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. <template>
  2. <a-card>
  3. <div :class="$style.action">
  4. <a-input-search @search="onSearch" />
  5. <span v-if="!module && !appNodeId">
  6. <a-button type="primary" @click="addNode">新建</a-button>
  7. <a-button v-if="!appId" @click="importVisible = true">导入</a-button>
  8. <a-button v-if="!appId" :disabled="!selectedRows.length" @click="exportTree">导出</a-button>
  9. <a-button :disabled="!selectedRows.length" @click="delNode">删除</a-button>
  10. </span>
  11. </div>
  12. <a-table
  13. :key="key"
  14. :columns="columns"
  15. :data-source="searchData || treeData"
  16. :expanded-row-keys.sync="expandedRowKeys"
  17. :replace-fields="replaceFields"
  18. row-key="id"
  19. :row-selection="rowSelection"
  20. :pagination="false"
  21. @expand="expand"
  22. >
  23. <a
  24. v-if="!module && !appNodeId"
  25. slot="name"
  26. slot-scope="text, record"
  27. @click="editNode(record)"
  28. >{{ text }}</a
  29. >
  30. <span v-else>{{ text }}</span>
  31. </a-table>
  32. <a-modal v-model="formVisible" title="数据字典" destroy-on-close @ok="handleOk">
  33. <sd-form ref="form" :init-values="pageFormData">
  34. <template>
  35. <sd-form-item name="appId" hidden :input-props="{ defaultValue: appId }"></sd-form-item>
  36. <sd-form-item name="id" hidden />
  37. <sd-form-item name="parentCode" hidden :input-props="{ defaultValue: -1 }">
  38. </sd-form-item>
  39. <a-form-item label="上级分类">
  40. <sd-tree-picker
  41. v-model="parentCat"
  42. single
  43. :load-tree-data="loadTreeData"
  44. @change="parentCatChange"
  45. ></sd-tree-picker>
  46. </a-form-item>
  47. <sd-form-item name="categoryName" />
  48. <sd-form-item name="categoryCode" />
  49. <sd-form-item name="sortNumber" />
  50. </template>
  51. </sd-form>
  52. </a-modal>
  53. <a-modal v-model="importVisible" title="导入树形字典" :footer="null" :width="700">
  54. <a-upload-dragger :file-list="[]" :custom-request="(file) => uploadExcel(file)">
  55. <p class="ant-upload-drag-icon">
  56. <a-icon type="inbox" />
  57. </p>
  58. <p class="ant-upload-text">
  59. 点击或将文件拖拽到这里上传
  60. </p>
  61. </a-upload-dragger>
  62. </a-modal>
  63. </a-card>
  64. </template>
  65. <script>
  66. import qs from 'qs'
  67. import axios from '@/common/services/axios-instance'
  68. import { Modal, Message } from 'ant-design-vue'
  69. import download from '@/common/services/download'
  70. import components from './_import-components/sd-treedata-dictionary-import'
  71. const columns = [
  72. {
  73. title: '名称',
  74. dataIndex: 'name',
  75. key: 'name',
  76. scopedSlots: { customRender: 'name' },
  77. },
  78. {
  79. title: '编码',
  80. dataIndex: 'code',
  81. key: 'code',
  82. },
  83. ]
  84. const handleTreeData = (arr, picker) => {
  85. return arr.map((item) => {
  86. item.isLeaf = item.leaf
  87. if (!item.leaf && !picker) item.children = []
  88. return item
  89. })
  90. }
  91. const searchParentNode = (arr, parentCode) => {
  92. let node = {}
  93. arr.forEach((a) => {
  94. if (a.id === parentCode) {
  95. node = a
  96. } else if (a.children) node = { ...searchParentNode(a.children, parentCode), ...node }
  97. })
  98. return node
  99. }
  100. export default {
  101. name: 'SdTreedataDictionary',
  102. metaInfo: {
  103. title: '树形字典',
  104. },
  105. components,
  106. props: {
  107. appId: {
  108. type: String,
  109. default: '',
  110. },
  111. module: {
  112. type: Boolean,
  113. default: false,
  114. },
  115. appNodeId: {
  116. type: String,
  117. default: undefined,
  118. },
  119. },
  120. data() {
  121. return {
  122. columns,
  123. treeData: [],
  124. expandedRowKeys: [],
  125. searchData: null,
  126. pageFlowId: '',
  127. importVisible: false,
  128. pageFormData: [],
  129. selectedRows: [],
  130. formVisible: false,
  131. disabledKeys: [],
  132. exporting: false,
  133. replaceFields: {
  134. key: 'id',
  135. title: 'name',
  136. },
  137. key: 0,
  138. parentCat: [],
  139. searchVal: '',
  140. }
  141. },
  142. computed: {
  143. rowSelection() {
  144. return {
  145. selectedRowKeys: this.selectedRows?.map((row) => row.id) || [],
  146. onChange: (selectedRowKeys, selectedRows) => {
  147. this.selectChange(selectedRowKeys, selectedRows)
  148. },
  149. }
  150. },
  151. },
  152. created() {
  153. this.getTreeData()
  154. },
  155. methods: {
  156. uploadExcel(file) {
  157. const formData = new FormData()
  158. formData.append('file', file.file)
  159. axios
  160. .post('api/framework/v1/onl-treemodule/excel-import', formData, {
  161. headers: {
  162. 'Content-Type': 'multipart/form-data',
  163. },
  164. })
  165. .then((res) => {
  166. Message.success('导入成功')
  167. })
  168. .catch((err) => {
  169. Message.error(err.response?.data?.errors || '导入失败')
  170. })
  171. .finally(() => {
  172. this.importVisible = false
  173. this.getTreeData()
  174. })
  175. },
  176. exportTree() {
  177. if (this.selectedRows.find((row) => row.parentCode !== -1)) {
  178. Message.warn('导出时只能选择根节点节点,请重新选择!')
  179. return
  180. }
  181. const ids = this.selectedRows.map((row) => row.props?.beanId).join()
  182. const url = `api/framework/v1/onl-treemodule/excel-export?ids=${ids}`
  183. const fname = '树形字典.xls'
  184. download(url, fname)
  185. this.selectedRows = []
  186. },
  187. onSearch(value) {
  188. this.searchVal = value
  189. const expressions = [
  190. {
  191. dataType: 'str',
  192. name: 'categoryName',
  193. op: 'like',
  194. stringValue: `%${this.searchVal}%`,
  195. },
  196. ]
  197. if (this.appId) {
  198. expressions.push({
  199. dataType: 'str',
  200. name: 'appId',
  201. op: 'eq',
  202. stringValue: this.appId,
  203. })
  204. } else {
  205. expressions.push({
  206. dataType: 'str',
  207. name: 'appId',
  208. op: 'is_null',
  209. })
  210. }
  211. if (value) {
  212. const params = {
  213. columns: 'categoryName,categoryCode,parentCode',
  214. expressions,
  215. formId: 'weOnlTreeModule',
  216. maxResults: -1,
  217. startPosition: 0,
  218. }
  219. axios.post('api/framework/v1/page/businessList', params).then((res) => {
  220. if (res.data.data) {
  221. this.searchData = res.data.data.map((item) => {
  222. return {
  223. id: item.id,
  224. code: item.categoryCode,
  225. name: item.categoryName,
  226. parentCode: item.parentCode,
  227. props: {
  228. beanId: item.id,
  229. },
  230. }
  231. })
  232. }
  233. })
  234. } else this.searchData = null
  235. },
  236. parentCatChange(parentCat) {
  237. this.$refs.form.setFieldValue('parentCode', parentCat.length ? parentCat[0].id : -1)
  238. },
  239. loadTreeData(parentCatId) {
  240. const fun = parentCatId ? this.getChildrenNode(parentCatId, true) : this.getRootNode(true)
  241. return fun.then((res) => {
  242. return res.filter((r) => !this.disabledKeys.includes(r.id))
  243. })
  244. },
  245. getRootNode(picker) {
  246. let params = { appId: this.appId, appNodeId: this.appNodeId }
  247. params = qs.stringify(params, { filter: (prefix, value) => value || undefined })
  248. return axios
  249. .get(
  250. `api/framework/v1/onl-treemodule/categories/topCategoryList${params ? '?' + params : ''}`
  251. )
  252. .then((res) => {
  253. return handleTreeData(res.data, picker)
  254. })
  255. },
  256. getTreeData() {
  257. this.searchData = null
  258. this.searchVal = ''
  259. this.getRootNode().then((res) => {
  260. if (res)
  261. this.treeData = res.map((r) => {
  262. r.parentCode = -1
  263. return r
  264. })
  265. })
  266. },
  267. expand(expanded, record) {
  268. if (expanded) {
  269. this.refreshNode(record)
  270. } else {
  271. this.expandedRowKeys = this.expandedRowKeys.filter((key) => key !== record.id)
  272. }
  273. },
  274. getChildrenNode(id, picker) {
  275. return axios
  276. .get(
  277. `api/framework/v1/onl-treemodule/categories/categoryTree?parentCode=${id}${
  278. this.appNodeId ? '&appNodeId=' + this.appNodeId : ''
  279. }`
  280. )
  281. .then((res) => {
  282. return handleTreeData(res.data, picker)
  283. })
  284. },
  285. refreshNode(record) {
  286. this.getChildrenNode(record.id).then((res) => {
  287. if (res.length) {
  288. record.children = res.map((r) => {
  289. r.parentCode = record.id
  290. return r
  291. })
  292. record.isLeaf = false
  293. this.expandedRowKeys.push(record.id)
  294. this.expandedRowKeys = this.expandedRowKeys.filter(
  295. (key) => res.findIndex((r) => r.id === key) === -1
  296. )
  297. } else {
  298. record.children = null
  299. res.isLeaf = true
  300. this.expandedRowKeys = this.expandedRowKeys.filter((key) => key !== record.id)
  301. }
  302. const replaceNode = (arr) => {
  303. arr.forEach((item, index) => {
  304. if (item.id === record.id) {
  305. arr[index] = record
  306. }
  307. if (item.children) replaceNode(item.children)
  308. })
  309. }
  310. replaceNode(this.treeData)
  311. this.key++
  312. })
  313. },
  314. addNode() {
  315. this.disabledKeys = []
  316. this.parentCat = []
  317. this.getDetail()
  318. },
  319. getDetail(id) {
  320. axios
  321. .get(
  322. `api/xcoa-mobile/v1/page/wp/base/onltreemodule/weOnlTreeModule${id ? '?id=' + id : ''}`
  323. )
  324. .then((res) => {
  325. this.pageFlowId = res.data.attrs?.pageflowId
  326. this.pageFormData = res.data.pageFormData.pageFieldInfos
  327. this.formVisible = true
  328. this.$nextTick(() => {
  329. this.parentCatChange(this.parentCat)
  330. })
  331. })
  332. },
  333. editNode(record) {
  334. this.disabledKeys = [record.id]
  335. const parentCat = searchParentNode(this.treeData, record.parentCode)
  336. this.parentCat = parentCat.id ? [parentCat] : []
  337. this.getDetail(record.props?.beanId)
  338. },
  339. handleOk() {
  340. const parentCode = this.$refs.form.getFieldValue('parentCode')
  341. const nodeId = this.$refs.form.getFieldValue('id')
  342. const params = {
  343. eventId: 'save',
  344. inputs: this.$refs.form.getBackendValues(),
  345. pageFlowId: this.pageFlowId,
  346. pagePath: '/base/onltreemodule/weOnlTreeModule',
  347. }
  348. axios
  349. .post('api/framework/v1/page/handleData', params)
  350. .then((res) => {
  351. if (res.data) {
  352. if (this.searchData) {
  353. this.onSearch(this.searchVal)
  354. } else {
  355. const record = searchParentNode(this.treeData, parentCode)
  356. this.delOldNode(this.treeData, nodeId)
  357. if (parentCode !== -1 && record.id) {
  358. this.refreshNode(record)
  359. } else {
  360. this.getTreeData()
  361. this.expandedRowKeys = []
  362. }
  363. }
  364. this.formVisible = false
  365. }
  366. })
  367. .catch((err) => {
  368. Message.error(err.response?.data?.message || '保存失败')
  369. })
  370. },
  371. delOldNode(treeData, targetId) {
  372. for (let i = 0; i < treeData.length; i++) {
  373. const node = treeData[i]
  374. if (node.id === targetId) treeData.splice(i, 1)
  375. else if (node.children) this.delOldNode(node.children, targetId)
  376. }
  377. },
  378. selectChange(selectedRowKeys, selectedRows) {
  379. this.selectedRows = selectedRows
  380. },
  381. delNode() {
  382. const ids = this.selectedRows.map((row) => row.props?.beanId).toString()
  383. Modal.confirm({
  384. content: '确认删除该项及其所有子项吗?',
  385. onOk: () => {
  386. axios.delete(`api/framework/v1/page/weOnlTreeModule?ids=${ids}`).then((res) => {
  387. if (this.searchData) this.onSearch(this.searchVal)
  388. else {
  389. this.selectedRows.forEach((row) => {
  390. const parentCode = row.parentCode
  391. if (parentCode === -1) {
  392. this.treeData = this.treeData.filter((t) => t.id !== row.id)
  393. } else {
  394. const parentNode = searchParentNode(this.treeData, row.parentCode)
  395. this.refreshNode(parentNode)
  396. }
  397. })
  398. }
  399. })
  400. },
  401. })
  402. },
  403. },
  404. }
  405. </script>
  406. <style module lang="scss">
  407. @use '@/common/design' as *;
  408. .action {
  409. position: relative;
  410. z-index: 100;
  411. float: right;
  412. :global .ant-btn {
  413. margin: 0 0 10px 10px;
  414. }
  415. :global .ant-input-search {
  416. width: auto;
  417. }
  418. }
  419. </style>