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

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 () => { const getCaptcha = async () => {
try { try {
loading.value = true loading.value = true
const response = await api.auth.captcha('/captcha/generate') const response = await api.auth.captcha()
if (response.data.success) { if (response.data.success) {
captchaImage.value = response.data.data.image captchaImage.value = response.data.data.image

View File

@@ -20,16 +20,16 @@
<template #title>仪表盘</template> <template #title>仪表盘</template>
</el-menu-item> </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> <el-icon><Goods /></el-icon>
<template #title>商品管理</template> <template #title>商品管理</template>
</el-menu-item> </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> <el-icon><List /></el-icon>
<template #title>订单管理</template> <template #title>订单管理</template>
</el-menu-item> </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> <el-icon><Money /></el-icon>
<template #title>提现审批</template> <template #title>提现审批</template>
</el-menu-item> </el-menu-item>
@@ -199,6 +199,7 @@ import {
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const userStore = useUserStore() const userStore = useUserStore()
import api from '@/utils/api'
// 组件挂载时不再自动验证token避免登录后立即触发401错误 // 组件挂载时不再自动验证token避免登录后立即触发401错误
// token验证交给具体的API调用时处理 // token验证交给具体的API调用时处理
@@ -228,8 +229,8 @@ const passwordRules = {
{ required: true, message: '请输入新密码', trigger: 'blur' }, { required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, message: '密码长度不能少于6位', trigger: 'blur' }, { min: 6, message: '密码长度不能少于6位', trigger: 'blur' },
{ {
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{6,}$/, pattern: /^(?=.*[a-zA-Z])(?=.*\d)/,
message: '密码必须包含大小写字母和数字', message: '密码必须包含字母和数字',
trigger: 'blur' trigger: 'blur'
} }
], ],
@@ -320,12 +321,13 @@ const handleChangePassword = async () => {
try { try {
await passwordFormRef.value.validate() await passwordFormRef.value.validate()
const result = await userStore.changePassword({ const result = await api.users.changePassword({
currentPassword: passwordForm.value.currentPassword, id: userStore.user.id,
oldPassword: passwordForm.value.currentPassword,
newPassword: passwordForm.value.newPassword newPassword: passwordForm.value.newPassword
}) })
if (result.data.success) {
if (result.success) { ElMessage.success('密码修改成功')
passwordDialogVisible.value = false passwordDialogVisible.value = false
resetPasswordForm() 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('您没有权限访问此页面') ElMessage.error('您没有权限访问此页面')
next('/dashboard') next('/dashboard')
return 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: { getters: {
isAuthenticated: (state) => !!state.token && !!state.user, 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: { 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 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接口定义 // API接口定义
@@ -170,7 +170,7 @@ const api = {
login: (data) => midRequest.post('/auth/login', data), login: (data) => midRequest.post('/auth/login', data),
register: (data) => apiRequest.post('/auth/register', data), register: (data) => apiRequest.post('/auth/register', data),
getCurrentUser: () => apiRequest.get('/auth/me'), 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'), captcha: () => midRequest.get('/captcha/generate'),
}, },
@@ -184,7 +184,8 @@ const api = {
getUserStats: () => apiRequest.get('/users/stats'), getUserStats: () => apiRequest.get('/users/stats'),
getUserGrowthTrend: (params) => apiRequest.get('/users/growth-trend', {params}), getUserGrowthTrend: (params) => apiRequest.get('/users/growth-trend', {params}),
getDailyRevenue: (params) => apiRequest.get('/users/daily-revenue', {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 { ArrowDown } from '@element-plus/icons-vue';
import api from '@/utils/api'; import api from '@/utils/api';
import { getImageUrl } from '@/utils/config'; import { getImageUrl } from '@/utils/config';
import { useUserStore } from '@/stores/user';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
const loading = ref(false); const loading = ref(false);
@@ -272,6 +273,7 @@ const orders = ref([]);
const dialogVisible = ref(false); const dialogVisible = ref(false);
const deliveryDialogVisible = ref(false); const deliveryDialogVisible = ref(false);
const selectedOrder = ref(null); const selectedOrder = ref(null);
const user = useUserStore().user;
const filters = reactive({ const filters = reactive({
orderNumber: '', orderNumber: '',
@@ -301,6 +303,7 @@ const loadOrders = async () => {
orderNumber: filters.orderNumber, orderNumber: filters.orderNumber,
username: filters.username, username: filters.username,
status: filters.status, status: filters.status,
shop_name: user.id,
}; };
if (filters.dateRange && filters.dateRange.length === 2) { if (filters.dateRange && filters.dateRange.length === 2) {

View File

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

View File

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

View File

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

View File

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