From 9a3253ad20fd384c3692941ea2fb44323bf6db00 Mon Sep 17 00:00:00 2001 From: dongshanshan <dongshanshan@zjgtsj.wecom.work> Date: Tue, 31 Oct 2023 16:53:44 +0800 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20=E4=BC=9A=E5=91=98=E5=95=86?= =?UTF-8?q?=E5=93=81=E6=94=B6=E8=97=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/mall/product/favorite.ts | 28 +++ src/hooks/web/useMessage.ts | 12 ++ src/locales/en.ts | 8 +- src/locales/zh-CN.ts | 8 +- .../member/user/detail/UserFavoriteList.vue | 195 ++++++++++++++++++ src/views/member/user/detail/index.vue | 5 +- 6 files changed, 253 insertions(+), 3 deletions(-) create mode 100644 src/api/mall/product/favorite.ts create mode 100644 src/views/member/user/detail/UserFavoriteList.vue diff --git a/src/api/mall/product/favorite.ts b/src/api/mall/product/favorite.ts new file mode 100644 index 00000000..2e54ec78 --- /dev/null +++ b/src/api/mall/product/favorite.ts @@ -0,0 +1,28 @@ +import request from '@/config/axios' + +export interface Favorite { + id?: number + userId?: string // 用户编号 + spuId?: number | null // 商品 SPU 编号 +} + +// 获得 ProductFavorite 列表 +export const getFavoritePage = (params: PageParam) => { + params.keyword = params.name + return request.get({ url: '/product/favorite/page', params }) +} + +// 收藏商品 Favorite +export const createFavorite = (data: Favorite) => { + return request.post({ url: '/product/favorite/create', data }) +} + +// 取消商品收藏 Favorite +export const delFavorite = (data: Favorite) => { + return request.delete({ url: '/product/favorite/delete', data }) +} + +// 是否收藏过商品 Favorite +export const exitsFavorite = (data: Favorite) => { + return request.post({ url: '/product/favorite/exits', data }) +} diff --git a/src/hooks/web/useMessage.ts b/src/hooks/web/useMessage.ts index ac2b552e..9bfb7a3a 100644 --- a/src/hooks/web/useMessage.ts +++ b/src/hooks/web/useMessage.ts @@ -90,6 +90,18 @@ export const useMessage = () => { cancelButtonText: t('common.cancel'), type: 'warning' }) + }, + // 取消收藏窗体 + delStarConfirm(content?: string, tip?: string) { + return ElMessageBox.confirm( + content ? content : t('common.confirmDelStar'), + tip ? tip : t('common.confirmTitle'), + { + confirmButtonText: t('common.ok'), + cancelButtonText: t('common.cancel'), + type: 'warning' + } + ) } } } diff --git a/src/locales/en.ts b/src/locales/en.ts index 4f4d4895..d82e616e 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -54,7 +54,13 @@ export default { updateTime: 'Update Time', copy: 'Copy', copySuccess: 'Copy Success', - copyError: 'Copy Error' + copyError: 'Copy Error', + confirmDelStar: 'Delete the Star?', + starSuccess: 'Star Success', + starFail: 'Star Error', + delStarSuccess: 'Del Star Success', + delStarFail: 'Del Star Error', + existStar: 'exist Star' }, error: { noPermission: `Sorry, you don't have permission to access this page.`, diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 4f95852f..49a3f895 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -54,7 +54,13 @@ export default { updateTime: '更新时间', copy: '复制', copySuccess: '复制成功', - copyError: '复制失败' + copyError: '复制失败', + confirmDelStar: '是否取消收藏?', + starSuccess: '收藏成功', + starFail: '收藏失败', + delStarSuccess: '取消收藏成功', + delStarFail: '取消收藏失败', + existStar: '收藏已存在' }, error: { noPermission: `抱歉,您无权访问此页面。`, diff --git a/src/views/member/user/detail/UserFavoriteList.vue b/src/views/member/user/detail/UserFavoriteList.vue new file mode 100644 index 00000000..009a8b10 --- /dev/null +++ b/src/views/member/user/detail/UserFavoriteList.vue @@ -0,0 +1,195 @@ +<template> + <ContentWrap> + <!-- 搜索工作栏 --> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="商品名称" prop="name"> + <el-input + v-model="queryParams.name" + placeholder="请输入商品名称" + clearable + @keyup.enter="handleQuery" + class="!w-240px" + /> + </el-form-item> + <el-form-item label="收藏时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + value-format="YYYY-MM-DD HH:mm:ss" + type="daterange" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + class="!w-240px" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"> + <Icon icon="ep:search" class="mr-5px" /> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon icon="ep:refresh" class="mr-5px" /> + 重置 + </el-button> + </el-form-item> + </el-form> + </ContentWrap> + + <!-- 列表 --> + <ContentWrap> + <el-table v-loading="loading" :data="list"> + <el-table-column key="id" align="center" label="商品编号" width="180" prop="id" /> + <el-table-column label="商品图" min-width="80"> + <template #default="{ row }"> + <el-image :src="row.picUrl" class="h-30px w-30px" @click="imagePreview(row.picUrl)" /> + </template> + </el-table-column> + <el-table-column :show-overflow-tooltip="true" label="商品名称" min-width="300" prop="name" /> + <el-table-column align="center" label="商品售价" min-width="90" prop="price"> + <template #default="{ row }"> {{ floatToFixed2(row.price) }}元</template> + </el-table-column> + <el-table-column align="center" label="销量" min-width="90" prop="salesCount" /> + <el-table-column + :formatter="dateFormatter" + align="center" + label="收藏时间" + prop="createTime" + width="180" + /> + <el-table-column align="center" label="状态" min-width="80"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.PRODUCT_SPU_STATUS" :value="scope.row.status" /> + </template> + </el-table-column> + <el-table-column align="center" fixed="right" label="操作" min-width="200"> + <template #default="{ row }"> + <el-button + v-if="row.favoriteStatus === 0" + v-hasPermi="['product:spu:delete']" + link + type="danger" + @click="handleAdd(row)" + > + 收藏 + </el-button> + <el-button + v-if="row.favoriteStatus === 1" + v-hasPermi="['product:spu:delete']" + link + type="danger" + @click="handleDelete(row)" + > + 取消收藏 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </ContentWrap> +</template> + +<script lang="ts" setup> +import { DICT_TYPE } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' +import * as FavoriteApi from '@/api/mall/product/favorite' +import { floatToFixed2 } from '@/utils' + +const message = useMessage() // 消息弹窗 +const { t } = useI18n() // 国际化 + +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + name: null, + createTime: [], + userId: NaN +}) +const queryFormRef = ref() // 搜索的表单 + +/** 查询列表 */ +const getList = async () => { + loading.value = true + try { + const data = await FavoriteApi.getFavoritePage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 取消收藏按钮操作 */ +const handleDelete = async (row: object) => { + try { + console.log(row) + // 取消的二次确认 + await message.delStarConfirm() + // 发起取消 + await FavoriteApi.delFavorite({ userId: queryParams.userId, spuId: row.spuId }) + message.success(t('common.delStarSuccess')) + row.favoriteStatus = 0 + // 刷新列表 + // await getList() + } catch {} +} + +/** 收藏按钮操作 */ +const handleAdd = async (row: object) => { + try { + const data = { userId: queryParams.userId, spuId: row.spuId } + // 发起收藏 + const result = await FavoriteApi.exitsFavorite(data) + if (result === false) { + // 发起收藏 + await FavoriteApi.createFavorite(data) + message.success(t('common.starSuccess')) + row.favoriteStatus = 1 + // 刷新列表 + // await getList() + } else { + message.warning(t('common.existStar')) + row.favoriteStatus = 1 + } + } catch {} +} + +const { userId } = defineProps({ + userId: { + type: Number, + required: true + } +}) + +/** 初始化 **/ +onMounted(() => { + queryParams.userId = userId + getList() +}) +</script> diff --git a/src/views/member/user/detail/index.vue b/src/views/member/user/detail/index.vue index 1bac010e..6237cca6 100644 --- a/src/views/member/user/detail/index.vue +++ b/src/views/member/user/detail/index.vue @@ -48,7 +48,9 @@ <UserOrderList :user-id="id" /> </el-tab-pane> <el-tab-pane label="售后管理" lazy>售后管理(WIP)</el-tab-pane> - <el-tab-pane label="收藏记录" lazy>收藏记录(WIP)</el-tab-pane> + <el-tab-pane label="收藏记录" lazy> + <UserFavoriteList :user-id="id" /> + </el-tab-pane> <el-tab-pane label="优惠劵" lazy> <UserCouponList :user-id="id" /> </el-tab-pane> @@ -76,6 +78,7 @@ import UserExperienceRecordList from './UserExperienceRecordList.vue' import UserOrderList from './UserOrderList.vue' import UserPointList from './UserPointList.vue' import UserSignList from './UserSignList.vue' +import UserFavoriteList from './UserFavoriteList.vue' import { CardTitle } from '@/components/Card/index' import { ElMessage } from 'element-plus' From f2c71d5836895b7d4ff6520388709f5426d88559 Mon Sep 17 00:00:00 2001 From: niou233 <2922564446@qq.com> Date: Wed, 8 Nov 2023 17:29:57 +0800 Subject: [PATCH 2/7] =?UTF-8?q?refactor:=20=E4=BC=9A=E5=91=98=E5=95=86?= =?UTF-8?q?=E5=93=81=E6=94=B6=E8=97=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/mall/product/favorite.ts | 16 --- .../member/user/detail/UserFavoriteList.vue | 99 ------------------- 2 files changed, 115 deletions(-) diff --git a/src/api/mall/product/favorite.ts b/src/api/mall/product/favorite.ts index 2e54ec78..3834eed0 100644 --- a/src/api/mall/product/favorite.ts +++ b/src/api/mall/product/favorite.ts @@ -8,21 +8,5 @@ export interface Favorite { // 获得 ProductFavorite 列表 export const getFavoritePage = (params: PageParam) => { - params.keyword = params.name return request.get({ url: '/product/favorite/page', params }) } - -// 收藏商品 Favorite -export const createFavorite = (data: Favorite) => { - return request.post({ url: '/product/favorite/create', data }) -} - -// 取消商品收藏 Favorite -export const delFavorite = (data: Favorite) => { - return request.delete({ url: '/product/favorite/delete', data }) -} - -// 是否收藏过商品 Favorite -export const exitsFavorite = (data: Favorite) => { - return request.post({ url: '/product/favorite/exits', data }) -} diff --git a/src/views/member/user/detail/UserFavoriteList.vue b/src/views/member/user/detail/UserFavoriteList.vue index 009a8b10..afab9a08 100644 --- a/src/views/member/user/detail/UserFavoriteList.vue +++ b/src/views/member/user/detail/UserFavoriteList.vue @@ -1,46 +1,4 @@ <template> - <ContentWrap> - <!-- 搜索工作栏 --> - <el-form - class="-mb-15px" - :model="queryParams" - ref="queryFormRef" - :inline="true" - label-width="68px" - > - <el-form-item label="商品名称" prop="name"> - <el-input - v-model="queryParams.name" - placeholder="请输入商品名称" - clearable - @keyup.enter="handleQuery" - class="!w-240px" - /> - </el-form-item> - <el-form-item label="收藏时间" prop="createTime"> - <el-date-picker - v-model="queryParams.createTime" - value-format="YYYY-MM-DD HH:mm:ss" - type="daterange" - start-placeholder="开始日期" - end-placeholder="结束日期" - :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" - class="!w-240px" - /> - </el-form-item> - <el-form-item> - <el-button @click="handleQuery"> - <Icon icon="ep:search" class="mr-5px" /> - 搜索 - </el-button> - <el-button @click="resetQuery"> - <Icon icon="ep:refresh" class="mr-5px" /> - 重置 - </el-button> - </el-form-item> - </el-form> - </ContentWrap> - <!-- 列表 --> <ContentWrap> <el-table v-loading="loading" :data="list"> @@ -67,28 +25,6 @@ <dict-tag :type="DICT_TYPE.PRODUCT_SPU_STATUS" :value="scope.row.status" /> </template> </el-table-column> - <el-table-column align="center" fixed="right" label="操作" min-width="200"> - <template #default="{ row }"> - <el-button - v-if="row.favoriteStatus === 0" - v-hasPermi="['product:spu:delete']" - link - type="danger" - @click="handleAdd(row)" - > - 收藏 - </el-button> - <el-button - v-if="row.favoriteStatus === 1" - v-hasPermi="['product:spu:delete']" - link - type="danger" - @click="handleDelete(row)" - > - 取消收藏 - </el-button> - </template> - </el-table-column> </el-table> <!-- 分页 --> <Pagination @@ -145,41 +81,6 @@ const resetQuery = () => { handleQuery() } -/** 取消收藏按钮操作 */ -const handleDelete = async (row: object) => { - try { - console.log(row) - // 取消的二次确认 - await message.delStarConfirm() - // 发起取消 - await FavoriteApi.delFavorite({ userId: queryParams.userId, spuId: row.spuId }) - message.success(t('common.delStarSuccess')) - row.favoriteStatus = 0 - // 刷新列表 - // await getList() - } catch {} -} - -/** 收藏按钮操作 */ -const handleAdd = async (row: object) => { - try { - const data = { userId: queryParams.userId, spuId: row.spuId } - // 发起收藏 - const result = await FavoriteApi.exitsFavorite(data) - if (result === false) { - // 发起收藏 - await FavoriteApi.createFavorite(data) - message.success(t('common.starSuccess')) - row.favoriteStatus = 1 - // 刷新列表 - // await getList() - } else { - message.warning(t('common.existStar')) - row.favoriteStatus = 1 - } - } catch {} -} - const { userId } = defineProps({ userId: { type: Number, From 2ff97758fd720b281e2b76afa56f2fc637476916 Mon Sep 17 00:00:00 2001 From: niou233 <2922564446@qq.com> Date: Fri, 10 Nov 2023 11:34:32 +0000 Subject: [PATCH 3/7] update src/hooks/web/useMessage.ts. Signed-off-by: niou233 <2922564446@qq.com> --- src/hooks/web/useMessage.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/hooks/web/useMessage.ts b/src/hooks/web/useMessage.ts index 9bfb7a3a..ac2b552e 100644 --- a/src/hooks/web/useMessage.ts +++ b/src/hooks/web/useMessage.ts @@ -90,18 +90,6 @@ export const useMessage = () => { cancelButtonText: t('common.cancel'), type: 'warning' }) - }, - // 取消收藏窗体 - delStarConfirm(content?: string, tip?: string) { - return ElMessageBox.confirm( - content ? content : t('common.confirmDelStar'), - tip ? tip : t('common.confirmTitle'), - { - confirmButtonText: t('common.ok'), - cancelButtonText: t('common.cancel'), - type: 'warning' - } - ) } } } From 1ab5b44254415e050615d91128df0afb703ccc1d Mon Sep 17 00:00:00 2001 From: niou233 <2922564446@qq.com> Date: Fri, 10 Nov 2023 11:35:54 +0000 Subject: [PATCH 4/7] update src/locales/en.ts. Signed-off-by: niou233 <2922564446@qq.com> --- src/locales/en.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/locales/en.ts b/src/locales/en.ts index d82e616e..4f4d4895 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -54,13 +54,7 @@ export default { updateTime: 'Update Time', copy: 'Copy', copySuccess: 'Copy Success', - copyError: 'Copy Error', - confirmDelStar: 'Delete the Star?', - starSuccess: 'Star Success', - starFail: 'Star Error', - delStarSuccess: 'Del Star Success', - delStarFail: 'Del Star Error', - existStar: 'exist Star' + copyError: 'Copy Error' }, error: { noPermission: `Sorry, you don't have permission to access this page.`, From af5d1cf25ee8898cab5e1d1b520b288021d982bc Mon Sep 17 00:00:00 2001 From: niou233 <2922564446@qq.com> Date: Fri, 10 Nov 2023 11:36:11 +0000 Subject: [PATCH 5/7] update src/locales/zh-CN.ts. Signed-off-by: niou233 <2922564446@qq.com> --- src/locales/zh-CN.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 49a3f895..4f95852f 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -54,13 +54,7 @@ export default { updateTime: '更新时间', copy: '复制', copySuccess: '复制成功', - copyError: '复制失败', - confirmDelStar: '是否取消收藏?', - starSuccess: '收藏成功', - starFail: '收藏失败', - delStarSuccess: '取消收藏成功', - delStarFail: '取消收藏失败', - existStar: '收藏已存在' + copyError: '复制失败' }, error: { noPermission: `抱歉,您无权访问此页面。`, From f74ddf46ae518c02ebcb1900ca39e39f97b524af Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Tue, 21 Nov 2023 22:53:49 +0800 Subject: [PATCH 6/7] =?UTF-8?q?sms=EF=BC=9A=E7=A7=BB=E9=99=A4=20sendCode?= =?UTF-8?q?=E3=80=81sendMsg=20=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/sms/smsLog/index.ts | 2 -- src/views/system/sms/log/SmsLogDetail.vue | 3 --- 2 files changed, 5 deletions(-) diff --git a/src/api/system/sms/smsLog/index.ts b/src/api/system/sms/smsLog/index.ts index 3d54fac1..f9891716 100644 --- a/src/api/system/sms/smsLog/index.ts +++ b/src/api/system/sms/smsLog/index.ts @@ -15,8 +15,6 @@ export interface SmsLogVO { userType: number | null sendStatus: number | null sendTime: Date | null - sendCode: number | null - sendMsg: string apiSendCode: string apiSendMsg: string apiRequestId: string diff --git a/src/views/system/sms/log/SmsLogDetail.vue b/src/views/system/sms/log/SmsLogDetail.vue index 34c5e58e..b0d22c2d 100644 --- a/src/views/system/sms/log/SmsLogDetail.vue +++ b/src/views/system/sms/log/SmsLogDetail.vue @@ -37,9 +37,6 @@ <el-descriptions-item label="发送时间"> {{ formatDate(detailData.sendTime) }} </el-descriptions-item> - <el-descriptions-item label="发送结果"> - {{ detailData.sendCode }} | {{ detailData.sendMsg }} - </el-descriptions-item> <el-descriptions-item label="API 发送结果"> {{ detailData.apiSendCode }} | {{ detailData.apiSendMsg }} </el-descriptions-item> From 1744c6ec6fea5897ee202d42c5e6198285ba09e4 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Fri, 24 Nov 2023 00:33:05 +0800 Subject: [PATCH 7/7] =?UTF-8?q?websocket=20=E9=87=8D=E6=96=B0=E5=B0=81?= =?UTF-8?q?=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.base | 4 +- src/api/system/notice/index.ts | 5 + src/locales/zh-CN.ts | 3 +- src/views/infra/webSocket/index.vue | 142 ++++++++++++++++++++-------- src/views/system/notice/index.vue | 14 +++ 5 files changed, 125 insertions(+), 43 deletions(-) diff --git a/.env.base b/.env.base index fdb26d85..cf433822 100644 --- a/.env.base +++ b/.env.base @@ -4,10 +4,10 @@ NODE_ENV=development VITE_DEV=true # 请求路径 -VITE_BASE_URL='http://localhost:48080' +VITE_BASE_URL='http://127.0.0.1:48080' # 上传路径 -VITE_UPLOAD_URL='http://localhost:48080/admin-api/infra/file/upload' +VITE_UPLOAD_URL='http://127.0.0.1:48080/admin-api/infra/file/upload' # 接口前缀 VITE_API_BASEPATH=/dev-api diff --git a/src/api/system/notice/index.ts b/src/api/system/notice/index.ts index 62bf5259..f6434697 100644 --- a/src/api/system/notice/index.ts +++ b/src/api/system/notice/index.ts @@ -35,3 +35,8 @@ export const updateNotice = (data: NoticeVO) => { export const deleteNotice = (id: number) => { return request.delete({ url: '/system/notice/delete?id=' + id }) } + +// 推送公告 +export const pushNotice = (id: number) => { + return request.post({ url: '/system/notice/push?id=' + id }) +} diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 4f95852f..6346a3d3 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -437,5 +437,6 @@ export default { btn_zoom_in: '放大', btn_zoom_out: '缩小', preview: '预览' - } + }, + 'OAuth 2.0': 'OAuth 2.0' // 避免菜单名是 OAuth 2.0 时,一直 warn 报错 } diff --git a/src/views/infra/webSocket/index.vue b/src/views/infra/webSocket/index.vue index ce6db798..0f609213 100644 --- a/src/views/infra/webSocket/index.vue +++ b/src/views/infra/webSocket/index.vue @@ -1,5 +1,6 @@ <template> <div class="flex"> + <!-- 左侧:建立连接、发送消息 --> <el-card :gutter="12" class="w-1/2" shadow="always"> <template #header> <div class="card-header"> @@ -11,28 +12,38 @@ <el-tag :color="getTagColor">{{ status }}</el-tag> </div> <hr class="my-4" /> - <div class="flex"> <el-input v-model="server" disabled> - <template #prepend> 服务地址</template> + <template #prepend>服务地址</template> </el-input> - <el-button :type="getIsOpen ? 'danger' : 'primary'" @click="toggle"> + <el-button :type="getIsOpen ? 'danger' : 'primary'" @click="toggleConnectStatus"> {{ getIsOpen ? '关闭连接' : '开启连接' }} </el-button> </div> - <p class="mt-4 text-lg font-medium">设置</p> + <p class="mt-4 text-lg font-medium">消息输入框</p> <hr class="my-4" /> <el-input - v-model="sendValue" + v-model="sendText" :autosize="{ minRows: 2, maxRows: 4 }" :disabled="!getIsOpen" clearable type="textarea" + placeholder="请输入你要发送的消息" /> - <el-button :disabled="!getIsOpen" block class="mt-4" type="primary" @click="handlerSend"> + <el-select v-model="sendUserId" class="mt-4" placeholder="请选择发送人"> + <el-option key="" label="所有人" value="" /> + <el-option + v-for="user in userList" + :key="user.id" + :label="user.nickname" + :value="user.id" + /> + </el-select> + <el-button :disabled="!getIsOpen" block class="ml-2 mt-4" type="primary" @click="handlerSend"> 发送 </el-button> </el-card> + <!-- 右侧:消息记录 --> <el-card :gutter="12" class="w-1/2" shadow="always"> <template #header> <div class="card-header"> @@ -41,13 +52,13 @@ </template> <div class="max-h-80 overflow-auto"> <ul> - <li v-for="item in getList" :key="item.time" class="mt-2"> + <li v-for="msg in messageList.reverse()" :key="msg.time" class="mt-2"> <div class="flex items-center"> <span class="text-primary mr-2 font-medium">收到消息:</span> - <span>{{ formatDate(item.time) }}</span> + <span>{{ formatDate(msg.time) }}</span> </div> <div> - {{ item.res }} + {{ msg.text }} </div> </li> </ul> @@ -57,62 +68,113 @@ </template> <script lang="ts" setup> import { formatDate } from '@/utils/formatTime' -import { useUserStore } from '@/store/modules/user' import { useWebSocket } from '@vueuse/core' +import { getAccessToken } from '@/utils/auth' +import * as UserApi from '@/api/system/user' defineOptions({ name: 'InfraWebSocket' }) -const userStore = useUserStore() - -const sendValue = ref('') +const message = useMessage() // 消息弹窗 const server = ref( - (import.meta.env.VITE_BASE_URL + '/websocket/message').replace('http', 'ws') + - '?userId=' + - userStore.getUser.id -) - -const state = reactive({ - recordList: [] as { id: number; time: number; res: string }[] -}) + (import.meta.env.VITE_BASE_URL + '/infra/ws').replace('http', 'ws') + '?token=' + getAccessToken() +) // WebSocket 服务地址 +const getIsOpen = computed(() => status.value === 'OPEN') // WebSocket 连接是否打开 +const getTagColor = computed(() => (getIsOpen.value ? 'success' : 'red')) // WebSocket 连接的展示颜色 +/** 发起 WebSocket 连接 */ const { status, data, send, close, open } = useWebSocket(server.value, { autoReconnect: false, heartbeat: true }) +/** 监听接收到的数据 */ +const messageList = ref([] as { time: number; text: string }[]) // 消息列表 watchEffect(() => { - if (data.value) { - try { - const res = JSON.parse(data.value) - state.recordList.push(res) - } catch (error) { - state.recordList.push({ - res: data.value, - id: Math.ceil(Math.random() * 1000), + if (!data.value) { + return + } + try { + // 1. 收到心跳 + if (data.value === 'pong') { + // state.recordList.push({ + // text: '【心跳】', + // time: new Date().getTime() + // }) + return + } + + // 2.1 解析 type 消息类型 + const jsonMessage = JSON.parse(data.value) + const type = jsonMessage.type + const content = JSON.parse(jsonMessage.content) + if (!type) { + message.error('未知的消息类型:' + data.value) + return + } + // 2.2 消息类型:demo-message-receive + if (type === 'demo-message-receive') { + const single = content.single + if (single) { + messageList.value.push({ + text: `【单发】用户编号(${content.fromUserId}):${content.text}`, + time: new Date().getTime() + }) + } else { + messageList.value.push({ + text: `【群发】用户编号(${content.fromUserId}):${content.text}`, + time: new Date().getTime() + }) + } + return + } + // 2.3 消息类型:notice-push + if (type === 'notice-push') { + messageList.value.push({ + text: `【系统通知】:${content.title}`, time: new Date().getTime() }) + return } + message.error('未处理消息:' + data.value) + } catch (error) { + message.error('处理消息发生异常:' + data.value) + console.error(error) } }) -const getIsOpen = computed(() => status.value === 'OPEN') -const getTagColor = computed(() => (getIsOpen.value ? 'success' : 'red')) - -const getList = computed(() => { - return [...state.recordList].reverse() -}) - -function handlerSend() { - send(sendValue.value) - sendValue.value = '' +/** 发送消息 */ +const sendText = ref('') // 发送内容 +const sendUserId = ref('') // 发送人 +const handlerSend = () => { + // 1.1 先 JSON 化 message 消息内容 + const messageContent = JSON.stringify({ + text: sendText.value, + toUserId: sendUserId.value + }) + // 1.2 再 JSON 化整个消息 + const jsonMessage = JSON.stringify({ + type: 'demo-message-send', + content: messageContent + }) + // 2. 最后发送消息 + send(jsonMessage) + sendText.value = '' } -function toggle() { +/** 切换 websocket 连接状态 */ +const toggleConnectStatus = () => { if (getIsOpen.value) { close() } else { open() } } + +/** 初始化 **/ +const userList = ref<any[]>([]) // 用户列表 +onMounted(async () => { + // 获取用户列表 + userList.value = await UserApi.getSimpleUserList() +}) </script> diff --git a/src/views/system/notice/index.vue b/src/views/system/notice/index.vue index dc776d3a..f482f91c 100644 --- a/src/views/system/notice/index.vue +++ b/src/views/system/notice/index.vue @@ -87,6 +87,9 @@ > 删除 </el-button> + <el-button link @click="handlePush(scope.row.id)" v-hasPermi="['system:notice:update']"> + 推送 + </el-button> </template> </el-table-column> </el-table> @@ -168,6 +171,17 @@ const handleDelete = async (id: number) => { } catch {} } +/** 推送按钮操作 */ +const handlePush = async (id: number) => { + try { + // 推送的二次确认 + await message.confirm('是否推送所选中通知?') + // 发起推送 + await NoticeApi.pushNotice(id) + message.success(t('推送成功')) + } catch {} +} + /** 初始化 **/ onMounted(() => { getList()