StdCurd.vue 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. <script setup lang="ts" generic="T=any">
  2. import type { ComputedRef } from 'vue'
  3. import type { StdCurdProps, StdTableProps } from '@/components/StdDesign/StdDataDisplay/types'
  4. import type { Column } from '@/components/StdDesign/types'
  5. import { message } from 'ant-design-vue'
  6. import StdBatchEdit from '@/components/StdDesign/StdDataDisplay/StdBatchEdit.vue'
  7. import StdCurdDetail from '@/components/StdDesign/StdDataDisplay/StdCurdDetail.vue'
  8. import StdDataEntry from '@/components/StdDesign/StdDataEntry'
  9. import StdTable from './StdTable.vue'
  10. const props = defineProps<StdTableProps<T> & StdCurdProps<T>>()
  11. const selectedRowKeys = defineModel<(number | string)[]>('selectedRowKeys', {
  12. default: () => reactive([]),
  13. })
  14. const selectedRows = defineModel<T[]>('selectedRows', {
  15. default: () => reactive([]),
  16. })
  17. const visible = ref(false)
  18. // eslint-disable-next-line ts/no-explicit-any
  19. const data: any = reactive({ id: null })
  20. const modifyMode = ref(true)
  21. const editMode = ref<string>()
  22. const shouldRefetchList = ref(false)
  23. provide('data', data)
  24. provide('editMode', editMode)
  25. provide('shouldRefetchList', shouldRefetchList)
  26. // eslint-disable-next-line ts/no-explicit-any
  27. const error: any = reactive({})
  28. const selected = ref([])
  29. // eslint-disable-next-line ts/no-explicit-any
  30. function onSelect(keys: any) {
  31. selected.value = keys
  32. }
  33. const editableColumns = computed(() => {
  34. return props.columns!.filter(c => {
  35. return c.edit
  36. })
  37. }) as ComputedRef<Column[]>
  38. // eslint-disable-next-line ts/no-explicit-any
  39. function add(preset: any = undefined) {
  40. if (props.onClickAdd)
  41. return
  42. Object.keys(data).forEach(v => {
  43. delete data[v]
  44. })
  45. if (preset)
  46. Object.assign(data, preset)
  47. clearError()
  48. visible.value = true
  49. editMode.value = 'create'
  50. modifyMode.value = true
  51. }
  52. const table = useTemplateRef('table')
  53. const inTrash = ref(false)
  54. const getParams = reactive(props.getParams ?? {})
  55. function get_list() {
  56. table.value?.get_list()
  57. }
  58. defineExpose({
  59. add,
  60. get_list,
  61. data,
  62. inTrash,
  63. })
  64. function clearError() {
  65. Object.keys(error).forEach(v => {
  66. delete error[v]
  67. })
  68. }
  69. // eslint-disable-next-line vue/require-typed-ref
  70. const stdEntryRef = ref()
  71. async function ok() {
  72. const { formRef } = stdEntryRef.value
  73. clearError()
  74. try {
  75. await formRef.validateFields()
  76. props?.beforeSave?.(data)
  77. props
  78. .api!.save(data.id, { ...data, ...props.overwriteParams }, { params: { ...props.overwriteParams } }).then(r => {
  79. message.success($gettext('Save successfully'))
  80. Object.assign(data, r)
  81. get_list()
  82. visible.value = false
  83. }).catch(e => {
  84. Object.assign(error, e.errors)
  85. })
  86. }
  87. catch {
  88. message.error($gettext('Please fill in the required fields'))
  89. }
  90. }
  91. function cancel() {
  92. visible.value = false
  93. clearError()
  94. if (shouldRefetchList.value) {
  95. get_list()
  96. shouldRefetchList.value = false
  97. }
  98. }
  99. function edit(id: number | string) {
  100. if (props.onClickEdit)
  101. return
  102. get(id).then(() => {
  103. visible.value = true
  104. modifyMode.value = true
  105. editMode.value = 'modify'
  106. })
  107. }
  108. function view(id: number | string) {
  109. get(id).then(() => {
  110. visible.value = true
  111. modifyMode.value = false
  112. })
  113. }
  114. async function get(id: number | string) {
  115. return props
  116. .api!.get(id, { ...props.overwriteParams }).then(async r => {
  117. Object.keys(data).forEach(k => {
  118. delete data[k]
  119. })
  120. data.id = null
  121. Object.assign(data, r)
  122. })
  123. }
  124. const modalTitle = computed(() => {
  125. // eslint-disable-next-line sonarjs/no-nested-conditional
  126. return data.id ? modifyMode.value ? $gettext('Modify') : $gettext('View Details') : $gettext('Add')
  127. })
  128. const localOverwriteParams = reactive(props.overwriteParams ?? {})
  129. const stdBatchEditRef = useTemplateRef('stdBatchEditRef')
  130. async function handleClickBatchEdit(batchColumns: Column[]) {
  131. stdBatchEditRef.value?.showModal(batchColumns, selectedRowKeys.value, selectedRows.value)
  132. }
  133. function handleBatchUpdated() {
  134. table.value?.get_list()
  135. table.value?.resetSelection()
  136. }
  137. </script>
  138. <template>
  139. <div class="std-curd">
  140. <ACard>
  141. <template #title>
  142. <div class="flex items-center">
  143. {{ title || $gettext('List') }}
  144. <slot name="title-slot" />
  145. </div>
  146. </template>
  147. <template #extra>
  148. <ASpace>
  149. <slot name="beforeAdd" />
  150. <AButton
  151. v-if="!disableAdd && !inTrash"
  152. type="link"
  153. size="small"
  154. @click="add()"
  155. >
  156. {{ $gettext('Add') }}
  157. </AButton>
  158. <slot name="extra" />
  159. <template v-if="!disableDelete">
  160. <AButton
  161. v-if="!inTrash"
  162. type="link"
  163. size="small"
  164. :loading="table?.loading"
  165. @click="inTrash = true"
  166. >
  167. {{ $gettext('Trash') }}
  168. </AButton>
  169. <AButton
  170. v-else
  171. type="link"
  172. size="small"
  173. :loading="table?.loading"
  174. @click="inTrash = false"
  175. >
  176. {{ $gettext('Back to list') }}
  177. </AButton>
  178. </template>
  179. </ASpace>
  180. </template>
  181. <slot name="beforeTable" />
  182. <StdTable
  183. ref="table"
  184. v-bind="{
  185. ...props,
  186. getParams,
  187. overwriteParams: localOverwriteParams,
  188. }"
  189. v-model:selected-row-keys="selectedRowKeys"
  190. v-model:selected-rows="selectedRows"
  191. :in-trash="inTrash"
  192. @click-edit="edit"
  193. @click-view="view"
  194. @selected="onSelect"
  195. @click-batch-modify="handleClickBatchEdit"
  196. >
  197. <template
  198. v-for="(_, key) in $slots"
  199. :key="key"
  200. #[key]="slotProps"
  201. >
  202. <slot
  203. :name="key"
  204. v-bind="slotProps"
  205. />
  206. </template>
  207. </StdTable>
  208. </ACard>
  209. <AModal
  210. class="std-curd-edit-modal"
  211. :mask="modalMask"
  212. :title="modalTitle"
  213. :open="visible"
  214. :cancel-text="$gettext('Cancel')"
  215. :ok-text="$gettext('Ok')"
  216. :width="modalMaxWidth"
  217. :footer="modifyMode ? undefined : null"
  218. destroy-on-close
  219. @cancel="cancel"
  220. @ok="ok"
  221. >
  222. <div
  223. v-if="!disableModify && !disableView && editMode === 'modify'"
  224. class="m-2 flex justify-end"
  225. >
  226. <ASwitch
  227. v-model:checked="modifyMode"
  228. class="mr-2"
  229. />
  230. {{ modifyMode ? $gettext('Modify Mode') : $gettext('View Mode') }}
  231. </div>
  232. <template v-if="modifyMode">
  233. <div
  234. v-if="$slots.beforeEdit"
  235. class="before-edit"
  236. >
  237. <slot
  238. name="beforeEdit"
  239. :data="data"
  240. />
  241. </div>
  242. <StdDataEntry
  243. ref="stdEntryRef"
  244. :data-list="editableColumns"
  245. :data-source="data"
  246. :errors="error"
  247. />
  248. <slot
  249. name="edit"
  250. :data="data"
  251. />
  252. </template>
  253. <StdCurdDetail
  254. v-else
  255. :columns
  256. :data
  257. />
  258. </AModal>
  259. <StdBatchEdit
  260. ref="stdBatchEditRef"
  261. :api
  262. :columns
  263. @save="handleBatchUpdated"
  264. />
  265. </div>
  266. </template>
  267. <style lang="less" scoped>
  268. :deep(.before-edit:last-child) {
  269. margin-bottom: 20px;
  270. }
  271. </style>