index.vue 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. <template>
  2. <Modal
  3. width="800px"
  4. title="媒体库"
  5. :open="visible"
  6. @ok="okHandler"
  7. :afterClose="afterClose"
  8. @cancel="emit('update:visible', false)"
  9. okText="确定"
  10. cancelText="取消"
  11. class="model-table"
  12. >
  13. <div>
  14. <div className="model-header">
  15. <p class="header-desc">
  16. 已选择数据<span>( {{ rowSelection.selectedRowKeys.length }} )</span>
  17. </p>
  18. <Search
  19. v-if="Object.keys(allData).length"
  20. className="content-header-search"
  21. placeholder="输入名称搜索"
  22. v-model:value="params.name"
  23. allow-clear
  24. style="width: 244px"
  25. />
  26. </div>
  27. <div class="table-layout">
  28. <Table
  29. v-if="Object.keys(allData).length"
  30. :row-key="(record: Material) => record.id"
  31. :columns="cloumns"
  32. :rowSelection="rowSelection"
  33. :data-source="origin.list"
  34. :pagination="{ ...origin, current: origin.pageNum }"
  35. @change="handleTableChange"
  36. >
  37. <template #bodyCell="{ column, record }">
  38. <template v-if="column.key === 'size'">
  39. {{ getSizeStr(record.size) }}
  40. </template>
  41. </template>
  42. </Table>
  43. <div style="padding: 1px" v-else>
  44. <Empty
  45. description="暂无结果"
  46. :image="Empty.PRESENTED_IMAGE_SIMPLE"
  47. className="ant-empty ant-empty-normal"
  48. />
  49. </div>
  50. </div>
  51. </div>
  52. </Modal>
  53. </template>
  54. <script lang="ts" setup>
  55. import { Modal, Input, Table, Empty, TableProps } from "ant-design-vue";
  56. import { computed, reactive, ref, watch } from "vue";
  57. import { createLoadPack, debounce, debounceStack, getSizeStr } from "@/utils";
  58. import type { PagingResult, Scene } from "@/api";
  59. import {
  60. fetchMaterialGroups,
  61. fetchMaterialPage,
  62. Material,
  63. MaterialGroup,
  64. MaterialPageProps,
  65. } from "@/api/material";
  66. import Message from "bill/components/message/message.vue";
  67. const props = defineProps<{
  68. format?: string[];
  69. maxSize?: number;
  70. visible: boolean;
  71. count?: number;
  72. afterClose?: () => void;
  73. }>();
  74. const emit = defineEmits<{
  75. (e: "update:visible", v: boolean): void;
  76. (e: "selectMaterials", v: Material[]): void;
  77. }>();
  78. const Search = Input.Search;
  79. const params = reactive({ pageNum: 1, pageSize: 12 }) as MaterialPageProps;
  80. const origin = ref<PagingResult<Material[]>>({
  81. list: [],
  82. pageNum: 1,
  83. pageSize: 12,
  84. total: 0,
  85. });
  86. const groups = ref<MaterialGroup[]>([]);
  87. const selectKeys = ref<Material["id"][]>([]);
  88. const allData: Record<Material["id"], Material> = reactive({});
  89. const rowSelection: any = ref({
  90. selectedRowKeys: selectKeys,
  91. onChange: (ids: number[]) => {
  92. const otherPageKeys = selectKeys.value.filter(
  93. (key) => !origin.value.list.some((item) => key === item.id)
  94. );
  95. const newKeys = Array.from(new Set([...otherPageKeys, ...ids]));
  96. if (!props.count || props.count >= newKeys.length) {
  97. selectKeys.value = newKeys;
  98. } else {
  99. Message.error(`最多选择${props.count}项`);
  100. }
  101. },
  102. getCheckboxProps: (record: Material) => {
  103. return {
  104. disabled:
  105. (props.format && !props.format.includes(record.format)) ||
  106. (props.maxSize && record.size > props.maxSize),
  107. };
  108. },
  109. });
  110. const cloumns = computed(() => [
  111. {
  112. width: "400px",
  113. title: "图片名称",
  114. dataIndex: "name",
  115. key: "name",
  116. },
  117. {
  118. title: "格式",
  119. dataIndex: "format",
  120. key: "format",
  121. },
  122. {
  123. title: "大小",
  124. dataIndex: "size",
  125. key: "size",
  126. },
  127. {
  128. title: "分组",
  129. dataIndex: "group",
  130. key: "group",
  131. filters: groups.value.map((g) => ({
  132. text: g.name,
  133. value: g.id,
  134. })),
  135. },
  136. ]);
  137. const fetchData = createLoadPack(() =>
  138. Promise.all([fetchMaterialGroups(), fetchMaterialPage(params)])
  139. );
  140. watch(
  141. params,
  142. debounceStack(
  143. fetchData,
  144. ([group, pag]) => {
  145. groups.value = group;
  146. origin.value = pag;
  147. pag.list.forEach((item) => (allData[item.id] = item));
  148. },
  149. 160
  150. ),
  151. { immediate: true, deep: true }
  152. );
  153. const okHandler = () => {
  154. emit(
  155. "selectMaterials",
  156. selectKeys.value.map((id) => allData[id])
  157. );
  158. };
  159. const handleTableChange: TableProps["onChange"] = (pag, filters) => {
  160. params.pageSize = pag.pageSize!;
  161. params.pageNum = pag.current!;
  162. params.groupIds = filters.group as number[];
  163. };
  164. </script>
  165. <style lang="less" scoped>
  166. .model-header {
  167. display: flex;
  168. justify-content: space-between;
  169. padding-bottom: 24px;
  170. align-items: center;
  171. }
  172. .table-layout {
  173. max-height: 500px;
  174. overflow-y: auto;
  175. }
  176. </style>
  177. <style lang="less">
  178. .model-header .header-desc {
  179. margin-bottom: 0;
  180. }
  181. .ant-modal-root .ant-table-tbody > tr > td {
  182. word-break: break-all;
  183. }
  184. </style>