index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. <template>
  2. <div :class="prefixCls" class="m-5 bg-white">
  3. <header class="p-3">
  4. <AButton type="primary" size="small" @click="toggleModal('add')">添加目录</AButton>
  5. <AButton type="primary" class="ml-2" size="small" @click="toggleModal('edit')">编辑</AButton>
  6. <AButton type="primary" class="ml-2" size="small" @click="handleTreeOpreate('upSerial')"
  7. >上移</AButton
  8. >
  9. <AButton type="primary" class="ml-2" size="small" @click="handleTreeOpreate('downSerial')"
  10. >下移</AButton
  11. >
  12. </header>
  13. <section class="p-3">
  14. <ATable
  15. v-if="treeData.length"
  16. :data-source="treeData"
  17. :columns="columns"
  18. size="small"
  19. row-key="id"
  20. :pagination="false"
  21. :defaultExpandAllRows="true"
  22. :custom-row="customRow"
  23. :row-class-name="rowClassName"
  24. >
  25. <template #name="{ text, record }">
  26. <div class="editable-cell">
  27. <div v-if="editableData[record.id]" class="editable-cell-input-wrapper">
  28. <a-input
  29. v-model:value="editableData[record.id].name"
  30. @pressEnter="onSave(record.id)"
  31. />
  32. <check-outlined class="editable-cell-icon-check" @click="onSave(record.id)" />
  33. <CloseOutlined
  34. class="editable-cell-icon-check"
  35. :style="{ right: '-24px' }"
  36. @click="onCancel(record.id)"
  37. />
  38. </div>
  39. <div v-else class="editable-cell-text-wrapper">
  40. {{ text || ' ' }}
  41. <edit-outlined class="editable-cell-icon" @click="onEdit(record.id, record)" />
  42. </div>
  43. </div>
  44. </template>
  45. <template #operation="{ record }">
  46. <a-popconfirm v-if="treeData.length" title="确认删除?" @confirm="onDelete(record.id)">
  47. <a>删除</a>
  48. </a-popconfirm>
  49. </template>
  50. </ATable>
  51. </section>
  52. <AModal
  53. :title="modalTitle"
  54. :visible="visible"
  55. @cancel="hideModal"
  56. @ok="onConfirm"
  57. :ok-button-props="{ size: 'small' }"
  58. :cancel-button-props="{ size: 'small' }"
  59. >
  60. <div class="p-3">
  61. <a-form :model="formState" :rules="rules" layout="vertical" ref="formRef">
  62. <a-form-item name="name" required label="名称">
  63. <a-input v-model:value="formState.name" autocomplete="off" class="w-6" />
  64. </a-form-item>
  65. <a-form-item
  66. name="id"
  67. required
  68. :label="treeSelectLabel"
  69. :hidden="type === 'edit' ? true : false"
  70. >
  71. <a-tree-select
  72. v-model:value="formState.id"
  73. :treeData="treeSelectData"
  74. :replace-fields="replaceFields"
  75. :tree-default-expand-all="true"
  76. :dropdown-style="{ height: '20rem' }"
  77. />
  78. </a-form-item>
  79. <a-form-item name="code2" label="图标编码">
  80. <a-input v-model:value="formState.code2" autocomplete="off" />
  81. </a-form-item>
  82. </a-form>
  83. </div>
  84. </AModal>
  85. </div>
  86. </template>
  87. <script lang="ts">
  88. import { computed, defineComponent, reactive, ref, toRaw, toRefs, unref, UnwrapRef } from 'vue'
  89. import { useDesign } from '/@/hooks/web/useDesign'
  90. import { Table, Modal, Form, Input, TreeSelect, Popconfirm, message } from 'ant-design-vue'
  91. import { CheckOutlined, EditOutlined, CloseOutlined } from '@ant-design/icons-vue'
  92. import {
  93. delSectionApi,
  94. sectionAddApi,
  95. sectionAllApi,
  96. sectionUpdateApi,
  97. treeResfulApi
  98. } from '/@/api/sys/tree'
  99. import { TreeResultModel } from '/@/api/model/tree'
  100. import { TreeRow } from '/#/tree'
  101. import { useMessage } from '/@/hooks/web/useMessage'
  102. interface FormState {
  103. id: string
  104. name: string
  105. code2: string
  106. }
  107. enum ModalType {
  108. ADD = 'add',
  109. EDIT = 'edit'
  110. }
  111. export default defineComponent({
  112. name: 'Management',
  113. components: {
  114. ATable: Table,
  115. AModal: Modal,
  116. AForm: Form,
  117. AInput: Input,
  118. ATreeSelect: TreeSelect,
  119. AFormItem: Form.Item,
  120. CheckOutlined,
  121. EditOutlined,
  122. CloseOutlined,
  123. APopconfirm: Popconfirm
  124. },
  125. setup() {
  126. const formRef = ref()
  127. const { prefixCls } = useDesign('management')
  128. const treeData = ref<TreeResultModel[]>([])
  129. const editableData: UnwrapRef<Record<string, TreeRow>> = reactive({})
  130. const row = ref<TreeRow>({
  131. name: '',
  132. code: '',
  133. id: '',
  134. parentId: '',
  135. depth: 0,
  136. serial: 0,
  137. attribution: '',
  138. contractId: '',
  139. createTime: '',
  140. code2: ''
  141. })
  142. const modal = reactive({
  143. visible: false,
  144. type: ''
  145. })
  146. const formState: UnwrapRef<FormState> = reactive({
  147. id: '',
  148. name: '',
  149. code2: ''
  150. })
  151. // 目录操作相关
  152. const toggleModal = (type?: string) => {
  153. // 要打开form弹窗了, 设置当前的表单值
  154. if (!modal.visible) {
  155. if (row.value.id && type) {
  156. const unRow = unref(row)
  157. if (type === ModalType.ADD) {
  158. formState.id = unRow.depth === 3 ? unRow.parentId : unRow.id
  159. } else {
  160. formState.id = unRow.id
  161. formState.name = unRow.name
  162. formState.code2 = unRow.code2
  163. }
  164. } else {
  165. const { createMessage } = useMessage()
  166. return createMessage.warning('请先选择节点再进行操作')
  167. }
  168. }
  169. modal.visible = !modal.visible
  170. type && (modal.type = type)
  171. }
  172. const hideModal = () => (modal.visible = false)
  173. const onSubmit = async () => {
  174. const values = await formRef.value.validate()
  175. const { type } = toRaw(modal)
  176. if (type === ModalType.ADD) {
  177. await sectionAddApi(values)
  178. } else {
  179. await sectionUpdateApi(values)
  180. }
  181. }
  182. const onConfirm = async () => {
  183. try {
  184. await onSubmit()
  185. await initData()
  186. toggleModal()
  187. } catch (error) {}
  188. }
  189. async function initData() {
  190. const result = await sectionAllApi()
  191. treeData.value = result.children
  192. }
  193. initData()
  194. const handleTreeOpreate = async (operation: 'upSerial' | 'downSerial') => {
  195. const id = row.value.id
  196. if (!id) {
  197. return message.error('请先选中节点')
  198. }
  199. await treeResfulApi({ id, type: 'serial', operation })
  200. await initData()
  201. }
  202. const modalTitle = computed(() => {
  203. let title = ''
  204. switch (modal.type) {
  205. case ModalType.ADD:
  206. title = '添加目录'
  207. break
  208. case ModalType.EDIT:
  209. title = '编辑'
  210. break
  211. default:
  212. break
  213. }
  214. return title
  215. })
  216. const columns = [
  217. {
  218. title: '名称',
  219. dataIndex: 'name',
  220. key: 'name',
  221. slots: { customRender: 'name' }
  222. },
  223. {
  224. title: '图表编码',
  225. dataIndex: 'code2',
  226. key: 'code2',
  227. slots: { customRender: 'code2' }
  228. },
  229. {
  230. title: '操作',
  231. dataIndex: 'operation',
  232. slots: { customRender: 'operation' }
  233. }
  234. ]
  235. const customRow = (record: TreeRow) => {
  236. return {
  237. onClick: () => {
  238. row.value = record
  239. }
  240. }
  241. }
  242. const rowClassName = (record: TreeRow) => {
  243. return record.id === row.value.id ? 'row-active' : ''
  244. }
  245. const onEdit = (key: string, record: TreeRow) => {
  246. editableData[key] = record
  247. }
  248. const onSave = async (key: string) => {
  249. const { id, name, code2 } = toRaw(editableData[key])
  250. await sectionUpdateApi({ id, name, code2 })
  251. // 执行保存请求 后初始化数据
  252. delete editableData[key]
  253. initData()
  254. }
  255. const onCancel = async (key: string) => {
  256. delete editableData[key]
  257. }
  258. const onDelete = async (key: string) => {
  259. try {
  260. await delSectionApi({ id: key })
  261. await initData()
  262. } catch (error) {}
  263. }
  264. const rules = {
  265. name: [{ required: true, message: '请输入名称' }],
  266. id: [
  267. { required: true, message: modal.type === ModalType.ADD ? '请选择父节点' : '请选择节点' }
  268. ]
  269. }
  270. const replaceFields = { children: 'children', title: 'name', value: 'id' }
  271. const treeSelectData = computed(() => {
  272. if (modal.type === ModalType.ADD) {
  273. return treeData.value.map(item => {
  274. const newItem = { ...item }
  275. if (item.children && item.children.length) {
  276. newItem.children = item.children.map(item => ({ ...item, children: [] }))
  277. }
  278. return newItem
  279. })
  280. }
  281. return treeData.value
  282. })
  283. const treeSelectLabel = computed(() => (modal.type === ModalType.ADD ? '父级目录' : '节点'))
  284. return {
  285. ...toRefs(modal),
  286. prefixCls,
  287. treeData,
  288. columns,
  289. toggleModal,
  290. formRef,
  291. hideModal,
  292. onConfirm,
  293. modalTitle,
  294. customRow,
  295. rowClassName,
  296. formState,
  297. rules,
  298. replaceFields,
  299. editableData,
  300. onEdit,
  301. onSave,
  302. onDelete,
  303. handleTreeOpreate,
  304. treeSelectData,
  305. treeSelectLabel,
  306. onCancel
  307. }
  308. }
  309. })
  310. </script>
  311. <style lang="less" scoped>
  312. @prefix-cls: ~'@{namespace}-management';
  313. .@{prefix-cls} {
  314. width: calc(100% - 2.5rem);
  315. height: calc(100% - 2.5rem);
  316. ::v-deep(.ant-table-row.row-active) {
  317. background: #cdefff;
  318. }
  319. ::v-deep(.ant-table-row > td:first-of-type) {
  320. display: flex;
  321. align-items: center;
  322. }
  323. .editable-cell {
  324. position: relative;
  325. width: 50%;
  326. .editable-cell-input-wrapper,
  327. .editable-cell-text-wrapper {
  328. padding-right: 24px;
  329. }
  330. .editable-cell-text-wrapper {
  331. padding: 5px 24px 5px 5px;
  332. }
  333. .editable-cell-icon,
  334. .editable-cell-icon-check {
  335. position: absolute;
  336. right: 0;
  337. width: 20px;
  338. cursor: pointer;
  339. }
  340. .editable-cell-icon {
  341. display: none;
  342. margin-top: 4px;
  343. }
  344. .editable-cell-icon-check {
  345. line-height: 28px;
  346. }
  347. .editable-cell-icon:hover,
  348. .editable-cell-icon-check:hover {
  349. color: #108ee9;
  350. }
  351. .editable-add-btn {
  352. margin-bottom: 8px;
  353. }
  354. }
  355. .editable-cell:hover .editable-cell-icon {
  356. display: inline-block;
  357. }
  358. }
  359. </style>