index.vue 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. <template>
  2. <div class="store-page">
  3. <Breadcrumb />
  4. <a-card title="首页弹窗公告">
  5. <a-space style="margin-bottom: 12px;">
  6. <a-button type="primary" @click="openCreate">新增公告</a-button>
  7. </a-space>
  8. <a-table :bordered="false"
  9. :data="data"
  10. :loading="loading"
  11. :pagination="{
  12. showPageSize: true,
  13. showJumper: true,
  14. showTotal: true,
  15. pageSize: pagination.pagesize,
  16. current: pagination.current,
  17. total: pagination.total
  18. }"
  19. @page-change="onPageChange"
  20. @page-size-change="onPageSizeChange"
  21. >
  22. <template #columns>
  23. <a-table-column title="ID" data-index="id" :width="80" />
  24. <a-table-column title="标题" data-index="title" :width="220" ellipsis tooltip />
  25. <a-table-column title="优先级" data-index="priority" :width="100" />
  26. <a-table-column title="状态" :width="100">
  27. <template #cell="{ record }">
  28. <a-tag :color="record.is_active ? 'green' : 'red'">{{ record.is_active ? '启用' : '停用' }}</a-tag>
  29. </template>
  30. </a-table-column>
  31. <a-table-column title="连续展示" :width="110">
  32. <template #cell="{ record }">
  33. <a-tag :color="record.repeat_show ? 'arcoblue' : 'gray'">{{ record.repeat_show ? '开启' : '关闭' }}</a-tag>
  34. </template>
  35. </a-table-column>
  36. <a-table-column title="已读人数" data-index="read_count" :width="100" />
  37. <a-table-column title="生效时间" :width="180">
  38. <template #cell="{ record }">{{ record.start_at || '-' }}</template>
  39. </a-table-column>
  40. <a-table-column title="失效时间" :width="180">
  41. <template #cell="{ record }">{{ record.end_at || '-' }}</template>
  42. </a-table-column>
  43. <a-table-column title="操作" :width="260">
  44. <template #cell="{ record }">
  45. <a-space>
  46. <a-button size="mini" @click="openEdit(record)">编辑</a-button>
  47. <a-button size="mini" @click="openReadList(record)">已读列表</a-button>
  48. <a-button status="danger" size="mini" @click="remove(record)">删除</a-button>
  49. </a-space>
  50. </template>
  51. </a-table-column>
  52. </template>
  53. </a-table>
  54. </a-card>
  55. <a-modal v-model:visible="editorVisible" :title="form.id ? '编辑公告' : '新增公告'" width="980px" @before-ok="submitEditor">
  56. <a-form :model="form" layout="vertical">
  57. <a-form-item label="标题">
  58. <a-input v-model="form.title" allow-clear />
  59. </a-form-item>
  60. <a-form-item label="优先级">
  61. <a-input-number v-model="form.priority" mode="button" />
  62. </a-form-item>
  63. <a-form-item label="状态">
  64. <a-switch v-model="form.is_active" :checked-value="1" :unchecked-value="0" />
  65. </a-form-item>
  66. <a-form-item label="连续展示(已读后仍展示)">
  67. <a-switch v-model="form.repeat_show" :checked-value="1" :unchecked-value="0" />
  68. </a-form-item>
  69. <a-form-item label="生效时间">
  70. <a-date-picker v-model="form.start_at" show-time format="YYYY-MM-DD HH:mm:ss" value-format="x" allow-clear />
  71. </a-form-item>
  72. <a-form-item label="失效时间">
  73. <a-date-picker v-model="form.end_at" show-time format="YYYY-MM-DD HH:mm:ss" value-format="x" allow-clear />
  74. </a-form-item>
  75. <a-form-item label="公告内容(富文本)">
  76. <WangEditor v-model="form.content_html" />
  77. </a-form-item>
  78. </a-form>
  79. </a-modal>
  80. <a-modal v-model:visible="readVisible" title="已读用户" :footer="false" width="860px" draggable>
  81. <a-table :bordered="false"
  82. :data="readData"
  83. :columns="readColumns"
  84. :loading="readLoading"
  85. :pagination="false"
  86. :scroll="{ y: 420 }"
  87. >
  88. <template #avatar="{ record }">
  89. <a-avatar :size="28">
  90. <img :src="record.avatar || 'https://lepao-cloud.xxoo365.top/view.php/25aa126dc406974ff3579a99a2c6501a.png'" />
  91. </a-avatar>
  92. {{ record.username || '-' }}
  93. </template>
  94. <template #read_at="{ record }">{{ formatTime(record.read_at) }}</template>
  95. <template #empty>
  96. <a-empty description="暂无已读记录" />
  97. </template>
  98. </a-table>
  99. </a-modal>
  100. </div>
  101. </template>
  102. <script setup>
  103. import { reactive, ref, onMounted } from 'vue'
  104. import { Modal, Notification, Message } from '@arco-design/web-vue'
  105. import WangEditor from '@/components/Editor/WangEditor.vue'
  106. import {
  107. getAdminPopupList,
  108. createAdminPopup,
  109. updateAdminPopup,
  110. deleteAdminPopup,
  111. getAdminPopupReadList
  112. } from '@/api/public'
  113. const loading = ref(false)
  114. const data = ref([])
  115. const pagination = reactive({ total: 0, current: 1, pagesize: 20 })
  116. const editorVisible = ref(false)
  117. const form = reactive({
  118. id: null,
  119. title: '',
  120. content_html: '',
  121. priority: 0,
  122. is_active: 1,
  123. repeat_show: 0,
  124. start_at: '',
  125. end_at: ''
  126. })
  127. const readVisible = ref(false)
  128. const readLoading = ref(false)
  129. const readData = ref([])
  130. const readColumns = [
  131. { title: '用户', slotName: 'avatar', width: 140 },
  132. { title: 'UUID', dataIndex: 'user_uuid', width: 220, ellipsis: true, tooltip: true },
  133. { title: '已读时间', slotName: 'read_at', width: 180 }
  134. ]
  135. const formatTime = (time) => new Date(time).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
  136. const getList = async () => {
  137. try {
  138. loading.value = true
  139. const res = await getAdminPopupList({
  140. pagesize: pagination.pagesize,
  141. current: pagination.current
  142. })
  143. if (!res || res.code !== 0) {
  144. return Notification.error({
  145. title: '获取公告列表失败',
  146. content: res?.msg ?? '请稍后再试'
  147. })
  148. }
  149. data.value = res.data || []
  150. pagination.total = res.pagination?.total || 0
  151. } catch (error) {
  152. Notification.error({
  153. title: '获取公告列表失败',
  154. content: error.message || '请稍后再试'
  155. })
  156. } finally {
  157. loading.value = false
  158. }
  159. }
  160. const openCreate = () => {
  161. form.id = null
  162. form.title = ''
  163. form.content_html = ''
  164. form.priority = 0
  165. form.is_active = 1
  166. form.repeat_show = 0
  167. form.start_at = ''
  168. form.end_at = ''
  169. editorVisible.value = true
  170. }
  171. const openEdit = (record) => {
  172. form.id = record.id
  173. form.title = record.title
  174. form.content_html = record.content_html
  175. form.priority = record.priority
  176. form.is_active = record.is_active
  177. form.repeat_show = record.repeat_show ? 1 : 0
  178. form.start_at = record.start_at ? new Date(record.start_at).getTime() : ''
  179. form.end_at = record.end_at ? new Date(record.end_at).getTime() : ''
  180. editorVisible.value = true
  181. }
  182. const submitEditor = async () => {
  183. if (!form.title || !form.content_html) {
  184. Message.error('请填写标题和内容')
  185. return false
  186. }
  187. const payload = {
  188. id: form.id,
  189. title: form.title,
  190. content_html: form.content_html,
  191. priority: form.priority,
  192. is_active: form.is_active,
  193. repeat_show: form.repeat_show,
  194. start_at: form.start_at,
  195. end_at: form.end_at
  196. }
  197. const res = form.id ? await updateAdminPopup(payload) : await createAdminPopup(payload)
  198. if (!res || res.code !== 0) {
  199. Notification.error({
  200. title: form.id ? '更新公告失败' : '创建公告失败',
  201. content: res?.msg ?? '请稍后再试'
  202. })
  203. return false
  204. }
  205. Message.success(form.id ? '更新成功' : '创建成功')
  206. getList()
  207. return true
  208. }
  209. const remove = (record) => {
  210. Modal.confirm({
  211. title: '删除公告',
  212. content: `确定删除公告「${record.title}」吗?`,
  213. onOk: async () => {
  214. const res = await deleteAdminPopup({ id: record.id })
  215. if (!res || res.code !== 0) {
  216. return Notification.error({
  217. title: '删除公告失败',
  218. content: res?.msg ?? '请稍后再试'
  219. })
  220. }
  221. Message.success('删除成功')
  222. getList()
  223. }
  224. })
  225. }
  226. const openReadList = async (record) => {
  227. readVisible.value = true
  228. readLoading.value = true
  229. readData.value = []
  230. try {
  231. const res = await getAdminPopupReadList({
  232. popup_id: record.id,
  233. pagesize: 200,
  234. current: 1
  235. })
  236. if (!res || res.code !== 0)
  237. return Notification.error({
  238. title: '获取已读列表失败',
  239. content: res?.msg ?? '请稍后再试'
  240. })
  241. readData.value = Array.isArray(res.data) ? res.data : []
  242. } catch (error) {
  243. Notification.error({
  244. title: '获取已读列表失败',
  245. content: error.message || '请稍后再试'
  246. })
  247. } finally {
  248. readLoading.value = false
  249. }
  250. }
  251. const onPageChange = (page) => {
  252. pagination.current = page
  253. getList()
  254. }
  255. const onPageSizeChange = (size) => {
  256. pagination.pagesize = size
  257. pagination.current = 1
  258. getList()
  259. }
  260. onMounted(() => {
  261. getList()
  262. })
  263. </script>
  264. <style scoped>
  265. </style>