新增二维码池独立权限
This commit is contained in:
@@ -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.PUT("/chats/listing-group-welcome", requirePerm("system_config:update"), chatHandler.AdminUpdateListingGroupWelcome)
|
||||||
|
|
||||||
// 二维码池管理
|
// 二维码池管理
|
||||||
adminRoutes.POST("/chats/qrcodes", requirePerm("chat:manage"), chatHandler.CreateQrCodeHandler)
|
adminRoutes.POST("/chats/qrcodes", requirePerm("qrcode:manage"), chatHandler.CreateQrCodeHandler)
|
||||||
adminRoutes.POST("/chats/qrcodes/batch", requirePerm("chat:manage"), chatHandler.BatchCreateQrCodeHandler)
|
adminRoutes.POST("/chats/qrcodes/batch", requirePerm("qrcode:manage"), chatHandler.BatchCreateQrCodeHandler)
|
||||||
adminRoutes.POST("/chats/qrcodes/batch-delete", requirePerm("chat:manage"), chatHandler.BatchDeleteQrCodeHandler)
|
adminRoutes.POST("/chats/qrcodes/batch-delete", requirePerm("qrcode:manage"), chatHandler.BatchDeleteQrCodeHandler)
|
||||||
adminRoutes.GET("/chats/qrcodes", requirePerm("chat:view"), chatHandler.ListQrCodesHandler)
|
adminRoutes.GET("/chats/qrcodes", requirePerm("qrcode:view"), chatHandler.ListQrCodesHandler)
|
||||||
adminRoutes.GET("/chats/qrcodes/stats", requirePerm("chat:view"), chatHandler.GetQrCodeStatsHandler)
|
adminRoutes.GET("/chats/qrcodes/stats", requirePerm("qrcode:view"), chatHandler.GetQrCodeStatsHandler)
|
||||||
adminRoutes.POST("/chats/qrcodes/ocr-group-name", requirePerm("chat:manage"), chatHandler.RecognizeQrCodeGroupNameHandler)
|
adminRoutes.POST("/chats/qrcodes/ocr-group-name", requirePerm("qrcode:manage"), chatHandler.RecognizeQrCodeGroupNameHandler)
|
||||||
adminRoutes.PATCH("/chats/qrcodes/:id", requirePerm("chat:manage"), chatHandler.UpdateQrCodeHandler)
|
adminRoutes.PATCH("/chats/qrcodes/:id", requirePerm("qrcode:manage"), chatHandler.UpdateQrCodeHandler)
|
||||||
adminRoutes.DELETE("/chats/qrcodes/:id", requirePerm("chat:manage"), chatHandler.DeleteQrCodeHandler)
|
adminRoutes.DELETE("/chats/qrcodes/:id", requirePerm("qrcode:manage"), chatHandler.DeleteQrCodeHandler)
|
||||||
|
|
||||||
// 客服分组
|
// 客服分组
|
||||||
adminRoutes.GET("/chat-support-groups", requirePerm("chat:manage"), supportGroupHandler.List)
|
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: '商品管理',
|
listing: '商品管理',
|
||||||
dispute: '仲裁中心',
|
dispute: '仲裁中心',
|
||||||
chat: '客服群聊',
|
chat: '客服群聊',
|
||||||
|
qrcode: '二维码池',
|
||||||
wallet: '资金流水',
|
wallet: '资金流水',
|
||||||
withdrawal: '提现审核',
|
withdrawal: '提现审核',
|
||||||
payment_config: '支付配置',
|
payment_config: '支付配置',
|
||||||
|
|||||||
@@ -29,10 +29,13 @@ import { formatDateTime } from '@/shared/utils/time'
|
|||||||
import AdminTablePagination from '../components/AdminTablePagination.vue'
|
import AdminTablePagination from '../components/AdminTablePagination.vue'
|
||||||
import AuthImage from '@/shared/components/business/AuthImage.vue'
|
import AuthImage from '@/shared/components/business/AuthImage.vue'
|
||||||
import { readError } from '@/shared/utils/error'
|
import { readError } from '@/shared/utils/error'
|
||||||
|
import { useAdminSessionStore } from '@/stores/adminSession'
|
||||||
|
|
||||||
|
const adminSession = useAdminSessionStore()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const qrcodes = ref<ChatQrCode[]>([])
|
const qrcodes = ref<ChatQrCode[]>([])
|
||||||
const stats = ref<QrCodeStats>({ unused_count: 0, used_count: 0, disabled_count: 0, total_count: 0 })
|
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 currentPage = ref(1)
|
||||||
const currentPageSize = ref(20)
|
const currentPageSize = ref(20)
|
||||||
@@ -239,6 +242,7 @@ function handleUploadExceed() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function openUpload() {
|
function openUpload() {
|
||||||
|
if (!canManageQrCodes.value) return
|
||||||
clearUploadedImages()
|
clearUploadedImages()
|
||||||
uploadForm.note = ''
|
uploadForm.note = ''
|
||||||
uploadForm.expires_at = ''
|
uploadForm.expires_at = ''
|
||||||
@@ -246,6 +250,7 @@ function openUpload() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function submitUpload() {
|
async function submitUpload() {
|
||||||
|
if (!canManageQrCodes.value) return
|
||||||
if (uploadPendingCount.value > 0) {
|
if (uploadPendingCount.value > 0) {
|
||||||
ElMessage.warning('图片还在上传中,请稍后保存')
|
ElMessage.warning('图片还在上传中,请稍后保存')
|
||||||
return
|
return
|
||||||
@@ -279,6 +284,7 @@ onBeforeUnmount(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function openEdit(row: ChatQrCode) {
|
function openEdit(row: ChatQrCode) {
|
||||||
|
if (!canManageQrCodes.value) return
|
||||||
editForm.id = row.id
|
editForm.id = row.id
|
||||||
editForm.image_url = row.image_url
|
editForm.image_url = row.image_url
|
||||||
editForm.group_name = row.group_name || ''
|
editForm.group_name = row.group_name || ''
|
||||||
@@ -295,6 +301,7 @@ function clearEditExpiresAt() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleEditImageUpload(options: { file: File }) {
|
async function handleEditImageUpload(options: { file: File }) {
|
||||||
|
if (!canManageQrCodes.value) return
|
||||||
editUploading.value = true
|
editUploading.value = true
|
||||||
try {
|
try {
|
||||||
const uploaded = await uploadAdminFile(options.file, 'qrcode')
|
const uploaded = await uploadAdminFile(options.file, 'qrcode')
|
||||||
@@ -308,6 +315,7 @@ async function handleEditImageUpload(options: { file: File }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function submitEdit() {
|
async function submitEdit() {
|
||||||
|
if (!canManageQrCodes.value) return
|
||||||
await updateQrCode(editForm.id, {
|
await updateQrCode(editForm.id, {
|
||||||
image_url: editForm.image_url,
|
image_url: editForm.image_url,
|
||||||
group_name: editForm.group_name,
|
group_name: editForm.group_name,
|
||||||
@@ -356,6 +364,7 @@ function setRenameLoading(id: number, loading: boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleRenameFlagChange(row: ChatQrCode, value: string | number | boolean) {
|
async function handleRenameFlagChange(row: ChatQrCode, value: string | number | boolean) {
|
||||||
|
if (!canManageQrCodes.value) return
|
||||||
const nextValue = Boolean(value)
|
const nextValue = Boolean(value)
|
||||||
const previous = !nextValue
|
const previous = !nextValue
|
||||||
row.wecom_renamed = nextValue
|
row.wecom_renamed = nextValue
|
||||||
@@ -372,6 +381,7 @@ async function handleRenameFlagChange(row: ChatQrCode, value: string | number |
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleDisable(row: ChatQrCode) {
|
async function handleDisable(row: ChatQrCode) {
|
||||||
|
if (!canManageQrCodes.value) return
|
||||||
await ElMessageBox.confirm('确定停用该二维码?停用后不再发放给新发布群。', '停用确认', {
|
await ElMessageBox.confirm('确定停用该二维码?停用后不再发放给新发布群。', '停用确认', {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
})
|
})
|
||||||
@@ -381,10 +391,10 @@ async function handleDisable(row: ChatQrCode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleDelete(row: ChatQrCode) {
|
async function handleDelete(row: ChatQrCode) {
|
||||||
const tip =
|
if (!canManageQrCodes.value) return
|
||||||
qrcodeWasIssued(row)
|
const tip = qrcodeWasIssued(row)
|
||||||
? '确定删除这条已发放二维码记录?删除后仅清理二维码池旧记录,不会影响已发送到群聊里的图片消息。'
|
? '确定删除这条已发放二维码记录?删除后仅清理二维码池旧记录,不会影响已发送到群聊里的图片消息。'
|
||||||
: '确定删除该二维码?删除后将从二维码池移除。'
|
: '确定删除该二维码?删除后将从二维码池移除。'
|
||||||
await ElMessageBox.confirm(tip, '删除确认', {
|
await ElMessageBox.confirm(tip, '删除确认', {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
})
|
})
|
||||||
@@ -394,6 +404,7 @@ async function handleDelete(row: ChatQrCode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleBatchDelete() {
|
async function handleBatchDelete() {
|
||||||
|
if (!canManageQrCodes.value) return
|
||||||
if (!selectedCount.value) {
|
if (!selectedCount.value) {
|
||||||
ElMessage.warning('请先选择要删除的二维码')
|
ElMessage.warning('请先选择要删除的二维码')
|
||||||
return
|
return
|
||||||
@@ -460,6 +471,7 @@ onMounted(reloadAll)
|
|||||||
<div class="action-area">
|
<div class="action-area">
|
||||||
<span v-if="selectedCount" class="selection-summary">已选 {{ selectedCount }} 条</span>
|
<span v-if="selectedCount" class="selection-summary">已选 {{ selectedCount }} 条</span>
|
||||||
<el-button
|
<el-button
|
||||||
|
v-if="canManageQrCodes"
|
||||||
type="danger"
|
type="danger"
|
||||||
:icon="Delete"
|
:icon="Delete"
|
||||||
:disabled="!selectedCount"
|
:disabled="!selectedCount"
|
||||||
@@ -468,7 +480,9 @@ onMounted(reloadAll)
|
|||||||
批量删除
|
批量删除
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button :icon="Refresh" @click="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>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
@@ -483,7 +497,12 @@ onMounted(reloadAll)
|
|||||||
row-key="id"
|
row-key="id"
|
||||||
@selection-change="handleSelectionChange"
|
@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">
|
<el-table-column label="二维码" class-name="qr-col" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="qrcode-thumb">
|
<div class="qrcode-thumb">
|
||||||
@@ -539,6 +558,7 @@ onMounted(reloadAll)
|
|||||||
<el-switch
|
<el-switch
|
||||||
:model-value="row.wecom_renamed"
|
:model-value="row.wecom_renamed"
|
||||||
:loading="renameLoadingIds.has(row.id)"
|
:loading="renameLoadingIds.has(row.id)"
|
||||||
|
:disabled="!canManageQrCodes"
|
||||||
inline-prompt
|
inline-prompt
|
||||||
:active-icon="Check"
|
:active-icon="Check"
|
||||||
active-text="已改"
|
active-text="已改"
|
||||||
@@ -560,7 +580,12 @@ onMounted(reloadAll)
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</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 }">
|
<template #default="{ row }">
|
||||||
<div class="table-actions">
|
<div class="table-actions">
|
||||||
<el-button text type="primary" size="small" @click="openEdit(row)">编辑</el-button>
|
<el-button text type="primary" size="small" @click="openEdit(row)">编辑</el-button>
|
||||||
@@ -572,13 +597,7 @@ onMounted(reloadAll)
|
|||||||
@click="handleDisable(row)"
|
@click="handleDisable(row)"
|
||||||
>停用</el-button
|
>停用</el-button
|
||||||
>
|
>
|
||||||
<el-button
|
<el-button text type="danger" size="small" @click="handleDelete(row)">删除</el-button>
|
||||||
text
|
|
||||||
type="danger"
|
|
||||||
size="small"
|
|
||||||
@click="handleDelete(row)"
|
|
||||||
>删除</el-button
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -594,7 +613,12 @@ onMounted(reloadAll)
|
|||||||
</el-card>
|
</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 class="qrcode-upload-form" label-width="108px">
|
||||||
<el-form-item label="二维码图片" required>
|
<el-form-item label="二维码图片" required>
|
||||||
<div class="batch-upload-area">
|
<div class="batch-upload-area">
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ const allNavGroups: NavGroup[] = [
|
|||||||
permission: 'dispute:view',
|
permission: 'dispute:view',
|
||||||
},
|
},
|
||||||
{ label: '客服群聊', to: adminPath('chats'), icon: ChatDotRound, permission: 'chat: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' },
|
{ label: '客服分组', to: adminPath('support-groups'), icon: User, permission: 'chat:manage' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user