增加权限审核,限制显示内容,细节优化

This commit is contained in:
dzl
2025-10-20 17:21:42 +08:00
parent 0967725c97
commit f515086162
10 changed files with 58 additions and 24 deletions

View File

@@ -81,7 +81,7 @@ const loading = ref(false)
const getCaptcha = async () => {
try {
loading.value = true
const response = await api.auth.captcha('/captcha/generate')
const response = await api.auth.captcha()
if (response.data.success) {
captchaImage.value = response.data.data.image

View File

@@ -20,16 +20,16 @@
<template #title>仪表盘</template>
</el-menu-item>
<el-menu-item v-if="userStore.isAdmin" index="/products">
<el-menu-item v-if="userStore.isSupplier" index="/products">
<el-icon><Goods /></el-icon>
<template #title>商品管理</template>
</el-menu-item>
<el-menu-item v-if="userStore.isAdmin" index="/orders">
<el-menu-item v-if="userStore.isSupplier" index="/orders">
<el-icon><List /></el-icon>
<template #title>订单管理</template>
</el-menu-item>
<el-menu-item v-if="userStore.isAdmin" index="/withdrawals">
<el-menu-item v-if="userStore.isSupplier" index="/withdrawals">
<el-icon><Money /></el-icon>
<template #title>提现审批</template>
</el-menu-item>
@@ -199,6 +199,7 @@ import {
const route = useRoute()
const router = useRouter()
const userStore = useUserStore()
import api from '@/utils/api'
// 组件挂载时不再自动验证token避免登录后立即触发401错误
// token验证交给具体的API调用时处理
@@ -228,8 +229,8 @@ const passwordRules = {
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, message: '密码长度不能少于6位', trigger: 'blur' },
{
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{6,}$/,
message: '密码必须包含大小写字母和数字',
pattern: /^(?=.*[a-zA-Z])(?=.*\d)/,
message: '密码必须包含字母和数字',
trigger: 'blur'
}
],
@@ -320,12 +321,13 @@ const handleChangePassword = async () => {
try {
await passwordFormRef.value.validate()
const result = await userStore.changePassword({
currentPassword: passwordForm.value.currentPassword,
const result = await api.users.changePassword({
id: userStore.user.id,
oldPassword: passwordForm.value.currentPassword,
newPassword: passwordForm.value.newPassword
})
if (result.success) {
if (result.data.success) {
ElMessage.success('密码修改成功')
passwordDialogVisible.value = false
resetPasswordForm()

View File

@@ -160,11 +160,22 @@ router.beforeEach(async (to, from, next) => {
}
// 检查管理员权限
if (to.meta.requiresAdmin && !userStore.isAdmin) {
if (!userStore.isSupplier || userStore.isDelete) {
ElMessage.error('您没有权限访问此页面')
next('/dashboard')
return
}
// 检查审核状态
if (userStore.audit_status === 'pending') {
ElMessage.warning('您的账号审核中,请稍后登录')
next('/dashboard')
return
} else if (userStore.audit_status === 'rejected') {
ElMessage.error('您的账号已被拒绝,请联系管理员')
next('/dashboard')
return
}
}
// 如果已登录用户访问登录页,重定向到仪表盘

View File

@@ -12,7 +12,12 @@ export const useUserStore = defineStore('user', {
getters: {
isAuthenticated: (state) => !!state.token && !!state.user,
isAdmin: (state) => state.user?.role === 'admin'
isAdmin: (state) => state.user?.role === 'admin',
isSupplier: (state) => state.user?.role === "supplier",
isDelete: (state) => state.user?.is_delete === 1,
audit_status: (state) => state.user?.audit_status || "pending",
// isSupplier: (state) => state.user?.user_type === "supplier",
},
actions: {

View File

@@ -160,7 +160,7 @@ export const createRequest = (baseURL) => {
}
// 生成不同的实例
export const apiRequest = createRequest(import.meta.env.VITE_API_BASE_URL || '/api')
export const midRequest = createRequest(import.meta.env.VITE_UPLOAD_BASE_URL || '/mid')
export const midRequest = createRequest(import.meta.env.VITE_UPLOAD_BASE_URL || 'http://192.168.0.12:3005/mid')
// API接口定义
@@ -170,7 +170,7 @@ const api = {
login: (data) => midRequest.post('/auth/login', data),
register: (data) => apiRequest.post('/auth/register', data),
getCurrentUser: () => apiRequest.get('/auth/me'),
changePassword: (data) => apiRequest.put('/auth/change-password', data),
// changePassword: (data) => apiRequest.put('/auth/change-password', data),
captcha: () => midRequest.get('/captcha/generate'),
},
@@ -184,7 +184,8 @@ const api = {
getUserStats: () => apiRequest.get('/users/stats'),
getUserGrowthTrend: (params) => apiRequest.get('/users/growth-trend', {params}),
getDailyRevenue: (params) => apiRequest.get('/users/daily-revenue', {params}),
getAgentOptions: (params) => apiRequest.get('/admin/agents', {params})
getAgentOptions: (params) => apiRequest.get('/admin/agents', {params}),
changePassword: (data) => apiRequest.put('/users/password', data),
},

View File

@@ -265,6 +265,7 @@ import { ElMessage, ElMessageBox } from 'element-plus';
import { ArrowDown } from '@element-plus/icons-vue';
import api from '@/utils/api';
import { getImageUrl } from '@/utils/config';
import { useUserStore } from '@/stores/user';
import dayjs from 'dayjs';
const loading = ref(false);
@@ -272,6 +273,7 @@ const orders = ref([]);
const dialogVisible = ref(false);
const deliveryDialogVisible = ref(false);
const selectedOrder = ref(null);
const user = useUserStore().user;
const filters = reactive({
orderNumber: '',
@@ -301,6 +303,7 @@ const loadOrders = async () => {
orderNumber: filters.orderNumber,
username: filters.username,
status: filters.status,
shop_name: user.id,
};
if (filters.dateRange && filters.dateRange.length === 2) {

View File

@@ -58,6 +58,7 @@
size="large"
clearable
@change="handlePrimaryCategoryChange"
filterable
>
<el-option
v-for="category in primaryCategories"
@@ -80,6 +81,7 @@
multiple
collapse-tags
collapse-tags-tooltip
filterable
>
<el-option
v-for="category in secondaryCategories"
@@ -703,6 +705,9 @@ import MediaUpload from '@/components/MediaUpload.vue'
import HorizontalImageDisplay from '@/components/HorizontalImageDisplay.vue'
import RichTextEditor from '@/components/RichTextEditor.vue'
import {useUserStore} from '@/stores/user'
import {getImageUrl} from '@/utils/config'
const route = useRoute()
const router = useRouter()
const formRef = ref()
@@ -711,6 +716,8 @@ const categories = ref([])
const newProductId = ref(null)
const user = useUserStore().user
// 计算属性:一级分类
const primaryCategories = computed(() => {
return categories.value.filter(cat => cat.level === 1)
@@ -755,7 +762,7 @@ const form = reactive({
images: [],
videos: [],
shop_name: '',
shop_avatar: '',
shop_avatar: getImageUrl(user.avatar),
payment_methods: ['points'],
description: '',
details: '',
@@ -1171,7 +1178,7 @@ const submitForm = async () => {
image_url: form.image.replace('https://minio.zrbjr.com', ''), // 前端 image -> 后端 image_url
images: JSON.stringify(form.images.map(img => img.replace('https://minio.zrbjr.com', ''))),
videos: JSON.stringify(form.videos),
shop_name: form.shop_name,
shop_name: user.id,
shop_avatar: form.shop_avatar,
payment_methods: JSON.stringify(form.payment_methods),
description: form.description,

View File

@@ -19,7 +19,7 @@
/>
</el-form-item>
<el-form-item label="分类">
<el-select v-model="filters.category" placeholder="请选择分类" clearable style="display: inline-block; width: 150px;">
<el-select v-model="filters.category" placeholder="请选择分类" clearable style="display: inline-block; width: 150px;" filterable>
<el-option
v-for="category in categories"
:key="category"
@@ -67,11 +67,11 @@
<span class="rongdou-text">{{ row.rongdou_price || 0 }} 融豆</span>
</template>
</el-table-column>
<el-table-column prop="shop_name" label="店家" width="120">
<el-table-column prop="shop_name" label="供应商" width="120">
<template #default="{ row }">
<div class="shop-info">
<el-avatar v-if="row.shop_avatar" :src="getImageUrl(row.shop_avatar)" :size="24" />
<span>{{ row.shop_name || '平台上架' }}</span>
<span>{{ row.provider ? row.provider.username : '平台上架' }}</span>
</div>
</template>
</el-table-column>
@@ -147,11 +147,13 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, ArrowDown, Setting, Grid } from '@element-plus/icons-vue'
import api from '@/utils/api'
import { getImageUrl } from '@/utils/config'
import {useUserStore} from '@/stores/user'
const router = useRouter()
const loading = ref(false)
const products = ref([])
const categories = ref([])
const user = useUserStore().user
const filters = reactive({
search: '',
@@ -170,6 +172,7 @@ const loadProducts = async () => {
loading.value = true
try {
const params = {
shop_name: user.id,
page: pagination.value.page,
limit: pagination.value.limit,
...filters

View File

@@ -394,8 +394,8 @@ const passwordRules = {
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, max: 20, message: '密码长度在 6 到 20 个字符', trigger: 'blur' },
{
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]/,
message: '密码必须包含大小写字母和数字',
pattern: /^(?=.*[a-zA-Z])(?=.*\d)/,
message: '密码必须包含字母和数字',
trigger: 'blur'
}
],
@@ -488,7 +488,8 @@ const changePassword = async () => {
changingPassword.value = true
await api.users.changePassword({
currentPassword: passwordForm.currentPassword,
id: userStore.user.id,
oldPassword: passwordForm.currentPassword,
newPassword: passwordForm.newPassword
})

View File

@@ -178,7 +178,8 @@ const loadWithdrawList = async () => {
try {
const { data } = await api.withdraw.getWithdrawList({
page: pagination.page,
limit: pagination.limit
limit: pagination.limit,
user_id: userStore.user.id
})
withdrawList.value = data.data.withdrawals || []
pagination.total = data.data.pagination.total || 0