新增二维码池独立权限

This commit is contained in:
yml2213
2026-06-19 18:54:13 +08:00
parent 72285ed638
commit f4343ed8a5
5 changed files with 100 additions and 24 deletions
+8 -8
View File
@@ -591,14 +591,14 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
adminRoutes.PUT("/chats/listing-group-welcome", requirePerm("system_config:update"), chatHandler.AdminUpdateListingGroupWelcome)
// 二维码池管理
adminRoutes.POST("/chats/qrcodes", requirePerm("chat:manage"), chatHandler.CreateQrCodeHandler)
adminRoutes.POST("/chats/qrcodes/batch", requirePerm("chat:manage"), chatHandler.BatchCreateQrCodeHandler)
adminRoutes.POST("/chats/qrcodes/batch-delete", requirePerm("chat:manage"), chatHandler.BatchDeleteQrCodeHandler)
adminRoutes.GET("/chats/qrcodes", requirePerm("chat:view"), chatHandler.ListQrCodesHandler)
adminRoutes.GET("/chats/qrcodes/stats", requirePerm("chat:view"), chatHandler.GetQrCodeStatsHandler)
adminRoutes.POST("/chats/qrcodes/ocr-group-name", requirePerm("chat:manage"), chatHandler.RecognizeQrCodeGroupNameHandler)
adminRoutes.PATCH("/chats/qrcodes/:id", requirePerm("chat:manage"), chatHandler.UpdateQrCodeHandler)
adminRoutes.DELETE("/chats/qrcodes/:id", requirePerm("chat:manage"), chatHandler.DeleteQrCodeHandler)
adminRoutes.POST("/chats/qrcodes", requirePerm("qrcode:manage"), chatHandler.CreateQrCodeHandler)
adminRoutes.POST("/chats/qrcodes/batch", requirePerm("qrcode:manage"), chatHandler.BatchCreateQrCodeHandler)
adminRoutes.POST("/chats/qrcodes/batch-delete", requirePerm("qrcode:manage"), chatHandler.BatchDeleteQrCodeHandler)
adminRoutes.GET("/chats/qrcodes", requirePerm("qrcode:view"), chatHandler.ListQrCodesHandler)
adminRoutes.GET("/chats/qrcodes/stats", requirePerm("qrcode:view"), chatHandler.GetQrCodeStatsHandler)
adminRoutes.POST("/chats/qrcodes/ocr-group-name", requirePerm("qrcode:manage"), chatHandler.RecognizeQrCodeGroupNameHandler)
adminRoutes.PATCH("/chats/qrcodes/:id", requirePerm("qrcode:manage"), chatHandler.UpdateQrCodeHandler)
adminRoutes.DELETE("/chats/qrcodes/:id", requirePerm("qrcode:manage"), chatHandler.DeleteQrCodeHandler)
// 客服分组
adminRoutes.GET("/chat-support-groups", requirePerm("chat:manage"), supportGroupHandler.List)
@@ -0,0 +1,51 @@
-- +goose Up
-- +goose StatementBegin
INSERT INTO permissions (code, name, resource, action) VALUES
('qrcode:view', '查看二维码池', 'qrcode', 'view'),
('qrcode:manage', '管理二维码池', 'qrcode', 'manage')
ON DUPLICATE KEY UPDATE
name = VALUES(name),
resource = VALUES(resource),
action = VALUES(action);
-- 超级管理员补齐新增权限。
INSERT IGNORE INTO role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM roles r
JOIN permissions p ON p.code IN ('qrcode:view', 'qrcode:manage')
WHERE r.code = 'super_admin';
-- 客服默认可以查看和维护二维码池。
INSERT IGNORE INTO role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM roles r
JOIN permissions p ON p.code IN ('qrcode:view', 'qrcode:manage')
WHERE r.code = 'cs';
-- 已拥有客服配置管理权限的角色,迁移后继续拥有二维码池管理能力。
INSERT IGNORE INTO role_permissions (role_id, permission_id)
SELECT DISTINCT rp.role_id, p.id
FROM role_permissions rp
JOIN permissions oldp ON oldp.id = rp.permission_id AND oldp.code = 'chat:manage'
JOIN permissions p ON p.code IN ('qrcode:view', 'qrcode:manage');
-- 已拥有客服群聊查看权限的角色,迁移后继续能进入二维码池查看页。
INSERT IGNORE INTO role_permissions (role_id, permission_id)
SELECT DISTINCT rp.role_id, p.id
FROM role_permissions rp
JOIN permissions oldp ON oldp.id = rp.permission_id AND oldp.code = 'chat:view'
JOIN permissions p ON p.code = 'qrcode:view';
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DELETE rp FROM role_permissions rp
JOIN permissions p ON p.id = rp.permission_id
WHERE p.code IN ('qrcode:view', 'qrcode:manage');
DELETE FROM permissions WHERE code IN ('qrcode:view', 'qrcode:manage');
-- +goose StatementEnd
@@ -102,6 +102,7 @@ const resourceLabels: Record<string, string> = {
listing: '商品管理',
dispute: '仲裁中心',
chat: '客服群聊',
qrcode: '二维码池',
wallet: '资金流水',
withdrawal: '提现审核',
payment_config: '支付配置',
@@ -29,10 +29,13 @@ import { formatDateTime } from '@/shared/utils/time'
import AdminTablePagination from '../components/AdminTablePagination.vue'
import AuthImage from '@/shared/components/business/AuthImage.vue'
import { readError } from '@/shared/utils/error'
import { useAdminSessionStore } from '@/stores/adminSession'
const adminSession = useAdminSessionStore()
const loading = ref(false)
const qrcodes = ref<ChatQrCode[]>([])
const stats = ref<QrCodeStats>({ unused_count: 0, used_count: 0, disabled_count: 0, total_count: 0 })
const canManageQrCodes = computed(() => adminSession.hasPermission('qrcode:manage'))
const currentPage = ref(1)
const currentPageSize = ref(20)
@@ -239,6 +242,7 @@ function handleUploadExceed() {
}
function openUpload() {
if (!canManageQrCodes.value) return
clearUploadedImages()
uploadForm.note = ''
uploadForm.expires_at = ''
@@ -246,6 +250,7 @@ function openUpload() {
}
async function submitUpload() {
if (!canManageQrCodes.value) return
if (uploadPendingCount.value > 0) {
ElMessage.warning('图片还在上传中,请稍后保存')
return
@@ -279,6 +284,7 @@ onBeforeUnmount(() => {
})
function openEdit(row: ChatQrCode) {
if (!canManageQrCodes.value) return
editForm.id = row.id
editForm.image_url = row.image_url
editForm.group_name = row.group_name || ''
@@ -295,6 +301,7 @@ function clearEditExpiresAt() {
}
async function handleEditImageUpload(options: { file: File }) {
if (!canManageQrCodes.value) return
editUploading.value = true
try {
const uploaded = await uploadAdminFile(options.file, 'qrcode')
@@ -308,6 +315,7 @@ async function handleEditImageUpload(options: { file: File }) {
}
async function submitEdit() {
if (!canManageQrCodes.value) return
await updateQrCode(editForm.id, {
image_url: editForm.image_url,
group_name: editForm.group_name,
@@ -356,6 +364,7 @@ function setRenameLoading(id: number, loading: boolean) {
}
async function handleRenameFlagChange(row: ChatQrCode, value: string | number | boolean) {
if (!canManageQrCodes.value) return
const nextValue = Boolean(value)
const previous = !nextValue
row.wecom_renamed = nextValue
@@ -372,6 +381,7 @@ async function handleRenameFlagChange(row: ChatQrCode, value: string | number |
}
async function handleDisable(row: ChatQrCode) {
if (!canManageQrCodes.value) return
await ElMessageBox.confirm('确定停用该二维码?停用后不再发放给新发布群。', '停用确认', {
type: 'warning',
})
@@ -381,10 +391,10 @@ async function handleDisable(row: ChatQrCode) {
}
async function handleDelete(row: ChatQrCode) {
const tip =
qrcodeWasIssued(row)
? '确定删除这条已发放二维码记录?删除后仅清理二维码池旧记录,不会影响已发送到群聊里的图片消息。'
: '确定删除该二维码?删除后将从二维码池移除。'
if (!canManageQrCodes.value) return
const tip = qrcodeWasIssued(row)
? '确定删除这条已发放二维码记录?删除后仅清理二维码池旧记录,不会影响已发送到群聊里的图片消息。'
: '确定删除该二维码?删除后将从二维码池移除。'
await ElMessageBox.confirm(tip, '删除确认', {
type: 'warning',
})
@@ -394,6 +404,7 @@ async function handleDelete(row: ChatQrCode) {
}
async function handleBatchDelete() {
if (!canManageQrCodes.value) return
if (!selectedCount.value) {
ElMessage.warning('请先选择要删除的二维码')
return
@@ -460,6 +471,7 @@ onMounted(reloadAll)
<div class="action-area">
<span v-if="selectedCount" class="selection-summary">已选 {{ selectedCount }} </span>
<el-button
v-if="canManageQrCodes"
type="danger"
:icon="Delete"
:disabled="!selectedCount"
@@ -468,7 +480,9 @@ onMounted(reloadAll)
批量删除
</el-button>
<el-button :icon="Refresh" @click="reloadAll">刷新</el-button>
<el-button type="primary" :icon="Plus" @click="openUpload">添加二维码</el-button>
<el-button v-if="canManageQrCodes" type="primary" :icon="Plus" @click="openUpload">
添加二维码
</el-button>
</div>
</div>
</el-card>
@@ -483,7 +497,12 @@ onMounted(reloadAll)
row-key="id"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" class-name="selection-col" align="center" />
<el-table-column
v-if="canManageQrCodes"
type="selection"
class-name="selection-col"
align="center"
/>
<el-table-column label="二维码" class-name="qr-col" align="center">
<template #default="{ row }">
<div class="qrcode-thumb">
@@ -539,6 +558,7 @@ onMounted(reloadAll)
<el-switch
:model-value="row.wecom_renamed"
:loading="renameLoadingIds.has(row.id)"
:disabled="!canManageQrCodes"
inline-prompt
:active-icon="Check"
active-text="已改"
@@ -560,7 +580,12 @@ onMounted(reloadAll)
</div>
</template>
</el-table-column>
<el-table-column label="操作" class-name="action-col" align="center">
<el-table-column
v-if="canManageQrCodes"
label="操作"
class-name="action-col"
align="center"
>
<template #default="{ row }">
<div class="table-actions">
<el-button text type="primary" size="small" @click="openEdit(row)">编辑</el-button>
@@ -572,13 +597,7 @@ onMounted(reloadAll)
@click="handleDisable(row)"
>停用</el-button
>
<el-button
text
type="danger"
size="small"
@click="handleDelete(row)"
>删除</el-button
>
<el-button text type="danger" size="small" @click="handleDelete(row)">删除</el-button>
</div>
</template>
</el-table-column>
@@ -594,7 +613,12 @@ onMounted(reloadAll)
</el-card>
<!-- 上传弹窗 -->
<el-dialog v-model="uploadVisible" title="批量添加企业微信群二维码" width="680px" @close="clearUploadedImages">
<el-dialog
v-model="uploadVisible"
title="批量添加企业微信群二维码"
width="680px"
@close="clearUploadedImages"
>
<el-form class="qrcode-upload-form" label-width="108px">
<el-form-item label="二维码图片" required>
<div class="batch-upload-area">
+1 -1
View File
@@ -104,7 +104,7 @@ const allNavGroups: NavGroup[] = [
permission: 'dispute:view',
},
{ label: '客服群聊', to: adminPath('chats'), icon: ChatDotRound, permission: 'chat:view' },
{ label: '二维码池', to: adminPath('qrcode-pool'), icon: Picture, permission: 'chat:view' },
{ label: '二维码池', to: adminPath('qrcode-pool'), icon: Picture, permission: 'qrcode:view' },
{ label: '客服分组', to: adminPath('support-groups'), icon: User, permission: 'chat:manage' },
],
},