StdCurd.vue 7.3 KB

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