NotifyManageList.vue 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. <script setup lang="ts">
  2. import { ref, reactive, shallowRef, onMounted, nextTick } from 'vue';
  3. import type { NotifyModel, NotifyQuery } from '@/model/notify.model';
  4. import EditNotify from '@/components/EditNotify.vue';
  5. // 接口数据
  6. import { notifyMethod, deleteNotifyMethod, updateNotifyStatusMethod } from '@/request/api/notify.api';
  7. import { usePagination } from 'alova/client';
  8. import { notification } from 'ant-design-vue';
  9. import dayjs from 'dayjs';
  10. import { type VxeFormListeners, type VxeFormProps, type VxeGridInstance, type VxeGridListeners, type VxeGridProps, VxeUI } from 'vxe-pc-ui';
  11. const model = shallowRef<NotifyQuery>();
  12. const searchFormProps = reactive<VxeFormProps<NotifyQuery>>({
  13. titleWidth: 100,
  14. titleAlign: 'right',
  15. titleColon: true,
  16. data: {},
  17. items: [
  18. {
  19. field: 'name',
  20. title: '名称',
  21. span: 6,
  22. itemRender: { name: 'VxeInput', props: { placeholder: '请输入通知名称' } },
  23. },
  24. {
  25. field: 'sendTime',
  26. title: '发送时间',
  27. span: 8,
  28. slots: {
  29. default: 'createTimes',
  30. },
  31. },
  32. {
  33. span: 6,
  34. itemRender: {
  35. name: 'VxeButtonGroup',
  36. options: [
  37. { name: 'submits', type: 'submit', content: '查询', status: 'primary' },
  38. { name: 'reset', type: 'reset', content: '重置', status: 'warning' },
  39. { name: 'add', content: '新增', status: 'primary' },
  40. ],
  41. events: {
  42. click(slotParams: any, { name }: any) {
  43. if (name === 'add') {
  44. // 新增
  45. editConfirmed();
  46. }
  47. },
  48. },
  49. },
  50. },
  51. ],
  52. });
  53. const searchFormEmits: VxeFormListeners<NotifyQuery> = {
  54. // 查询
  55. submit({ data }) {
  56. data.sendTimeStart = sendTimeStart.value ? dayjs(sendTimeStart.value).format('YYYY-MM-DD HH:mm') : '';
  57. data.sendTimeEnd = sendTimeEnd.value ? dayjs(sendTimeEnd.value).format('YYYY-MM-DD HH:mm') : '';
  58. onSearch(data);
  59. },
  60. // 重置
  61. reset({ data }) {
  62. // 清空开始时间和结束时间
  63. sendTimeStart.value = '';
  64. sendTimeEnd.value = '';
  65. const resetData = { ...data };
  66. resetData.sendTimeStart = '';
  67. resetData.sendTimeEnd = '';
  68. onSearch(resetData);
  69. },
  70. };
  71. function onSearch(data: NotifyQuery) {
  72. model.value = { ...data };
  73. nextTick(() => {
  74. (searchFormProps.data as any)! = { ...data };
  75. });
  76. }
  77. const gridRef = ref<VxeGridInstance<NotifyModel>>();
  78. const gridOptions = reactive<VxeGridProps<NotifyModel>>({
  79. id: 'tag-list',
  80. border: true,
  81. showOverflow: true,
  82. height: 'auto',
  83. autoResize: true,
  84. syncResize: true,
  85. scrollY: { enabled: true, gt: 0 },
  86. rowConfig: {
  87. isHover: true,
  88. isCurrent: true,
  89. },
  90. toolbarConfig: {
  91. custom: true,
  92. zoom: true,
  93. slots: {
  94. tools: 'toolbar-extra',
  95. },
  96. },
  97. columnConfig: {
  98. resizable: true,
  99. },
  100. customConfig: {
  101. storage: true,
  102. },
  103. columns: [
  104. { type: 'seq', width: 70, fixed: 'left' },
  105. { field: 'name', title: '名称' },
  106. { field: 'pushType', title: '通知渠道', slots: { default: 'pushTypeCell' } },
  107. { field: 'tagNames', title: '用户标签' },
  108. { field: 'sendTime', title: '发送时间' },
  109. { field: 'sendCount', title: '已发条数' },
  110. {
  111. field: 'status',
  112. title: '启用状态',
  113. align: 'center',
  114. cellRender: {
  115. name: 'VxeSwitch',
  116. props: {
  117. openLabel: '启用',
  118. openValue: '0',
  119. closeLabel: '禁用',
  120. closeValue: '1',
  121. },
  122. events: {
  123. change({ row, rowIndex }, { value }) {
  124. row.status = { '1': '0', '0': '1' }[value as string] as any;
  125. updatePlanStatus(row, rowIndex, value);
  126. },
  127. },
  128. },
  129. },
  130. {
  131. field: 'action',
  132. title: '操作',
  133. align: 'center',
  134. width: 180,
  135. showOverflow: false,
  136. cellRender: {
  137. name: 'VxeButtonGroup',
  138. props: {
  139. mode: 'text',
  140. },
  141. options: [
  142. { content: '编辑', status: 'primary', name: 'editConfirmed' },
  143. { content: '删除', status: 'primary', name: 'deleteConfirmed' },
  144. ],
  145. events: {
  146. click({ row, rowIndex }: any, { name }: any) {
  147. let method;
  148. if (name === 'editConfirmed') {
  149. method = editConfirmed;
  150. } else if (name === 'deleteConfirmed') {
  151. method = deleteConfirmed;
  152. }
  153. method?.(row, rowIndex);
  154. },
  155. },
  156. },
  157. },
  158. ],
  159. data: [],
  160. });
  161. const gridEvents: VxeGridListeners = {};
  162. const {
  163. loading,
  164. page,
  165. pageSize,
  166. total,
  167. onSuccess,
  168. replace,
  169. refresh,
  170. remove,
  171. send: sendRefresh,
  172. } = usePagination((page, size) => notifyMethod(page, size, model.value), {
  173. initialData: { data: [], total: 0 },
  174. initialPage: 1,
  175. initialPageSize: 100,
  176. watchingStates: [model],
  177. immediate: true,
  178. });
  179. onSuccess((res: any) => {
  180. gridRef.value?.loadData(res?.data?.data ?? []);
  181. });
  182. onMounted(() => {
  183. onSearch(toRaw(searchFormProps.data) as any);
  184. });
  185. function deleteConfirmed(model: NotifyModel, index: number) {
  186. const { name } = model;
  187. VxeUI.modal.confirm({
  188. title: `删除通知`,
  189. content: `确认要删除 ${name} 通知吗?`,
  190. showClose: false,
  191. onConfirm() {
  192. deleteNotifyMethod(model).then(() => {
  193. notification.success({
  194. message: `删除通知: ${name}`,
  195. description: '操作成功',
  196. });
  197. refresh(page.value);
  198. });
  199. },
  200. });
  201. }
  202. function editConfirmed(model?: NotifyModel, index?: number) {
  203. const addType = `itemsList`;
  204. VxeUI.modal.open({
  205. title: model?.name ?? '通知',
  206. fullscreen: true,
  207. escClosable: true,
  208. destroyOnClose: true,
  209. id: `edit-notify-modal`,
  210. remember: true,
  211. storage: true,
  212. slots: {
  213. default() {
  214. return h(EditNotify, <any>{
  215. data: {
  216. ...model,
  217. addType,
  218. },
  219. onSubmit(data: NotifyModel) {
  220. refresh(page.value);
  221. VxeUI.modal.close(`edit-notify-modal`);
  222. },
  223. });
  224. },
  225. },
  226. });
  227. }
  228. function updatePlanStatus(model: NotifyModel, index: number, status: NotifyModel['status']) {
  229. const { id, name } = model;
  230. const label = { '1': '禁用', '0': '启用' }[status];
  231. VxeUI.modal.confirm({
  232. title: `启用状态`,
  233. content: `确认要 ${label} ${name} 吗?`,
  234. showClose: false,
  235. onConfirm() {
  236. updateNotifyStatusMethod({ id, status }).then(() => {
  237. notification.success({
  238. message: `${label}: ${name}`,
  239. description: '操作成功',
  240. });
  241. model.status = status;
  242. replace(model, index);
  243. });
  244. },
  245. });
  246. }
  247. // 日期验证
  248. const sendTimeStart = ref<string>('');
  249. const sendTimeEnd = ref<string>('');
  250. // 禁用结束时间的日期(早于开始时间的日期)
  251. function disabledEndDate(current: any) {
  252. if (!sendTimeStart.value) return false;
  253. return current && current < dayjs(sendTimeStart.value);
  254. }
  255. defineExpose({
  256. send: sendRefresh,
  257. });
  258. </script>
  259. <template>
  260. <div class="page-container flex flex-col">
  261. <header class="flex-none mt-4">
  262. <vxe-form v-bind="searchFormProps" v-on="searchFormEmits">
  263. <template #createTimes>
  264. <div class="date-range-container">
  265. <a-date-picker
  266. v-model:value="sendTimeStart"
  267. placeholder="请选择开始时间"
  268. style="flex: 1"
  269. format="YYYY-MM-DD HH:mm"
  270. :show-time="{ format: 'HH:mm' }"
  271. />
  272. <span class="date-separator">至</span>
  273. <a-date-picker
  274. v-model:value="sendTimeEnd"
  275. placeholder="请选择结束时间"
  276. style="flex: 1"
  277. format="YYYY-MM-DD HH:mm"
  278. :disabledDate="disabledEndDate"
  279. :show-time="{ format: 'HH:mm' }"
  280. />
  281. </div>
  282. </template>
  283. </vxe-form>
  284. </header>
  285. <main class="flex-auto overflow-hidden">
  286. <vxe-grid ref="gridRef" v-bind="gridOptions" v-on="gridEvents" :loading="loading">
  287. <template #pushTypeCell="{ row }">
  288. {{ row.pushType === '0' ? '站内' : '企业微信' }}
  289. </template>
  290. <template #conditioningProgramSupplierNameCell="{ row }">
  291. {{ row.conditioningProgramSupplierName == '0' ? '启用' : '禁用' }}
  292. </template>
  293. <template #toolbar-extra>
  294. <vxe-button style="margin-right: 12px" icon="vxe-icon-repeat" circle @click="refresh(page)"></vxe-button>
  295. </template>
  296. </vxe-grid>
  297. </main>
  298. <footer class="flex-none">
  299. <vxe-pager
  300. v-model:current-page="page"
  301. v-model:page-size="pageSize"
  302. :total="total"
  303. :layouts="['Home', 'PrevJump', 'PrevPage', 'Number', 'NextPage', 'NextJump', 'End', 'Sizes', 'FullJump', 'Total']"
  304. />
  305. </footer>
  306. </div>
  307. </template>
  308. <style scoped lang="scss">
  309. .page-container {
  310. height: 100%;
  311. display: flex;
  312. flex-direction: column;
  313. }
  314. .date-range-container {
  315. display: flex;
  316. align-items: center;
  317. gap: 12px;
  318. width: 100%;
  319. .vxe-input {
  320. flex: 1;
  321. min-width: 0;
  322. }
  323. .date-separator {
  324. color: #666;
  325. font-size: 14px;
  326. font-weight: 500;
  327. white-space: nowrap;
  328. padding: 0 8px;
  329. }
  330. }
  331. </style>