index.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  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. <div class="up-se">
  19. <span class="upload fun-ctrls">
  20. <ui-input
  21. class="input"
  22. :accept="ft"
  23. :maxSize="maxSize"
  24. @update:modelValue="(file: File) => uploadHandler(file)"
  25. type="file"
  26. >
  27. <template v-slot:replace>
  28. <Button type="primary" ghost>
  29. <ui-icon type="add" class="icon" />上传文件
  30. </Button>
  31. </template>
  32. </ui-input>
  33. </span>
  34. <Search
  35. v-if="Object.keys(allData).length"
  36. className="content-header-search"
  37. placeholder="输入名称搜索"
  38. v-model:value="params.name"
  39. allow-clear
  40. style="width: 244px"
  41. />
  42. </div>
  43. </div>
  44. <div class="table-layout">
  45. <Table
  46. v-if="Object.keys(allData).length"
  47. :row-key="(record: Material) => record.id"
  48. :columns="cloumns"
  49. :rowSelection="rowSelection"
  50. :data-source="origin.list"
  51. :pagination="{ ...origin, current: origin.pageNum }"
  52. @change="handleTableChange"
  53. >
  54. <template #bodyCell="{ column, record }">
  55. <template v-if="column.key === 'size'">
  56. {{ getSizeStr(record.size) }}
  57. </template>
  58. <template v-else-if="column.key === 'action'">
  59. <span>
  60. <a @click="delHandler(record.id)">删除</a>
  61. </span>
  62. </template>
  63. </template>
  64. </Table>
  65. <div style="padding: 1px" v-else>
  66. <Empty
  67. description="暂无结果"
  68. :image="Empty.PRESENTED_IMAGE_SIMPLE"
  69. className="ant-empty ant-empty-normal"
  70. />
  71. </div>
  72. </div>
  73. </div>
  74. </Modal>
  75. </template>
  76. <script lang="ts" setup>
  77. import { Modal, Input, Table, Empty, TableProps, Button } from "ant-design-vue";
  78. import { computed, reactive, ref, watch } from "vue";
  79. import { createLoadPack, debounceStack, getSizeStr } from "@/utils";
  80. import type { PagingResult } from "@/api";
  81. import {
  82. addMaterial,
  83. delMaterial,
  84. fetchMaterialGroups,
  85. fetchMaterialPage,
  86. Material,
  87. MaterialGroup,
  88. MaterialPageProps,
  89. } from "@/api/material";
  90. import Message from "bill/components/message/message.vue";
  91. import { Dialog } from "bill/expose-common";
  92. const props = defineProps<{
  93. uploadFormat?: string[];
  94. format?: string[];
  95. maxSize?: number;
  96. visible: boolean;
  97. count?: number;
  98. afterClose?: () => void;
  99. }>();
  100. const emit = defineEmits<{
  101. (e: "update:visible", v: boolean): void;
  102. (e: "selectMaterials", v: Material[]): void;
  103. }>();
  104. const ft = computed(() => {
  105. const ft = props.uploadFormat || props.format;
  106. if (!ft) return void 0;
  107. return ft.map((t) => `.${t}`).join(",");
  108. });
  109. const Search = Input.Search;
  110. const params = reactive({
  111. pageNum: 1,
  112. pageSize: 10,
  113. groupIds: [],
  114. formats: props.format,
  115. }) as MaterialPageProps;
  116. const origin = ref<PagingResult<Material[]>>({
  117. list: [],
  118. pageNum: 1,
  119. pageSize: 10,
  120. total: 0,
  121. });
  122. const groups = ref<MaterialGroup[]>([]);
  123. const selectKeys = ref<Material["id"][]>([]);
  124. const allData: Record<Material["id"], Material> = reactive({});
  125. const rowSelection: any = ref({
  126. selectedRowKeys: selectKeys,
  127. onChange: (ids: number[]) => {
  128. const otherPageKeys = selectKeys.value.filter(
  129. (key) => !origin.value.list.some((item) => key === item.id)
  130. );
  131. const newKeys = Array.from(new Set([...otherPageKeys, ...ids]));
  132. if (typeof props.count !== "number" || props.count >= newKeys.length) {
  133. selectKeys.value = newKeys;
  134. } else {
  135. Message.error(`最多选择${props.count}项`);
  136. }
  137. },
  138. getCheckboxProps: (record: Material) => {
  139. return {
  140. disabled:
  141. (props.format && !props.format.includes(record.format)) ||
  142. (props.maxSize && record.size > props.maxSize),
  143. };
  144. },
  145. });
  146. const cloumns = computed(() => [
  147. {
  148. title: "名称",
  149. dataIndex: "name",
  150. key: "name",
  151. },
  152. {
  153. width: "100px",
  154. title: "格式",
  155. dataIndex: "format",
  156. key: "format",
  157. },
  158. {
  159. width: "100px",
  160. title: "大小",
  161. dataIndex: "size",
  162. key: "size",
  163. },
  164. {
  165. width: "100px",
  166. title: "分组",
  167. dataIndex: "group",
  168. key: "group",
  169. filters: groups.value.map((g) => ({
  170. text: g.name,
  171. value: g.id,
  172. })),
  173. },
  174. {
  175. title: "操作",
  176. key: "action",
  177. },
  178. ]);
  179. const fetchData = createLoadPack(() =>
  180. Promise.all([fetchMaterialGroups(), fetchMaterialPage(params)])
  181. );
  182. const refresh = debounceStack(
  183. fetchData,
  184. ([group, pag]) => {
  185. groups.value = group;
  186. origin.value = pag;
  187. pag.list.forEach((item) => (allData[item.id] = item));
  188. if (pag.pageNum > 1 && pag.pageNum > Math.ceil(pag.total / pag.pageSize)) {
  189. params.pageNum = Math.ceil(pag.total / pag.pageSize);
  190. refresh();
  191. }
  192. },
  193. 160
  194. );
  195. const uploadHandler = async (file: File) => {
  196. await addMaterial(file);
  197. refresh();
  198. };
  199. watch(params, refresh, { immediate: true, deep: true });
  200. const addHandler = async (file: File) => {
  201. await addMaterial(file);
  202. refresh();
  203. };
  204. const delHandler = async (id: Material["id"]) => {
  205. if (await Dialog.confirm("确定要删除此数据吗?")) {
  206. await delMaterial(id);
  207. const ndx = selectKeys.value.indexOf(id);
  208. console.log(selectKeys.value, id);
  209. if (~ndx) {
  210. selectKeys.value.splice(ndx, 1);
  211. }
  212. refresh();
  213. }
  214. };
  215. const okHandler = () => {
  216. emit(
  217. "selectMaterials",
  218. selectKeys.value.map((id) => allData[id])
  219. );
  220. };
  221. const handleTableChange: TableProps["onChange"] = (pag, filters) => {
  222. params.pageSize = pag.pageSize!;
  223. params.pageNum = pag.current!;
  224. params.groupIds = filters.group as number[];
  225. };
  226. </script>
  227. <style lang="less" scoped>
  228. .model-header {
  229. display: flex;
  230. justify-content: space-between;
  231. padding-bottom: 24px;
  232. align-items: center;
  233. }
  234. .table-layout {
  235. max-height: 500px;
  236. overflow-y: auto;
  237. }
  238. .up-se {
  239. display: flex;
  240. align-items: center;
  241. .upload {
  242. margin-right: 10px;
  243. width: 100px;
  244. // overflow: hidden;
  245. }
  246. }
  247. </style>
  248. <style lang="less">
  249. .model-header .header-desc {
  250. margin-bottom: 0;
  251. }
  252. .ant-modal-root .ant-table-tbody > tr > td {
  253. word-break: break-all;
  254. }
  255. .content-header-search {
  256. flex: 1;
  257. }
  258. </style>