StdCurd.vue 7.1 KB

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