feat:添加admin相关接口

This commit is contained in:
2025-11-13 15:05:54 +08:00
parent 11cb40c86a
commit 067e0539ba
58 changed files with 18736 additions and 273 deletions
+1
View File
@@ -16,6 +16,7 @@ import {getUserInfo} from "@/api/login.js";
const userStore = useUserStore()
onMounted(async () => {
let resp = await getUserInfo()
console.log("用户信息:",resp)
userStore.setUserInfo(resp.data)
console.log(userStore.userInfo)
})
+69
View File
@@ -0,0 +1,69 @@
import {http2} from "@/utils/request.js";
/* -------------------------------------------------------------- */
/**管理员权限管理 */
/**-------------------------------------------------------- */
/**路由权限管理 */
/**获取权限列表 */
export const getPermissionList = (params) => {
return http2.get('/api/v1/admin/server/permission/path/list', {params: params})
}
/**新增权限信息 */
export const addPermissionInfo = (data) => {
return http2.post('/api/v1/admin/server/permission/path/add', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**修改权限信息 */
export const updatePermissionInfo = (data) => {
return http2.post('/api/v1/admin/server/permission/path/update', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**删除权限信息 */
export const deletePermissionInfo = (data) => {
return http2.delete('/api/v1/admin/server/permission/path/delete', {
data: data,
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**-------------------------------------------------------- */
/**管理员权限分配 */
/**获取指定管理员的权限列表 */
export const getPermissionListByAdmin = (params) => {
return http2.get('/api/v1/admin/server/permission/admin/list', {params: params})
}
/**新增管理员权限 */
export const addPermissionAdmin = (data) => {
return http2.post('/api/v1/admin/server/permission/admin/add', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**修改管理员权限 */
export const updatePermissionAdmin = (data) => {
return http2.post('/api/v1/admin/server/permission/admin/update', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**删除管理员权限 */
export const deletePermissionAdmin = (data) => {
return http2.delete('/api/v1/admin/server/permission/admin/delete', {
data: data,
headers:{
'Content-Type':'multipart/form-data'
}
})
}
+17
View File
@@ -0,0 +1,17 @@
import {http2} from "@/utils/request.js";
/**新增签到奖励 */
export const addSignReward = (data) => {
return http2.post('/api/v1/admin/activity/signin/add_reward', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**新增签到奖励类型 */
export const addSignRewardType = (data) => {
return http2.post('/api/v1/admin/activity/signin/add_reward_type', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
+9
View File
@@ -0,0 +1,9 @@
import {http2} from "@/utils/request.js";
/**新增goedge服务器 */
export const addGoedgeServer = (data) => {
return http2.post('/api/v1/admin/api/goedge/add_server', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
+172
View File
@@ -0,0 +1,172 @@
import {http2} from "@/utils/request.js";
/**---------------------------------- */
/**优惠码/代金券管理 (统一接口) */
/**获取优惠码/代金券列表 */
export const getDiscountCodeList = (params) => {
return http2.get('/api/v1/admin/code/discount/list', {params: params})
}
/**获取优惠码/代金券详情 */
export const getDiscountCodeDetail = (params) => {
return http2.get('/api/v1/admin/code/discount/detail', {params: params})
}
/**创建优惠码/代金券 */
export const createDiscountCode = (data) => {
return http2.post('/api/v1/admin/code/discount/create', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**更新优惠码/代金券 */
export const updateDiscountCode = (data) => {
return http2.post('/api/v1/admin/code/discount/update', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**删除优惠码/代金券 */
export const deleteDiscountCode = (data) => {
return http2.delete('/api/v1/admin/code/discount/delete?code_id=' + data.code_id)
}
/**---------------------------------- */
/**商品关联管理 */
/**获取优惠码/代金券商品列表 */
export const getDiscountGoodsList = (params) => {
return http2.get('/api/v1/admin/code/discount/goods/list', {params: params})
}
/**新增优惠码/代金券商品关联 */
export const addDiscountGoods = (data) => {
return http2.post('/api/v1/admin/code/discount/goods/add', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**修改优惠码/代金券商品关联 */
export const updateDiscountGoods = (data) => {
return http2.post('/api/v1/admin/code/discount/goods/update', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**删除优惠码/代金券商品关联 */
export const deleteDiscountGoods = (data) => {
return http2.delete('/api/v1/admin/code/discount/goods/delete', {
data: data,
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**---------------------------------- */
/**用户关联管理 */
/**获取优惠码/代金券用户关联列表 */
export const getDiscountUsersList = (params) => {
return http2.get('/api/v1/admin/code/discount/users/list', {params: params})
}
/**新增优惠码/代金券用户关联 */
export const addDiscountUsers = (data) => {
return http2.post('/api/v1/admin/code/discount/users/add', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**修改优惠码/代金券用户关联 */
export const updateDiscountUsers = (data) => {
return http2.post('/api/v1/admin/code/discount/users/update', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**删除优惠码/代金券用户关联 */
export const deleteDiscountUsers = (data) => {
return http2.delete('/api/v1/admin/code/discount/users/delete', {
data: data,
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**---------------------------------- */
/**用户代金券管理 */
/**获取用户优惠码/代金券列表 */
export const getUserVoucherList = (params) => {
return http2.get('/api/v1/admin/code/discount/user/list', {params: params})
}
/**为用户添加代金券 */
export const addUserVoucher = (data) => {
return http2.post('/api/v1/admin/code/discount/user/add_coupon', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**修改用户代金券 */
export const updateUserVoucher = (data) => {
return http2.post('/api/v1/admin/code/discount/user/update_coupon', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**删除用户代金券 */
export const deleteUserVoucher = (data) => {
return http2.delete('/api/v1/admin/code/discount/user/delete_coupon', {
data: data,
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**获取用户优惠码/代金券使用记录 */
export const getUserVoucherHistory = (params) => {
return http2.get('/api/v1/admin/code/discount/user/history', {params: params})
}
/**为用户分配代金券 */
export const allocateVoucher = (data) => {
return http2.post('/api/v1/admin/code/discount/coupon/allocate', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**查询代金券的拥有者列表 */
export const getVoucherHolderList = (params) => {
return http2.get('/api/v1/admin/code/discount/coupon/holder_list', {params: params})
}
/**---------------------------------- */
/**兼容旧接口别名 */
export const getVoucherList = getDiscountCodeList
export const getVoucherDetail = getDiscountCodeDetail
export const createVoucher = createDiscountCode
export const updateVoucher = updateDiscountCode
export const deleteVoucher = deleteDiscountCode
+42
View File
@@ -0,0 +1,42 @@
import {http2} from "@/utils/request.js";
/**获取文件列表 */
export const getFileList = (params) => {
return http2.get('/api/v1/admin/file/list',{params})
}
/**获取文件详情 */
export const getFileDetail = (data) => {
return http2.get('/api/v1/admin/file/detail?file_id='+data.file_id)
}
/**删除文件 */
export const deleteFile = (data) => {
return http2.delete('/api/v1/admin/file/delete', {
params: data
})
}
/**修改文件信息 */
export const updateFile = (data) => {
return http2.post('/api/v1/admin/file/update', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**公共接口 获取文件信息 */
export const getFile = (data) => {
return http2.get('/api/v1/tools/file/info?file_id='+data.file_id)
}
/**文件上传 */
export const uploadFile = (data) => {
return http2.post('/api/v1/tools/file/upload', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**文件下载 */
export const downloadFile = (data) => {
return http2.get('/api/v1/tool/file/down?file_id='+data.file_id)
}
+25
View File
@@ -0,0 +1,25 @@
import {http2} from "@/utils/request.js";
/**获取管理员组列表 */
export const getAdminGroupList = (params) => {
return http2.get('/api/v1/admin/admin_group/list', {params: params})
}
/**获取管理员组成员列表 */
export const getAdminGroupMemberList = (params) => {
return http2.get('/api/v1/admin/admin_group/member_list', {params:params})
}
/**获取管理员组详情 */
export const getAdminGroupDetail = (params) => {
return http2.get('/api/v1/admin/admin_group/detail', {params: params})
}
/**新增管理员组 */
export const addAdminGroup = (data) => {
return http2.post('/api/v1/admin/admin_group/create', data)
}
/**更新管理员组信息 */
export const updateAdminGroupInfo = (data) => {
return http2.post('/api/v1/admin/admin_group/update', data)
}
/**删除管理员组 */
export const deleteAdminGroup = (data) => {
return http2.delete('/api/v1/admin/admin_group/delete?group_id=' + data.group_id)
}
+35
View File
@@ -0,0 +1,35 @@
import {http2} from "@/utils/request.js";
/**获取订单列表 */
export const getOrderList = (params) => {
return http2.get('/api/v1/admin/order/list', {params: params})
}
/**获取订单详情 */
export const getOrderDetail = (params) => {
return http2.get('/api/v1/admin/order/detail', {params: params})
}
/**删除订单 (未提供删除接口,暂时保留) */
/**删除订单 */
export const deleteOrder = (data) => {
return http2.delete('/api/v1/admin/trades/delete_trade', {
data: data,
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**创建订单 */
export const createOrder = (data) => {
return http2.post('/api/v1/admin/order/create', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**修改订单 */
export const updateOrder = (data) => {
return http2.post('/api/v1/admin/order/update', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
+142
View File
@@ -0,0 +1,142 @@
import {http2} from "@/utils/request.js";
/**---------------------------------- */
/**商品组管理 */
/**获取商品分组列表 */
export const getProductGroupList = (params) => {
return http2.get('/api/v1/admin/good/group/list', {params: params})
}
/**创建商品分组 */
export const createProductGroup = (data) => {
return http2.post('/api/v1/admin/good/group/create', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**更新商品分组 */
export const updateProductGroup = (data) => {
return http2.post('/api/v1/admin/good/group/update', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**隐藏商品组 */
export const hideProductGroup = (data) => {
return http2.post('/api/v1/admin/good/group/disable', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**启动商品组 */
export const startProductGroup = (data) => {
return http2.post('/api/v1/admin/good/group/enable', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**删除商品分组 */
export const deleteProductGroup = (data) => {
return http2.delete('/api/v1/admin/good/group/delete',{
data: data,
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**---------------------------------- */
/**商品管理 */
/**获取商品列表 */
export const getProductList = (params) => {
return http2.get('/api/v1/admin/good/goods/list', {params: params})
}
/**创建商品 */
export const createProduct = (data) => {
return http2.post('/api/v1/admin/good/goods/create', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**更新商品 */
export const updateProduct = (data) => {
return http2.post('/api/v1/admin/good/goods/update', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**删除商品 */
export const deleteProduct = (data) => {
return http2.delete('/api/v1/admin/good/goods/delete',{
data:data,
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**---------------------------------- */
/**商品参数管理 */
/**获取商品参数列表 */
export const getProductParameterList = (params) => {
return http2.get('/api/v1/admin/good/spec/list', {params: params})
}
/**创建商品参数 */
export const createProductParameter = (data) => {
return http2.post('/api/v1/admin/good/spec/create', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**获取商品参数详情 */
export const getProductParameterDetail = (params) => {
return http2.get('/api/v1/admin/good/spec/detail', {params: params})
}
/**更新商品参数 */
export const updateProductParameter = (data) => {
return http2.post('/api/v1/admin/good/spec/update', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**删除商品参数 */
export const deleteProductParameter = (data) => {
return http2.delete('/api/v1/admin/good/spec/delete', {
params: data
})
}
/**增加商品参数值 */
export const addProductParameterValue = (data) => {
return http2.post('/api/v1/admin/good/spec/add_value', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**删除商品参数值 */
export const deleteProductParameterValue = (data) => {
return http2.delete('/api/v1/admin/good/spec/delete_value', {
params: data
})
}
/**更新商品参数值 */
export const updateProductParameterValue = (data) => {
return http2.post('/api/v1/admin/good/spec/update_value', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
+19
View File
@@ -0,0 +1,19 @@
import {http2} from "@/utils/request.js";
/**路由管理 */
/**新增前端路由 */
export const addRouter = (data) => {
return http2.post('/api/v1/admin/web_routs/add', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**更新前端路由 */
export const updateRouter = (data) => {
return http2.post('/api/v1/admin/web_routs/update', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
+86
View File
@@ -0,0 +1,86 @@
import { http2 } from "@/utils/request.js"
// ========== 配置组管理 ==========
/** 获取配置分组列表 */
export const getSettingGroupList = (params) => {
return http2.get('/api/v1/admin/server/setting/group/list', { params })
}
/** 获取配置分组信息 */
export const getSettingGroupInfo = (params) => {
return http2.get('/api/v1/admin/server/setting/group/info', { params })
}
/** 创建配置分组 */
export const createSettingGroup = (data) => {
return http2.post('/api/v1/admin/server/setting/group/create', data, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
/** 修改配置分组 */
export const updateSettingGroup = (data) => {
return http2.post('/api/v1/admin/server/setting/group/update', data, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
/** 删除配置分组 */
export const deleteSettingGroup = (params) => {
return http2.delete('/api/v1/admin/server/setting/group/delete', { params })
}
// ========== 配置管理 ==========
/** 获取配置列表 */
export const getSettingList = (params) => {
return http2.get('/api/v1/admin/server/setting/list', { params })
}
/** 获取配置信息 */
export const getSettingInfo = (params) => {
return http2.get('/api/v1/admin/server/setting/info', { params })
}
/** 创建配置 */
export const createSetting = (data) => {
return http2.post('/api/v1/admin/server/setting/create', data, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
/** 修改配置 */
export const updateSetting = (data) => {
return http2.post('/api/v1/admin/server/setting/update', data, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
/** 修改配置是否开放访问 */
export const setSettingOpen = (data) => {
return http2.post('/api/v1/admin/server/setting/set_open', data, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
/** 删除配置 */
export const deleteSetting = (data) => {
return http2.delete('/api/v1/admin/server/setting/delete', {
data,
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
+165
View File
@@ -0,0 +1,165 @@
import {http2} from "@/utils/request.js";
/**用户余额管理 */
/**修改用户余额 */
export const editUserBalance = (data) => {
return http2.post('/api/v1/admin/user/balance/update', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**添加用户消费记录 */
export const addUserConsumption = (data) => {
return http2.post('/api/v1/admin/user/balance/add_history', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**查询用户余额 */
export const getUserBalance = (data) => {
return http2.get('/api/v1/admin/user/balance/select?user_id='+data.user_id)
}
/**获取用户余额记录 */
export const getUserBalanceRecord = (data) => {
return http2.get('/api/v1/admin/user/balance/history?user_id='+data.user_id + '&balance_type=' + data.balance_type + '&page=' + data.page + '&count=' + data.count)
}
/**获取用户余额 */
export const getUserBalanceCount = (data) => {
return http2.get('/api/v1/admin/user/balance/get?user_id='+data.user_id)
}
/**获取用户信息 */
export const getUserInfo = (data) => {
return http2.get('/api/v1/admin/user/user/detail?user_id='+data.user_id)
}
/**获取用户列表 */
export const getUserList = (data) => {
return http2.get('/api/v1/admin/user/user/list?page=' + data.page + '&count=' + data.count + '&key=' + data.key)
}
/**更新用户信息 */
export const updateUserInfo = (data) => {
return http2.post('/api/v1/admin/user/user/update', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**删除用户 */
export const deleteUser = (data) => {
return http2.delete('/api/v1/admin/user/user/delete?group_id='+data.group_id)
}
/**修改用户头像 */
export const updateUserAvatar = (data) => {
return http2.post('/api/v1/admin/user/user/update_cover', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**修改用户密码 */
export const updateUserPassword = (data) => {
return http2.post('/api/v1/admin/user/user/update_password', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**修改用户组 */
export const updateUserGroup = (data) => {
return http2.post('/api/v1/admin/user/user/update_group', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**修改用户管理员权限*/
export const updateUserAdmin = (data) => {
return http2.post('/api/v1/admin/user/user/user2admin', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**修改用户实名信息*/
export const updateUserRealName = (data) => {
return http2.post('/api/v1/admin/user/user/update_real_name', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**获取用户登录记录*/
export const getUserLoginRecord = (data) => {
return http2.get('/api/v1/admin/user/user/login_history?user_id='+data.user_id + '&page=' + data.page + '&count=' + data.count)
}
/**获取用户操作记录 */
export const getUserOperationRecord = (data) => {
return http2.get('/api/v1/admin/user/user/manage_history?user_id='+data.user_id + '&page=' + data.page + '&count=' + data.count)
}
/**模拟用户登录 */
export const mockUserLogin = (data) => {
return http2.get('/api/v1/admin/user/user/simulation_login?user_id='+data.user_id)
}
/**新建任务 */
export const createTask = (data) => {
return http2.post('/api/v1/admin/user/user/create', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**用户组管理 */
/**获取用户组列表 */
export const getUserGroupList = (data) => {
return http2.get('/api/v1/admin/user_group/list?page=' + data.page + '&count=' + data.count)
}
/**获取用户组成员列表 */
export const getUserGroupMemberList = (data) => {
return http2.get('/api/v1/admin/user_group/member_list?group_id=' + data.group_id + '&page=' + data.page + '&count=' + data.count)
}
/**获取用户组详情信息 */
export const getUserGroupDetail = (data) => {
return http2.get('/api/v1/admin/user_group/detail?group_id=' + data.group_id)
}
/**新建用户组 */
export const createUserGroup = (data) => {
return http2.post('/api/v1/admin/user_group/create', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**更新用户组信息 */
export const updateUserGroupInfo = (data) => {
return http2.post('/api/v1/admin/user_group/update', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**删除用户组 */
export const deleteUserGroup = (data) => {
return http2.delete(`/api/v1/admin/user_group/delete?group_id=`+data.group_id,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**添加用户组成员 */
export const addUserGroupMember = (data) => {
return http2.post('/api/v1/admin/user_group/add_member', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
+8 -3
View File
@@ -11,15 +11,20 @@ export function addDomain(data) {
}
// 删除域名白名单
export function deleteDomain(id) {
return request.post("/api/v1/admin/server/domain_withe/delete",{domain_id: id})
export function deleteDomain(data) {
return request.post("/api/v1/admin/server/domain_withe/delete",data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
// 批量删除域名白名单
export async function batchDeleteDomain(ids) {
let promises = []
for (let id of ids) {
promises.push(deleteDomain(id))
promises.push(deleteDomain({domain_id:id}))
}
return await Promise.all(promises)
}
+52 -3
View File
@@ -29,12 +29,12 @@ export function getCompletedTicketList(count, page) {
return getTickerList(count,page,3)
}
// 获取详情
// 获取工单详情
export function getTicketDetail(work_id) {
return request.get('/api/v1/admin/work_order/detail', { work_id })
}
// 回复
// 回复工单
export function replyTicket(work_id, content, files) {
return request.post('/api/v1/admin/work_order/reply', { work_id, content, files })
}
@@ -67,4 +67,53 @@ export async function parseFilesToImages(files) {
const fileIds = files.split(',')
return await Promise.all(fileIds.map(async (id) => await getFileImage(id.trim())))
}
}
/**获取工单数量 */
export function getTicketCount() {
return request.get('/api/v1/admin/work_order/count')
}
/**修改工单信息 */
export function updateTicketInfo(data) {
return request.post('/api/v1/admin/work_order/update', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**添加工单类型 */
export function addTicketType(data) {
return request.post('/api/v1/admin/work_order/add_type', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**修改工单类型 */
export function updateTicketType(data) {
return request.post('/api/v1/admin/work_order/update_type', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**删除工单类型 */
export function deleteTicketType(data) {
return request.delete('/api/v1/admin/work_order/delete_type', data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
/**获取工单类型列表 */
export function getTicketTypeList(data) {
return request.get('/api/v1/admin/work_order/type_list', data)
}
/**修改工单回复信息 */
export function updateTicketReplayInfo(data){
return request.post('/api/v1/admin/work_order/update_reply',data,{
headers:{
'Content-Type':'multipart/form-data'
}
})
}
+431
View File
@@ -0,0 +1,431 @@
<template>
<el-dialog
v-model="visible"
title="选择头像"
width="800px"
append-to-body
@close="handleClose"
>
<div class="avatar-selector">
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
<!-- 用户文件列表 -->
<el-tab-pane label="用户文件" name="userFiles">
<div class="file-list-container">
<div class="file-list-header">
<h4>用户文件列表</h4>
<el-button type="primary" @click="switchToUpload" :icon="Upload">
上传新头像
</el-button>
</div>
<div class="file-grid" v-loading="loading">
<div
v-for="file in fileList"
:key="file.cover_id"
class="file-item"
:class="{ 'selected': selectedId === file.cover_id }"
@click="selectFile(file)"
>
<div class="file-preview">
<img
v-if="isImageFile(file)"
:src="file.url"
:alt="file.realName"
@error="handleImageError"
/>
<el-icon v-else class="file-icon"><Document /></el-icon>
</div>
<div class="file-info">
<p class="file-name">{{ file.realName }}</p>
<p class="file-size">{{ formatFileSize(file.size) }}</p>
</div>
</div>
</div>
<el-empty v-if="fileList.length === 0 && !loading" description="暂无文件" />
<!-- 分页 -->
<div class="pagination-container" v-if="total > 0">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 30, 50]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
background
@size-change="handleSizeChange"
@current-change="handlePageChange"
/>
</div>
</div>
</el-tab-pane>
<!-- 上传头像 -->
<el-tab-pane label="上传头像" name="upload">
<div class="upload-section">
<el-upload
:http-request="handleUpload"
:before-upload="beforeUpload"
:show-file-list="false"
accept="image/*"
drag
>
<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
<div class="el-upload__text">
将文件拖到此处<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
只能上传jpg/png文件且不超过2MB
</div>
</template>
</el-upload>
</div>
</el-tab-pane>
</el-tabs>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button
type="primary"
@click="handleConfirm"
:disabled="!selectedId"
>
确定选择
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { Upload, UploadFilled, Document } from '@element-plus/icons-vue'
import { getFileList, getFileDetail, uploadFile } from '@/api/admin/file'
import { closeAllMessage } from '../../utils/message'
// Props
const props = defineProps({
modelValue: {
type: Boolean,
default: false
},
userId: {
type: [String, Number],
required: true
},
currentCoverId: {
type: [String, Number],
default: ''
}
})
// Emits
const emit = defineEmits(['update:modelValue', 'confirm'])
// 响应式数据
const visible = ref(false)
const activeTab = ref('userFiles')
const fileList = ref([])
const loading = ref(false)
const selectedId = ref('')
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
// 监听 modelValue 变化
watch(() => props.modelValue, (newVal) => {
visible.value = newVal
if (newVal) {
selectedId.value = props.currentCoverId
currentPage.value = 1
fetchFileList()
}
})
// 监听 visible 变化
watch(visible, (newVal) => {
emit('update:modelValue', newVal)
})
// 获取文件列表
const fetchFileList = async () => {
if (!props.userId) return
loading.value = true
fileList.value = [] // 清空列表
try {
const res = await getFileList({
page: currentPage.value,
count: pageSize.value
})
console.log("获取文件列表:", res)
if (res.data.code === 200) {
const list = res.data.data.list || []
total.value = res.data.data.all_count || 0
// 获取每个文件的详情
for (let i = 0; i < list.length; i++) {
try {
console.log("获取文件详情:", list[i].id)
const res2 = await getFileDetail({ file_id: list[i].id })
if (res2.data.code === 200) {
fileList.value.push({
url: res2.data.data.url,
cover_id: res2.data.data.data.id,
size: res2.data.data.data.size,
realName: res2.data.data.data.realName
})
}
} catch (error) {
console.error('获取文件详情失败:', error)
}
}
console.log("文件列表1237", fileList.value)
}
} catch (error) {
console.error('获取文件列表失败:', error)
ElMessage.error('获取文件列表失败')
} finally {
loading.value = false
}
}
// 处理标签页切换
const handleTabClick = (tab) => {
if (tab.name === 'userFiles') {
currentPage.value = 1
fetchFileList()
}
}
// 分页处理
const handleSizeChange = (size) => {
pageSize.value = size
currentPage.value = 1
fetchFileList()
}
const handlePageChange = (page) => {
currentPage.value = page
fetchFileList()
}
// 切换到上传标签页
const switchToUpload = () => {
activeTab.value = 'upload'
}
// 判断是否为图片文件
const isImageFile = (file) => {
const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']
const extension = file.realName?.split('.').pop()?.toLowerCase()
return imageTypes.includes(extension)
}
// 格式化文件大小
const formatFileSize = (size) => {
if (!size) return '0 B'
const units = ['B', 'KB', 'MB', 'GB']
let unitIndex = 0
let fileSize = size
while (fileSize >= 1024 && unitIndex < units.length - 1) {
fileSize /= 1024
unitIndex++
}
return `${fileSize.toFixed(1)} ${units[unitIndex]}`
}
// 选择文件
const selectFile = (file) => {
selectedId.value = file.cover_id
}
// 上传前验证
const beforeUpload = (file) => {
const isImage = file.type.startsWith('image/')
const isLt2M = file.size / 1024 / 1024 < 2
if (!isImage) {
ElMessage.error('只能上传图片文件!')
return false
}
if (!isLt2M) {
ElMessage.error('图片大小不能超过 2MB!')
return false
}
return true
}
// 自定义上传
const handleUpload = async (options) => {
const { file } = options
const formData = new FormData()
formData.append('files', file)
formData.append('file_names', file.name)
formData.append('update_type', 'cover')
try {
const res = await uploadFile(formData)
console.log("上传文件:", res)
if (res.data.code === 200) {
ElMessage.success("上传成功")
// 重置到第一页并刷新文件列表
currentPage.value = 1
await fetchFileList()
// 切换到文件列表标签页
activeTab.value = 'userFiles'
// 自动选择新上传的文件
if (res.data.data?.id) {
selectedId.value = res.data.data.id
}
} else {
ElMessage.error(res.data.msg || '上传失败')
}
} catch (error) {
console.error('上传失败:', error)
ElMessage.error('上传失败')
}
}
// 图片加载错误处理
const handleImageError = (event) => {
event.target.style.display = 'none'
}
// 关闭对话框
const handleClose = () => {
visible.value = false
selectedId.value = ''
fileList.value = []
currentPage.value = 1
total.value = 0
}
// 确认选择
const handleConfirm = () => {
if (selectedId.value) {
const selectedFile = fileList.value.find(file => file.cover_id === selectedId.value)
emit('confirm', {
cover_id: selectedId.value,
url: selectedFile?.url || ''
})
handleClose()
}
}
</script>
<style scoped>
.avatar-selector {
min-height: 400px;
}
.file-list-container {
padding: 20px 0;
}
.file-list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.file-list-header h4 {
margin: 0;
color: #303133;
}
.file-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 16px;
max-height: 400px;
overflow-y: auto;
}
.file-item {
border: 2px solid #e4e7ed;
border-radius: 8px;
padding: 12px;
cursor: pointer;
transition: all 0.3s ease;
text-align: center;
}
.file-item:hover {
border-color: #409EFF;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
}
.file-item.selected {
border-color: #409EFF;
background-color: #f0f9ff;
}
.file-preview {
width: 80px;
height: 80px;
margin: 0 auto 8px;
border-radius: 4px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f7fa;
}
.file-preview img {
width: 100%;
height: 100%;
object-fit: cover;
}
.file-preview .file-icon {
font-size: 32px;
color: #909399;
}
.file-info {
text-align: center;
}
.file-name {
font-size: 12px;
color: #303133;
margin: 0 0 4px 0;
word-break: break-all;
line-height: 1.3;
}
.file-size {
font-size: 11px;
color: #909399;
margin: 0;
}
.upload-section {
padding: 40px 20px;
text-align: center;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: center;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>
+30 -2
View File
@@ -5,7 +5,7 @@
<div class="logo-container">
<h1 class="title">零零七云计算后台控制面板</h1>
</div>
<el-scrollbar>
<el-scrollbar class="sidebar-scrollbar">
<el-menu
:default-active="activeMenu"
class="sidebar-menu"
@@ -176,9 +176,13 @@ const handleLogout = () => {
color: #1890ff;
}
.sidebar-scrollbar {
height: calc(100vh - 60px);
}
.sidebar-menu {
border-right: none;
height: calc(100vh - 60px);
min-height: 100%;
}
/* 主容器样式 */
@@ -279,4 +283,28 @@ const handleLogout = () => {
:deep(.el-dropdown-menu__item i) {
margin-right: 8px;
}
/* 侧边栏滚动条样式优化 */
:deep(.sidebar-scrollbar .el-scrollbar__wrap) {
overflow-x: hidden;
}
:deep(.sidebar-scrollbar .el-scrollbar__view) {
height: 100%;
}
/* Element Plus 菜单项样式优化 */
:deep(.el-menu) {
border-right: none;
}
:deep(.el-sub-menu__title) {
height: 48px;
line-height: 48px;
}
:deep(.el-menu-item) {
height: 48px;
line-height: 48px;
}
</style>
@@ -0,0 +1,295 @@
<template>
<el-dialog
v-model="visible"
:title="dialogTitle"
width="600px"
:close-on-click-modal="false"
@close="handleClose"
>
<div v-if="detailData" class="detail-content">
<table class="detail-table">
<!-- 优惠码特有字段 -->
<tr v-if="type === 'code'">
<td class="label">优惠码</td>
<td class="value">{{ detailData.code }}</td>
</tr>
<!-- 名称 -->
<tr>
<td class="label">{{ type === 'code' ? '名称' : '代金券名称' }}</td>
<td class="value">{{ detailData.name }}</td>
</tr>
<!-- 备注 -->
<tr>
<td class="label">备注</td>
<td class="value secondary">{{ detailData.note || '无' }}</td>
</tr>
<!-- 优惠类型仅优惠码 -->
<tr v-if="type === 'code'" class="alternate">
<td class="label">优惠类型</td>
<td class="value">
<span :class="['type-tag', detailData.percentage ? 'percentage' : 'amount']">
{{ detailData.percentage ? '百分比折扣' : '固定金额' }}
</span>
</td>
</tr>
<!-- 优惠值/面额 -->
<tr :class="type === 'code' ? '' : 'alternate'">
<td class="label">{{ type === 'code' ? '优惠值' : '面额' }}</td>
<td class="value">
<span v-if="detailData.percentage" class="highlight-value percentage">
{{ (detailData.percentage / 100).toFixed(0) }}%
</span>
<span v-else class="highlight-value amount">
¥{{ (detailData.amount / 100).toFixed(2) }}
</span>
</td>
</tr>
<!-- 最低消费 -->
<tr>
<td class="label">最低消费</td>
<td class="value">¥{{ (detailData.minAmount / 100).toFixed(2) }}</td>
</tr>
<!-- 最大抵扣 -->
<tr>
<td class="label">最大抵扣</td>
<td class="value">
<span v-if="detailData.maxAmount">¥{{ (detailData.maxAmount / 100).toFixed(2) }}</span>
<span v-else class="secondary">无限制</span>
</td>
</tr>
<!-- 最大使用次数 -->
<tr class="alternate">
<td class="label">最大使用次数</td>
<td class="value">
<span v-if="detailData.maxTimes">{{ detailData.maxTimes }}</span>
<span v-else class="secondary">无限制</span>
</td>
</tr>
<!-- 单用户次数 -->
<tr>
<td class="label">单用户次数</td>
<td class="value">
<span v-if="detailData.userTimes">{{ detailData.userTimes }}</span>
<span v-else class="secondary">无限制</span>
</td>
</tr>
<!-- 有效期仅代金券 -->
<tr v-if="type === 'coupon'" class="alternate">
<td class="label">有效期()</td>
<td class="value">
{{ detailData.duration ? (detailData.duration / 86400).toFixed(0) + '天' : '-' }}
</td>
</tr>
<!-- 有效期开始 -->
<tr :class="type === 'coupon' ? '' : 'alternate'">
<td class="label">{{ type === 'code' ? '有效期开始' : '发放时间开始' }}</td>
<td class="value">{{ formatISODate(detailData.startTime) }}</td>
</tr>
<!-- 有效期结束 -->
<tr :class="type === 'coupon' ? 'alternate' : ''">
<td class="label">{{ type === 'code' ? '有效期结束' : '发放时间结束' }}</td>
<td class="value">{{ formatISODate(detailData.endTime) }}</td>
</tr>
<!-- 续费可用 -->
<tr :class="type === 'coupon' ? '' : 'alternate'">
<td class="label">续费可用</td>
<td class="value">
<span :class="['status-icon', detailData.renew ? 'success' : 'danger']">
{{ detailData.renew ? '✓ 是' : '✗ 否' }}
</span>
</td>
</tr>
<!-- 同类型可叠加 -->
<tr :class="type === 'coupon' ? 'alternate' : ''">
<td class="label">同类型可叠加</td>
<td class="value">
<span :class="['status-icon', detailData.canStacking ? 'success' : 'danger']">
{{ detailData.canStacking ? '✓ 是' : '✗ 否' }}
</span>
</td>
</tr>
<!-- 其他类型可叠加 -->
<tr :class="type === 'coupon' ? '' : 'alternate'">
<td class="label">其他类型可叠加</td>
<td class="value">
<span :class="['status-icon', detailData.canCombine ? 'success' : 'danger']">
{{ detailData.canCombine ? '✓ 是' : '✗ 否' }}
</span>
</td>
</tr>
<!-- 创建时间 -->
<tr :class="type === 'coupon' ? 'alternate' : ''">
<td class="label">创建时间</td>
<td class="value timestamp">{{ formatISODate(detailData.CreatedAt) }}</td>
</tr>
<!-- 更新时间 -->
<tr>
<td class="label">更新时间</td>
<td class="value timestamp">{{ formatISODate(detailData.UpdatedAt) }}</td>
</tr>
</table>
</div>
<template #footer>
<el-button @click="handleClose">关闭</el-button>
</template>
</el-dialog>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
modelValue: {
type: Boolean,
default: false
},
type: {
type: String,
required: true,
validator: (value) => ['code', 'coupon'].includes(value)
},
detailData: {
type: Object,
default: null
}
})
const emit = defineEmits(['update:modelValue'])
const visible = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
const dialogTitle = computed(() => {
return props.type === 'code' ? '优惠码详情' : '代金券详情'
})
// 格式化ISO 8601日期字符串
const formatISODate = (isoStr) => {
if (!isoStr) return '-'
try {
const date = new Date(isoStr)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
} catch {
return isoStr
}
}
const handleClose = () => {
emit('update:modelValue', false)
}
</script>
<style scoped>
.detail-content {
max-height: 500px;
overflow-y: auto;
padding: 10px;
}
.detail-table {
width: 100%;
border-collapse: collapse;
}
.detail-table tr {
border-bottom: 1px solid #f0f0f0;
}
.detail-table tr.alternate {
background-color: #fafafa;
}
.detail-table td {
padding: 12px 8px;
}
.detail-table .label {
width: 140px;
color: #606266;
font-weight: 500;
}
.detail-table .value {
color: #303133;
}
.detail-table .value.secondary {
color: #606266;
}
.detail-table .value.timestamp {
color: #909399;
font-size: 13px;
}
/* 类型标签 */
.type-tag {
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
display: inline-block;
}
.type-tag.percentage {
background-color: #f0f9ff;
color: #67c23a;
}
.type-tag.amount {
background-color: #eff6ff;
color: #409eff;
}
/* 突出显示的值 */
.highlight-value {
font-weight: bold;
font-size: 18px;
}
.highlight-value.percentage {
color: #67c23a;
}
.highlight-value.amount {
color: #f56c6c;
}
/* 状态图标 */
.status-icon.success {
color: #67c23a;
}
.status-icon.danger {
color: #f56c6c;
}
.secondary {
color: #909399;
}
</style>
+142 -11
View File
@@ -8,7 +8,115 @@ export const menus = [
path : '/ticket',
title: '工单处理',
icon: 'DataBoard'
},
{
path:'/user',
title: '用户管理',
icon: 'User',
children: [
{
path: '/user/list',
title: '用户列表'
},
{
path: '/user/balance',
title: '用户余额管理'
},
{
path: '/user/group',
title: '用户组管理'
},
{
path: '/user/admin-group',
title: '管理员组管理'
}
]
},
{
path: '/product',
title: '商品管理',
icon: 'Goods',
children: [
{
path: '/product/list',
title: '商品列表'
},
{
path: '/product/group',
title: '商品分组'
},
{
path: '/product/parameter',
title: '商品参数'
}
]
},
{
path: '/order',
title: '订单管理',
icon: 'Document',
children: [
{
path: '/order/list',
title: '订单列表'
}
]
},
{
path: '/marketing',
title: '优惠营销',
icon: 'Present',
children: [
{
path: '/marketing/discount',
title: '优惠码管理'
},
{
path: '/marketing/voucher',
title: '代金券管理'
},
{
path: '/marketing/user-distribution',
title: '用户分发管理'
},
{
id: 'discount-goods',
title: '商品关联管理',
path: '/marketing/discount-goods',
badge: 'NEW'
},
{
id: 'discount-users',
title: '用户关联管理',
path: '/marketing/discount-users',
badge: 'NEW'
},
{
id: 'user-info',
title: '用户信息管理',
path: '/marketing/user-info',
badge: 'NEW'
},
{
id: 'user-history',
title: '用户使用记录管理',
path: '/marketing/user-history',
badge: 'NEW'
}
]
},
{
path: '/activity',
title: '活动管理',
icon: 'TrophyBase',
children: [
{
path: '/activity/signin',
title: '签到活动'
}
]
},
{
path: '/acs',
@@ -34,24 +142,24 @@ export const menus = [
]
},
{
path: '/acs/nodes', title: '节点管理'
path: '/acs/nodes',
title: '节点管理'
},
{
path: '/acs/guacamole',
title: '远程桌面网关管理',
icon: 'Monitor'
},{
title: '远程桌面网关管理'
},
{
path: '/audit',
title: '站点审计',
icon: 'Monitor',
children: [
{ path: '/audit/all', title: '所有站点' },
{ path: '/audit/violation', title: '违规站点' }
]
},{
},
{
path:'/setting',
title:'全局设置管理',
icon:'Setting',
children:[
{path:'/setting/global',title:'全局设置'}
]
@@ -63,9 +171,32 @@ export const menus = [
title: '系统管理',
icon: 'Setting',
children: [
// { path: '/system/users', title: '用户管理' },
// { path: '/system/operation-log', title: '操作日志' },
{ path: '/system/domain-whitelist', title: '域名白名单' }
{
path: '/system/permission',
title: '权限管理',
children: [
{ path: '/system/permission/route', title: '路由权限' },
{ path: '/system/permission/admin', title: '管理员权限' }
]
},
{
path: '/system/file',
title: '文件管理'
},
{
path: '/system/domain-whitelist',
title: '域名白名单'
},
{
path: '/system/setting-group',
title: '配置组管理'
},
{
path: '/system/setting-list',
title: '配置管理'
}
]
}
]
+239 -16
View File
@@ -140,6 +140,206 @@ const routes = [
}
]
},
// 用户管理路由
{
path: 'user',
name: 'User',
meta: {
title: '用户管理',
icon: 'User'
},
redirect: '/user/list',
children: [
{
path: 'list',
name: 'UserList',
component: () => import('../views/user/UserList.vue'),
meta: {
title: '用户列表'
}
},
{
path: 'detail',
name: 'UserDetail',
component: () => import('../views/user/UserDetail.vue'),
meta: {
title: '用户详情'
}
},
{
path: 'balance',
name: 'UserBalance',
component: () => import('../views/user/UserBalance.vue'),
meta: {
title: '用户余额管理'
}
},
{
path: 'group',
name: 'UserGroup',
component: () => import('../views/user/UserGroup.vue'),
meta: {
title: '用户组管理'
}
},
{
path: 'admin-group',
name: 'AdminGroup',
component: () => import('../views/user/AdminGroup.vue'),
meta: {
title: '管理员组管理'
}
}
]
},
// 商品管理路由
{
path: 'product',
name: 'Product',
meta: {
title: '商品管理',
icon: 'Goods'
},
redirect: '/product/list',
children: [
{
path: 'list',
name: 'ProductList',
component: () => import('../views/product/ProductList.vue'),
meta: {
title: '商品列表'
}
},
{
path: 'group',
name: 'ProductGroup',
component: () => import('../views/product/ProductGroup.vue'),
meta: {
title: '商品分组'
}
},
{
path: 'parameter',
name: 'ProductParameter',
component: () => import('../views/product/ProductParameter.vue'),
meta: {
title: '商品参数'
}
}
]
},
// 订单管理路由
{
path: 'order',
name: 'Order',
meta: {
title: '订单管理',
icon: 'Document'
},
redirect: '/order/list',
children: [
{
path: 'list',
name: 'OrderList',
component: () => import('../views/order/OrderList.vue'),
meta: {
title: '订单列表'
}
}
]
},
// 优惠营销路由
{
path: 'marketing',
name: 'Marketing',
meta: {
title: '优惠营销',
icon: 'Present'
},
redirect: '/marketing/discount',
children: [
{
path: 'discount',
name: 'DiscountCode',
component: () => import('../views/marketing/DiscountCode.vue'),
meta: {
title: '优惠码管理'
}
},
{
path: 'voucher',
name: 'Voucher',
component: () => import('../views/marketing/Voucher.vue'),
meta: {
title: '代金券管理'
}
},
{
path: 'user-distribution',
name: 'UserDistribution',
component: () => import('../views/marketing/UserVoucher.vue'),
meta: {
title: '用户分发管理'
}
},
{
path: 'discount-goods',
name: 'DiscountGoods',
component: () => import('../views/marketing/DiscountGoods.vue'),
meta: {
title: '商品关联管理',
badge: 'NEW'
}
},
{
path: 'discount-users',
name: 'DiscountUsers',
component: () => import('../views/marketing/DiscountUsers.vue'),
meta: {
title: '用户关联管理',
badge: 'NEW'
}
},
{
path: 'user-info',
name: 'UserInfo',
component: () => import('../views/marketing/VoucherHolders.vue'),
meta: {
title: '用户信息管理',
badge: 'NEW'
}
},
{
path: 'user-history',
name: 'UserHistory',
component: () => import('../views/marketing/VoucherHistory.vue'),
meta: {
title: '用户使用记录管理',
badge: 'NEW'
}
}
]
},
// 活动管理路由
{
path: 'activity',
name: 'Activity',
meta: {
title: '活动管理',
icon: 'TrophyBase'
},
redirect: '/activity/signin',
children: [
{
path: 'signin',
name: 'SigninActivity',
component: () => import('../views/activity/SigninActivity.vue'),
meta: {
title: '签到活动'
}
}
]
},
{
path: 'system',
name: 'System',
@@ -147,28 +347,51 @@ const routes = [
title: '系统管理',
icon: 'Setting'
},
redirect: '/system/domain-whitelist',
redirect: '/system/permission/route',
children: [
// 注释掉的用户管理和操作日志路由,与菜单配置保持一致
// {
// path: 'users',
// name: 'Users',
// component: () => import('../views/system/Users.vue'),
// meta: {
// title: '用户管理'
// }
// },
// {
// path: 'operation-log',
// name: 'OperationLog',
// component: OperationLog,
// meta: { title: '操作日志' }
// },
{
path: 'permission/route',
name: 'PermissionRoute',
component: () => import('../views/system/PermissionRoute.vue'),
meta: {
title: '路由权限'
}
},
{
path: 'permission/admin',
name: 'PermissionAdmin',
component: () => import('../views/system/PermissionAdmin.vue'),
meta: {
title: '管理员权限'
}
},
{
path: 'file',
name: 'SystemFile',
component: () => import('../views/system/SystemFile.vue'),
meta: {
title: '文件管理'
}
},
{
path: 'domain-whitelist',
name: 'DomainWhitelist',
component: () => import('../views/system/DomainWhitelist.vue'),
meta: { title: '域名白名单' }
},
{
path: 'setting-group',
name: 'SettingGroup',
component: () => import('../views/system/SettingGroup.vue'),
meta: { title: '配置组管理' }
},
{
path: 'setting-list',
name: 'SettingList',
component: () => import('../views/system/Setting.vue'),
meta: { title: '配置管理' }
}
]
},
+4 -4
View File
@@ -3,15 +3,15 @@ import {http2} from "@/utils/request.js";
/**获取所有站点 */
export const getSiteList = (data) => {
return http2.get(`/v1/admin/audit/list?page=${data.page}&server_id=${data.server_id}&user_id=${data.user_id}&count=${data.count}&key=${data.key}`)
return http2.get(`/acs/v1/admin/audit/list?page=${data.page}&server_id=${data.server_id}&user_id=${data.user_id}&count=${data.count}&key=${data.key}`)
}
/**手动触发站点审计 */
export const auditSite = () => {
return http2.get(`/v1/admin/audit/start`)
return http2.get(`/acs/v1/admin/audit/start`)
}
/**删除违规网页审计 传入参数: web_key 站点名*/
export const delAudit = (data) => {
return http2.post(`/v1/admin/audit/delete`,data,{
return http2.post(`/acs/v1/admin/audit/delete`,data,{
headers: {
'Content-Type': 'multipart/form-data'
}
@@ -19,5 +19,5 @@ export const delAudit = (data) => {
}
/**获取违规网页审计列表 */
export const getAuditList = (data) => {
return http2.get(`/v1/admin/audit/violation_list?page=${data.page}&count=${data.count}&key=${data.key}`)
return http2.get(`/acs/v1/admin/audit/violation_list?page=${data.page}&count=${data.count}&key=${data.key}`)
}
+9 -9
View File
@@ -1,10 +1,10 @@
import {http2} from "@/utils/request.js";
export const getFileList = (data) => {
return http2.get(`/v1/file/list?container_id=${data.container_id}&path=${data.path}`)
return http2.get(`/acs/v1/file/list?container_id=${data.container_id}&path=${data.path}`)
}
/** 读取文件内容 */
export const readFile = (data) => {
return http2.post(`/v1/file/read`,data, {
return http2.post(`/acs/v1/file/read`,data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -12,7 +12,7 @@ export const readFile = (data) => {
}
/*删除文件或文件夹 */
export const deleteFile = (data) => {
return http2.post(`/v1/file/delete`,data, {
return http2.post(`/acs/v1/file/delete`,data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -20,7 +20,7 @@ export const deleteFile = (data) => {
}
/*写入文件 */
export const writeFile = (data) => {
return http2.post(`/v1/file/write`,data, {
return http2.post(`/acs/v1/file/write`,data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -28,7 +28,7 @@ export const writeFile = (data) => {
}
/*创建文件夹 */
export const createFolder = (data) => {
return http2.post(`/v1/file/mkdir`,data, {
return http2.post(`/acs/v1/file/mkdir`,data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -36,7 +36,7 @@ export const createFolder = (data) => {
}
/**上传文件 */
export const uploadFile = (data) => {
return http2.post(`/v1/file/upload_file`,data, {
return http2.post(`/acs/v1/file/upload_file`,data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -44,7 +44,7 @@ export const uploadFile = (data) => {
}
/**下载文件链接 */
export const downloadFile = (data) => {
return http2.post(`/v1/file/get_down_link`,data, {
return http2.post(`/acs/v1/file/get_down_link`,data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -52,7 +52,7 @@ export const downloadFile = (data) => {
}
/**压缩文件 */
export const compressFile = (data) => {
return http2.post(`/v1/file/zip_file`,data, {
return http2.post(`/acs/v1/file/zip_file`,data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -60,7 +60,7 @@ export const compressFile = (data) => {
}
/**解压文件 */
export const decompressFile = (data) => {
return http2.post(`/v1/file/unzip_file`,data, {
return http2.post(`/acs/v1/file/unzip_file`,data, {
headers: {
"Content-Type": "multipart/form-data"
}
+5 -5
View File
@@ -2,15 +2,15 @@ import {http2} from "@/utils/request.js";
/**获取 guacamole 列表 */
export const getGuacamoleList = data => {
return http2.get(`/v1/admin/server/get_guacamole_list`);
return http2.get(`/acs/v1/admin/server/get_guacamole_list`);
};
/**获取服务器 guacamole 信息 */
export const getGuacamoleInfo = data => {
return http2.get(`/v1/admin/server/get_server_guacamole?server_id=${data}`);
return http2.get(`/acs/v1/admin/server/get_server_guacamole?server_id=${data}`);
};
/**新增 guacamole 参数 url:string,username:string,password:string*/
export const addGuacamoleInfo = data => {
return http2.post(`/v1/admin/server/add_guacamole`, data,{
return http2.post(`/acs/v1/admin/server/add_guacamole`, data,{
headers:{
'Content-Type': 'multipart/form-data'
}
@@ -18,7 +18,7 @@ export const addGuacamoleInfo = data => {
};
/**修改guacamole 参数 id:string,url:string,username:string,password:string*/
export const updateGuacamoleInfo = data => {
return http2.post(`/v1/admin/server/edit_guacamole`, data,{
return http2.post(`/acs/v1/admin/server/edit_guacamole`, data,{
headers:{
'Content-Type': 'multipart/form-data'
}
@@ -26,7 +26,7 @@ export const updateGuacamoleInfo = data => {
};
/**删除guacamole 参数 id:string */
export const deleteGuacamoleInfo = data => {
return http2.post(`/v1/admin/server/delete_guacamole`, data,{
return http2.post(`/acs/v1/admin/server/delete_guacamole`, data,{
headers:{
'Content-Type': 'multipart/form-data'
}
+11 -11
View File
@@ -1,15 +1,15 @@
import {http2} from "@/utils/request.js";
/**获取消息列表 */
export const getMessageList = (data) => {
return http2.get(`/v1/messages/get_message_list?page=${data.page}&count=${data.count}&message_type=${data.message_type}`)
return http2.get(`/acs/v1/messages/get_message_list?page=${data.page}&count=${data.count}&message_type=${data.message_type}`)
}
/**获取单条消息 */
export const getMessage = (data) => {
return http2.get(`/v1/messages/get_message?message_id=${data}`)
return http2.get(`/acs/v1/messages/get_message?message_id=${data}`)
}
/**添加消息 */
export const addMessage = (data) => {
return http2.post(`/v1/messages/add_message`, data,{
return http2.post(`/acs/v1/messages/add_message`, data,{
headers: {
'Content-Type': 'multipart/form-data'
}
@@ -17,7 +17,7 @@ headers: {
}
/**删除消息 */
export const deleteMessage = (data) => {
return http2.post(`/v1/messages/delete_message`, data,{
return http2.post(`/acs/v1/messages/delete_message`, data,{
headers: {
'Content-Type': 'multipart/form-data'
}
@@ -25,7 +25,7 @@ headers: {
}
/**修改消息 */
export const editMessage = (data) => {
return http2.post(`/v1/messages/update_message`, data,{
return http2.post(`/acs/v1/messages/update_message`, data,{
headers: {
'Content-Type': 'multipart/form-data'
}
@@ -33,11 +33,11 @@ headers: {
}
/**获取附件列表 */
export const getFileList = (data) => {
return http2.get(`/v1/attachment/get_attachment_list?page=${data.page}&count=${data.count}&key=${data.key}&user_type=${data.user_type}`)
return http2.get(`/acs/v1/attachment/get_attachment_list?page=${data.page}&count=${data.count}&key=${data.key}&user_type=${data.user_type}`)
}
/**上传附件 */
export const uploadFile = (data) => {
return http2.post(`/v1/attachment/add_attachment`, data,{
return http2.post(`/acs/v1/attachment/add_attachment`, data,{
headers: {
'Content-Type': 'multipart/form-data'
}
@@ -45,20 +45,20 @@ headers: {
}
/**删除附件 */
export const deleteFile = (data) => {
return http2.get(`/v1/attachment/delete_attachment?aid=${data}`)
return http2.get(`/acs/v1/attachment/delete_attachment?aid=${data}`)
}
/**用户获取消息列表 */
export const getUserMessageList = (data) => {
return http2.get(`/v1/messages/get_message_list?page=${data.page}&count=${data.count}&message_type=${data.message_type}`)
return http2.get(`/acs/v1/messages/get_message_list?page=${data.page}&count=${data.count}&message_type=${data.message_type}`)
}
/**用户获取单条消息 */
export const getUserMessage = (data) => {
return http2.get(`/v1/messages/get_message?message_id=${data}`)
return http2.get(`/acs/v1/messages/get_message?message_id=${data}`)
}
/**获取消息详情 */
export const getMessageDetail = (data) => {
return http2.get(`/v1/messages/get_message?message_id=${data.message_id}`)
return http2.get(`/acs/v1/messages/get_message?message_id=${data.message_id}`)
}
/**修改图片大小 */
export const compressAndConvertFileToBase64=async(file)=> {
+13 -13
View File
@@ -2,19 +2,19 @@ import {http2} from "@/utils/request.js";
/**获取镜像列表 */
export const getMirrorList = data => {
if(typeof data == "string"){
return http2.get("/v1/image/list?server_id=" + data + "&count=9999999")
return http2.get("/acs/v1/image/list?server_id=" + data + "&count=9999999")
}
return http2.get(`/v1/image/list?server_id=${data.server_id}&page=${data.page}&count=${data.count}&key=${data.key}&class_id=${data.class_id}`);
return http2.get(`/acs/v1/image/list?server_id=${data.server_id}&page=${data.page}&count=${data.count}&key=${data.key}&class_id=${data.class_id}`);
};
/*用户获取镜像列表 */
export const getUserMirrorList = data => {
return http2.get(
`/v1/image/list?server_id=${data.server_id}&count=${data.count}&page=${data.page}&key=${data.key}`
`/acs/v1/image/list?server_id=${data.server_id}&count=${data.count}&page=${data.page}&key=${data.key}`
);
};
/**上传镜像 */
export const uploadMirror = data => {
return http2.post("/v1/image/pull", data, {
return http2.post("/acs/v1/image/pull", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -22,7 +22,7 @@ export const uploadMirror = data => {
};
/**编辑镜像 */
export const editMirror = data => {
return http2.post("/v1/image/update", data, {
return http2.post("/acs/v1/image/update", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -30,7 +30,7 @@ export const editMirror = data => {
};
/**删除镜像 */
export const delMirror = data => {
return http2.post("/v1/image/delete", data, {
return http2.post("/acs/v1/image/delete", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -38,11 +38,11 @@ export const delMirror = data => {
};
/**镜像同步 */
export const syncMirror = data => {
return http2.get(`/v1/image/sync?server_id=${data}`);
return http2.get(`/acs/v1/image/sync?server_id=${data}`);
};
/**重新拉取镜像 */
export const pullMirror = data => {
return http2.post(`/v1/image/repull`, data, {
return http2.post(`/acs/v1/image/repull`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -53,12 +53,12 @@ export const pullMirror = data => {
export const Mirrorinfo = data => {
const serverType = data.server_type || "dockerContainer"; // 设置默认值
return http2.get(
`/v1/image/info?image_id=${data.image_id}&server_type=${serverType}`
`/acs/v1/image/info?image_id=${data.image_id}&server_type=${serverType}`
);
};
export const addVirtualMirror = data => {
return http2.post("/v1/image/create", data, {
return http2.post("/acs/v1/image/create", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -67,11 +67,11 @@ export const addVirtualMirror = data => {
export const getImageTypeList = (server_id) => {
return http2.get(`/v1/image/class_list?server_id=${server_id}`);
return http2.get(`/acs/v1/image/class_list?server_id=${server_id}`);
};
export const createImageType = (server_id,class_name,class_ico) => {
return http2.post("/v1/image/class_create", {
return http2.post("/acs/v1/image/class_create", {
server_id,
class_name,
class_ico
@@ -83,7 +83,7 @@ export const createImageType = (server_id,class_name,class_ico) => {
};
export const updateImageType = (class_id,class_name,class_ico) => {
return http2.post("/v1/image/class_update", {
return http2.post("/acs/v1/image/class_update", {
class_id,
class_name,
class_ico
+4 -4
View File
@@ -1,11 +1,11 @@
import {http2} from "@/utils/request.js";
/**获取订单列表 */
export const getOrderList = (data) => {
return http2.get(`/v1/admin/trades/get_trades?page=${data.page}&count=${data.count}&key=${data.key}`)
return http2.get(`/acs/v1/admin/trades/get_trades?page=${data.page}&count=${data.count}&key=${data.key}`)
}
/**编辑订单 */
export const editOrder = (data) => {
return http2.post('/v1/admin/trades/update_trades',data,{
return http2.post('/acs/v1/admin/trades/update_trades',data,{
headers: {
'Content-Type': 'multipart/form-data'
}
@@ -13,7 +13,7 @@ headers: {
}
/**删除订单 */
export const deleteOrder = (data) => {
return http2.post('/v1/admin/trades/delete_trade',data,{
return http2.post('/acs/v1/admin/trades/delete_trade',data,{
headers: {
'Content-Type': 'multipart/form-data'
}
@@ -21,5 +21,5 @@ headers: {
}
/**用户获取订单列表 */
export const getUserOrderList = (data) => {
return http2.get(`/v1/user/procedure/get_trade_list?page=${data.page}&count=${data.count}&key=${data.key}`)
return http2.get(`/acs/v1/user/procedure/get_trade_list?page=${data.page}&count=${data.count}&key=${data.key}`)
}
+3 -3
View File
@@ -7,7 +7,7 @@ export const get_pay_code = data => {
};
// /**email验证码 */
// export const ask_update_user_email = data => {
// return http2.post("/v1/user/info/ask_update_user_email", data, {
// return http2.post("/acs/v1/user/info/ask_update_user_email", data, {
// headers: {
// "Content-Type": "multipart/form-data"
// }
@@ -15,7 +15,7 @@ export const get_pay_code = data => {
// };
/**获取容器订单金额 */
export const procedure_get_price = data => {
return http2.post("/v1/user/procedure/get_price", data, {
return http2.post("/acs/v1/user/procedure/get_price", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -24,7 +24,7 @@ export const procedure_get_price = data => {
/**获取虚拟机订单金额 */
export const procedure_vir_price = data => {
return http2.post("/v1/user/procedure/get_vm_price", data, {
return http2.post("/acs/v1/user/procedure/get_vm_price", data, {
headers: {
"Content-Type": "multipart/form-data"
}
+70 -70
View File
@@ -3,13 +3,13 @@ import {http2} from "@/utils/request.js";
/** 获取所有服务器 */
export const getServer = (page, count, key, type = "dockerContainer") => {
return http2.get(
`/v1/admin/server/get_server_list?page=${page}&count=${count}&key=${key}&server_type=${type}`
`/acs/v1/admin/server/get_server_list?page=${page}&count=${count}&key=${key}&server_type=${type}`
);
};
/**新增服务器 */
export const addServer = data => {
return http2.post("/v1/admin/server/add_server", data, {
return http2.post("/acs/v1/admin/server/add_server", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -17,7 +17,7 @@ export const addServer = data => {
};
/**编辑服务器 */
export const editServer = data => {
return http2.post("/v1/admin/server/update_server", data, {
return http2.post("/acs/v1/admin/server/update_server", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -25,7 +25,7 @@ export const editServer = data => {
};
/**删除服务器 */
export const deleteServer = data => {
return http2.post("/v1/admin/server/delete_server", data, {
return http2.post("/acs/v1/admin/server/delete_server", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -33,7 +33,7 @@ export const deleteServer = data => {
};
/**查询指定服务器 */
export const selectServer = data => {
return http2.post("/v1/admin/server/select_server", data, {
return http2.post("/acs/v1/admin/server/select_server", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -42,12 +42,12 @@ export const selectServer = data => {
/**获取服务器套餐列表*/
export const getServerPlan = data => {
return http2.get(
`/v1/admin/container_plan/get_server_plan_list?server_id=${data.server_id}&count=${data.count}`
`/acs/v1/admin/container_plan/get_server_plan_list?server_id=${data.server_id}&count=${data.count}`
);
};
/**获取指定套餐 */
export const selectServerPlan = data => {
return http2.post("/v1/admin/container_plan/get_server_plan_detail", data, {
return http2.post("/acs/v1/admin/container_plan/get_server_plan_detail", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -55,7 +55,7 @@ export const selectServerPlan = data => {
};
/**新增容器 */
export const addContainer = data => {
return http2.post("/v1/admin/container/add_container", data, {
return http2.post("/acs/v1/admin/container/add_container", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -63,7 +63,7 @@ export const addContainer = data => {
};
/**删除容器网络 */
export const deleteContainerNetwork = data => {
return http2.post("/v1/user/container/delete_connect", data, {
return http2.post("/acs/v1/user/container/delete_connect", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -72,7 +72,7 @@ export const deleteContainerNetwork = data => {
/**修改套餐信息 */
export const editServerPlan = data => {
return http2.post("/v1/admin/container_plan/update_server_plan", data, {
return http2.post("/acs/v1/admin/container_plan/update_server_plan", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -80,7 +80,7 @@ export const editServerPlan = data => {
};
/**新增套餐 */
export const addServerPlan = data => {
return http2.post("/v1/admin/container_plan/add_server_plan", data, {
return http2.post("/acs/v1/admin/container_plan/add_server_plan", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -88,7 +88,7 @@ export const addServerPlan = data => {
};
/**删除套餐 */
export const deleteServerPlan = data => {
return http2.post("/v1/admin/container_plan/delete_server_plan", data, {
return http2.post("/acs/v1/admin/container_plan/delete_server_plan", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -97,19 +97,19 @@ export const deleteServerPlan = data => {
/**获取容器列表 */
export const getContainer = data => {
return http2.get(
`/v1/admin/container/get_container_list?server_id=${data.server_id}&user_id=${data.user_id}&page=${data.page}&count=${data.count}&key=${data.key}`
`/acs/v1/admin/container/get_container_list?server_id=${data.server_id}&user_id=${data.user_id}&page=${data.page}&count=${data.count}&key=${data.key}`
);
};
/**获取虚拟机列表 */
export const getInstanceList = data => {
return http2.get(
`/v1/admin/instance/list?server_id=${data.server_id}&user_id=${data.user_id}&page=${data.page}&count=${data.count}&key=${data.key}`
`/acs/v1/admin/instance/list?server_id=${data.server_id}&user_id=${data.user_id}&page=${data.page}&count=${data.count}&key=${data.key}`
);
};
/**获取单个指定容器 */
export const getOneContainer = data => {
return http2.post("/v1/admin/container/get_container_detail", data, {
return http2.post("/acs/v1/admin/container/get_container_detail", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -117,11 +117,11 @@ export const getOneContainer = data => {
};
/**查询指定虚拟机信息(管理员查询) */
export const getVmAdminContainer = id => {
return http2.get(`/v1/admin/instance/detail/${id}`);
return http2.get(`/acs/v1/admin/instance/detail/${id}`);
};
// 暂停容器
export const pauseContainer = data => {
return http2.post("/v1/admin/container/pause_container", data, {
return http2.post("/acs/v1/admin/container/pause_container", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -129,7 +129,7 @@ export const pauseContainer = data => {
};
// 暂停虚拟机
export const pauseInstance = (data, id) => {
return http2.post(`/v1/admin/instance/pause/${id}`, data, {
return http2.post(`/acs/v1/admin/instance/pause/${id}`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -137,7 +137,7 @@ export const pauseInstance = (data, id) => {
};
/**恢复虚拟机 */
export const unpauseInstance = (id, data = "") => {
return http2.post(`/v1/admin/instance/resume/${id}`, data, {
return http2.post(`/acs/v1/admin/instance/resume/${id}`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -146,7 +146,7 @@ export const unpauseInstance = (id, data = "") => {
// 解除暂停
export const unpauseContainer = data => {
return http2.post("/v1/admin/container/unpause_container", data, {
return http2.post("/acs/v1/admin/container/unpause_container", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -154,7 +154,7 @@ export const unpauseContainer = data => {
};
/**获取容器状态 */
export const getContainerStatus = data => {
return http2.post("/v1/admin/container/get_container_status", data, {
return http2.post("/acs/v1/admin/container/get_container_status", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -162,15 +162,15 @@ export const getContainerStatus = data => {
};
/**获取虚拟机状态 */
export const getInstanceStatus = id => {
return http2.get(`/v1/admin/instance/get_state/${id}`);
return http2.get(`/acs/v1/admin/instance/get_state/${id}`);
};
/**查询服务器状态 */
export const getServerStatus = id => {
return http2.get(`/v1/admin/server/send_server_status?server_id=${id}`);
return http2.get(`/acs/v1/admin/server/send_server_status?server_id=${id}`);
};
/**开通容器 */
export const openContainer = data => {
return http2.post("/v1/admin/container/open_container", data, {
return http2.post("/acs/v1/admin/container/open_container", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -178,7 +178,7 @@ export const openContainer = data => {
};
/**开通虚拟机 */
export const openInstance = (id, data = "") => {
return http2.post(`/v1/admin/instance/approve/${id}`, data, {
return http2.post(`/acs/v1/admin/instance/approve/${id}`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -186,7 +186,7 @@ export const openInstance = (id, data = "") => {
};
/**启动容器 */
export const startContainer = data => {
return http2.post("/v1/admin/container/start_container", data, {
return http2.post("/acs/v1/admin/container/start_container", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -194,11 +194,11 @@ export const startContainer = data => {
};
/**启动虚拟机 */
export const startInstance = data => {
return http2.get(`/v1/admin/instance/start/${data}`);
return http2.get(`/acs/v1/admin/instance/start/${data}`);
};
/**重装容器 */
export const reinstallC = data => {
return http2.post("/v1/admin/container/reinstall_container", data, {
return http2.post("/acs/v1/admin/container/reinstall_container", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -206,7 +206,7 @@ export const reinstallC = data => {
};
/**重装虚拟机 */
export const reinstallI = (data, id) => {
return http2.post(`/v1/admin/instance/reinstall/${id}`, data, {
return http2.post(`/acs/v1/admin/instance/reinstall/${id}`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -215,7 +215,7 @@ export const reinstallI = (data, id) => {
/**获取容器日志 */
export const getContainerLog = data => {
return http2.post(`/v1/admin/container/get_container_log`, data, {
return http2.post(`/acs/v1/admin/container/get_container_log`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -224,13 +224,13 @@ export const getContainerLog = data => {
/**获取虚拟机操作日志 */
export const getInstanceLog = (id, data) => {
return http2.get(
`/v1/admin/instance/log/${id}?page=${data.page}&count=${data.count}`
`/acs/v1/admin/instance/log/${id}?page=${data.page}&count=${data.count}`
);
};
/**重启容器 */
export const restartContainer = data => {
return http2.post("/v1/admin/container/reboot_container", data, {
return http2.post("/acs/v1/admin/container/reboot_container", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -238,12 +238,12 @@ export const restartContainer = data => {
};
/**重启虚拟机 */
export const restartInstance = data => {
return http2.get(`/v1/admin/instance/reboot/${data}`);
return http2.get(`/acs/v1/admin/instance/reboot/${data}`);
};
/**停止容器 */
export const stopContainer = data => {
return http2.post("/v1/admin/container/stop_container", data, {
return http2.post("/acs/v1/admin/container/stop_container", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -251,12 +251,12 @@ export const stopContainer = data => {
};
/**停止虚拟机 */
export const stopInstance = data => {
return http2.get(`/v1/admin/instance/stop/${data}`);
return http2.get(`/acs/v1/admin/instance/stop/${data}`);
};
/**删除容器 */
export const deleteContainer = data => {
return http2.post("/v1/admin/container/delete_container", data, {
return http2.post("/acs/v1/admin/container/delete_container", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -264,7 +264,7 @@ export const deleteContainer = data => {
};
/**删除虚拟机 */
export const deleteInstance = (id, data = "") => {
return http2.post(`/v1/admin/instance/delete/${id}`, data, {
return http2.post(`/acs/v1/admin/instance/delete/${id}`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -272,7 +272,7 @@ export const deleteInstance = (id, data = "") => {
};
/**清除容器流量 */
export const clearContainerTraffic = data => {
return http2.post("/v1/admin/container/clear_container_traffic", data, {
return http2.post("/acs/v1/admin/container/clear_container_traffic", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -280,7 +280,7 @@ export const clearContainerTraffic = data => {
};
/**连接控制台 */
export const connectConsole = data => {
return http2.post("/v1/admin/container/get_container_console", data, {
return http2.post("/acs/v1/admin/container/get_container_console", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -288,7 +288,7 @@ export const connectConsole = data => {
};
/**新增虚拟机 (管理员) */
export const addInstance = data => {
return http2.post("/v1/admin/instance/create_vm", data, {
return http2.post("/acs/v1/admin/instance/create_vm", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -296,21 +296,21 @@ export const addInstance = data => {
};
/**获取虚拟机控制台 */
export const getInstanceConsole = data => {
return http2.get(`/v1/admin/instance/console/${data}`);
return http2.get(`/acs/v1/admin/instance/console/${data}`);
};
/**查询容器所有卷信息 */
export const getVolumeList = data => {
return http2.get(`/v1/admin/volume/get_volume_list?instance_id=${data.instance_id}&page=${data.page}&count=${data.count}`);
return http2.get(`/acs/v1/admin/volume/get_volume_list?instance_id=${data.instance_id}&page=${data.page}&count=${data.count}`);
};
/**查询虚拟机所有卷信息 */
export const getInstanceVolumeList = data => {
return http2.get(
`/v1/admin/volume/get_volume_list?instance_id=${data.instance_id}&page=${data.page}&count=${data.count}`
`/acs/v1/admin/volume/get_volume_list?instance_id=${data.instance_id}&page=${data.page}&count=${data.count}`
);
};
/**新增卷 */
export const addVolume = data => {
return http2.post("/v1/admin/volume/add_volume", data, {
return http2.post("/acs/v1/admin/volume/add_volume", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -318,7 +318,7 @@ export const addVolume = data => {
};
/**修改卷大小 */
export const updateVolume = data => {
return http2.post("/v1/admin/volume/update_volume_size", data, {
return http2.post("/acs/v1/admin/volume/update_volume_size", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -326,7 +326,7 @@ export const updateVolume = data => {
};
/**删除数据卷 */
export const deleteVolume = data => {
return http2.post("/v1/admin/volume/delete_volume", data, {
return http2.post("/acs/v1/admin/volume/delete_volume", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -336,7 +336,7 @@ export const deleteVolume = data => {
/**获取容器网络信息 */
export const getNetworkList = data => {
return http2.get(
`/v1/container/proxy/get_container_proxy?container_id=${data}`
`/acs/v1/container/proxy/get_container_proxy?container_id=${data}`
);
};
/**获取虚拟机端口列表 */
@@ -347,12 +347,12 @@ export const getInstancePortList = data => {
if (data.internal_port !== undefined)
params.append("internal_port", data.internal_port.toString());
return http2.get(
`/v1/admin/instance_port/list?instance_id=${data.instance_id}&${params.toString()}`
`/acs/v1/admin/instance_port/list?instance_id=${data.instance_id}&${params.toString()}`
);
};
/**添加容器网络 */
export const addNetwork = data => {
return http2.post("/v1/container/proxy/add_container_proxy", data, {
return http2.post("/acs/v1/container/proxy/add_container_proxy", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -360,7 +360,7 @@ export const addNetwork = data => {
};
/**创建端口 */
export const addPort = data => {
return http2.post("/v1/admin/instance_port/create", data, {
return http2.post("/acs/v1/admin/instance_port/create", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -369,12 +369,12 @@ export const addPort = data => {
/**获取浮动ip列表 */
export const getFloatingIpList = data => {
return http2.get(
`/v1/admin/floating_ip/get_list?server_id=${data.server_id}&page=${data.page}&count=${data.count}`
`/acs/v1/admin/floating_ip/get_list?server_id=${data.server_id}&page=${data.page}&count=${data.count}`
);
};
/**新增浮动ip */
export const addFloatingIp = data => {
return http2.post("/v1/admin/floating_ip/add", data, {
return http2.post("/acs/v1/admin/floating_ip/add", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -382,7 +382,7 @@ export const addFloatingIp = data => {
};
/**批量添加浮动ip */
export const addFloatingIpBatch = data => {
return http2.post("/v1/admin/floating_ip/add_list", data, {
return http2.post("/acs/v1/admin/floating_ip/add_list", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -390,7 +390,7 @@ export const addFloatingIpBatch = data => {
};
/**删除浮动ip */
export const delFloatingIp = data => {
return http2.post("/v1/admin/floating_ip/delete", data, {
return http2.post("/acs/v1/admin/floating_ip/delete", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -400,13 +400,13 @@ export const delFloatingIp = data => {
/**获取单个用户操作日志 */
export const getUserLog = data => {
return http2.get(
`/v1/user/procedure/get_user_log?user_id=${data.user_id}&page=${data.page}&count=${data.count}`
`/acs/v1/user/procedure/get_user_log?user_id=${data.user_id}&page=${data.page}&count=${data.count}`
);
};
/**管理员修改头像 */
export const editAvatar = data => {
return http2.post("/v1/admin/users/upload_user_avatar", data, {
return http2.post("/acs/v1/admin/users/upload_user_avatar", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -415,28 +415,28 @@ export const editAvatar = data => {
/**获取服务器硬盘信息 */
export const getDiskInfo = data => {
return http2.get(`/v1/admin/server/get_server_disk?server_id=${data}`);
return http2.get(`/acs/v1/admin/server/get_server_disk?server_id=${data}`);
};
/**获取服务器实际划分硬盘信息 */
export const getRealDisk = data => {
return http2.get(`/v1/admin/server/get_server_disk_info?server_id=${data}`);
return http2.get(`/acs/v1/admin/server/get_server_disk_info?server_id=${data}`);
};
/**获取服务器流量信息 */
export const getTraffic = data => {
return http2.get(`/v1/admin/server/get_server_bandwidth?server_id=${data}`);
return http2.get(`/acs/v1/admin/server/get_server_bandwidth?server_id=${data}`);
};
/**获取服务器总流量信息 */
export const getTotalTraffic = data => {
return http2.get(`/v1/admin/server/get_server_total_bandwidth?server_id=${data}`);
return http2.get(`/acs/v1/admin/server/get_server_total_bandwidth?server_id=${data}`);
};
/**获取版本更新 */
export const getVersion = () => {
return http2.get(`/v1/admin/version`);
return http2.get(`/acs/v1/admin/version`);
};
// 管理员删除https网络
export const AdminDelHttps = data => {
return http2.post("/v1/container/proxy/del_https_connet", data, {
return http2.post("/acs/v1/container/proxy/del_https_connet", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -445,7 +445,7 @@ export const AdminDelHttps = data => {
// 管理员添加https网络
export const AdminAddHttps = data => {
return http2.post("/v1/container/proxy/add_https_proxy", data, {
return http2.post("/acs/v1/container/proxy/add_https_proxy", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -453,11 +453,11 @@ export const AdminAddHttps = data => {
};
/**获取指定端口信息 */
export const getPortInfo = data => {
return http2.get(`/v1/admin/instance_port/detail?port_id=${data}`);
return http2.get(`/acs/v1/admin/instance_port/detail?port_id=${data}`);
};
/**新增卷 */
export const addVolumeMount = data => {
return http2.post("/v1/admin/volume/add_volume", data, {
return http2.post("/acs/v1/admin/volume/add_volume", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -466,17 +466,17 @@ export const addVolumeMount = data => {
/**进入救援系统 */
export const rescueInstance = id => {
return http2.get(`/v1/admin/instance/rescue/enter/${id}`);
return http2.get(`/acs/v1/admin/instance/rescue/enter/${id}`);
};
/**退出救援系统 */
export const exitRescueInstance = id => {
return http2.get(`/v1/admin/instance/rescue/exit/${id}`);
return http2.get(`/acs/v1/admin/instance/rescue/exit/${id}`);
};
/**修改虚拟机密码 */
export const changeInstancePassword = (id, data) => {
return http2.post(`/v1/admin/instance/update_password/${id}`, data, {
return http2.post(`/acs/v1/admin/instance/update_password/${id}`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -484,7 +484,7 @@ export const changeInstancePassword = (id, data) => {
};
/**修改虚拟机密码(用户) */
export const changeInstancePasswordUser = (id, data) => {
return http2.post(`/v1/user/instance/update_password/${id}`, data, {
return http2.post(`/acs/v1/user/instance/update_password/${id}`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -493,7 +493,7 @@ export const changeInstancePasswordUser = (id, data) => {
/**删除端口 */
export const deletePort = data => {
return http2.post("/v1/admin/instance_port/delete", data, {
return http2.post("/acs/v1/admin/instance_port/delete", data, {
headers: {
"Content-Type": "multipart/form-data"
}
+7 -7
View File
@@ -1,11 +1,11 @@
import {http2} from "@/utils/request.js";
/**获取全局配置 */
export const getSetting = () => {
return http2.get('/v1/admin/settings/get_settings')
return http2.get('/acs/v1/admin/settings/get_settings')
}
/**变更设置 */
export const updateSetting = (data) => {
return http2.post('/v1/admin/settings/update_settings', data, {
return http2.post('/acs/v1/admin/settings/update_settings', data, {
headers: {
'Content-Type': 'multipart/form-data'
}
@@ -13,7 +13,7 @@ export const updateSetting = (data) => {
}
/**新增设置 */
export const addSetting = (data) => {
return http2.post('/v1/admin/settings/add_settings', data, {
return http2.post('/acs/v1/admin/settings/add_settings', data, {
headers: {
'Content-Type': 'multipart/form-data'
}
@@ -21,7 +21,7 @@ export const addSetting = (data) => {
}
/**删除设置 */
export const deleteSetting = (data) => {
return http2.post('/v1/admin/settings/delete_settings', data,{
return http2.post('/acs/v1/admin/settings/delete_settings', data,{
headers: {
'Content-Type': 'multipart/form-data'
}
@@ -29,12 +29,12 @@ export const deleteSetting = (data) => {
}
/**获取单项配置 */
export const getOneSetting = (data) => {
return http2.get(`/v1/admin/settings/get_setting?name=${data}`)
return http2.get(`/acs/v1/admin/settings/get_setting?name=${data}`)
}
/**获取多个配置 */
export const getSettings = (data) => {
// return http2.get(`/v1/admin/settings/get_settings?names=${data}`);
// return http2.get(`/acs/v1/admin/settings/get_settings?names=${data}`);
const namesParam = data.join(',');
// 将处理后的namesParam放入URL中
return http2.get(`/v1/admin/settings/get_setting?names=${encodeURIComponent(namesParam)}`);
return http2.get(`/acs/v1/admin/settings/get_setting?names=${encodeURIComponent(namesParam)}`);
}
+6 -6
View File
@@ -1,11 +1,11 @@
import {http2} from "@/utils/request.js";
/**获取用户列表 */
export const ask_update_user_email11 = data => {
return http2.get(`/v1/user/info/ask_update_user_email?email=${data.email}`);
return http2.get(`/acs/v1/user/info/ask_update_user_email?email=${data.email}`);
};
/**email验证码 */
export const ask_update_user_email = data => {
return http2.post("/v1/user/info/ask_update_user_email", data, {
return http2.post("/acs/v1/user/info/ask_update_user_email", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -13,7 +13,7 @@ export const ask_update_user_email = data => {
};
/**email修改 */
export const update_user_email = data => {
return http2.post("/v1/user/info/update_user_email", data, {
return http2.post("/acs/v1/user/info/update_user_email", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -21,7 +21,7 @@ export const update_user_email = data => {
};
/**phone验证码 */
export const ask_update_user_phone = data => {
return http2.post("/v1/user/info/ask_update_user_phone", data, {
return http2.post("/acs/v1/user/info/ask_update_user_phone", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -29,7 +29,7 @@ export const ask_update_user_phone = data => {
};
/**phone修改 */
export const update_user_phone = data => {
return http2.post("/v1/user/info/update_user_phone", data, {
return http2.post("/acs/v1/user/info/update_user_phone", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -37,7 +37,7 @@ export const update_user_phone = data => {
};
/**密码修改 */
export const update_user_password = data => {
return http2.post("/v1/user/info/update_user_password", data, {
return http2.post("/acs/v1/user/info/update_user_password", data, {
headers: {
"Content-Type": "multipart/form-data"
}
+67 -67
View File
@@ -4,12 +4,12 @@ import {http2} from "@/utils/request.js";
// 获取图像验证码
export const Captch = data => {
return http2.get(`/v1/user/check/get_code_img`);
return http2.get(`/acs/v1/user/check/get_code_img`);
};
/** 登录 */
export const getLogin = data => {
return http2.post("/v1/user/login", data, {
return http2.post("/acs/v1/user/login", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -24,12 +24,12 @@ export const getLogin = data => {
/**获取用户列表 */
export const getUserList = data => {
return http2.get(
`/v1/admin/users/get_user_list?page=${data.page}&count=${data.count}&key=${data.key}`
`/acs/v1/admin/users/get_user_list?page=${data.page}&count=${data.count}&key=${data.key}`
);
};
/**添加用户 */
export const addUser = data => {
return http2.post("/v1/admin/users/add_user", data, {
return http2.post("/acs/v1/admin/users/add_user", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -37,7 +37,7 @@ export const addUser = data => {
};
/**编辑用户信息 */
export const editUser = data => {
return http2.post("/v1/admin/users/update_user", data, {
return http2.post("/acs/v1/admin/users/update_user", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -45,7 +45,7 @@ export const editUser = data => {
};
/**修改用户密码 */
export const editPassword = data => {
return http2.post("/v1/admin/users/update_user_password", data, {
return http2.post("/acs/v1/admin/users/update_user_password", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -53,7 +53,7 @@ export const editPassword = data => {
};
/**删除用户 */
export const deleteUser = data => {
return http2.post("/v1/admin/users/del_user", data, {
return http2.post("/acs/v1/admin/users/del_user", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -61,7 +61,7 @@ export const deleteUser = data => {
};
/**查询单个用户 */
export const userDetail = data => {
return http2.post("/v1/admin/users/select_user", data, {
return http2.post("/acs/v1/admin/users/select_user", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -69,7 +69,7 @@ export const userDetail = data => {
};
/**修改用户余额 */
export const editBalance = data => {
return http2.post("/v1/admin/users/update_user_balance", data, {
return http2.post("/acs/v1/admin/users/update_user_balance", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -79,12 +79,12 @@ export const editBalance = data => {
export const getUserServer = (data = {}) => {
const serverType = data.server_type || "dockerContainer"; // 设置默认值
return http2.get(
`/v1/user/procedure/get_server_list?server_type=${serverType}`
`/acs/v1/user/procedure/get_server_list?server_type=${serverType}`
);
};
/**用户获取虚拟机列表 */
export const getVirtualList = data => {
let url = `/v1/user/instance/list?page=${data.page}&count=${data.count}`;
let url = `/acs/v1/user/instance/list?page=${data.page}&count=${data.count}`;
if (data.key) {
url += `&key=${data.key}`;
}
@@ -95,35 +95,35 @@ export const getVirtualList = data => {
};
/**用户获取服务器套餐 */
export const getUserPackage = data => {
return http2.get(`/v1/user/procedure/get_server_plan_list?server_id=${data}`);
return http2.get(`/acs/v1/user/procedure/get_server_plan_list?server_id=${data}`);
};
/**获取用户容器列表 */
export const getUserContainer = data => {
return http2.get(
`/v1/user/container/list?page=${data.page}&count=${data.count}`
`/acs/v1/user/container/list?page=${data.page}&count=${data.count}`
);
};
/**用户按地区获取容器 */
export const getUserContainerD = data => {
return http2.get(
`/v1/user/container/list?page=${data.page}&count=${data.count}&server_id=${data.server_id}`
`/acs/v1/user/container/list?page=${data.page}&count=${data.count}&server_id=${data.server_id}`
);
};
/**获取用户操作日志 */
export const get_user_log = () => {
return http2.get(`/v1/user/procedure/get_user_log`);
return http2.get(`/acs/v1/user/procedure/get_user_log`);
};
/**获取用户自身信息 */
export const getUserInfo = () => {
return http2.get(`/v1/user/procedure/get_user_info`);
return http2.get(`/acs/v1/user/procedure/get_user_info`);
};
/**获取用户自身信息 */
export const getUserInfoV1 = () => {
return http2.get(`/v1/user/info/get_user_info`);
return http2.get(`/acs/v1/user/info/get_user_info`);
};
/**用户实名 */
export const realName = data => {
return http2.post("/v1/external/real_name", data, {
return http2.post("/acs/v1/external/real_name", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -131,7 +131,7 @@ export const realName = data => {
};
/**发送手机验证码 */
export const sendCode = data => {
return http2.post("/v1/external/send_message", data, {
return http2.post("/acs/v1/external/send_message", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -139,7 +139,7 @@ export const sendCode = data => {
};
/**发送邮箱验证码 */
export const sendEmailCode = data => {
return http2.post("/v1/external/send_email", data, {
return http2.post("/acs/v1/external/send_email", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -147,7 +147,7 @@ export const sendEmailCode = data => {
};
/**用户注册 */
export const register = data => {
return http2.post("/v1/user/register", data, {
return http2.post("/acs/v1/user/register", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -155,7 +155,7 @@ export const register = data => {
};
/**手机号修改校证码 */
export const CodePhone = data => {
return http2.post("/v1/user/info/ask_update_user_phone", data, {
return http2.post("/acs/v1/user/info/ask_update_user_phone", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -163,7 +163,7 @@ export const CodePhone = data => {
};
/**修改手机号码 */
export const SetPhone = data => {
return http2.post("/v1/user/info/update_user_phone", data, {
return http2.post("/acs/v1/user/info/update_user_phone", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -171,7 +171,7 @@ export const SetPhone = data => {
};
/**邮箱修改校证码 */
export const CodeEmail = data => {
return http2.post("/v1/user/info/ask_update_user_email", data, {
return http2.post("/acs/v1/user/info/ask_update_user_email", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -179,7 +179,7 @@ export const CodeEmail = data => {
};
/**修改邮箱 */
export const SetEmail = data => {
return http2.post("/v1/user/info/update_user_email", data, {
return http2.post("/acs/v1/user/info/update_user_email", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -187,7 +187,7 @@ export const SetEmail = data => {
};
/**上传头像 */
export const uploadAvatar = data => {
return http2.post("/v1/user/info/upload_user_avatar", data, {
return http2.post("/acs/v1/user/info/upload_user_avatar", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -195,7 +195,7 @@ export const uploadAvatar = data => {
};
/**手机号忘记密码 */
export const forgetphone = data => {
return http2.post("/v1/user/info/forget_user_password_phone", data, {
return http2.post("/acs/v1/user/info/forget_user_password_phone", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -204,7 +204,7 @@ export const forgetphone = data => {
/**邮箱忘记密码 */
export const forgetemail = data => {
return http2.post("/v1/user/info/forget_user_password_email", data, {
return http2.post("/acs/v1/user/info/forget_user_password_email", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -212,7 +212,7 @@ export const forgetemail = data => {
};
/**管理员全局搜索 */
export const Find = data => {
return http2.post("/v1/admin/search", data, {
return http2.post("/acs/v1/admin/search", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -220,7 +220,7 @@ export const Find = data => {
};
// 管理员删除容器网络
export const delContainer = data => {
return http2.post("/v1/user/container/delete_connect", data, {
return http2.post("/acs/v1/user/container/delete_connect", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -228,7 +228,7 @@ export const delContainer = data => {
};
// 删除端口
export const delPort = data => {
return http2.post("/v1/admin/instance_port/delete", data, {
return http2.post("/acs/v1/admin/instance_port/delete", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -236,7 +236,7 @@ export const delPort = data => {
};
// 自定义容器价格
export const Containerpay = data => {
return http2.post("/v1/admin/container/update_container_price", data, {
return http2.post("/acs/v1/admin/container/update_container_price", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -244,7 +244,7 @@ export const Containerpay = data => {
};
// 修改虚拟机续费价格
export const Containerpaytime = (data, id) => {
return http2.post(`/v1/admin/instance/update_price/${id}`, data, {
return http2.post(`/acs/v1/admin/instance/update_price/${id}`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -252,7 +252,7 @@ export const Containerpaytime = (data, id) => {
};
// 自定义容器到期时间
export const Containertime = data => {
return http2.post("/v1/admin/container/update_container_expire_time", data, {
return http2.post("/acs/v1/admin/container/update_container_expire_time", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -260,7 +260,7 @@ export const Containertime = data => {
};
// 修改虚拟机续到期时间
export const Containertimetime = (data, id) => {
return http2.post(`/v1/admin/instance/update_expire_time/${id}`, data, {
return http2.post(`/acs/v1/admin/instance/update_expire_time/${id}`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -268,7 +268,7 @@ export const Containertimetime = (data, id) => {
};
// 修改虚拟机信息
export const editContainer = (data, id) => {
return http2.post(`/v1/admin/instance/update/${id}`, data, {
return http2.post(`/acs/v1/admin/instance/update/${id}`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -280,7 +280,7 @@ export const editContainer = (data, id) => {
/** 容器操作 */
export const startUserContainer = (type, id) => {
return http2.post(
"/v1/user/container/" + type,
"/acs/v1/user/container/" + type,
{
container_id: id
},
@@ -293,7 +293,7 @@ export const startUserContainer = (type, id) => {
};
/**用户容器退款 */
export const backUserContainer = data => {
return http2.post("/v1/user/container/delete", data, {
return http2.post("/acs/v1/user/container/delete", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -301,7 +301,7 @@ export const backUserContainer = data => {
};
/**重装容器 */
export const reinContainer = data => {
return http2.post("/v1/user/container/reinstall", data, {
return http2.post("/acs/v1/user/container/reinstall", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -309,7 +309,7 @@ export const reinContainer = data => {
};
/**重装虚拟机 */
export const reinVmContainer = (id, data) => {
return http2.post(`/v1/user/instance/reinstall/${id}`, data, {
return http2.post(`/acs/v1/user/instance/reinstall/${id}`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -318,7 +318,7 @@ export const reinVmContainer = (id, data) => {
/** 容器操作 */
export const startAdminContainer = (type, id) => {
return http2.post(
"/v1/admin/container/" + type,
"/acs/v1/admin/container/" + type,
{
container_id: id
},
@@ -331,7 +331,7 @@ export const startAdminContainer = (type, id) => {
};
/** 容器操作 */
export const procedureUpdateContainerRenew = data => {
return http2.post("/v1/user/procedure/update_container_renew", data, {
return http2.post("/acs/v1/user/procedure/update_container_renew", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -339,15 +339,15 @@ export const procedureUpdateContainerRenew = data => {
};
/**获取容器完整信息 */
export const getContainerDetail = id => {
return http2.get(`/v1/user/container/detail?container_id=${id}`);
return http2.get(`/acs/v1/user/container/detail?container_id=${id}`);
};
/**获取虚拟机完整信息 */
export const getVmContainerDetail = id => {
return http2.get(`/v1/user/instance/detail/${id}`);
return http2.get(`/acs/v1/user/instance/detail/${id}`);
};
/**容器操作信息 */
export const containerLog = data => {
return http2.post("/v1/user/container/logs", data, {
return http2.post("/acs/v1/user/container/logs", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -356,12 +356,12 @@ export const containerLog = data => {
/**虚拟机操作日志 */
export const vmLog = data => {
return http2.get(
`/v1/user/instance/log/${data.id}?page=${data.page}&count=${data.count}`
`/acs/v1/user/instance/log/${data.id}?page=${data.page}&count=${data.count}`
);
};
/**获取容器状态 */
export const getContainerStatus = data => {
return http2.post(`/v1/user/container/status`, data, {
return http2.post(`/acs/v1/user/container/status`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -369,11 +369,11 @@ export const getContainerStatus = data => {
};
/**获取虚拟机状态 */
export const getVmStatus = id => {
return http2.get(`/v1/user/instance/get_state/${id}`);
return http2.get(`/acs/v1/user/instance/get_state/${id}`);
};
/**获取容器运行日志 */
export const getContainerLog = data => {
return http2.post(`/v1/user/container/run_logs`, data, {
return http2.post(`/acs/v1/user/container/run_logs`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -381,7 +381,7 @@ export const getContainerLog = data => {
};
/**获取容器购买网络订单 */
export const getContainerList = data => {
return http2.post(`/v1/user/procedure/add_network`, data, {
return http2.post(`/acs/v1/user/procedure/add_network`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -389,7 +389,7 @@ export const getContainerList = data => {
};
/**计算容器网络价格 */
export const getContainerPrice = data => {
return http2.post(`/v1/user/procedure/get_price_network`, data, {
return http2.post(`/acs/v1/user/procedure/get_price_network`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -397,33 +397,33 @@ export const getContainerPrice = data => {
};
/** 启动虚拟机 */
export const start_vm = id => {
return http2.get(`/v1/user/instance/start/${id}`);
return http2.get(`/acs/v1/user/instance/start/${id}`);
};
/** 停止虚拟机(关机) */
export const stop_vm = id => {
return http2.get(`/v1/user/instance/stop/${id}`);
return http2.get(`/acs/v1/user/instance/stop/${id}`);
};
/**重启虚拟机 */
export const restart_vm = id => {
return http2.get(`/v1/user/instance/reboot/${id}`);
return http2.get(`/acs/v1/user/instance/reboot/${id}`);
};
/**获取虚拟机控制台 */
export const get_vm_console = id => {
return http2.get(`/v1/user/instance/console/${id}`);
return http2.get(`/acs/v1/user/instance/console/${id}`);
};
/**进入救援系统 */
export const rescue_vm = id => {
return http2.get(`/v1/user/instance/rescue/enter/${id}`);
return http2.get(`/acs/v1/user/instance/rescue/enter/${id}`);
};
/**退出救援系统 */
export const unrescue_vm = id => {
return http2.get(`/v1/user/instance/rescue/exit/${id}`);
return http2.get(`/acs/v1/user/instance/rescue/exit/${id}`);
};
// ******************************* new
/** 提交充值订单 */
export const user_update_container_recharge = data => {
return http2.post("/v1/user/procedure/update_container_recharge", data, {
return http2.post("/acs/v1/user/procedure/update_container_recharge", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -431,7 +431,7 @@ export const user_update_container_recharge = data => {
};
/** 提交容器订单 */
export const user_update_plan_info = data => {
return http2.post("/v1/user/procedure/update_plan_info", data, {
return http2.post("/acs/v1/user/procedure/update_plan_info", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -439,7 +439,7 @@ export const user_update_plan_info = data => {
};
/** 提交虚拟机订单 */
export const user_update_vm_info = data => {
return http2.post("/v1/user/procedure/create_vm_trade", data, {
return http2.post("/acs/v1/user/procedure/create_vm_trade", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -447,11 +447,11 @@ export const user_update_vm_info = data => {
};
/**获取订单简略信息 */
export const getOrderDetail = id => {
return http2.get(`/v1/user/procedure/get_low_trade_info?trade_id=${id}`);
return http2.get(`/acs/v1/user/procedure/get_low_trade_info?trade_id=${id}`);
};
/**支付请求 */
export const pay_request = data => {
return http2.post("/v1/external/pay", data, {
return http2.post("/acs/v1/external/pay", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -459,7 +459,7 @@ export const pay_request = data => {
};
/**用户删除容器网络 */
export const deleteConNet = data => {
return http2.post("/v1/user/container/delete_connect", data, {
return http2.post("/acs/v1/user/container/delete_connect", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -468,7 +468,7 @@ export const deleteConNet = data => {
// 添加https
export const additionHttp = data => {
return http2.post("/v1/user/container/add_https_connet", data, {
return http2.post("/acs/v1/user/container/add_https_connet", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -477,7 +477,7 @@ export const additionHttp = data => {
// 删除https
export const DelHttp = data => {
return http2.post("/v1/user/container/del_https_connet", data, {
return http2.post("/acs/v1/user/container/del_https_connet", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -485,7 +485,7 @@ export const DelHttp = data => {
};
/**获取新增虚拟机端口价格 */
export const getVmPortPrice = data => {
return http2.post("/v1/user/procedure/get_price_instance_port", data, {
return http2.post("/acs/v1/user/procedure/get_price_instance_port", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -493,7 +493,7 @@ export const getVmPortPrice = data => {
};
/**提交新增虚拟机端口订单 */
export const addVmPort = data => {
return http2.post("/v1/user/procedure/add_instance_port", data, {
return http2.post("/acs/v1/user/procedure/add_instance_port", data, {
headers: {
"Content-Type": "multipart/form-data"
}
+22 -22
View File
@@ -3,7 +3,7 @@ import {http2} from "@/utils/request.js";
/**获取虚拟机列表 */
export const getVirtualList = data => {
let url = `/v1/admin/instance/list?page=${data.page}&count=${data.count}`;
let url = `/acs/v1/admin/instance/list?page=${data.page}&count=${data.count}`;
if (data.key) {
url += `&key=${data.key}`;
}
@@ -18,12 +18,12 @@ export const getVirtualList = data => {
/**新增虚拟机 */
export const addVirtual = data => {
return http2.post("/v1/admin/instance/create_vm", data);
return http2.post("/acs/v1/admin/instance/create_vm", data);
};
/**迁移数据卷 */
export const migrate_disk = data => {
return http2.post("/v1/admin/volume/migrate_volume", data, {
return http2.post("/acs/v1/admin/volume/migrate_volume", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -32,18 +32,18 @@ export const migrate_disk = data => {
/**获取虚拟机访问控制列表 */
export const getVirtualAccessList = data => {
let url = `/v1/admin/instance/access_control/list?page=${data.page}&count=${data.count}&instance_id=${data.instance_id}`;
let url = `/acs/v1/admin/instance/access_control/list?page=${data.page}&count=${data.count}&instance_id=${data.instance_id}`;
return http2.get(url);
};
/**获取虚拟机访问控制列表(用户) */
export const getUserAccessList = data => {
let url = `/v1/user/instance/access_control/list?page=${data.page}&count=${data.count}&instance_id=${data.instance_id}`;
let url = `/acs/v1/user/instance/access_control/list?page=${data.page}&count=${data.count}&instance_id=${data.instance_id}`;
return http2.get(url);
};
/**创建访问控制 */
export const createAccessControl = data => {
return http2.post("/v1/admin/instance/access_control/create", data, {
return http2.post("/acs/v1/admin/instance/access_control/create", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -51,7 +51,7 @@ export const createAccessControl = data => {
};
/**创建访问控制(用户) */
export const createUserAccessControl = data => {
return http2.post("/v1/user/instance/access_control/create", data, {
return http2.post("/acs/v1/user/instance/access_control/create", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -59,7 +59,7 @@ export const createUserAccessControl = data => {
};
/**删除访问控制 */
export const deleteAccessControl = data => {
return http2.post("/v1/admin/instance/access_control/delete", data, {
return http2.post("/acs/v1/admin/instance/access_control/delete", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -67,7 +67,7 @@ export const deleteAccessControl = data => {
};
/**删除访问控制(用户) */
export const deleteUserAccessControl = data => {
return http2.post("/v1/user/instance/access_control/delete", data, {
return http2.post("/acs/v1/user/instance/access_control/delete", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -76,17 +76,17 @@ export const deleteUserAccessControl = data => {
/**获取虚拟机快照列表 */
export const getSnapshotList = data => {
let url = `/v1/admin/instance/snapshot/list/${data.instance_id}?page=${data.page}&count=${data.count}`;
let url = `/acs/v1/admin/instance/snapshot/list/${data.instance_id}?page=${data.page}&count=${data.count}`;
return http2.get(url);
};
/**获取虚拟机快照列表(用户) */
export const getUserSnapshotList = data => {
let url = `/v1/user/instance/snapshot/list/${data.instance_id}?page=${data.page}&count=${data.count}`;
let url = `/acs/v1/user/instance/snapshot/list/${data.instance_id}?page=${data.page}&count=${data.count}`;
return http2.get(url);
};
/**创建虚拟机快照 */
export const createSnapshot = (data, id) => {
return http2.post(`/v1/admin/instance/snapshot/create/${id}`, data, {
return http2.post(`/acs/v1/admin/instance/snapshot/create/${id}`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -94,7 +94,7 @@ export const createSnapshot = (data, id) => {
};
/**创建虚拟机快照(用户) */
export const createUserSnapshot = (data, id) => {
return http2.post(`/v1/user/instance/snapshot/create/${id}`, data, {
return http2.post(`/acs/v1/user/instance/snapshot/create/${id}`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -103,7 +103,7 @@ export const createUserSnapshot = (data, id) => {
/**删除虚拟机快照 */
export const deleteSnapshot = (data, id) => {
return http2.post(`/v1/admin/instance/snapshot/delete/${id}`, data, {
return http2.post(`/acs/v1/admin/instance/snapshot/delete/${id}`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -112,7 +112,7 @@ export const deleteSnapshot = (data, id) => {
/**恢复虚拟机快照 */
export const recoverSnapshot = (data, id) => {
return http2.post(`/v1/admin/instance/snapshot/restore/${id}`, data, {
return http2.post(`/acs/v1/admin/instance/snapshot/restore/${id}`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -120,7 +120,7 @@ export const recoverSnapshot = (data, id) => {
};
/**恢复虚拟机快照(用户) */
export const recoverUserSnapshot = (data, id) => {
return http2.post(`/v1/user/instance/snapshot/restore/${id}`, data, {
return http2.post(`/acs/v1/user/instance/snapshot/restore/${id}`, data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -129,24 +129,24 @@ export const recoverUserSnapshot = (data, id) => {
/**获取实时监控 */
export const getVirtualLog = data => {
return http2.get(
`/v1/admin/instance/run_logs/${data.id}?start_time=${data.start_time}&end_time=${data.end_time}`
`/acs/v1/admin/instance/run_logs/${data.id}?start_time=${data.start_time}&end_time=${data.end_time}`
);
};
/**获取实时监控(用户) */
export const getUserVirtualLog = data => {
return http2.get(
`/v1/user/instance/run_logs/${data.id}?start_time=${data.start_time}&end_time=${data.end_time}`
`/acs/v1/user/instance/run_logs/${data.id}?start_time=${data.start_time}&end_time=${data.end_time}`
);
};
/**获取新增虚拟机快照数量价格 */
export const getSnapshotPrice = data => {
return http2.get(`/v1/user/procedure/get_price_snapshot?num=${data}`);
return http2.get(`/acs/v1/user/procedure/get_price_snapshot?num=${data}`);
};
/**提交虚拟机购买快照订单 */
export const submitSnapshotOrder = data => {
return http2.post("/v1/user/procedure/update_container_snapshot", data, {
return http2.post("/acs/v1/user/procedure/update_container_snapshot", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -154,7 +154,7 @@ export const submitSnapshotOrder = data => {
};
// 获取购买虚拟机数据卷价格
export const getVolumePrice = data => {
return http2.post("/v1/user/procedure/get_price_volume", data, {
return http2.post("/acs/v1/user/procedure/get_price_volume", data, {
headers: {
"Content-Type": "multipart/form-data"
}
@@ -163,7 +163,7 @@ export const getVolumePrice = data => {
// 提交虚拟机数据卷订单
export const submitVolumeOrder = data => {
return http2.post("/v1/user/procedure/update_container_volume", data, {
return http2.post("/acs/v1/user/procedure/update_container_volume", data, {
headers: {
"Content-Type": "multipart/form-data"
}
+5 -4
View File
@@ -93,8 +93,8 @@ class Request {
}
// DELETE 请求
delete(url, config = {}) {
return this.instance.delete(url, config)
delete(url,data={}, config = {}) {
return this.instance.delete(url,data, config)
}
// PATCH 请求
@@ -112,7 +112,8 @@ const request = new Request({
}
})
export const mainUrl = baseUrl + "/acs"
export const mainUrl = baseUrl + '/acs'
export const baseURL = baseUrl
export const http2 = axios.create({
baseURL: baseUrl,
@@ -132,7 +133,7 @@ http2.interceptors.request.use(config => {
}
config.headers.Authorization = `Bearer ${token}`;
config.url = '/acs' + config.url
config.url = config.url
return config
})
+51 -1
View File
@@ -1,4 +1,54 @@
export const FileName = (data) =>{
let name = data.split("/").pop()
return name
}
}
export const formatTime = (time) => {
return new Date(time).toLocaleString()
}
export const formatDate = (dateStr) => {
if (!dateStr || dateStr === '0001-01-01T00:00:00Z' || dateStr === null) return '-'
const date = new Date(dateStr)
if (isNaN(date.getTime())) return '-'
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}`
}
/**
* 时间格式转 Unix 时间戳(毫秒级)
* @param {string|Date} time - 输入时间(支持 '2025-10-28 00:00:00'、'2025/10/28'、Date 对象等)
* @returns {number|null} 转换后的毫秒级时间戳(失败返回 null)
*/
export function timeToTimestamp(time) {
let date;
// 处理字符串格式(如 '2025-10-28 00:00:00' 或 '2025/10/28'
if (typeof time === 'string') {
// 替换 '-' 为 '/'(避免 Safari 等浏览器对 '-' 格式解析失败)
const formattedTime = time.replace(/-/g, '/');
date = new Date(formattedTime);
}
// 处理 Date 对象
else if (time instanceof Date) {
date = time;
}
// 无效输入
else {
console.error('无效的时间格式,支持字符串(如 "2025-10-28 00:00:00")或 Date 对象');
return null;
}
// 验证时间是否有效
const timestamp = date.getTime();
if (isNaN(timestamp)) {
console.error(`无法解析时间:${time}`);
return null;
}
return Math.floor(timestamp / 1000); // 返回毫秒级时间戳(如 1751107200000
}
+1
View File
@@ -108,6 +108,7 @@ const handleLogin = () => {
if (valid) {
loading.value = true
let resp = await userLogin(loginForm.username, loginForm.password)
console.log("login:",resp)
loading.value = false
if(resp.code === 200){
+436
View File
@@ -0,0 +1,436 @@
<template>
<div class="signin-activity-container">
<!-- 操作栏 -->
<el-card class="filter-container" shadow="never">
<div class="action-bar">
<el-button type="primary" @click="handleAddReward">
<el-icon><Plus /></el-icon>新增签到奖励
</el-button>
<el-button type="success" @click="handleAddRewardType">
<el-icon><Plus /></el-icon>新增奖励类型
</el-button>
</div>
</el-card>
<!-- 签到奖励列表 -->
<el-card class="table-container" shadow="never">
<template #header>
<div class="card-header">
<span>签到奖励配置</span>
</div>
</template>
<el-table
v-loading="loading"
:data="rewardList"
style="width: 100%"
>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="day" label="签到天数" width="120" />
<el-table-column prop="reward_type" label="奖励类型" width="150">
<template #default="{ row }">
<el-tag :type="getRewardTypeColor(row.reward_type)">
{{ getRewardTypeText(row.reward_type) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="reward_value" label="奖励值" width="120">
<template #default="{ row }">
<span v-if="row.reward_type === 'balance'">¥{{ row.reward_value }}</span>
<span v-else>{{ row.reward_value }}</span>
</template>
</el-table-column>
<el-table-column prop="description" label="描述" min-width="200" />
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
{{ row.status === 1 ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="create_time" label="创建时间" width="180" />
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 奖励类型列表 -->
<el-card class="table-container" shadow="never" style="margin-top: 20px;">
<template #header>
<div class="card-header">
<span>奖励类型配置</span>
</div>
</template>
<el-table
v-loading="typeLoading"
:data="typeList"
style="width: 100%"
>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="type_code" label="类型代码" width="150" />
<el-table-column prop="type_name" label="类型名称" min-width="200" />
<el-table-column prop="description" label="描述" min-width="250" />
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
{{ row.status === 1 ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="create_time" label="创建时间" width="180" />
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEditType(row)">编辑</el-button>
<el-button type="danger" link @click="handleDeleteType(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 签到奖励表单对话框 -->
<el-dialog
v-model="rewardDialogVisible"
title="签到奖励配置"
width="600px"
>
<el-form
ref="rewardFormRef"
:model="rewardForm"
:rules="rewardRules"
label-width="120px"
>
<el-form-item label="签到天数" prop="day">
<el-input-number v-model="rewardForm.day" :min="1" placeholder="请输入签到天数" style="width: 100%" />
</el-form-item>
<el-form-item label="奖励类型" prop="reward_type">
<el-select v-model="rewardForm.reward_type" placeholder="请选择奖励类型" style="width: 100%">
<el-option
v-for="item in typeOptions"
:key="item.type_code"
:label="item.type_name"
:value="item.type_code"
/>
</el-select>
</el-form-item>
<el-form-item label="奖励值" prop="reward_value">
<el-input v-model.number="rewardForm.reward_value" placeholder="请输入奖励值" />
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="rewardForm.description" type="textarea" :rows="3" placeholder="请输入描述" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="rewardForm.status">
<el-radio :label="1">启用</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="rewardDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitRewardForm">确定</el-button>
</template>
</el-dialog>
<!-- 奖励类型表单对话框 -->
<el-dialog
v-model="typeDialogVisible"
title="奖励类型配置"
width="500px"
>
<el-form
ref="typeFormRef"
:model="typeForm"
:rules="typeRules"
label-width="120px"
>
<el-form-item label="类型代码" prop="type_code">
<el-input v-model="typeForm.type_code" placeholder="请输入类型代码" />
</el-form-item>
<el-form-item label="类型名称" prop="type_name">
<el-input v-model="typeForm.type_name" placeholder="请输入类型名称" />
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="typeForm.description" type="textarea" :rows="3" placeholder="请输入描述" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="typeForm.status">
<el-radio :label="1">启用</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="typeDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitTypeForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { addSignReward, addSignRewardType } from '@/api/admin/activity'
// 签到奖励表单
const rewardForm = reactive({
id: undefined,
day: 1,
reward_type: '',
reward_value: 0,
description: '',
status: 1
})
const rewardRules = {
day: [
{ required: true, message: '请输入签到天数', trigger: 'blur' }
],
reward_type: [
{ required: true, message: '请选择奖励类型', trigger: 'change' }
],
reward_value: [
{ required: true, message: '请输入奖励值', trigger: 'blur' }
]
}
// 奖励类型表单
const typeForm = reactive({
id: undefined,
type_code: '',
type_name: '',
description: '',
status: 1
})
const typeRules = {
type_code: [
{ required: true, message: '请输入类型代码', trigger: 'blur' }
],
type_name: [
{ required: true, message: '请输入类型名称', trigger: 'blur' }
]
}
// 状态数据
const loading = ref(false)
const typeLoading = ref(false)
const rewardList = ref([])
const typeList = ref([])
const typeOptions = ref([])
const rewardDialogVisible = ref(false)
const typeDialogVisible = ref(false)
const rewardFormRef = ref(null)
const typeFormRef = ref(null)
// 获取奖励类型文本
const getRewardTypeText = (type) => {
const typeMap = {
'balance': '余额',
'points': '积分',
'coupon': '优惠券',
'voucher': '代金券'
}
return typeMap[type] || type
}
// 获取奖励类型颜色
const getRewardTypeColor = (type) => {
const colorMap = {
'balance': 'success',
'points': 'primary',
'coupon': 'warning',
'voucher': 'info'
}
return colorMap[type] || 'info'
}
// 新增签到奖励
const handleAddReward = () => {
rewardDialogVisible.value = true
Object.assign(rewardForm, {
id: undefined,
day: 1,
reward_type: '',
reward_value: 0,
description: '',
status: 1
})
rewardFormRef.value?.resetFields()
}
// 新增奖励类型
const handleAddRewardType = () => {
typeDialogVisible.value = true
Object.assign(typeForm, {
id: undefined,
type_code: '',
type_name: '',
description: '',
status: 1
})
typeFormRef.value?.resetFields()
}
// 编辑签到奖励
const handleEdit = (row) => {
Object.assign(rewardForm, {
id: row.id,
day: row.day,
reward_type: row.reward_type,
reward_value: row.reward_value,
description: row.description,
status: row.status
})
rewardDialogVisible.value = true
}
// 编辑奖励类型
const handleEditType = (row) => {
Object.assign(typeForm, {
id: row.id,
type_code: row.type_code,
type_name: row.type_name,
description: row.description,
status: row.status
})
typeDialogVisible.value = true
}
// 删除签到奖励
const handleDelete = (row) => {
ElMessageBox.confirm(`确认删除第${row.day}天的签到奖励吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
ElMessage.success('删除成功')
// 这里应该调用删除API
}).catch(() => {})
}
// 删除奖励类型
const handleDeleteType = (row) => {
ElMessageBox.confirm(`确认删除奖励类型 ${row.type_name} 吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
ElMessage.success('删除成功')
// 这里应该调用删除API
}).catch(() => {})
}
// 提交签到奖励表单
const submitRewardForm = () => {
rewardFormRef.value?.validate(async (valid) => {
if (valid) {
try {
const res = await addSignReward(rewardForm)
if (res.code === 200) {
ElMessage.success('添加成功')
rewardDialogVisible.value = false
// 刷新列表
}
} catch (error) {
ElMessage.error('添加失败')
}
}
})
}
// 提交奖励类型表单
const submitTypeForm = () => {
typeFormRef.value?.validate(async (valid) => {
if (valid) {
try {
const res = await addSignRewardType(typeForm)
if (res.code === 200) {
ElMessage.success('添加成功')
typeDialogVisible.value = false
// 刷新列表
}
} catch (error) {
ElMessage.error('添加失败')
}
}
})
}
// 初始化
onMounted(() => {
// 模拟数据
rewardList.value = [
{
id: 1,
day: 1,
reward_type: 'balance',
reward_value: 1,
description: '连续签到1天奖励1元余额',
status: 1,
create_time: '2024-01-01 10:00:00'
},
{
id: 2,
day: 7,
reward_type: 'balance',
reward_value: 10,
description: '连续签到7天奖励10元余额',
status: 1,
create_time: '2024-01-01 10:00:00'
}
]
typeList.value = [
{
id: 1,
type_code: 'balance',
type_name: '余额',
description: '用户账户余额',
status: 1,
create_time: '2024-01-01 10:00:00'
},
{
id: 2,
type_code: 'points',
type_name: '积分',
description: '用户积分',
status: 1,
create_time: '2024-01-01 10:00:00'
}
]
typeOptions.value = typeList.value
})
</script>
<style scoped>
.signin-activity-container {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
}
.action-bar {
display: flex;
gap: 12px;
}
.table-container {
border-radius: 8px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
+533
View File
@@ -0,0 +1,533 @@
<template>
<div class="discount-code-container">
<!-- 搜索和操作栏 -->
<el-card class="filter-container" shadow="never">
<div class="action-bar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增优惠码
</el-button>
<el-button type="success" @click="fetchDiscountList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
<el-icon><Delete /></el-icon>批量删除
</el-button>
</div>
</el-card>
<!-- 优惠码列表 -->
<el-card class="table-container" shadow="never">
<el-table
v-loading="loading"
:data="discountList"
@selection-change="handleSelectionChange"
style="width: 100%"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="code" label="优惠码" min-width="150" />
<el-table-column prop="name" label="名称" min-width="180" />
<el-table-column label="优惠类型" width="120">
<template #default="{ row }">
<el-tag :type="row.percentage ? 'success' : 'primary'">
{{ row.percentage ? '百分比折扣' : '固定金额' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="优惠值" width="120">
<template #default="{ row }">
<span v-if="row.percentage" class="discount-value">{{ (row.percentage / 100).toFixed(0) }}%</span>
<span v-else class="amount">¥{{ (row.amount / 100).toFixed(2) }}</span>
</template>
</el-table-column>
<el-table-column label="最低消费" width="120">
<template #default="{ row }">
¥{{ (row.minAmount / 100).toFixed(2) }}
</template>
</el-table-column>
<el-table-column label="最大抵扣" width="120">
<template #default="{ row }">
<span v-if="row.maxAmount">¥{{ (row.maxAmount / 100).toFixed(2) }}</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="maxTimes" label="最大使用次数" width="120" />
<el-table-column prop="userTimes" label="单用户次数" width="120" />
<el-table-column label="可叠加" width="100" align="center">
<template #default="{ row }">
<el-icon v-if="row.canStacking" color="#67c23a" :size="20"><SuccessFilled /></el-icon>
<el-icon v-else color="#f56c6c" :size="20"><CircleCloseFilled /></el-icon>
</template>
</el-table-column>
<el-table-column label="续费可用" width="100" align="center">
<template #default="{ row }">
<el-icon v-if="row.renew" color="#67c23a" :size="20"><SuccessFilled /></el-icon>
<el-icon v-else color="#f56c6c" :size="20"><CircleCloseFilled /></el-icon>
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="success" link @click="handleView(row)">查看</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</el-card>
<!-- 优惠码表单对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增优惠码' : '编辑优惠码'"
width="700px"
>
<el-form
ref="discountFormRef"
:model="discountForm"
:rules="discountRules"
label-width="140px"
>
<el-form-item label="优惠码" prop="code">
<el-input v-model="discountForm.code" placeholder="请输入优惠码" />
</el-form-item>
<el-form-item label="优惠码名称" prop="name">
<el-input v-model="discountForm.name" placeholder="请输入优惠码名称" />
</el-form-item>
<el-form-item label="备注" prop="note">
<el-input v-model="discountForm.note" type="textarea" :rows="2" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="优惠类型" prop="discount_mode">
<el-radio-group v-model="discountForm.discount_mode">
<el-radio label="amount">固定金额</el-radio>
<el-radio label="percentage">百分比折扣</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="discountForm.discount_mode === 'amount'" label="优惠金额(元)" prop="amount">
<el-input-number v-model="discountForm.amount" :min="0" :precision="2" :step="0.01" placeholder="请输入优惠金额" style="width: 100%" />
</el-form-item>
<el-form-item v-if="discountForm.discount_mode === 'percentage'" label="优惠百分比(%)" prop="percentage">
<el-input-number v-model="discountForm.percentage" :min="0" :max="100" :precision="0" placeholder="请输入百分比(1-100)" style="width: 100%" />
</el-form-item>
<el-form-item label="最低消费(元)" prop="min_amount">
<el-input-number v-model="discountForm.min_amount" :min="0" :precision="2" :step="0.01" placeholder="满多少可使用" style="width: 100%" />
</el-form-item>
<el-form-item label="最大抵扣(元)" prop="max_amount">
<el-input-number v-model="discountForm.max_amount" :min="0" :precision="2" :step="0.01" placeholder="0表示无限制" style="width: 100%" />
</el-form-item>
<el-form-item label="最大使用次数" prop="max_times">
<el-input-number v-model="discountForm.max_times" :min="0" placeholder="0表示无限制" style="width: 100%" />
</el-form-item>
<el-form-item label="单用户最大次数" prop="user_times">
<el-input-number v-model="discountForm.user_times" :min="0" placeholder="0表示无限制" style="width: 100%" />
</el-form-item>
<el-form-item label="有效期" prop="timeRange">
<el-date-picker
v-model="discountForm.timeRange"
type="datetimerange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
:teleported="true"
popper-class="discount-date-picker"
placement="top-start"
:editable="true"
:clearable="true"
style="width: 100%"
@keyup.enter="handleDatePickerEnter"
/>
</el-form-item>
<el-form-item label="续费可用" prop="renew">
<el-switch v-model="discountForm.renew" active-text="是" inactive-text="否" />
</el-form-item>
<el-form-item label="同类型可叠加" prop="can_stacking">
<el-switch v-model="discountForm.can_stacking" active-text="是" inactive-text="否" />
</el-form-item>
<el-form-item label="其他类型可叠加" prop="can_combine">
<el-switch v-model="discountForm.can_combine" active-text="是" inactive-text="否" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
<!-- 详情查看对话框 -->
<DiscountDetailDialog
v-model="detailDialogVisible"
type="code"
:detail-data="currentDetail"
/>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Delete, Search, Refresh, SuccessFilled, CircleCloseFilled } from '@element-plus/icons-vue'
import {
getDiscountCodeList,
getDiscountCodeDetail,
createDiscountCode,
updateDiscountCode,
deleteDiscountCode
} from '@/api/admin/discount'
import { timeToTimestamp } from '@/utils/tool'
import DiscountDetailDialog from '@/components/marketing/DiscountDetailDialog.vue'
// 查询参数
const queryParams = reactive({
discount_type: 'code', // 固定为code表示优惠码
page: 1,
count: 10
})
// 优惠码表单
const discountForm = reactive({
code_id: undefined,
discount_type: 'code', // 固定为code
code: '',
name: '',
note: '',
discount_mode: 'amount', // amount 或 percentage
amount: 0,
percentage: 0,
min_amount: 0,
max_amount: 0,
max_times: 0,
user_times: 0,
timeRange: [],
renew: false,
can_stacking: false,
can_combine: false
})
const discountRules = {
code: [
{ required: true, message: '请输入优惠码', trigger: 'blur' }
],
name: [
{ required: true, message: '请输入优惠码名称', trigger: 'blur' }
],
discount_mode: [
{ required: true, message: '请选择优惠类型', trigger: 'change' }
]
}
// 状态数据
const loading = ref(false)
const discountList = ref([])
const total = ref(0)
const selectedRows = ref([])
const dialogVisible = ref(false)
const dialogType = ref('add')
const discountFormRef = ref(null)
const detailDialogVisible = ref(false)
const currentDetail = ref(null)
// 获取优惠码列表
const fetchDiscountList = async () => {
loading.value = true
try {
const res = await getDiscountCodeList(queryParams)
console.log('优惠码列表数据:', res.data)
if (res.data.code === 200) {
discountList.value = res.data.data?.data || []
total.value = res.data.data?.all_count || 0
}
} catch (error) {
console.error('获取优惠码列表失败:', error)
ElMessage.error('获取优惠码列表失败')
} finally {
loading.value = false
}
}
// 选择项变化
const handleSelectionChange = (selection) => {
selectedRows.value = selection
}
// 分页
const handleSizeChange = (size) => {
queryParams.count = size
fetchDiscountList()
}
const handleCurrentChange = (page) => {
queryParams.page = page
fetchDiscountList()
}
// 新增优惠码
const handleAdd = () => {
dialogType.value = 'add'
dialogVisible.value = true
Object.assign(discountForm, {
code_id: undefined,
discount_type: 'code',
code: '',
name: '',
note: '',
discount_mode: 'amount',
amount: 0,
percentage: 0,
min_amount: 0,
max_amount: 0,
max_times: 0,
user_times: 0,
timeRange: [],
renew: false,
can_stacking: false,
can_combine: false
})
discountFormRef.value?.resetFields()
}
// 编辑优惠码
const handleEdit = (row) => {
dialogType.value = 'edit'
dialogVisible.value = true
// 转换日期字符串为日期选择器格式
const startTime = row.startTime ? new Date(row.startTime).toLocaleString('zh-CN', {year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit'}).replace(/\//g, '-') : ''
const endTime = row.endTime ? new Date(row.endTime).toLocaleString('zh-CN', {year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit'}).replace(/\//g, '-') : ''
Object.assign(discountForm, {
code_id: row.id,
discount_type: 'code',
code: row.code,
name: row.name,
note: row.note || '',
discount_mode: row.percentage ? 'percentage' : 'amount',
amount: row.amount ? row.amount / 100 : 0,
percentage: row.percentage ? row.percentage / 100 : 0,
min_amount: row.minAmount ? row.minAmount / 100 : 0,
max_amount: row.maxAmount ? row.maxAmount / 100 : 0,
max_times: row.maxTimes || 0,
user_times: row.userTimes || 0,
timeRange: startTime && endTime ? [startTime, endTime] : [],
renew: row.renew || false,
can_stacking: row.canStacking || false,
can_combine: row.canCombine || false
})
}
// 查看优惠码详情
const handleView = async (row) => {
try {
const res = await getDiscountCodeDetail({ code_id: row.id })
console.log('优惠码详情:', res.data)
if (res.data.code === 200) {
currentDetail.value = res.data.data
detailDialogVisible.value = true
}
} catch (error) {
console.error('获取优惠码详情失败:', error)
ElMessage.error('获取优惠码详情失败')
}
}
// 删除优惠码
const handleDelete = (row) => {
ElMessageBox.confirm(`确认删除优惠码 ${row.code} 吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deleteDiscountCode({ code_id: row.id })
console.log('删除响应:', res.data)
if (res.data.code === 200) {
ElMessage.success('删除成功')
fetchDiscountList()
}
} catch (error) {
console.error('删除失败:', error)
ElMessage.error(error.response?.data?.message || '删除失败')
}
}).catch(() => {})
}
// 批量删除
const handleBatchDelete = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请至少选择一条记录')
return
}
ElMessageBox.confirm(`确认删除选中的 ${selectedRows.value.length} 条记录吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
loading.value = true
try {
const deletePromises = selectedRows.value.map(row =>
deleteDiscountCode({ code_id: row.id })
)
const results = await Promise.allSettled(deletePromises)
const successCount = results.filter(r => r.status === 'fulfilled' && r.value?.data?.code === 200).length
const failCount = results.length - successCount
if (failCount === 0) {
ElMessage.success(`批量删除成功,共删除 ${successCount} 条记录`)
} else if (successCount === 0) {
ElMessage.error(`批量删除失败,所有 ${failCount} 条记录删除失败`)
} else {
ElMessage.warning(`批量删除完成,成功 ${successCount} 条,失败 ${failCount}`)
}
fetchDiscountList()
} catch (error) {
console.error('批量删除失败:', error)
ElMessage.error('批量删除操作异常')
} finally {
loading.value = false
}
}).catch(() => {})
}
// 处理日期选择器回车事件
const handleDatePickerEnter = (event) => {
// 回车键确认日期选择
const datePicker = event.target.closest('.el-date-editor')
if (datePicker) {
// 触发失焦事件,确认日期选择
event.target.blur()
}
}
// 提交表单
const submitForm = () => {
discountFormRef.value?.validate(async (valid) => {
if (valid) {
try {
const submitData = {
discount_type: 'code',
code: discountForm.code,
name: discountForm.name,
note: discountForm.note,
min_amount: Math.round(discountForm.min_amount * 100),
max_amount: Math.round(discountForm.max_amount * 100),
max_times: discountForm.max_times || 0,
user_times: discountForm.user_times || 0,
renew: discountForm.renew,
can_stacking: discountForm.can_stacking,
can_combine: discountForm.can_combine
}
// 根据优惠类型设置amount或percentage
if (discountForm.discount_mode === 'percentage') {
submitData.percentage = Math.round(discountForm.percentage * 100)
submitData.amount = 0
} else {
submitData.amount = Math.round(discountForm.amount * 100)
submitData.percentage = 0
}
// 处理时间(转换为秒级时间戳)
if (discountForm.timeRange && discountForm.timeRange.length === 2) {
submitData.start_time = timeToTimestamp(discountForm.timeRange[0])
submitData.end_time = timeToTimestamp(discountForm.timeRange[1])
} else {
submitData.start_time = ''
submitData.end_time = ''
}
// 如果是编辑,添加code_id
if (dialogType.value === 'edit') {
submitData.code_id = discountForm.code_id
}
console.log('提交优惠码数据:', submitData)
let res
if (dialogType.value === 'add') {
res = await createDiscountCode(submitData)
} else {
res = await updateDiscountCode(submitData)
}
console.log('提交响应:', res.data)
if (res.data.code === 200) {
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '修改成功')
dialogVisible.value = false
fetchDiscountList()
}
} catch (error) {
console.error('操作失败:', error)
ElMessage.error(error.response?.data?.message || '操作失败')
}
}
})
}
// 初始化
onMounted(() => {
fetchDiscountList()
})
</script>
<style scoped>
.discount-code-container {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
}
.action-bar {
display: flex;
gap: 12px;
}
.table-container {
border-radius: 8px;
}
.amount {
color: #f56c6c;
font-weight: bold;
font-size: 14px;
}
.discount-value {
color: #67c23a;
font-weight: bold;
font-size: 14px;
}
.pagination {
margin-top: 24px;
justify-content: flex-end;
}
</style>
<style>
/* 时间选择器弹出层样式 - 非 scoped */
.discount-date-picker {
z-index: 9999 !important;
}
.discount-date-picker .el-picker-panel {
max-width: 90vw;
}
</style>
+719
View File
@@ -0,0 +1,719 @@
<template>
<div class="discount-goods-container">
<!-- 搜索和操作栏 -->
<el-card class="filter-container" shadow="never">
<el-form :inline="true" :model="queryParams" class="search-form">
<el-form-item label="代金卷">
<el-select
v-model="queryParams.code_id"
placeholder="请选择代金券"
filterable
clearable
style="width: 280px"
>
<el-option
v-for="item in voucherListOptions"
:key="item.id"
:label="`${item.name} (¥${(item.amount / 100).toFixed(2)})`"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery" :disabled="!queryParams.code_id">
<el-icon><Search /></el-icon>查询
</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div class="action-bar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增商品关联
</el-button>
<el-button type="success" @click="fetchGoodsList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
<el-icon><Delete /></el-icon>批量删除
</el-button>
</div>
</el-card>
<!-- 商品关联列表 -->
<el-card class="table-container" shadow="never">
<el-table
v-loading="loading"
:data="goodsList"
@selection-change="handleSelectionChange"
style="width: 100%"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="discountId" label="代金券ID" width="120" />
<el-table-column label="关联对象ID" width="120">
<template #default="{ row }">
{{ row.goodId || row.goodGroupId || '-' }}
</template>
</el-table-column>
<el-table-column label="名称" min-width="200">
<template #default="{ row }">
{{ row.good?.name || row.goodGroup?.name || '-' }}
</template>
</el-table-column>
<el-table-column label="类型" width="120">
<template #default="{ row }">
<el-tag :type="getGoodsTypeTagByRow(row)">
{{ getGoodsTypeNameByRow(row) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="备注" min-width="150">
<template #default="{ row }">
{{ row.good?.table || row.goodGroup?.note || '-' }}
</template>
</el-table-column>
<el-table-column label="商品价格" width="120">
<template #default="{ row }">
<span v-if="row.good?.price" class="price">¥{{ (row.good.price / 100).toFixed(2) }}</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="创建时间" width="180">
<template #default="{ row }">
{{ formatDate(row.CreatedAt) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</el-card>
<!-- 添加/编辑商品关联对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增商品关联' : '编辑商品关联'"
width="600px"
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="代金券" prop="code_id">
<el-select
v-model="form.code_id"
placeholder="请选择代金券"
filterable
clearable
:disabled="dialogType === 'edit'"
style="width: 100%"
>
<el-option
v-for="item in voucherListOptions"
:key="item.id"
:label="`${item.name} (¥${(item.amount / 100).toFixed(2)})`"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="选择类型" prop="select_type" v-if="dialogType === 'add'">
<el-radio-group v-model="form.select_type" @change="handleSelectTypeChange">
<el-radio value="product">商品</el-radio>
<el-radio value="product_group">商品组</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="选择商品" prop="selected_product" v-if="dialogType === 'add' && form.select_type === 'product'">
<el-select
v-model="form.selected_product"
placeholder="请选择商品"
filterable
clearable
style="width: 100%"
@change="handleProductChange"
>
<el-option
v-for="item in productOptions"
:key="item.id"
:label="`${item.name} (ID: ${item.id})`"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="选择商品组" prop="selected_group" v-if="dialogType === 'add' && form.select_type === 'product_group'">
<el-select
v-model="form.selected_group"
placeholder="请选择商品组"
filterable
clearable
style="width: 100%"
@change="handleProductGroupChange"
>
<el-option
v-for="item in productGroupOptions"
:key="item.id"
:label="`${item.name} (ID: ${item.id})`"
:value="item.id"
/>
</el-select>
</el-form-item>
<!-- 编辑模式显示字段 -->
<template v-if="dialogType === 'edit'">
<el-form-item label="关联类型">
<el-tag :type="form.goods_type === 'product' ? 'primary' : 'warning'">
{{ form.goods_type === 'product' ? '商品' : '商品组' }}
</el-tag>
</el-form-item>
<el-form-item label="关联对象ID">
<el-select v-model="form.goods_id" style="width: 100%">
<template v-if="form.goods_type === 'product'">
<el-option v-for="item in productOptions" :key="item.id" :label="`${item.name} (ID: ${item.id})`" :value="item.id" />
</template>
<template v-else>
<el-option v-for="item in productGroupOptions" :key="item.id" :label="`${item.name} (ID: ${item.id})`" :value="item.id" />
</template>
</el-select>
</el-form-item>
</template>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Delete, Search, Plus, Refresh } from '@element-plus/icons-vue'
import {
getDiscountGoodsList,
addDiscountGoods,
updateDiscountGoods,
deleteDiscountGoods,
getDiscountCodeList
} from '@/api/admin/discount'
import {
getProductList,
getProductGroupList
} from '@/api/admin/product'
// 查询参数
const queryParams = reactive({
code_id: '',
page: 1,
count: 10
})
// 表单数据
const form = reactive({
id: undefined,
code_id: undefined,
goods_id: undefined,
goods_name: '',
goods_type: '',
select_type: 'product', // 选择类型:product 或 product_group
selected_product: undefined, // 选中的商品ID
selected_group: undefined // 选中的商品组ID
})
const formRules = {
code_id: [
{ required: true, message: '请选择代金券', trigger: 'change' }
],
select_type: [
{ required: true, message: '请选择类型', trigger: 'change' }
],
selected_product: [
{ required: true, message: '请选择商品', trigger: 'change' }
],
selected_group: [
{ required: true, message: '请选择商品组', trigger: 'change' }
],
goods_id: [
{ required: true, message: '请输入商品ID', trigger: 'blur' }
],
goods_name: [
{ required: true, message: '请输入商品名称', trigger: 'blur' }
],
goods_type: [
{ required: true, message: '请选择商品类型', trigger: 'change' }
]
}
// 状态数据
const loading = ref(false)
const goodsList = ref([])
const total = ref(0)
const selectedRows = ref([])
const dialogVisible = ref(false)
const dialogType = ref('add')
const formRef = ref(null)
const voucherListOptions = ref([]) // 代金券列表选项
const productOptions = ref([]) // 商品列表选项
const productGroupOptions = ref([]) // 商品组列表选项
// 格式化日期
const formatDate = (dateStr) => {
if (!dateStr) return '-'
const date = new Date(dateStr)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}`
}
// 获取商品类型名称(根据行数据)
const getGoodsTypeNameByRow = (row) => {
// 判断是否有 goodGroup 对象(选择的是商品组)
if (row.goodGroup) {
return '商品组'
}
// 判断是否有 good 对象(选择的是商品)
if (row.good) {
return '商品'
}
return '-'
}
// 获取商品类型标签(根据行数据)
const getGoodsTypeTagByRow = (row) => {
// 商品组用橙色
if (row.goodGroup) {
return 'warning'
}
// 商品用蓝色
if (row.good) {
return 'primary'
}
return 'info'
}
// 获取商品类型名称(兼容旧版)
const getGoodsTypeName = (type) => {
const typeMap = {
'product': '商品',
'product_group': '商品组',
'cloud_server': '云服务器',
'cloud_database': '云数据库',
'cloud_storage': '云存储',
'cdn': 'CDN',
'other': '其他'
}
return typeMap[type] || type
}
// 获取商品类型标签(兼容旧版)
const getGoodsTypeTag = (type) => {
const tagMap = {
'product': 'primary',
'product_group': 'warning',
'cloud_server': 'primary',
'cloud_database': 'success',
'cloud_storage': 'warning',
'cdn': 'info',
'other': 'default'
}
return tagMap[type] || 'default'
}
// 获取代金券列表选项
const fetchVoucherListOptions = async () => {
try {
const res = await getDiscountCodeList({
page: 1,
count: 1000,
discount_type: 'coupon'
})
console.log('获取代金券列表:', res.data)
if (res.data.code === 200) {
voucherListOptions.value = res.data.data?.data || []
}
} catch (error) {
console.error('获取代金券列表失败:', error)
ElMessage.error('获取代金券列表失败')
}
}
// 获取商品列表
const fetchProductList = async () => {
try {
const res = await getProductList({
page: 1,
count: 1000
})
console.log('获取商品列表:', res.data)
if (res.data.code === 200) {
productOptions.value = res.data.data?.data || []
}
} catch (error) {
console.error('获取商品列表失败:', error)
ElMessage.error('获取商品列表失败')
}
}
// 获取商品组列表
const fetchProductGroupList = async () => {
try {
const res = await getProductGroupList({
page: 1,
count: 1000
})
console.log('获取商品组列表:', res.data)
if (res.data.code === 200) {
productGroupOptions.value = res.data.data?.data || []
}
} catch (error) {
console.error('获取商品组列表失败:', error)
ElMessage.error('获取商品组列表失败')
}
}
// 选择类型变化
const handleSelectTypeChange = (type) => {
form.selected_product = undefined
form.selected_group = undefined
form.goods_id = undefined
form.goods_name = ''
form.goods_type = ''
}
// 选择商品变化
const handleProductChange = (productId) => {
const product = productOptions.value.find(item => item.id === productId)
if (product) {
form.goods_id = product.id
form.goods_name = product.goodsName || product.name || ''
form.goods_type = 'product'
}
}
// 选择商品组变化
const handleProductGroupChange = (groupId) => {
const group = productGroupOptions.value.find(item => item.id === groupId)
if (group) {
form.goods_id = group.id
form.goods_name = group.name || ''
form.goods_type = 'product_group'
}
}
// 获取商品关联列表
const fetchGoodsList = async () => {
if (!queryParams.code_id) {
ElMessage.warning('请选择代金券进行查询')
return
}
loading.value = true
try {
const res = await getDiscountGoodsList(queryParams)
console.log('商品关联列表数据:', res.data)
if (res.data.code === 200) {
goodsList.value = res.data.data || []
total.value = res.data.data?.length || 0
}
} catch (error) {
console.error('获取商品关联列表失败:', error)
ElMessage.error('获取商品关联列表失败')
} finally {
loading.value = false
}
}
// 查询
const handleQuery = () => {
queryParams.page = 1
fetchGoodsList()
}
// 重置查询
const resetQuery = () => {
queryParams.code_id = ''
queryParams.page = 1
goodsList.value = []
total.value = 0
}
// 选择项变化
const handleSelectionChange = (selection) => {
selectedRows.value = selection
}
// 分页
const handleSizeChange = (size) => {
queryParams.count = size
fetchGoodsList()
}
const handleCurrentChange = (page) => {
queryParams.page = page
fetchGoodsList()
}
// 新增商品关联
const handleAdd = () => {
dialogType.value = 'add'
dialogVisible.value = true
Object.assign(form, {
id: undefined,
code_id: queryParams.code_id ? Number(queryParams.code_id) : undefined,
goods_id: undefined,
goods_name: '',
goods_type: '',
select_type: 'product',
selected_product: undefined,
selected_group: undefined
})
formRef.value?.resetFields()
// 加载商品和商品组列表
fetchProductList()
fetchProductGroupList()
}
// 编辑商品关联
const handleEdit = (row) => {
dialogType.value = 'edit'
dialogVisible.value = true
// 判断是商品还是商品组
let goodsId, goodsName, goodsType
if (row.goodGroup) {
// 商品组
goodsId = row.goodGroupId
goodsName = row.goodGroup.name
goodsType = 'product_group'
} else if (row.good) {
// 商品
goodsId = row.goodId
goodsName = row.good.name
goodsType = 'product'
}
Object.assign(form, {
id: row.id,
code_id: row.discountId,
goods_id: goodsId,
goods_name: goodsName,
goods_type: goodsType
})
// 加载商品和商品组列表以便编辑
fetchProductList()
fetchProductGroupList()
}
// 删除商品关联
const handleDelete = (row) => {
ElMessageBox.confirm(`确认删除该商品关联吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deleteDiscountGoods({
discount_good_id: String(row.id),
code_id: String(row.discountId)
})
console.log('删除响应:', res.data)
if (res.data.code === 200) {
ElMessage.success('删除成功')
fetchGoodsList()
}
} catch (error) {
console.error('删除失败:', error)
ElMessage.error(error.response?.data?.message || '删除失败')
}
}).catch(() => {})
}
// 批量删除
const handleBatchDelete = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请至少选择一条记录')
return
}
ElMessageBox.confirm(`确认删除选中的 ${selectedRows.value.length} 条记录吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
loading.value = true
try {
const deletePromises = selectedRows.value.map(row =>
deleteDiscountGoods({
discount_good_id: String(row.id),
code_id: String(row.discountId)
})
)
const results = await Promise.allSettled(deletePromises)
const successCount = results.filter(r => r.status === 'fulfilled' && r.value?.data?.code === 200).length
const failCount = results.length - successCount
if (failCount === 0) {
ElMessage.success(`批量删除成功,共删除 ${successCount} 条记录`)
} else if (successCount === 0) {
ElMessage.error(`批量删除失败,所有 ${failCount} 条记录删除失败`)
} else {
ElMessage.warning(`批量删除完成,成功 ${successCount} 条,失败 ${failCount}`)
}
fetchGoodsList()
} catch (error) {
console.error('批量删除失败:', error)
ElMessage.error('批量删除操作异常')
} finally {
loading.value = false
}
}).catch(() => {})
}
// 提交表单
const submitForm = () => {
// 新增模式下的额外验证
if (dialogType.value === 'add') {
if (!form.code_id) {
ElMessage.warning('请选择代金券')
return
}
if (form.select_type === 'product' && !form.selected_product) {
ElMessage.warning('请选择商品')
return
}
if (form.select_type === 'product_group' && !form.selected_group) {
ElMessage.warning('请选择商品组')
return
}
if (!form.goods_id || !form.goods_name || !form.goods_type) {
ElMessage.warning('请先选择商品或商品组')
return
}
}
formRef.value?.validate(async (valid) => {
if (valid) {
try {
const submitData = {
code_id: String(form.code_id)
}
// 根据选择类型决定传 good_id 还是 good_group_id
if (dialogType.value === 'add') {
if (form.select_type === 'product') {
// 选择的是商品,传 good_id
submitData.good_ids = String(form.goods_id)
} else if (form.select_type === 'product_group') {
// 选择的是商品组,传 good_group_id
submitData.good_group_ids = String(form.goods_id)
}
} else {
// 编辑模式:传 discount_good_id
submitData.discount_good_id = String(form.id)
// 根据 goods_type 判断传 good_id 还是 good_group_id
if (form.goods_type === 'product') {
submitData.good_id = String(form.goods_id)
} else if (form.goods_type === 'product_group') {
submitData.good_group_id = String(form.goods_id)
} else {
// 其他类型默认使用 good_id
submitData.good_id = String(form.goods_id)
}
}
console.log('提交商品关联数据:', submitData)
let res
if (dialogType.value === 'add') {
res = await addDiscountGoods(submitData)
} else {
res = await updateDiscountGoods(submitData)
}
console.log('提交响应:', res.data)
if (res.data.code === 200) {
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '修改成功')
dialogVisible.value = false
fetchGoodsList()
}
} catch (error) {
console.error('操作失败:', error)
ElMessage.error(error.response?.data?.message || '操作失败')
}
}
})
}
// 初始化
onMounted(() => {
fetchVoucherListOptions()
if (queryParams.code_id) {
fetchGoodsList()
}
})
</script>
<style scoped>
.discount-goods-container {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
}
.search-form {
margin-bottom: 15px;
}
.action-bar {
display: flex;
gap: 12px;
}
.table-container {
border-radius: 8px;
}
.pagination {
margin-top: 24px;
justify-content: flex-end;
}
.price {
color: #f56c6c;
font-weight: bold;
font-size: 14px;
}
</style>
+883
View File
@@ -0,0 +1,883 @@
<template>
<div class="discount-users-container">
<!-- 搜索和操作栏 -->
<el-card class="filter-container" shadow="never">
<el-form :inline="true" :model="queryParams" class="search-form">
<el-form-item label="代金卷">
<el-select
v-model="queryParams.code_id"
placeholder="请选择代金券"
filterable
clearable
style="width: 280px"
>
<el-option
v-for="item in voucherListOptions"
:key="item.id"
:label="`${item.name} (¥${(item.amount / 100).toFixed(2)})`"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery" :disabled="!queryParams.code_id">
<el-icon><Search /></el-icon>查询
</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div class="action-bar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增用户关联
</el-button>
<el-button type="success" @click="fetchUsersList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
<el-icon><Delete /></el-icon>批量删除
</el-button>
</div>
</el-card>
<!-- 用户关联列表 -->
<el-card class="table-container" shadow="never">
<el-table
v-loading="loading"
:data="usersList"
@selection-change="handleSelectionChange"
style="width: 100%"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="discountId" label="代金券ID" width="120" />
<el-table-column label="关联对象ID" width="130">
<template #default="{ row }">
{{ row.userId || row.userGroupId || '-' }}
</template>
</el-table-column>
<el-table-column label="类型" width="120">
<template #default="{ row }">
<el-tag :type="getUserTypeTagByRow(row)">
{{ getUserTypeNameByRow(row) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="创建时间" width="180">
<template #default="{ row }">
{{ formatDate(row.CreatedAt) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</el-card>
<!-- 添加/编辑用户关联对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增用户关联' : '编辑用户关联'"
width="600px"
>
<el-form
ref="formRef"
:model="form"
:rules="formRules"
label-width="120px"
>
<el-form-item label="代金券" prop="code_id">
<el-select
v-model="form.code_id"
placeholder="请选择代金券"
filterable
clearable
:disabled="dialogType === 'edit'"
style="width: 100%"
>
<el-option
v-for="item in voucherListOptions"
:key="item.id"
:label="`${item.name} (¥${(item.amount / 100).toFixed(2)})`"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="选择类型" prop="select_type" v-if="dialogType === 'add'">
<el-radio-group v-model="form.select_type" @change="handleSelectTypeChange">
<el-radio value="user">用户</el-radio>
<el-radio value="user_group">用户组</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="选择用户" prop="selected_user" v-if="dialogType === 'add' && form.select_type === 'user'">
<div class="user-selector-wrapper">
<div class="selected-user-display" v-if="form.selected_user">
<el-tag type="primary" closable @close="clearSelectedUser">
{{ getSelectedUserName() }}
</el-tag>
</div>
<el-button
type="primary"
plain
@click="openUserSelector"
style="width: 100%"
>
<el-icon><User /></el-icon>
{{ form.selected_user ? '重新选择用户' : '选择用户' }}
</el-button>
</div>
</el-form-item>
<el-form-item label="选择用户组" prop="selected_group" v-if="dialogType === 'add' && form.select_type === 'user_group'">
<el-select
v-model="form.selected_group"
placeholder="请选择用户组"
filterable
clearable
style="width: 100%"
@change="handleUserGroupChange"
>
<el-option
v-for="item in userGroupOptions"
:key="item.Id"
:label="`${item.Name} (ID: ${item.Id})`"
:value="item.Id"
/>
</el-select>
</el-form-item>
<!-- 编辑模式显示字段 -->
<template v-if="dialogType === 'edit'">
<el-form-item label="关联类型">
<el-tag :type="form.select_type === 'user' ? 'primary' : 'warning'">
{{ form.select_type === 'user' ? '用户' : '用户组' }}
</el-tag>
</el-form-item>
<el-form-item v-if="form.select_type === 'user'" label="用户ID" prop="user_id">
<div class="user-selector-wrapper">
<div class="selected-user-display" v-if="form.user_id">
<el-tage type="primary" closable @close="clearSelectedUser">
{{ getSelectedUserName() }}
</el-tage>
</div>
<el-button type="primary" plan @click="openUserSelector" style="width: 100%;">
<el-icon><User /></el-icon>
{{ form.user_id ? '重新选择用户' : '选择用户' }}
</el-button>
</div>
</el-form-item>
<el-form-item v-if="form.select_type === 'user_group'" label="用户组ID">
<el-select v-model="form.user_group_id" placeholder="">
<el-option v-for="item in userGroupOptions" :key="item.Id" :label="`${item.Name} (ID: ${item.Id})`" :value="item.Id">
</el-option>
</el-select>
</el-form-item>
</template>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
<!-- 用户选择弹窗 -->
<el-dialog
v-model="userSelectorVisible"
title="选择用户"
width="800px"
class="user-selector-dialog"
>
<!-- 搜索栏 -->
<div class="selector-search">
<el-input
v-model="userSearchParams.key"
placeholder="搜索用户名或ID"
clearable
@keyup.enter="searchUsers"
style="width: 300px; margin-right: 12px"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
<el-button type="primary" @click="searchUsers">
<el-icon><Search /></el-icon>
搜索
</el-button>
<el-button @click="resetUserSearch">重置</el-button>
</div>
<!-- 用户表格 -->
<el-table
v-loading="userSelectorLoading"
:data="userSelectorList"
highlight-current-row
@current-change="handleUserSelectChange"
style="width: 100%; margin-top: 16px"
:height="400"
>
<el-table-column type="index" label="序号" width="60" />
<el-table-column prop="UserId" label="用户ID" width="100" />
<el-table-column prop="UserName" label="用户名" min-width="150" />
<el-table-column prop="Email" label="邮箱" min-width="180" />
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.Status === 1 ? 'success' : 'danger'" size="small">
{{ row.Status === 1 ? '正常' : '禁用' }}
</el-tag>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="userSearchParams.page"
v-model:page-size="userSearchParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="userSelectorTotal"
@size-change="handleUserSelectorSizeChange"
@current-change="handleUserSelectorPageChange"
background
class="selector-pagination"
/>
<template #footer>
<el-button @click="userSelectorVisible = false">取消</el-button>
<el-button type="primary" @click="confirmUserSelection" :disabled="!selectedUserTemp">
确定选择
</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Delete, Search, Plus, Refresh, User } from '@element-plus/icons-vue'
import {
getDiscountUsersList,
addDiscountUsers,
updateDiscountUsers,
deleteDiscountUsers,
getDiscountCodeList
} from '@/api/admin/discount'
import {
getUserList,
getUserGroupList
} from '@/api/admin/user'
// 查询参数
const queryParams = reactive({
code_id: '',
page: 1,
count: 10
})
// 表单数据
const form = reactive({
id: undefined,
code_id: undefined,
user_id: undefined,
username: '',
email: '',
status: 1,
select_type: 'user', // 选择类型:user 或 user_group
selected_user: undefined, // 选中的用户ID
selected_group: undefined, // 选中的用户组ID
user_group_id: undefined // 用户组ID(用于提交)
})
const formRules = {
code_id: [
{ required: true, message: '请选择代金券', trigger: 'change' }
],
select_type: [
{ required: true, message: '请选择类型', trigger: 'change' }
],
selected_user: [
{ required: true, message: '请选择用户', trigger: 'change' }
],
selected_group: [
{ required: true, message: '请选择用户组', trigger: 'change' }
]
}
// 状态数据
const loading = ref(false)
const usersList = ref([])
const total = ref(0)
const selectedRows = ref([])
const dialogVisible = ref(false)
const dialogType = ref('add')
const formRef = ref(null)
const voucherListOptions = ref([]) // 代金券列表选项
const userOptions = ref([]) // 用户列表选项
const userGroupOptions = ref([]) // 用户组列表选项
// 用户选择弹窗相关
const userSelectorVisible = ref(false)
const userSelectorLoading = ref(false)
const userSelectorList = ref([])
const userSelectorTotal = ref(0)
const selectedUserTemp = ref(null) // 临时存储选中的用户
const userSearchParams = reactive({
key: '',
page: 1,
count: 10
})
// 格式化日期
const formatDate = (dateStr) => {
if (!dateStr) return '-'
const date = new Date(dateStr)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}`
}
// 获取用户类型名称(根据行数据)
const getUserTypeNameByRow = (row) => {
// userId 不为 0 说明是用户
if (row.userId && row.userId !== 0) {
return '用户'
}
// userGroupId 不为 0 说明是用户组
if (row.userGroupId && row.userGroupId !== 0) {
return '用户组'
}
return '-'
}
// 获取用户类型标签(根据行数据)
const getUserTypeTagByRow = (row) => {
// 用户用蓝色
if (row.userId && row.userId !== 0) {
return 'primary'
}
// 用户组用橙色
if (row.userGroupId && row.userGroupId !== 0) {
return 'warning'
}
return 'info'
}
// 获取代金券列表选项
const fetchVoucherListOptions = async () => {
try {
const res = await getDiscountCodeList({
page: 1,
count: 1000,
discount_type: 'coupon'
})
console.log('获取代金券列表:', res.data)
if (res.data.code === 200) {
voucherListOptions.value = res.data.data?.data || []
}
} catch (error) {
console.error('获取代金券列表失败:', error)
ElMessage.error('获取代金券列表失败')
}
}
// 获取用户列表
const fetchUserList = async () => {
try {
const res = await getUserList({
page: 1,
count: 10,
key: ''
})
console.log('获取用户列表:', res.data)
if (res.data.code === 200) {
userOptions.value = res.data.data?.data || []
}
} catch (error) {
console.error('获取用户列表失败:', error)
ElMessage.error('获取用户列表失败')
}
}
// 获取用户组列表
const fetchUserGroupList = async () => {
try {
const res = await getUserGroupList({
page: 1,
count: 10000,
key: ''
})
console.log('获取用户组列表:', res.data)
if (res.data.code === 200) {
userGroupOptions.value = res.data.data?.data || []
}
} catch (error) {
console.error('获取用户组列表失败:', error)
ElMessage.error('获取用户组列表失败')
}
}
// 打开用户选择器
const openUserSelector = () => {
userSelectorVisible.value = true
selectedUserTemp.value = null
userSearchParams.key = ''
userSearchParams.page = 1
fetchUserSelectorList()
}
// 获取用户选择器列表
const fetchUserSelectorList = async () => {
userSelectorLoading.value = true
try {
const res = await getUserList(userSearchParams)
console.log('用户选择器列表:', res.data)
if (res.data.code === 200) {
userSelectorList.value = res.data.data?.data || []
userSelectorTotal.value = res.data.data?.all_count || 0
}
} catch (error) {
console.error('获取用户列表失败:', error)
ElMessage.error('获取用户列表失败')
} finally {
userSelectorLoading.value = false
}
}
// 搜索用户
const searchUsers = () => {
userSearchParams.page = 1
fetchUserSelectorList()
}
// 重置用户搜索
const resetUserSearch = () => {
userSearchParams.key = ''
userSearchParams.page = 1
fetchUserSelectorList()
}
// 用户选择变化
const handleUserSelectChange = (row) => {
selectedUserTemp.value = row
}
// 用户选择器分页
const handleUserSelectorSizeChange = (size) => {
userSearchParams.count = size
fetchUserSelectorList()
}
const handleUserSelectorPageChange = (page) => {
userSearchParams.page = page
fetchUserSelectorList()
}
// 确认用户选择
const confirmUserSelection = () => {
if (!selectedUserTemp.value) {
ElMessage.warning('请选择一个用户')
return
}
form.selected_user = selectedUserTemp.value.UserId
form.user_id = selectedUserTemp.value.UserId
// 将选中的用户添加到 userOptions 中(如果不存在)
if (!userOptions.value.find(u => u.UserId === selectedUserTemp.value.UserId)) {
userOptions.value.push(selectedUserTemp.value)
}
userSelectorVisible.value = false
ElMessage.success('用户选择成功')
}
// 清除选中的用户
const clearSelectedUser = () => {
form.selected_user = undefined
form.user_id = undefined
}
// 获取选中用户的显示名称
const getSelectedUserName = () => {
let user;
user = userOptions.value.find(u => u.UserId === form.selected_user)
console.log("是否有用户的user_id信息",form.user_id)
//需要进行判断是否是编辑的用户通过user_id进行刷选到所需要的用户对象信息
if(form.user_id){
console.log("用户表信息:",userOptions.value)
user = userOptions.value.find(u => u.UserId === form.user_id)
}
console.log("获取用户的名称",user)
return user ? `${user.UserName} (ID: ${user.UserId})` : '未知用户'
}
// 选择类型变化
const handleSelectTypeChange = (type) => {
form.selected_user = undefined
form.selected_group = undefined
form.user_id = undefined
form.user_group_id = undefined
}
// 选择用户变化
const handleUserChange = (userId) => {
const user = userOptions.value.find(item => item.UserId === userId)
if (user) {
form.user_id = user.UserId
}
}
// 选择用户组变化
const handleUserGroupChange = (groupId) => {
const group = userGroupOptions.value.find(item => item.Id === groupId)
if (group) {
form.user_group_id = group.Id
}
}
// 获取用户关联列表
const fetchUsersList = async () => {
if (!queryParams.code_id) {
ElMessage.warning('请选择代金券进行查询')
return
}
loading.value = true
try {
const res = await getDiscountUsersList(queryParams)
console.log('用户关联列表数据:', res.data)
if (res.data.code === 200) {
usersList.value = res.data.data || []
total.value = res.data.data?.length || 0
}
} catch (error) {
console.error('获取用户关联列表失败:', error)
ElMessage.error('获取用户关联列表失败')
} finally {
loading.value = false
}
}
// 查询
const handleQuery = () => {
queryParams.page = 1
fetchUsersList()
}
// 重置查询
const resetQuery = () => {
queryParams.code_id = ''
queryParams.page = 1
usersList.value = []
total.value = 0
}
// 选择项变化
const handleSelectionChange = (selection) => {
selectedRows.value = selection
}
// 分页
const handleSizeChange = (size) => {
queryParams.count = size
fetchUsersList()
}
const handleCurrentChange = (page) => {
queryParams.page = page
fetchUsersList()
}
// 新增用户关联
const handleAdd = () => {
dialogType.value = 'add'
dialogVisible.value = true
Object.assign(form, {
id: undefined,
code_id: queryParams.code_id ? Number(queryParams.code_id) : undefined,
user_id: undefined,
username: '',
email: '',
status: 1,
select_type: 'user',
selected_user: undefined,
selected_group: undefined,
user_group_id: undefined
})
formRef.value?.resetFields()
fetchUserGroupList()
}
// 编辑用户关联
const handleEdit = (row) => {
dialogType.value = 'edit'
dialogVisible.value = true
// 判断是用户还是用户组
let userId, userGroupId, selectType
console.log("获取编辑的当前信息:",row)
if (row.userId && row.userId !== 0) {
// 用户
userId = row.userId
userGroupId = undefined
selectType = 'user'
} else if (row.userGroupId && row.userGroupId !== 0) {
// 用户组
userId = undefined
userGroupId = row.userGroupId
selectType = 'user_group'
}
Object.assign(form, {
id: row.id,
code_id: row.discountId,
user_id: userId,
user_group_id: userGroupId,
select_type: selectType,
username: '',
email: '',
status: 1
})
//点击编辑需要初始化加载用户列表
fetchUserList()
}
// 删除用户关联
const handleDelete = (row) => {
ElMessageBox.confirm(`确认删除该用户关联吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deleteDiscountUsers({
discount_user_id: String(row.id),
code_id: String(row.discountId)
})
console.log('删除响应:', res.data)
if (res.data.code === 200) {
ElMessage.success('删除成功')
fetchUsersList()
}
} catch (error) {
console.error('删除失败:', error)
ElMessage.error(error.response?.data?.message || '删除失败')
}
}).catch(() => {})
}
// 批量删除
const handleBatchDelete = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请至少选择一条记录')
return
}
ElMessageBox.confirm(`确认删除选中的 ${selectedRows.value.length} 条记录吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
loading.value = true
try {
const deletePromises = selectedRows.value.map(row =>
deleteDiscountUsers({
discount_user_id: String(row.id),
code_id: String(row.discountId)
})
)
const results = await Promise.allSettled(deletePromises)
const successCount = results.filter(r => r.status === 'fulfilled' && r.value?.data?.code === 200).length
const failCount = results.length - successCount
if (failCount === 0) {
ElMessage.success(`批量删除成功,共删除 ${successCount} 条记录`)
} else if (successCount === 0) {
ElMessage.error(`批量删除失败,所有 ${failCount} 条记录删除失败`)
} else {
ElMessage.warning(`批量删除完成,成功 ${successCount} 条,失败 ${failCount}`)
}
fetchUsersList()
} catch (error) {
console.error('批量删除失败:', error)
ElMessage.error('批量删除操作异常')
} finally {
loading.value = false
}
}).catch(() => {})
}
// 提交表单
const submitForm = () => {
// 新增模式下的额外验证
if (dialogType.value === 'add') {
if (!form.code_id) {
ElMessage.warning('请选择代金券')
return
}
if (form.select_type === 'user' && !form.selected_user) {
ElMessage.warning('请选择用户')
return
}
if (form.select_type === 'user_group' && !form.selected_group) {
ElMessage.warning('请选择用户组')
return
}
if (!form.user_id && !form.user_group_id) {
ElMessage.warning('请先选择用户或用户组')
return
}
}
formRef.value?.validate(async (valid) => {
if (valid) {
try {
const submitData = {
code_id: String(form.code_id)
}
// 根据选择类型决定传 user_id 还是 user_group_id
if (dialogType.value === 'add') {
if (form.select_type === 'user') {
// 选择的是用户,传 user_id
submitData.user_id = String(form.user_id)
} else if (form.select_type === 'user_group') {
// 选择的是用户组,传 user_group_id
submitData.user_group_id = String(form.user_group_id)
}
} else {
// 编辑模式:根据类型传递对应的ID
if (form.select_type === 'user') {
submitData.user_id = String(form.user_id)
} else if (form.select_type === 'user_group') {
submitData.user_group_id = String(form.user_group_id)
}
// 编辑需要传 discount_user_id
submitData.discount_user_id = String(form.id)
}
console.log('提交用户关联数据:', submitData)
let res
if (dialogType.value === 'add') {
res = await addDiscountUsers(submitData)
} else {
res = await updateDiscountUsers(submitData)
}
console.log('提交响应:', res.data)
if (res.data.code === 200) {
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '修改成功')
dialogVisible.value = false
fetchUsersList()
}
} catch (error) {
console.error('操作失败:', error)
ElMessage.error(error.response?.data?.message || '操作失败')
}
}
})
}
// 初始化
onMounted(() => {
fetchVoucherListOptions()
if (queryParams.code_id) {
fetchUsersList()
}
})
</script>
<style scoped>
.discount-users-container {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
}
.search-form {
margin-bottom: 15px;
}
.action-bar {
display: flex;
gap: 12px;
}
.table-container {
border-radius: 8px;
}
.pagination {
margin-top: 24px;
justify-content: flex-end;
}
/* 用户选择器样式 */
.user-selector-wrapper {
width: 100%;
}
.selected-user-display {
margin-bottom: 12px;
padding: 8px 12px;
background: #f5f7fa;
border-radius: 4px;
display: flex;
align-items: center;
gap: 8px;
}
.user-selector-dialog .selector-search {
display: flex;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #ebeef5;
}
.selector-pagination {
margin-top: 16px;
justify-content: flex-end;
}
:deep(.el-table__row) {
cursor: pointer;
}
:deep(.el-table__row):hover {
background-color: #f5f7fa;
}
:deep(.current-row) {
background-color: #ecf5ff !important;
}
</style>
+892
View File
@@ -0,0 +1,892 @@
<template>
<div class="user-voucher-container">
<!-- 搜索和操作栏 -->
<el-card class="filter-container" shadow="never">
<el-form :inline="true" :model="queryParams" class="search-form">
<el-form-item label="代金券">
<el-select
v-model="queryParams.code_id"
placeholder="请选择代金券"
filterable
clearable
style="width: 280px"
@change="handleVoucherSelect"
>
<el-option
v-for="item in voucherListOptions"
:key="item.id"
:label="`${item.name} (¥${(item.amount / 100).toFixed(2)})`"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery" :disabled="!queryParams.code_id">
<el-icon><Search /></el-icon>查询
</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div class="action-bar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>为用户添加代金券
</el-button>
<el-button type="success" @click="fetchUserVoucherList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
<el-icon><Delete /></el-icon>批量删除
</el-button>
</div>
</el-card>
<!-- 用户代金券列表 -->
<el-card class="table-container" shadow="never">
<el-table
v-loading="loading"
:data="userVoucherList"
@selection-change="handleSelectionChange"
style="width: 100%"
>
<el-table-column type="selection" width="55" />
<el-table-column label="ID" width="80">
<template #default="{ row }">
{{ row.Id || row.id }}
</template>
</el-table-column>
<el-table-column label="用户ID" width="100">
<template #default="{ row }">
{{ row.UserId || row.userId }}
</template>
</el-table-column>
<el-table-column label="代金券ID" width="100">
<template #default="{ row }">
{{ row.discountId }}
</template>
</el-table-column>
<el-table-column label="代金券名称" min-width="150">
<template #default="{ row }">
{{ row.discount?.name || '-' }}
</template>
</el-table-column>
<el-table-column label="面额" width="120">
<template #default="{ row }">
<span class="amount">¥{{ row.discount?.amount ? (row.discount.amount / 100).toFixed(2) : '0.00' }}</span>
</template>
</el-table-column>
<el-table-column label="已使用/最大次数" width="150">
<template #default="{ row }">
<el-tag type="info">{{ row.useTimes || 0 }} / {{ row.maxUseTimes || row.discount?.maxTimes || 0 }}</el-tag>
</template>
</el-table-column>
<el-table-column label="过期时间" width="180">
<template #default="{ row }">
{{ formatDate(row.expireAt) }}
</template>
</el-table-column>
<el-table-column label="创建时间" width="180">
<template #default="{ row }">
{{ formatDate(row.CreatedAt) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</el-card>
<!-- 添加用户代金券对话框 -->
<el-dialog
v-model="addDialogVisible"
title="为用户分发优惠券/优惠码"
width="600px"
>
<el-form
ref="addFormRef"
:model="addForm"
:rules="addRules"
label-width="140px"
>
<el-form-item label="优惠类型" prop="discount_type">
<el-radio-group v-model="addForm.discount_type" @change="handleDiscountTypeChange">
<el-radio value="coupon">代金券</el-radio>
<!-- <el-radio value="code">优惠码</el-radio> -->
</el-radio-group>
</el-form-item>
<el-form-item label="代金券" prop="voucher_id">
<el-select
v-model="addForm.voucher_id"
placeholder="请选择代金券"
:disabled="addForm.discount_type === 'code'"
filterable
clearable
style="width: 100%"
@change="handleVoucherChange"
>
<el-option
v-for="item in voucherOptions"
:key="item.id"
:label="`${item.name} (¥${(item.amount / 100).toFixed(2)})`"
:value="item.id"
/>
</el-select>
</el-form-item>
<!-- <el-form-item label="优惠码" prop="code_id">
<el-select
v-model="addForm.code_id"
placeholder="请选择优惠码"
:disabled="addForm.discount_type === 'coupon'"
filterable
clearable
style="width: 100%"
@change="handleCodeChange"
>
<el-option
v-for="item in codeOptions"
:key="item.id"
:label="`${item.name} (${item.code})`"
:value="item.id"
/>
</el-select>
</el-form-item> -->
<el-divider />
<el-form-item label="分发对象" prop="target_type">
<el-radio-group v-model="addForm.target_type" @change="handleTargetTypeChange">
<el-radio value="user">指定用户</el-radio>
<el-radio value="group">用户组</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="用户" prop="user_id">
<el-select
v-model="addForm.user_id"
placeholder="请选择用户"
:disabled="addForm.target_type === 'group'"
filterable
clearable
remote
:remote-method="searchUsers"
:loading="userSearchLoading"
style="width: 100%"
@change="handleUserChange"
>
<el-option
v-for="item in userOptions"
:key="item.UserId"
:label="`${item.UserName} (ID: ${item.UserId})`"
:value="item.UserId"
/>
</el-select>
</el-form-item>
<el-form-item label="用户组" prop="group_id">
<el-select
v-model="addForm.group_id"
placeholder="请选择用户组"
:disabled="addForm.target_type === 'user'"
filterable
clearable
style="width: 100%"
@change="handleGroupChange"
>
<el-option
v-for="item in groupOptions"
:key="item.Id"
:label="item.Name"
:value="item.Id"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="addDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitAdd" :loading="submitLoading">确定</el-button>
</template>
</el-dialog>
<!-- 编辑用户代金券对话框 -->
<el-dialog
v-model="editDialogVisible"
title="编辑用户代金券"
width="600px"
>
<el-form
ref="editFormRef"
:model="editForm"
:rules="editRules"
label-width="140px"
>
<el-form-item label="用户ID" prop="user_id">
<el-input-number v-model="editForm.user_id" :min="1" disabled style="width: 100%" />
</el-form-item>
<el-form-item label="代金券ID" prop="discount_id">
<el-input-number v-model="editForm.discount_id" :min="1" placeholder="请输入代金券ID" style="width: 100%" />
</el-form-item>
<el-form-item label="已使用次数" prop="use_times">
<el-input-number v-model="editForm.use_times" :min="0" placeholder="请输入已使用次数" style="width: 100%" />
</el-form-item>
<el-form-item label="最大使用次数" prop="max_use_times">
<el-input-number v-model="editForm.max_use_times" :min="0" placeholder="0表示无限制" style="width: 100%" />
</el-form-item>
<el-form-item label="过期时间" prop="expire_at">
<el-date-picker
v-model="editForm.expire_at"
type="datetime"
placeholder="选择过期时间"
value-format="YYYY-MM-DD HH:mm:ss"
:teleported="true"
placement="top-start"
:editable="true"
:clearable="true"
style="width: 100%"
@keyup.enter="handleDatePickerEnter"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="editDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitEdit">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Delete, Search, Plus, Refresh } from '@element-plus/icons-vue'
import {
getUserVoucherList,
addUserVoucher,
updateUserVoucher,
deleteUserVoucher,
getDiscountCodeList,
getVoucherHolderList,
allocateVoucher
} from '@/api/admin/discount'
import { getUserList, getUserGroupList } from '@/api/admin/user'
// 查询参数
const queryParams = reactive({
code_id: undefined,
page: 1,
count: 10
})
// 添加表单
const addForm = reactive({
discount_type: 'coupon', // 优惠类型:coupon-代金券, code-优惠码
voucher_id: undefined, // 代金券ID
code_id: undefined, // 优惠码ID
target_type: 'user', // 分发对象:user-指定用户, group-用户组
user_id: undefined, // 用户ID
group_id: undefined // 用户组ID
})
const addRules = {
discount_type: [
{ required: true, message: '请选择优惠类型', trigger: 'change' }
],
target_type: [
{ required: true, message: '请选择分发对象', trigger: 'change' }
]
}
// 下拉选项数据
const voucherListOptions = ref([]) // 用于搜索的代金券列表
const voucherOptions = ref([]) // 代金券选项
const codeOptions = ref([]) // 优惠码选项
const userOptions = ref([]) // 用户选项
const groupOptions = ref([]) // 用户组选项
const userSearchLoading = ref(false) // 用户搜索加载状态
const submitLoading = ref(false) // 提交加载状态
const dataList = ref([]) // 优惠列表
// 编辑表单
const editForm = reactive({
id: undefined,
user_id: undefined,
discount_id: undefined,
use_times: 0,
max_use_times: 0,
expire_at: ''
})
const editRules = {
discount_id: [
{ required: true, message: '请输入代金券ID', trigger: 'blur' }
],
use_times: [
{ required: true, message: '请输入已使用次数', trigger: 'blur' }
],
max_use_times: [
{ required: true, message: '请输入最大使用次数', trigger: 'blur' }
],
expire_at: [
{ required: true, message: '请选择过期时间', trigger: 'change' }
]
}
// 状态数据
const loading = ref(false)
const userVoucherList = ref([])
const total = ref(0)
const selectedRows = ref([])
const addDialogVisible = ref(false)
const editDialogVisible = ref(false)
const addFormRef = ref(null)
const editFormRef = ref(null)
// 格式化日期
const formatDate = (dateStr) => {
if (!dateStr) return '-'
try {
const date = new Date(dateStr)
// 检查日期是否有效且不是默认的1970年
if (isNaN(date.getTime()) || date.getFullYear() <= 1970) {
return '-'
}
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}`
} catch (error) {
console.error('日期格式化失败:', error)
return '-'
}
}
// 处理日期选择器回车事件
const handleDatePickerEnter = (event) => {
const datePicker = event.target.closest('.el-date-editor')
if (datePicker) {
event.target.blur()
}
}
// 获取代金券持有者列表
const fetchUserVoucherList = async () => {
if (!queryParams.code_id) {
ElMessage.warning('请先选择代金券')
return
}
loading.value = true
try {
const params = {
code_id: queryParams.code_id,
page: queryParams.page,
count: queryParams.count
}
const res = await getVoucherHolderList(params)
console.log('代金券持有者列表数据:', res.data)
if (res.data.code === 200) {
userVoucherList.value = res.data.data?.data || []
total.value = res.data.data?.all_count || 0
}
} catch (error) {
console.error('获取代金券持有者列表失败:', error)
ElMessage.error('获取代金券持有者列表失败')
} finally {
loading.value = false
}
}
// 查询
const handleQuery = () => {
queryParams.page = 1
fetchUserVoucherList()
}
// 重置查询
const resetQuery = () => {
queryParams.code_id = undefined
queryParams.page = 1
userVoucherList.value = []
total.value = 0
}
// 处理代金券选择
const handleVoucherSelect = (value) => {
if (value) {
queryParams.page = 1
fetchUserVoucherList()
} else {
userVoucherList.value = []
total.value = 0
}
}
// 选择项变化
const handleSelectionChange = (selection) => {
selectedRows.value = selection
}
// 分页
const handleSizeChange = (size) => {
queryParams.count = size
fetchUserVoucherList()
}
const handleCurrentChange = (page) => {
queryParams.page = page
fetchUserVoucherList()
}
// 获取代金券列表(用于搜索下拉框)
const fetchVoucherListOptions = async () => {
try {
const res = await getDiscountCodeList({
page: 1,
count: 1000,
discount_type: 'coupon'
})
console.log('获取代金券列表:', res.data)
if (res.data.code === 200) {
voucherListOptions.value = res.data.data?.data || []
}
} catch (error) {
console.error('获取代金券列表失败:', error)
ElMessage.error('获取代金券列表失败')
}
}
//获取优惠列表
const fetchDiscountList = async () => {
try {
const res = await getDiscountCodeList({
page: 1,
count: 100,
discount_type: 'coupon'
})
console.log('获取代金券列表:', res.data)
if (res.data.code === 200) {
voucherOptions.value = res.data.data?.data || []
dataList.value.push(...res.data.data?.data || [])
}
const res2 = await getDiscountCodeList({
page: 1,
count: 100,
discount_type: 'code'
})
console.log('获取优惠码列表:', res2.data)
if (res2.data.code === 200) {
codeOptions.value = res2.data.data?.data || []
dataList.value.push(...res2.data.data?.data || [])
}
console.log('获取优惠列表最终:', dataList.value)
} catch (error) {
console.error('获取代金券列表失败:', error)
}
}
// 获取代金券列表
const fetchVoucherOptions = async () => {
try {
const res = await getDiscountCodeList({
discount_type: 'coupon',
page: 1,
count: 100
})
if (res.data.code === 200) {
voucherOptions.value = res.data.data?.data || []
}
} catch (error) {
console.error('获取代金券列表失败:', error)
}
}
// 获取优惠码列表
const fetchCodeOptions = async () => {
try {
const res = await getDiscountCodeList({
discount_type: 'code',
page: 1,
count: 100
})
if (res.data.code === 200) {
codeOptions.value = res.data.data?.data || []
}
} catch (error) {
console.error('获取优惠码列表失败:', error)
}
}
//获取用户列表
const fetchUserList = async () => {
try {
const res = await getUserList({
page: 1,
count: 100,
key: ''
})
console.log('获取用户列表:', res.data)
if (res.data.code === 200) {
userOptions.value = res.data.data?.data || []
}
}
catch (error) {
console.error('获取用户列表失败:', error)
}
finally {
userSearchLoading.value = false
}
}
// 获取用户组列表
const fetchGroupOptions = async () => {
try {
const res = await getUserGroupList({
page: 1,
count: 100
})
if (res.data.code === 200) {
groupOptions.value = res.data.data?.data || []
}
} catch (error) {
console.error('获取用户组列表失败:', error)
}
}
// 处理优惠类型变化(代金券/优惠码互斥)
const handleDiscountTypeChange = (val) => {
if (val === 'coupon') {
addForm.code_id = undefined
} else if (val === 'code') {
addForm.voucher_id = undefined
}
}
// 处理代金券选择变化
const handleVoucherChange = (val) => {
if (val) {
addForm.code_id = undefined
addForm.discount_type = 'coupon'
}
}
// 处理优惠码选择变化
const handleCodeChange = (val) => {
if (val) {
addForm.voucher_id = undefined
addForm.discount_type = 'code'
}
}
// 处理分发对象类型变化(用户/用户组互斥)
const handleTargetTypeChange = (val) => {
if (val === 'user') {
addForm.group_id = undefined
} else if (val === 'group') {
addForm.user_id = undefined
}
}
// 处理用户选择变化
const handleUserChange = (val) => {
if (val) {
addForm.group_id = undefined
addForm.target_type = 'user'
}
}
// 处理用户组选择变化
const handleGroupChange = (val) => {
if (val) {
addForm.user_id = undefined
addForm.target_type = 'group'
}
}
// 添加用户代金券
const handleAdd = async () => {
addDialogVisible.value = true
// 重置表单
Object.assign(addForm, {
discount_type: 'coupon',
voucher_id: undefined,
code_id: undefined,
target_type: 'user',
user_id: undefined,
group_id: undefined
})
addFormRef.value?.resetFields()
// 加载下拉选项数据
await Promise.all([
fetchVoucherOptions(),
// fetchCodeOptions(),
fetchGroupOptions(),
fetchUserList()
])
}
// 编辑用户代金券
const handleEdit = (row) => {
editDialogVisible.value = true
// 处理过期时间 - 支持ISO字符串和时间戳
let expireAt = ''
if (row.expireAt) {
try {
// 如果是ISO字符串格式
if (typeof row.expireAt === 'string') {
const date = new Date(row.expireAt)
if (!isNaN(date.getTime()) && date.getFullYear() > 1970) {
expireAt = date.toISOString().slice(0, 19).replace('T', ' ')
}
}
// 如果是时间戳
else if (typeof row.expireAt === 'number' && row.expireAt > 0) {
expireAt = new Date(row.expireAt * 1000).toISOString().slice(0, 19).replace('T', ' ')
}
} catch (error) {
console.error('时间转换失败:', error)
}
}
Object.assign(editForm, {
id: row.Id || row.id,
user_id: row.UserId || row.userId,
discount_id: row.discountId,
use_times: row.useTimes || 0,
max_use_times: row.maxUseTimes || 0,
expire_at: expireAt
})
}
// 删除用户代金券
const handleDelete = (row) => {
ElMessageBox.confirm(`确认删除该用户代金券吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deleteUserVoucher({
user_id: String(row.UserId || row.userId),
id: String(row.Id || row.id)
})
console.log('删除响应:', res.data)
if (res.data.code === 200) {
ElMessage.success('删除成功')
fetchUserVoucherList()
}
} catch (error) {
console.error('删除失败:', error)
ElMessage.error(error.response?.data?.message || '删除失败')
}
}).catch(() => {})
}
// 批量删除
const handleBatchDelete = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请至少选择一条记录')
return
}
ElMessageBox.confirm(`确认删除选中的 ${selectedRows.value.length} 条记录吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
loading.value = true
try {
const deletePromises = selectedRows.value.map(row =>
deleteUserVoucher({
user_id: String(row.UserId || row.userId),
id: String(row.Id || row.id)
})
)
const results = await Promise.allSettled(deletePromises)
const successCount = results.filter(r => r.status === 'fulfilled' && r.value?.data?.code === 200).length
const failCount = results.length - successCount
if (failCount === 0) {
ElMessage.success(`批量删除成功,共删除 ${successCount} 条记录`)
} else if (successCount === 0) {
ElMessage.error(`批量删除失败,所有 ${failCount} 条记录删除失败`)
} else {
ElMessage.warning(`批量删除完成,成功 ${successCount} 条,失败 ${failCount}`)
}
fetchUserVoucherList()
} catch (error) {
console.error('批量删除失败:', error)
ElMessage.error('批量删除操作异常')
} finally {
loading.value = false
}
}).catch(() => {})
}
// 提交添加表单
const submitAdd = () => {
addFormRef.value?.validate(async (valid) => {
if (valid) {
// 验证是否选择了优惠券/优惠码
const discountId = addForm.discount_type === 'coupon' ? addForm.voucher_id : addForm.code_id
if (!discountId) {
ElMessage.warning('请选择代金券或优惠码')
return
}
// 验证是否选择了用户或用户组
const targetId = addForm.target_type === 'user' ? addForm.user_id : addForm.group_id
if (!targetId) {
ElMessage.warning('请选择用户或用户组')
return
}
submitLoading.value = true
try {
const submitData = {
code_id: String(discountId)
}
// 根据分发对象添加不同参数
if (addForm.target_type === 'user') {
submitData.user_id = String(addForm.user_id)
} else {
submitData.user_group_id = String(addForm.group_id)
}
console.log('分发优惠券/优惠码数据:', submitData)
const res = await allocateVoucher(submitData)
console.log('分发响应:', res.data)
if (res.data.code === 200) {
ElMessage.success('分发成功')
addDialogVisible.value = false
// 如果是为当前查询的用户分发,则刷新列表
//if (addForm.target_type === 'user' && queryParams.user_id) {
fetchUserVoucherList()
//}
}
} catch (error) {
console.error('分发失败:', error)
ElMessage.error(error.response?.data?.message || '分发失败')
} finally {
submitLoading.value = false
}
}
})
}
// 提交编辑表单
const submitEdit = () => {
editFormRef.value?.validate(async (valid) => {
if (valid) {
try {
const submitData = {
user_id: Number(editForm.user_id),
id: Number(editForm.id),
discount_id: Number(editForm.discount_id),
use_times: Number(editForm.use_times),
max_use_times: Number(editForm.max_use_times),
expire_at: Math.floor(new Date(editForm.expire_at).getTime() / 1000)
}
console.log('更新用户代金券数据:', submitData)
const res = await updateUserVoucher(submitData)
console.log('更新响应:', res.data)
if (res.data.code === 200) {
ElMessage.success('更新成功')
editDialogVisible.value = false
fetchUserVoucherList()
}
} catch (error) {
console.error('更新失败:', error)
ElMessage.error(error.response?.data?.message || '更新失败')
}
}
})
}
// 初始化
onMounted(() => {
// 加载代金券列表供选择
fetchVoucherListOptions()
fetchDiscountList()
})
</script>
<style scoped>
.user-voucher-container {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
}
.search-form {
margin-bottom: 15px;
}
.action-bar {
display: flex;
gap: 12px;
}
.table-container {
border-radius: 8px;
}
.user-info {
padding: 4px 0;
}
.username {
font-size: 14px;
font-weight: 500;
margin-bottom: 4px;
}
.user-id {
font-size: 12px;
color: #999;
}
.amount {
color: #f56c6c;
font-weight: bold;
font-size: 14px;
}
.pagination {
margin-top: 24px;
justify-content: flex-end;
}
</style>
+512
View File
@@ -0,0 +1,512 @@
<template>
<div class="voucher-container">
<!-- 搜索和操作栏 -->
<el-card class="filter-container" shadow="never">
<div class="action-bar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增代金券
</el-button>
<el-button type="success" @click="fetchVoucherList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
<el-icon><Delete /></el-icon>批量删除
</el-button>
</div>
</el-card>
<!-- 代金券列表 -->
<el-card class="table-container" shadow="never">
<el-table
v-loading="loading"
:data="voucherList"
@selection-change="handleSelectionChange"
style="width: 100%"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="代金券名称" min-width="200" />
<el-table-column label="面额" width="120">
<template #default="{ row }">
<span class="amount">¥{{ (row.amount / 100).toFixed(2) }}</span>
</template>
</el-table-column>
<el-table-column label="最低消费" width="120">
<template #default="{ row }">
¥{{ (row.minAmount / 100).toFixed(2) }}
</template>
</el-table-column>
<el-table-column label="最大抵扣" width="120">
<template #default="{ row }">
<span v-if="row.maxAmount">¥{{ (row.maxAmount / 100).toFixed(2) }}</span>
<span v-else>无限制</span>
</template>
</el-table-column>
<el-table-column prop="maxTimes" label="最大使用次数" width="130">
<template #default="{ row }">
{{ row.maxTimes || '无限制' }}
</template>
</el-table-column>
<el-table-column prop="userTimes" label="单用户次数" width="120">
<template #default="{ row }">
{{ row.userTimes || '无限制' }}
</template>
</el-table-column>
<el-table-column label="有效期(天)" width="100">
<template #default="{ row }">
{{ row.duration ? (row.duration / 86400).toFixed(0) : '-' }}
</template>
</el-table-column>
<el-table-column label="续费可用" width="100" align="center">
<template #default="{ row }">
<el-icon v-if="row.renew" color="#67c23a" :size="20"><SuccessFilled /></el-icon>
<el-icon v-else color="#f56c6c" :size="20"><CircleCloseFilled /></el-icon>
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="success" link @click="handleView(row)">查看</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</el-card>
<!-- 代金券表单对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增代金券' : '编辑代金券'"
width="700px"
>
<el-form
ref="voucherFormRef"
:model="voucherForm"
:rules="voucherRules"
label-width="140px"
>
<el-form-item label="代金券名称" prop="name">
<el-input v-model="voucherForm.name" placeholder="请输入代金券名称" />
</el-form-item>
<el-form-item label="备注" prop="note">
<el-input v-model="voucherForm.note" type="textarea" :rows="2" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="面额(元)" prop="amount">
<el-input-number v-model="voucherForm.amount" :min="0" :precision="2" :step="0.01" placeholder="请输入面额" style="width: 100%" />
</el-form-item>
<el-form-item label="最低消费(元)" prop="min_amount">
<el-input-number v-model="voucherForm.min_amount" :min="0" :precision="2" :step="0.01" placeholder="满多少可使用" style="width: 100%" />
</el-form-item>
<el-form-item label="最大抵扣(元)" prop="max_amount">
<el-input-number v-model="voucherForm.max_amount" :min="0" :precision="2" :step="0.01" placeholder="0表示无限制" style="width: 100%" />
</el-form-item>
<el-form-item label="最大使用次数" prop="max_times">
<el-input-number v-model="voucherForm.max_times" :min="0" placeholder="0表示无限制" style="width: 100%" />
</el-form-item>
<el-form-item label="单用户最大次数" prop="user_times">
<el-input-number v-model="voucherForm.user_times" :min="0" placeholder="0表示无限制" style="width: 100%" />
</el-form-item>
<el-form-item label="有效期(天)" prop="duration_days">
<el-input-number v-model="voucherForm.duration_days" :min="1" placeholder="代金券有效天数" style="width: 100%" />
<div class="form-tip">代金券领取后的有效持续时间</div>
</el-form-item>
<el-form-item label="发放时间范围" prop="timeRange">
<el-date-picker
v-model="voucherForm.timeRange"
type="datetimerange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
:teleported="true"
popper-class="voucher-date-picker"
placement="top-start"
:editable="true"
:clearable="true"
style="width: 100%"
@keyup.enter="handleDatePickerEnter"
/>
<div class="form-tip">代金券可以发放给用户的时间范围</div>
</el-form-item>
<el-form-item label="续费可用" prop="renew">
<el-switch v-model="voucherForm.renew" active-text="是" inactive-text="否" />
</el-form-item>
<el-form-item label="同类型可叠加" prop="can_stacking">
<el-switch v-model="voucherForm.can_stacking" active-text="是" inactive-text="否" />
</el-form-item>
<el-form-item label="其他类型可叠加" prop="can_combine">
<el-switch v-model="voucherForm.can_combine" active-text="是" inactive-text="否" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
<!-- 详情查看对话框 -->
<DiscountDetailDialog
v-model="detailDialogVisible"
type="coupon"
:detail-data="currentDetail"
/>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Delete, Refresh, SuccessFilled, CircleCloseFilled } from '@element-plus/icons-vue'
import {
getDiscountCodeList,
getDiscountCodeDetail,
createDiscountCode,
updateDiscountCode,
deleteDiscountCode
} from '@/api/admin/discount'
import { timeToTimestamp } from '@/utils/tool'
import DiscountDetailDialog from '@/components/marketing/DiscountDetailDialog.vue'
// 查询参数
const queryParams = reactive({
discount_type: 'coupon', // 固定为coupon表示代金券
page: 1,
count: 10
})
// 代金券表单
const voucherForm = reactive({
code_id: undefined,
discount_type: 'coupon', // 固定为coupon
name: '',
note: '',
amount: 0,
min_amount: 0,
max_amount: 0,
max_times: 0,
user_times: 0,
duration_days: 30, // 默认30天
timeRange: [],
renew: false,
can_stacking: false,
can_combine: false
})
const voucherRules = {
name: [
{ required: true, message: '请输入代金券名称', trigger: 'blur' }
],
amount: [
{ required: true, message: '请输入面额', trigger: 'blur' }
],
duration_days: [
{ required: true, message: '请输入有效期天数', trigger: 'blur' }
]
}
// 状态数据
const loading = ref(false)
const voucherList = ref([])
const total = ref(0)
const selectedRows = ref([])
const dialogVisible = ref(false)
const dialogType = ref('add')
const voucherFormRef = ref(null)
const detailDialogVisible = ref(false)
const currentDetail = ref(null)
// 获取代金券列表
const fetchVoucherList = async () => {
loading.value = true
try {
const res = await getDiscountCodeList(queryParams)
console.log('代金券列表数据:', res.data)
if (res.data.code === 200) {
voucherList.value = res.data.data?.data || []
total.value = res.data.data?.all_count || 0
}
} catch (error) {
console.error('获取代金券列表失败:', error)
ElMessage.error('获取代金券列表失败')
} finally {
loading.value = false
}
}
// 选择项变化
const handleSelectionChange = (selection) => {
selectedRows.value = selection
}
// 分页
const handleSizeChange = (size) => {
queryParams.count = size
fetchVoucherList()
}
const handleCurrentChange = (page) => {
queryParams.page = page
fetchVoucherList()
}
// 新增代金券
const handleAdd = () => {
dialogType.value = 'add'
dialogVisible.value = true
Object.assign(voucherForm, {
code_id: undefined,
discount_type: 'coupon',
name: '',
note: '',
amount: 0,
min_amount: 0,
max_amount: 0,
max_times: 0,
user_times: 0,
duration_days: 30,
timeRange: [],
renew: false,
can_stacking: false,
can_combine: false
})
voucherFormRef.value?.resetFields()
}
// 编辑代金券
const handleEdit = (row) => {
dialogType.value = 'edit'
dialogVisible.value = true
// 转换日期字符串为日期选择器格式
const startTime = row.startTime ? new Date(row.startTime).toLocaleString('zh-CN', {year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit'}).replace(/\//g, '-') : ''
const endTime = row.endTime ? new Date(row.endTime).toLocaleString('zh-CN', {year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit'}).replace(/\//g, '-') : ''
Object.assign(voucherForm, {
code_id: row.id,
discount_type: 'coupon',
name: row.name,
note: row.note || '',
amount: row.amount ? row.amount / 100 : 0,
min_amount: row.minAmount ? row.minAmount / 100 : 0,
max_amount: row.maxAmount ? row.maxAmount / 100 : 0,
max_times: row.maxTimes || 0,
user_times: row.userTimes || 0,
duration_days: row.duration ? row.duration / 86400 : 30, // 秒转天
timeRange: startTime && endTime ? [startTime, endTime] : [],
renew: row.renew || false,
can_stacking: row.canStacking || false,
can_combine: row.canCombine || false
})
}
// 查看代金券详情
const handleView = async (row) => {
try {
const res = await getDiscountCodeDetail({ code_id: row.id })
console.log('代金券详情:', res.data)
if (res.data.code === 200) {
currentDetail.value = res.data.data
detailDialogVisible.value = true
}
} catch (error) {
console.error('获取代金券详情失败:', error)
ElMessage.error('获取代金券详情失败')
}
}
// 删除代金券
const handleDelete = (row) => {
ElMessageBox.confirm(`确认删除代金券 ${row.name} 吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deleteDiscountCode({ code_id: row.id })
console.log('删除响应:', res.data)
if (res.data.code === 200) {
ElMessage.success('删除成功')
fetchVoucherList()
}
} catch (error) {
console.error('删除失败:', error)
ElMessage.error(error.response?.data?.message || '删除失败')
}
}).catch(() => {})
}
// 批量删除
const handleBatchDelete = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请至少选择一条记录')
return
}
ElMessageBox.confirm(`确认删除选中的 ${selectedRows.value.length} 条记录吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
loading.value = true
try {
const deletePromises = selectedRows.value.map(row =>
deleteDiscountCode({ code_id: row.id })
)
const results = await Promise.allSettled(deletePromises)
const successCount = results.filter(r => r.status === 'fulfilled' && r.value?.data?.code === 200).length
const failCount = results.length - successCount
if (failCount === 0) {
ElMessage.success(`批量删除成功,共删除 ${successCount} 条记录`)
} else if (successCount === 0) {
ElMessage.error(`批量删除失败,所有 ${failCount} 条记录删除失败`)
} else {
ElMessage.warning(`批量删除完成,成功 ${successCount} 条,失败 ${failCount}`)
}
fetchVoucherList()
} catch (error) {
console.error('批量删除失败:', error)
ElMessage.error('批量删除操作异常')
} finally {
loading.value = false
}
}).catch(() => {})
}
// 处理日期选择器回车事件
const handleDatePickerEnter = (event) => {
// 回车键确认日期选择
const datePicker = event.target.closest('.el-date-editor')
if (datePicker) {
// 触发失焦事件,确认日期选择
event.target.blur()
}
}
// 提交代金券表单
const submitForm = () => {
voucherFormRef.value?.validate(async (valid) => {
if (valid) {
try {
const submitData = {
discount_type: 'coupon',
name: voucherForm.name,
note: voucherForm.note,
amount: Math.round(voucherForm.amount * 100),
percentage: 0, // 代金券固定为0
min_amount: Math.round(voucherForm.min_amount * 100),
max_amount: Math.round(voucherForm.max_amount * 100),
max_times: voucherForm.max_times || 0,
user_times: voucherForm.user_times || 0,
duration: voucherForm.duration_days * 86400, // 天转秒
renew: voucherForm.renew,
can_stacking: voucherForm.can_stacking,
can_combine: voucherForm.can_combine
}
// 处理时间(转换为秒级时间戳)
if (voucherForm.timeRange && voucherForm.timeRange.length === 2) {
submitData.start_time = timeToTimestamp(voucherForm.timeRange[0])
submitData.end_time = timeToTimestamp(voucherForm.timeRange[1])
} else {
submitData.start_time = ''
submitData.end_time = ''
}
// 如果是编辑,添加code_id
if (dialogType.value === 'edit') {
submitData.code_id = voucherForm.code_id
}
console.log('提交代金券数据:', submitData)
let res
if (dialogType.value === 'add') {
res = await createDiscountCode(submitData)
} else {
res = await updateDiscountCode(submitData)
}
console.log('提交响应:', res.data)
if (res.data.code === 200) {
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '修改成功')
dialogVisible.value = false
fetchVoucherList()
}
} catch (error) {
console.error('操作失败:', error)
ElMessage.error(error.response?.data?.message || '操作失败')
}
}
})
}
// 初始化
onMounted(() => {
fetchVoucherList()
})
</script>
<style scoped>
.voucher-container {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
}
.action-bar {
display: flex;
gap: 12px;
}
.table-container {
border-radius: 8px;
}
.amount {
color: #f56c6c;
font-weight: bold;
font-size: 14px;
}
.form-tip {
font-size: 12px;
color: #909399;
margin-top: 4px;
}
.pagination {
margin-top: 24px;
justify-content: flex-end;
}
</style>
<style>
/* 时间选择器弹出层样式 - 非 scoped */
.voucher-date-picker {
z-index: 9999 !important;
}
.voucher-date-picker .el-picker-panel {
max-width: 90vw;
}
</style>
+581
View File
@@ -0,0 +1,581 @@
<template>
<div class="voucher-history-container">
<!-- 搜索和操作栏 -->
<el-card class="filter-container" shadow="never">
<el-form :inline="true" :model="queryParams" class="search-form">
<el-form-item label="用户">
<div class="user_selector-inline">
<el-tag v-if="queryParams.user_id" type="primary" closable @close="clearQueryUser" style="margin-right: 8px;">
{{ getQueryUserName() }}
</el-tag>
<el-button type="primary" plain @click="openQueryUserSelector" size="default">
<el-icon><User /></el-icon>
{{ queryParams.user_id ? '重新选择' : '选择用户' }}
</el-button>
</div>
</el-form-item>
<el-form-item label="关联信息ID">
<el-input v-model="queryParams.id" placeholder="请输入关联信息ID" clearable style="width: 180px" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>查询
</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div class="action-bar">
<el-button type="success" @click="fetchHistoryList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
</div>
</el-card>
<!-- 使用记录列表 -->
<el-card class="table-container" shadow="never">
<el-table
v-loading="loading"
:data="historyList"
style="width: 100%"
border
stripe
>
<el-table-column prop="id" label="记录ID" width="80" fixed="left" />
<el-table-column prop="user_id" label="用户ID" width="100" />
<el-table-column prop="username" label="用户名" width="150" />
<el-table-column prop="email" label="邮箱" min-width="200" />
<el-table-column prop="discount_id" label="代金券ID" width="120" />
<el-table-column prop="discount_name" label="代金券名称" min-width="180" />
<el-table-column label="优惠金额" width="120">
<template #default="{ row }">
<span class="amount">¥{{ row.discount_amount ? (row.discount_amount / 100).toFixed(2) : '0.00' }}</span>
</template>
</el-table-column>
<el-table-column label="订单金额" width="120">
<template #default="{ row }">
<span>¥{{ row.order_amount ? (row.order_amount / 100).toFixed(2) : '0.00' }}</span>
</template>
</el-table-column>
<el-table-column prop="order_id" label="订单ID" width="150" />
<el-table-column label="使用状态" width="100">
<template #default="{ row }">
<el-tag :type="getStatusType(row.status)">
{{ getStatusText(row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="使用时间" width="180">
<template #default="{ row }">
{{ formatDate(row.used_at) }}
</template>
</el-table-column>
<el-table-column label="创建时间" width="180">
<template #default="{ row }">
{{ formatDate(row.created_at) }}
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip />
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleView(row)">查看详情</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</el-card>
<!-- 详情对话框 -->
<el-dialog
v-model="detailDialogVisible"
title="使用记录详情"
width="800px"
>
<el-descriptions :column="2" border>
<el-descriptions-item label="记录ID">{{ currentDetail.id }}</el-descriptions-item>
<el-descriptions-item label="用户ID">{{ currentDetail.user_id }}</el-descriptions-item>
<el-descriptions-item label="用户名">{{ currentDetail.username }}</el-descriptions-item>
<el-descriptions-item label="邮箱">{{ currentDetail.email }}</el-descriptions-item>
<el-descriptions-item label="代金券ID">{{ currentDetail.discount_id }}</el-descriptions-item>
<el-descriptions-item label="代金券名称">{{ currentDetail.discount_name }}</el-descriptions-item>
<el-descriptions-item label="优惠金额">
<span class="amount">¥{{ currentDetail.discount_amount ? (currentDetail.discount_amount / 100).toFixed(2) : '0.00' }}</span>
</el-descriptions-item>
<el-descriptions-item label="订单金额">
<span>¥{{ currentDetail.order_amount ? (currentDetail.order_amount / 100).toFixed(2) : '0.00' }}</span>
</el-descriptions-item>
<el-descriptions-item label="订单ID" :span="2">{{ currentDetail.order_id || '-' }}</el-descriptions-item>
<el-descriptions-item label="使用状态">
<el-tag :type="getStatusType(currentDetail.status)">
{{ getStatusText(currentDetail.status) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="使用时间">{{ formatDate(currentDetail.used_at) }}</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">{{ formatDate(currentDetail.created_at) }}</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">{{ formatDate(currentDetail.updated_at) }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ currentDetail.remark || '-' }}</el-descriptions-item>
</el-descriptions>
<template #footer>
<el-button type="primary" @click="detailDialogVisible = false">确定</el-button>
</template>
</el-dialog>
<!-- 用户选择弹窗 -->
<el-dialog
v-model="userSelectorVisible"
title="选择用户"
width="800px"
class="user-selector-dialog"
>
<!-- 搜索栏 -->
<div class="selector-search">
<el-input
v-model="userSearchParams.key"
placeholder="搜索用户名或ID"
clearable
@keyup.enter="searchUsers"
style="width: 300px; margin-right: 12px"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
<el-button type="primary" @click="searchUsers">
<el-icon><Search /></el-icon>
搜索
</el-button>
<el-button @click="resetUserSearch">重置</el-button>
</div>
<!-- 用户表格 -->
<el-table
v-loading="userSelectorLoading"
:data="userSelectorList"
highlight-current-row
@current-change="handleUserSelectChange"
style="width: 100%; margin-top: 16px"
:height="400"
>
<el-table-column type="index" label="序号" width="60" />
<el-table-column prop="UserId" label="用户ID" width="100" />
<el-table-column prop="UserName" label="用户名" min-width="150" />
<el-table-column prop="Email" label="邮箱" min-width="180" />
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.Status === 1 ? 'success' : 'danger'" size="small">
{{ row.Status === 1 ? '正常' : '禁用' }}
</el-tag>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="userSearchParams.page"
v-model:page-size="userSearchParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="userSelectorTotal"
@size-change="handleUserSelectorSizeChange"
@current-change="handleUserSelectorPageChange"
background
class="selector-pagination"
/>
<template #footer>
<el-button @click="userSelectorVisible = false">取消</el-button>
<el-button type="primary" @click="confirmUserSelection" :disabled="!selectedUserTemp">
确定选择
</el-button>
</template>
</el-dialog>
<!-- 统计卡片 -->
<el-row :gutter="20" style="margin-top: 20px">
<el-col :span="6">
<el-card shadow="hover">
<el-statistic title="总使用次数" :value="statistics.totalCount">
<template #suffix></template>
</el-statistic>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover">
<el-statistic title="总优惠金额" :value="(statistics.totalAmount / 100).toFixed(2)">
<template #prefix>¥</template>
</el-statistic>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover">
<el-statistic title="成功使用" :value="statistics.successCount">
<template #suffix></template>
</el-statistic>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover">
<el-statistic title="失败/取消" :value="statistics.failedCount">
<template #suffix></template>
</el-statistic>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { Search, Refresh, Download } from '@element-plus/icons-vue'
import { getUserVoucherHistory, getDiscountCodeList } from '@/api/admin/discount'
import { getUserList } from '@/api/admin/user'
// 查询参数
const queryParams = reactive({
user_id: undefined,
id: '',
page: 1,
count: 10
})
// 状态数据
const loading = ref(false)
const historyList = ref([])
const total = ref(0)
const detailDialogVisible = ref(false)
const currentDetail = ref({})
// const userOptions = ref([])
const discountOptions = ref([])
const selectorType = ref('query')
const userSelectorVisible = ref(false)
const userSelectorList = ref([])
const userSelectorTotal = ref(0)
const userSearchParams = reactive({
key: '',
page: 1,
count: 10
})
const selectedUserTemp = ref(null)
const userSelectorLoading = ref(false)
const UserOptions = ref([])
// 格式化日期
const formatDate = (dateStr) => {
if (!dateStr) return '-'
const date = new Date(dateStr)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
// 获取状态文本
const getStatusText = (status) => {
const statusMap = {
0: '未使用',
1: '已使用',
2: '使用失败',
3: '已取消'
}
return statusMap[status] || '未知'
}
// 获取状态标签类型
const getStatusType = (status) => {
const typeMap = {
0: 'info',
1: 'success',
2: 'danger',
3: 'warning'
}
return typeMap[status] || 'info'
}
// 统计数据
const statistics = computed(() => {
const stats = {
totalCount: historyList.value.length,
totalAmount: 0,
successCount: 0,
failedCount: 0
}
historyList.value.forEach(item => {
if (item.status === 1) {
stats.successCount++
stats.totalAmount += item.discount_amount || 0
} else if (item.status === 2 || item.status === 3) {
stats.failedCount++
}
})
return stats
})
// 获取查询用户名称
const getQueryUserName = () => {
const user = UserOptions.value.find(u => u.UserId === queryParams.user_id)
return user ? `${user.UserName} (ID: ${user.UserId})` : `用户ID: ${queryParams.user_id}`
}
// 获取使用记录列表
const fetchHistoryList = async () => {
loading.value = true
try {
const params = { ...queryParams }
// 清除空参数
Object.keys(params).forEach(key => {
if (params[key] === '' || params[key] === null || params[key] === undefined) {
delete params[key]
}
})
const res = await getUserVoucherHistory(params)
console.log('使用记录数据:', res.data)
if (res.data.code === 200) {
historyList.value = res.data.data?.list || []
total.value = res.data.data?.all_count || 0
} else {
ElMessage.error(res.data.message || '获取使用记录失败')
}
} catch (error) {
console.error('获取使用记录失败:', error)
ElMessage.error('获取使用记录失败')
} finally {
loading.value = false
}
}
// 清除查询用户
const clearQueryUser = () => {
queryParams.user_id = undefined
}
// 重置用户搜索
const resetUserSearch = () => {
userSearchParams.key = ''
userSearchParams.page = 1
// fetchUserSelectorList()
}
// 确认用户选择
const confirmUserSelection = () => {
if (!selectedUserTemp.value) {
ElMessage.warning('请选择一个用户')
return
}
if (selectorType.value === 'query') {
// 查询表单选择
queryParams.user_id = selectedUserTemp.value.UserId
} else {
// 编辑表单选择
editForm.user_id = selectedUserTemp.value.UserId
}
// 将选中的用户添加到 UserOptions 中(如果不存在)
if (!UserOptions.value.find(u => u.UserId === selectedUserTemp.value.UserId)) {
UserOptions.value.push(selectedUserTemp.value)
}
userSelectorVisible.value = false
ElMessage.success('用户选择成功')
}
// 打开查询用户选择器
const openQueryUserSelector = () => {
selectorType.value = 'query'
userSelectorVisible.value = true
selectedUserTemp.value = null
userSearchParams.key = ''
userSearchParams.page = 1
fetchUserSelectorList()
}
// 打开编辑用户选择器
const openEditUserSelector = () => {
selectorType.value = 'edit'
userSelectorVisible.value = true
selectedUserTemp.value = null
userSearchParams.key = ''
userSearchParams.page = 1
fetchUserSelectorList()
}
// 用户选择变化
const handleUserSelectChange = (row) => {
selectedUserTemp.value = row
}
// 搜索用户
const searchUsers = () => {
userSearchParams.page = 1
fetchUserSelectorList()
}
// 用户选择器分页
const handleUserSelectorSizeChange = (size) => {
userSearchParams.count = size
fetchUserSelectorList()
}
const handleUserSelectorPageChange = (page) => {
userSearchParams.page = page
fetchUserSelectorList()
}
// 获取用户选择器列表
const fetchUserSelectorList = async () => {
userSelectorLoading.value = true
try {
const res = await getUserList(userSearchParams)
console.log('用户选择器列表:', res.data)
if (res.data.code === 200) {
userSelectorList.value = res.data.data?.data || []
userSelectorTotal.value = res.data.data?.all_count || 0
}
} catch (error) {
console.error('获取用户列表失败:', error)
ElMessage.error('获取用户列表失败')
} finally {
userSelectorLoading.value = false
}
}
// 查询
const handleQuery = () => {
queryParams.page = 1
fetchHistoryList()
}
// 重置查询
const resetQuery = () => {
queryParams.user_id = undefined
queryParams.discount_id = undefined
queryParams.id = ''
queryParams.page = 1
fetchHistoryList()
}
// 分页
const handleSizeChange = (size) => {
queryParams.count = size
fetchHistoryList()
}
const handleCurrentChange = (page) => {
queryParams.page = page
fetchHistoryList()
}
// 查看详情
const handleView = (row) => {
currentDetail.value = { ...row }
detailDialogVisible.value = true
}
// 导出
const handleExport = () => {
if (historyList.value.length === 0) {
ElMessage.warning('暂无数据可导出')
return
}
ElMessage.info('导出功能开发中...')
}
// 获取用户列表
const fetchUserList = async () => {
try {
const res = await getUserList({
page: 1,
count: 10000,
key: ''
})
UserOptions.value = res.data.data?.data || []
} catch (error) {
console.error('获取用户列表失败:', error)
}
}
// 获取代金券列表
const fetchDiscountList = async () => {
try {
const res = await getDiscountCodeList({
discount_type: 'coupon',
page: 1,
count: 1000
})
if (res.data.code === 200) {
discountOptions.value = res.data.data?.data || []
}
} catch (error) {
console.error('获取代金券列表失败:', error)
}
}
// 初始化
onMounted(() => {
fetchUserList()
fetchDiscountList()
// 默认加载第一页数据
// fetchHistoryList()
})
</script>
<style scoped>
.voucher-history-container {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
}
.search-form {
margin-bottom: 15px;
}
.action-bar {
display: flex;
gap: 12px;
}
.table-container {
border-radius: 8px;
}
.amount {
color: #f56c6c;
font-weight: bold;
font-size: 14px;
}
.pagination {
margin-top: 24px;
justify-content: flex-end;
}
:deep(.el-statistic__head) {
color: #606266;
font-size: 14px;
}
:deep(.el-statistic__number) {
font-size: 24px;
font-weight: bold;
color: #409eff;
}
</style>
+781
View File
@@ -0,0 +1,781 @@
<template>
<div class="voucher-holders-container">
<!-- 搜索和操作栏 -->
<el-card class="filter-container" shadow="never">
<el-form :inline="true" :model="queryParams" class="search-form">
<el-form-item label="用户">
<div class="user-selector-inline">
<el-tag v-if="queryParams.user_id" type="primary" closable @close="clearQueryUser" style="margin-right: 8px">
{{ getQueryUserName() }}
</el-tag>
<el-button type="primary" plain @click="openQueryUserSelector" size="default">
<el-icon><User /></el-icon>
{{ queryParams.user_id ? '重新选择' : '选择用户' }}
</el-button>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>查询
</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div class="action-bar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>添加代金券
</el-button>
<el-button type="success" @click="fetchHoldersList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
</div>
</el-card>
<!-- 拥有者列表 -->
<el-card class="table-container" shadow="never">
<el-table
v-loading="loading"
:data="holdersList"
style="width: 100%"
>
<el-table-column prop="Id" label="ID" width="80" />
<el-table-column prop="UserId" label="用户ID" width="100" />
<el-table-column label="代金券ID" width="120">
<template #default="{ row }">
{{ row.discountId || '-' }}
</template>
</el-table-column>
<el-table-column label="代金券名称" min-width="180">
<template #default="{ row }">
{{ row.discount?.name || '-' }}
</template>
</el-table-column>
<el-table-column label="代金券编码" width="150">
<template #default="{ row }">
{{ row.discount?.code || '-' }}
</template>
</el-table-column>
<el-table-column label="面额" width="120">
<template #default="{ row }">
<span class="amount">¥{{ row.discount?.amount ? (row.discount.amount / 100).toFixed(2) : '0.00' }}</span>
</template>
</el-table-column>
<el-table-column prop="useTimes" label="已使用次数" width="120" />
<el-table-column prop="maxUseTimes" label="最大使用次数" width="120" />
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag :type="getStatusType(row)">
{{ getStatusText(row) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="过期时间" width="180">
<template #default="{ row }">
{{ formatDate(row.expireAt) }}
</template>
</el-table-column>
<el-table-column label="创建时间" width="180">
<template #default="{ row }">
{{ formatDate(row.CreatedAt) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleView(row)">查看详情</el-button>
<el-button type="warning" link @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</el-card>
<!-- 详情对话框 -->
<el-dialog
v-model="detailDialogVisible"
title="拥有者详情"
width="700px"
>
<el-descriptions :column="2" border>
<el-descriptions-item label="记录ID">{{ currentDetail.Id }}</el-descriptions-item>
<el-descriptions-item label="用户ID">{{ currentDetail.UserId }}</el-descriptions-item>
<el-descriptions-item label="代金券ID">{{ currentDetail.discountId }}</el-descriptions-item>
<el-descriptions-item label="代金券编码">{{ currentDetail.discount?.code || '-' }}</el-descriptions-item>
<el-descriptions-item label="代金券名称" :span="2">{{ currentDetail.discount?.name || '-' }}</el-descriptions-item>
<el-descriptions-item label="面额">
<span class="amount">¥{{ currentDetail.discount?.amount ? (currentDetail.discount.amount / 100).toFixed(2) : '0.00' }}</span>
</el-descriptions-item>
<el-descriptions-item label="最低消费">
¥{{ currentDetail.discount?.minAmount ? (currentDetail.discount.minAmount / 100).toFixed(2) : '0.00' }}
</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="getStatusType(currentDetail)">
{{ getStatusText(currentDetail) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="类型">
{{ currentDetail.discount?.type === 'coupon' ? '代金券' : '优惠码' }}
</el-descriptions-item>
<el-descriptions-item label="已使用次数">{{ currentDetail.useTimes || 0 }}</el-descriptions-item>
<el-descriptions-item label="最大使用次数">{{ currentDetail.maxUseTimes || '无限制' }}</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">{{ formatDate(currentDetail.CreatedAt) }}</el-descriptions-item>
<el-descriptions-item label="过期时间" :span="2">{{ formatDate(currentDetail.expireAt) }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ currentDetail.discount?.note || '-' }}</el-descriptions-item>
</el-descriptions>
<template #footer>
<el-button type="primary" @click="detailDialogVisible = false">确定</el-button>
</template>
</el-dialog>
<!-- 添加/编辑代金券对话框 -->
<el-dialog
v-model="editDialogVisible"
:title="dialogType === 'add' ? '添加代金券' : '编辑代金券'"
width="600px"
>
<el-form
ref="editFormRef"
:model="editForm"
:rules="editRules"
label-width="120px"
>
<el-form-item label="用户" prop="user_id" v-if="dialogType === 'add'">
<div class="user-selector-wrapper">
<div class="selected-user-display" v-if="editForm.user_id">
<el-tag type="primary" closable @close="clearEditUser">
{{ getEditUserName() }}
</el-tag>
</div>
<el-button
type="primary"
plain
@click="openEditUserSelector"
style="width: 100%"
>
<el-icon><User /></el-icon>
{{ editForm.user_id ? '重新选择用户' : '选择用户' }}
</el-button>
</div>
</el-form-item>
<el-form-item label="代金券" prop="discount_id">
<el-select v-model="editForm.discount_id" placeholder="请选择代金券" filterable style="width: 100%" >
<el-option v-for="item in discountOptions" :key="item.id" :label="`${item.name} (¥${(item.amount/100).toFixed(2)})`" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="已使用次数" prop="use_times" v-if="dialogType === 'edit'">
<el-input-number v-model="editForm.use_times" :min="0" style="width: 100%" />
</el-form-item>
<el-form-item label="最大使用次数" prop="max_use_times" v-if="dialogType === 'edit'">
<el-input-number v-model="editForm.max_use_times" :min="0" placeholder="0表示无限制" style="width: 100%" />
</el-form-item>
<el-form-item label="过期时间" prop="expire_at" v-if="dialogType === 'edit'">
<el-date-picker
v-model="editForm.expire_at"
type="datetime"
placeholder="选择过期时间"
format="YYYY-MM-DD HH:mm:ss"
:teleported="true"
placement="top-start"
:editable="true"
:clearable="true"
value-format="X"
style="width: 100%"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="editDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitEditForm">确定</el-button>
</template>
</el-dialog>
<!-- 用户选择弹窗 -->
<el-dialog
v-model="userSelectorVisible"
title="选择用户"
width="800px"
class="user-selector-dialog"
>
<!-- 搜索栏 -->
<div class="selector-search">
<el-input
v-model="userSearchParams.key"
placeholder="搜索用户名或ID"
clearable
@keyup.enter="searchUsers"
style="width: 300px; margin-right: 12px"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
<el-button type="primary" @click="searchUsers">
<el-icon><Search /></el-icon>
搜索
</el-button>
<el-button @click="resetUserSearch">重置</el-button>
</div>
<!-- 用户表格 -->
<el-table
v-loading="userSelectorLoading"
:data="userSelectorList"
highlight-current-row
@current-change="handleUserSelectChange"
style="width: 100%; margin-top: 16px"
:height="400"
>
<el-table-column type="index" label="序号" width="60" />
<el-table-column prop="UserId" label="用户ID" width="100" />
<el-table-column prop="UserName" label="用户名" min-width="150" />
<el-table-column prop="Email" label="邮箱" min-width="180" />
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.Status === 1 ? 'success' : 'danger'" size="small">
{{ row.Status === 1 ? '正常' : '禁用' }}
</el-tag>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="userSearchParams.page"
v-model:page-size="userSearchParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="userSelectorTotal"
@size-change="handleUserSelectorSizeChange"
@current-change="handleUserSelectorPageChange"
background
class="selector-pagination"
/>
<template #footer>
<el-button @click="userSelectorVisible = false">取消</el-button>
<el-button type="primary" @click="confirmUserSelection" :disabled="!selectedUserTemp">
确定选择
</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Refresh, Download, Plus, User } from '@element-plus/icons-vue'
import {
getUserVoucherList,
allocateVoucher,
updateUserVoucher,
deleteUserVoucher,
getDiscountCodeList
} from '@/api/admin/discount'
import { getUserList } from '@/api/admin/user'
// 查询参数
const queryParams = reactive({
user_id: undefined,
page: 1,
count: 10
})
// 状态数据
const loading = ref(false)
const holdersList = ref([])
const total = ref(0)
const detailDialogVisible = ref(false)
const currentDetail = ref({})
const UserOptions = ref([])
const editDialogVisible = ref(false)
const dialogType = ref('add')
const editFormRef = ref(null)
const discountOptions = ref([])
// 用户选择弹窗相关
const userSelectorVisible = ref(false)
const userSelectorLoading = ref(false)
const userSelectorList = ref([])
const userSelectorTotal = ref(0)
const selectedUserTemp = ref(null) // 临时存储选中的用户
const selectorType = ref('query') // 'query' 或 'edit' 用于区分是查询还是编辑
const userSearchParams = reactive({
key: '',
page: 1,
count: 10
})
// 编辑表单
const editForm = reactive({
user_id: undefined,
discount_id: undefined,
id: undefined,
use_times: 0,
max_use_times: 0,
expire_at: undefined
})
// 表单验证规则
const editRules = {
user_id: [{ required: true, message: '请选择用户', trigger: 'change' }],
discount_id: [{ required: true, message: '请选择代金券', trigger: 'change' }]
}
// 格式化日期
const formatDate = (dateStr) => {
if (!dateStr || dateStr === '1970-01-01T08:01:40+08:00' || dateStr === '0001-01-01T00:00:00Z') return '-'
const date = new Date(dateStr)
if (isNaN(date.getTime())) return '-'
const year = date.getFullYear()
// 过滤掉1970年的日期(通常是零值)
if (year === 1970) return '-'
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}`
}
// 获取状态文本
const getStatusText = (row) => {
const now = Date.now()
const expireAt = row.expireAt ? new Date(row.expireAt).getTime() : 0
// 检查是否已用完
if (row.useTimes >= row.maxUseTimes && row.maxUseTimes > 0) {
return '已用完'
}
// 检查是否已过期(排除1970年的零值日期)
if (expireAt > 0 && expireAt < now && new Date(row.expireAt).getFullYear() !== 1970) {
return '已过期'
}
return '可使用'
}
// 获取状态标签类型
const getStatusType = (row) => {
const now = Date.now()
const expireAt = row.expireAt ? new Date(row.expireAt).getTime() : 0
if (row.useTimes >= row.maxUseTimes && row.maxUseTimes > 0) {
return 'info'
}
if (expireAt > 0 && expireAt < now && new Date(row.expireAt).getFullYear() !== 1970) {
return 'warning'
}
return 'success'
}
// 获取拥有者列表
const fetchHoldersList = async () => {
if (!queryParams.user_id) {
ElMessage.warning('请选择用户ID进行查询')
return
}
loading.value = true
try {
const params = { ...queryParams }
// 清除空参数
Object.keys(params).forEach(key => {
if (params[key] === '' || params[key] === null || params[key] === undefined) {
delete params[key]
}
})
const res = await getUserVoucherList(params)
console.log('拥有者列表数据:', res.data)
if (res.data.code === 200) {
holdersList.value = res.data.data?.data || []
total.value = res.data.data?.all_count || 0
console.log('解析后的列表数据:', holdersList.value)
}
} catch (error) {
console.error('获取拥有者列表失败:', error)
ElMessage.error('获取拥有者列表失败')
} finally {
loading.value = false
}
}
// 查询
const handleQuery = () => {
queryParams.page = 1
fetchHoldersList()
}
// 重置查询
const resetQuery = () => {
queryParams.code_id = ''
queryParams.username = ''
queryParams.page = 1
holdersList.value = []
total.value = 0
}
// 分页
const handleSizeChange = (size) => {
queryParams.count = size
fetchHoldersList()
}
const handleCurrentChange = (page) => {
queryParams.page = page
fetchHoldersList()
}
// 查看详情
const handleView = (row) => {
currentDetail.value = { ...row }
detailDialogVisible.value = true
}
// 导出
const handleExport = () => {
ElMessage.info('导出功能开发中...')
}
// 获取用户列表
const fetchUserList = async () => {
const res = await getUserList({
page: 1,
count: 10000,
key: ''
})
UserOptions.value = res.data.data?.data || []
}
// 打开查询用户选择器
const openQueryUserSelector = () => {
selectorType.value = 'query'
userSelectorVisible.value = true
selectedUserTemp.value = null
userSearchParams.key = ''
userSearchParams.page = 1
fetchUserSelectorList()
}
// 打开编辑用户选择器
const openEditUserSelector = () => {
selectorType.value = 'edit'
userSelectorVisible.value = true
selectedUserTemp.value = null
userSearchParams.key = ''
userSearchParams.page = 1
fetchUserSelectorList()
}
// 获取用户选择器列表
const fetchUserSelectorList = async () => {
userSelectorLoading.value = true
try {
const res = await getUserList(userSearchParams)
console.log('用户选择器列表:', res.data)
if (res.data.code === 200) {
userSelectorList.value = res.data.data?.data || []
userSelectorTotal.value = res.data.data?.all_count || 0
}
} catch (error) {
console.error('获取用户列表失败:', error)
ElMessage.error('获取用户列表失败')
} finally {
userSelectorLoading.value = false
}
}
// 搜索用户
const searchUsers = () => {
userSearchParams.page = 1
fetchUserSelectorList()
}
// 重置用户搜索
const resetUserSearch = () => {
userSearchParams.key = ''
userSearchParams.page = 1
fetchUserSelectorList()
}
// 用户选择变化
const handleUserSelectChange = (row) => {
selectedUserTemp.value = row
}
// 用户选择器分页
const handleUserSelectorSizeChange = (size) => {
userSearchParams.count = size
fetchUserSelectorList()
}
const handleUserSelectorPageChange = (page) => {
userSearchParams.page = page
fetchUserSelectorList()
}
// 确认用户选择
const confirmUserSelection = () => {
if (!selectedUserTemp.value) {
ElMessage.warning('请选择一个用户')
return
}
if (selectorType.value === 'query') {
// 查询表单选择
queryParams.user_id = selectedUserTemp.value.UserId
} else {
// 编辑表单选择
editForm.user_id = selectedUserTemp.value.UserId
}
// 将选中的用户添加到 UserOptions 中(如果不存在)
if (!UserOptions.value.find(u => u.UserId === selectedUserTemp.value.UserId)) {
UserOptions.value.push(selectedUserTemp.value)
}
userSelectorVisible.value = false
ElMessage.success('用户选择成功')
}
// 清除查询用户
const clearQueryUser = () => {
queryParams.user_id = undefined
}
// 清除编辑用户
const clearEditUser = () => {
editForm.user_id = undefined
}
// 获取查询用户名称
const getQueryUserName = () => {
const user = UserOptions.value.find(u => u.UserId === queryParams.user_id)
return user ? `${user.UserName} (ID: ${user.UserId})` : `用户ID: ${queryParams.user_id}`
}
// 获取编辑用户名称
const getEditUserName = () => {
const user = UserOptions.value.find(u => u.UserId === editForm.user_id)
return user ? `${user.UserName} (ID: ${user.UserId})` : `用户ID: ${editForm.user_id}`
}
// 获取代金券列表
const fetchDiscountList = async () => {
try {
const res = await getDiscountCodeList({
discount_type: 'coupon',
page: 1,
count: 1000
})
if (res.data.code === 200) {
discountOptions.value = res.data.data?.data || []
}
} catch (error) {
console.error('获取代金券列表失败:', error)
}
}
// 添加代金券
const handleAdd = () => {
dialogType.value = 'add'
editDialogVisible.value = true
Object.assign(editForm, {
user_id: queryParams.user_id,
discount_id: undefined,
id: undefined,
use_times: 0,
max_use_times: 0,
expire_at: undefined
})
editFormRef.value?.resetFields()
}
// 编辑
const handleEdit = (row) => {
dialogType.value = 'edit'
editDialogVisible.value = true
// 处理过期时间
let expireTime = undefined
if (row.expireAt && row.expireAt !== '1970-01-01T08:01:40+08:00' && row.expireAt !== '0001-01-01T00:00:00Z') {
const date = new Date(row.expireAt)
if (!isNaN(date.getTime()) && date.getFullYear() !== 1970) {
expireTime = Math.floor(date.getTime() / 1000)
}
}
Object.assign(editForm, {
user_id: row.UserId,
discount_id: row.discountId,
id: row.Id,
use_times: row.useTimes || 0,
max_use_times: row.maxUseTimes || 0,
expire_at: expireTime
})
}
// 删除
const handleDelete = (row) => {
const discountName = row.discount?.name || '该代金券'
ElMessageBox.confirm(`确认删除用户ID ${row.UserId} 的代金券 ${discountName} 吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deleteUserVoucher({
user_id: row.UserId,
id: row.Id
})
if (res.data.code === 200) {
ElMessage.success('删除成功')
fetchHoldersList()
} else {
ElMessage.error(res.data.message || '删除失败')
}
} catch (error) {
console.error('删除失败:', error)
ElMessage.error(error.response?.data?.message || '删除失败')
}
}).catch(() => {})
}
// 提交表单
const submitEditForm = () => {
editFormRef.value?.validate(async (valid) => {
if (valid) {
try {
let res
if (dialogType.value === 'add') {
// 使用 allocateVoucher 接口为用户添加代金券
res = await allocateVoucher({
user_id: editForm.user_id,
code_id: editForm.discount_id
})
} else {
res = await updateUserVoucher({
user_id: editForm.user_id,
id: editForm.id,
discount_id: editForm.discount_id,
use_times: editForm.use_times,
max_use_times: editForm.max_use_times,
expire_at: editForm.expire_at
})
}
if (res.data.code === 200) {
ElMessage.success(dialogType.value === 'add' ? '添加成功' : '修改成功')
editDialogVisible.value = false
fetchHoldersList()
} else {
ElMessage.error(res.data.message || '操作失败')
}
} catch (error) {
console.error('操作失败:', error)
ElMessage.error(error.response?.data?.message || '操作失败')
}
}
})
}
// 初始化
onMounted(() => {
fetchUserList()
fetchDiscountList()
if (queryParams.user_id) {
fetchHoldersList()
}
})
</script>
<style scoped>
.voucher-holders-container {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
}
.search-form {
margin-bottom: 15px;
}
.action-bar {
display: flex;
gap: 12px;
}
.table-container {
border-radius: 8px;
}
.amount {
color: #f56c6c;
font-weight: bold;
font-size: 14px;
}
.pagination {
margin-top: 24px;
justify-content: flex-end;
}
/* 用户选择器样式 */
.user-selector-inline {
display: flex;
align-items: center;
}
.user-selector-wrapper {
width: 100%;
}
.selected-user-display {
margin-bottom: 12px;
padding: 8px 12px;
background: #f5f7fa;
border-radius: 4px;
display: flex;
align-items: center;
gap: 8px;
}
.user-selector-dialog .selector-search {
display: flex;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #ebeef5;
}
.selector-pagination {
margin-top: 16px;
justify-content: flex-end;
}
:deep(.el-table__row) {
cursor: pointer;
}
:deep(.el-table__row):hover {
background-color: #f5f7fa;
}
:deep(.current-row) {
background-color: #ecf5ff !important;
}
</style>
+584
View File
@@ -0,0 +1,584 @@
<template>
<div class="order-list-container">
<!-- 搜索和操作栏 -->
<el-card class="filter-container" shadow="never">
<!-- <el-form :inline="true" :model="queryParams" class="search-form">
<el-form-item label="订单号">
<el-input v-model="queryParams.order_no" placeholder="请输入订单号" clearable style="width: 200px" />
</el-form-item>
<el-form-item label="用户ID">
<el-input v-model="queryParams.user_id" placeholder="请输入用户ID" clearable style="width: 150px" />
</el-form-item>
<el-form-item label="订单状态">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 150px">
<el-option label="待支付" value="0" />
<el-option label="已支付" value="1" />
<el-option label="已完成" value="2" />
<el-option label="已取消" value="3" />
</el-select>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker
v-model="queryParams.dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>查询
</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form> -->
<div class="action-bar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增订单
</el-button>
<el-button type="success" @click="fetchOrderList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
<!-- <el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
<el-icon><Delete /></el-icon>批量删除
</el-button> -->
<!-- <el-button type="success">
<el-icon><Download /></el-icon>导出订单
</el-button> -->
</div>
</el-card>
<!-- 订单列表 -->
<el-card class="table-container" shadow="never">
<el-table
v-loading="loading"
:data="orderList"
@selection-change="handleSelectionChange"
style="width: 100%"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="id" label="订单ID" width="100" />
<el-table-column prop="name" label="订单名称" min-width="180" />
<el-table-column prop="userId" label="用户ID" width="100" />
<el-table-column prop="commodityId" label="商品ID" width="100" />
<el-table-column label="表名" width="120">
<template #default="{ row }">
<el-tag size="small">{{ row.table || '未知' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="订单金额" width="120">
<template #default="{ row }">
<span class="amount">¥{{ (row.price / 100).toFixed(2) }}</span>
</template>
</el-table-column>
<el-table-column label="续费价格" width="120">
<template #default="{ row }">
<span class="renew-price">¥{{ (row.renewPrice / 100).toFixed(2) }}</span>
</template>
</el-table-column>
<el-table-column label="数量" width="80">
<template #default="{ row }">
<span>{{ row.payNum }}</span>
</template>
</el-table-column>
<el-table-column label="订单状态" width="100">
<template #default="{ row }">
<el-tag :type="getStatusType(row.state)">
{{ getStatusText(row.state) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="支付方式" width="100">
<template #default="{ row }">
<span>{{ row.payType || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="过期时间" width="170">
<template #default="{ row }">
<span>{{ formatDate(row.expireTime) }}</span>
</template>
</el-table-column>
<el-table-column label="创建时间" width="170">
<template #default="{ row }">
<span>{{ formatDate(row.CreatedAt) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleView(row)">查看</el-button>
<el-button type="warning" link @click="handleEdit(row)">编辑</el-button>
<!-- <el-button type="danger" link @click="handleDelete(row)">删除</el-button> -->
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</el-card>
<!-- 订单详情对话框 -->
<el-dialog
v-model="detailDialogVisible"
title="订单详情"
width="800px"
>
<el-descriptions :column="2" border v-if="orderDetail">
<el-descriptions-item label="订单ID">{{ orderDetail.id }}</el-descriptions-item>
<el-descriptions-item label="订单名称">{{ orderDetail.name }}</el-descriptions-item>
<el-descriptions-item label="用户ID">{{ orderDetail.userId }}</el-descriptions-item>
<el-descriptions-item label="商品ID">{{ orderDetail.commodityId }}</el-descriptions-item>
<el-descriptions-item label="表名">{{ orderDetail.table }}</el-descriptions-item>
<el-descriptions-item label="数量">{{ orderDetail.payNum }}</el-descriptions-item>
<el-descriptions-item label="订单金额">¥{{ (orderDetail.price / 100).toFixed(2) }}</el-descriptions-item>
<el-descriptions-item label="续费价格">¥{{ (orderDetail.renewPrice / 100).toFixed(2) }}</el-descriptions-item>
<el-descriptions-item label="订单状态">
<el-tag :type="getStatusType(orderDetail.state)">
{{ getStatusText(orderDetail.state) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="支付方式">{{ orderDetail.payType || '-' }}</el-descriptions-item>
<el-descriptions-item label="过期时间">{{ formatDate(orderDetail.expireTime) }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ formatDate(orderDetail.CreatedAt) }}</el-descriptions-item>
<el-descriptions-item label="更新时间">{{ formatDate(orderDetail.UpdatedAt) }}</el-descriptions-item>
<el-descriptions-item label="参数信息">{{ orderDetail.args || '-' }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ orderDetail.note || '无' }}</el-descriptions-item>
</el-descriptions>
</el-dialog>
<!-- 订单表单对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增订单' : '编辑订单'"
width="700px"
>
<el-form
ref="orderFormRef"
:model="orderForm"
:rules="orderRules"
label-width="120px"
>
<el-form-item label="订单名称" prop="name">
<el-input v-model="orderForm.name" placeholder="请输入订单名称" />
</el-form-item>
<el-form-item label="所属表" prop="table">
<el-input v-model="orderForm.table" placeholder="请输入所属表" />
</el-form-item>
<el-form-item label="用户ID" prop="user_id">
<el-input-number v-model="orderForm.user_id" :min="1" placeholder="请输入用户ID" style="width: 100%" />
</el-form-item>
<el-form-item label="商品ID" prop="commodity_id">
<el-input-number v-model="orderForm.commodity_id" :min="0" placeholder="请输入商品ID" style="width: 100%" />
</el-form-item>
<el-form-item label="购买数量" prop="pay_num">
<el-input-number v-model="orderForm.pay_num" :min="1" placeholder="请输入数量" style="width: 100%" />
</el-form-item>
<el-form-item label="价格(分)" prop="price">
<el-input-number v-model="orderForm.price" :min="0" placeholder="请输入价格(分)" style="width: 100%" />
</el-form-item>
<el-form-item label="续费价格(分)" prop="renew_price">
<el-input-number v-model="orderForm.renew_price" :min="0" placeholder="请输入续费价格(分)" style="width: 100%" />
</el-form-item>
<el-form-item label="过期时间" prop="expire_time">
<el-input-number v-model="orderForm.expire_time" :min="0" placeholder="请输入过期时间(时间戳)" style="width: 100%" />
</el-form-item>
<el-form-item label="优惠码ID" prop="discount_code_id">
<el-input-number v-model="orderForm.discount_code_id" :min="0" placeholder="请输入优惠码ID" style="width: 100%" />
</el-form-item>
<el-form-item label="代金券ID" prop="coupon_id">
<el-input-number v-model="orderForm.coupon_id" :min="0" placeholder="请输入代金券ID (必填)" style="width: 100%" />
</el-form-item>
<el-form-item label="订单状态" prop="state">
<el-radio-group v-model="orderForm.state">
<el-radio :label="0">待支付</el-radio>
<el-radio :label="1">已支付</el-radio>
<el-radio :label="2">已失效</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="支付方式" prop="pay_type">
<el-input v-model="orderForm.pay_type" placeholder="请输入支付类型" />
</el-form-item>
<el-form-item label="订单参数" prop="args">
<el-input v-model="orderForm.args" placeholder="请输入订单参数" />
</el-form-item>
<el-form-item label="备注" prop="note">
<el-input v-model="orderForm.note" type="textarea" :rows="3" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Delete, Search, Download, Refresh } from '@element-plus/icons-vue'
import { getOrderList, getOrderDetail, createOrder, updateOrder, deleteOrder } from '@/api/admin/order'
// 查询参数
const queryParams = reactive({
page: 1,
count: 10
})
// 订单表单
const orderForm = reactive({
order_id: undefined,
name: '',
table: '',
user_id: undefined,
commodity_id: 0,
pay_num: 1,
price: 0,
renew_price: 0,
expire_time: 0,
discount_code_id: 0,
coupon_id: 0,
state: 0,
pay_type: '',
args: '',
note: ''
})
const orderRules = {
name: [
{ required: true, message: '请输入订单名称', trigger: 'blur' }
],
table: [
{ required: true, message: '请输入所属表', trigger: 'blur' }
],
user_id: [
{ required: true, message: '请输入用户ID', trigger: 'blur' },
{ type: 'number', message: '用户ID必须是数字', trigger: 'blur' }
],
coupon_id: [
{ required: true, message: '请输入代金券ID', trigger: 'blur' },
{ type: 'number', message: '代金券ID必须是数字', trigger: 'blur' }
],
pay_num: [
{ required: true, message: '请输入购买数量', trigger: 'blur' }
],
price: [
{ required: true, message: '请输入价格', trigger: 'blur' }
]
}
// 状态数据
const loading = ref(false)
const orderList = ref([])
const orderDetail = ref(null)
const total = ref(0)
const selectedRows = ref([])
const dialogVisible = ref(false)
const detailDialogVisible = ref(false)
const dialogType = ref('add')
const orderFormRef = ref(null)
// 获取订单列表
const fetchOrderList = async () => {
loading.value = true
try {
const res = await getOrderList(queryParams)
console.log('订单列表数据:', res.data)
if (res.data.code === 200) {
orderList.value = res.data.data.list || []
total.value = res.data.data.all_count || 0
}
} catch (error) {
console.error('获取订单列表失败:', error)
ElMessage.error('获取订单列表失败')
} finally {
loading.value = false
}
}
// 格式化日期
const formatDate = (dateStr) => {
if (!dateStr) return '-'
const date = new Date(dateStr)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}`
}
// 获取订单状态类型
const getStatusType = (status) => {
const statusMap = {
0: 'warning', // 待支付
1: 'success', // 已支付
2: 'info', // 已失效
}
return statusMap[status] || 'info'
}
// 获取订单状态文本
// state 0:未支付 1:已支付 2:已失效
const getStatusText = (status) => {
const statusMap = {
0: '待支付',
1: '已支付',
2: '已失效'
}
return statusMap[status] || '未知'
}
// 查询
const handleQuery = () => {
queryParams.page = 1
fetchOrderList()
}
// 重置查询
const resetQuery = () => {
queryParams.order_no = ''
queryParams.user_id = ''
queryParams.status = ''
queryParams.dateRange = []
queryParams.page = 1
fetchOrderList()
}
// 选择项变化
const handleSelectionChange = (selection) => {
selectedRows.value = selection
}
// 分页
const handleSizeChange = (size) => {
queryParams.count = size
fetchOrderList()
}
const handleCurrentChange = (page) => {
queryParams.page = page
fetchOrderList()
}
// 新增订单
const handleAdd = () => {
dialogType.value = 'add'
dialogVisible.value = true
Object.assign(orderForm, {
order_id: undefined,
name: '',
table: '',
user_id: undefined,
commodity_id: 0,
pay_num: 1,
price: 0,
renew_price: 0,
expire_time: 0,
discount_code_id: 0,
coupon_id: 0,
state: 0,
pay_type: '',
args: '',
note: ''
})
orderFormRef.value?.resetFields()
}
// 查看订单详情
const handleView = async (row) => {
try {
const res = await getOrderDetail({ order_id: row.id })
if (res.data.code === 200) {
orderDetail.value = res.data.data
detailDialogVisible.value = true
}
} catch (error) {
console.error('获取订单详情失败:', error)
ElMessage.error('获取订单详情失败')
}
}
// 编辑订单
const handleEdit = (row) => {
dialogType.value = 'edit'
dialogVisible.value = true
Object.assign(orderForm, {
order_id: row.id,
name: row.name,
table: row.table,
user_id: row.userId,
commodity_id: row.commodityId,
pay_num: row.payNum,
price: row.price,
renew_price: row.renewPrice,
expire_time: row.expireTime ? new Date(row.expireTime).getTime() / 1000 : 0,
discount_code_id: 0, // 从详情接口获取
coupon_id: 0, // 从详情接口获取
state: row.state,
pay_type: row.payType || '',
args: row.args || '',
note: row.note || ''
})
}
// 删除订单
const handleDelete = (row) => {
ElMessageBox.confirm(`确认删除订单 ${row.name} 吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deleteOrder({ id: row.id })
if (res.data.code === 200) {
ElMessage.success('删除成功')
fetchOrderList()
}
} catch (error) {
console.error('删除失败:', error)
ElMessage.error(error.response?.data?.message || '删除失败')
}
}).catch(() => {})
}
// 批量删除
const handleBatchDelete = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请至少选择一条记录')
return
}
ElMessageBox.confirm(`确认删除选中的 ${selectedRows.value.length} 条记录吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
ElMessage.success('批量删除成功')
fetchOrderList()
}).catch(() => {})
}
// 提交表单
const submitForm = () => {
orderFormRef.value?.validate(async (valid) => {
if (valid) {
try {
// 准备提交的数据
const submitData = {
name: orderForm.name,
table: orderForm.table,
user_id: Number(orderForm.user_id),
commodity_id: Number(orderForm.commodity_id),
pay_num: Number(orderForm.pay_num),
price: Number(orderForm.price),
renew_price: Number(orderForm.renew_price),
expire_time: Number(orderForm.expire_time),
discount_code_id: Number(orderForm.discount_code_id),
coupon_id: Number(orderForm.coupon_id),
state: Number(orderForm.state),
pay_type: orderForm.pay_type || '',
args: orderForm.args || '',
note: orderForm.note || ''
}
// 如果是编辑,添加order_id
if (dialogType.value === 'edit') {
submitData.order_id = Number(orderForm.order_id)
}
console.log('提交订单数据:', submitData)
let res
if (dialogType.value === 'add') {
res = await createOrder(submitData)
} else {
res = await updateOrder(submitData)
}
if (res.data.code === 200) {
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '修改成功')
dialogVisible.value = false
fetchOrderList()
}
} catch (error) {
console.error('操作失败:', error)
ElMessage.error(error.response?.data?.message || '操作失败')
}
}
})
}
// 初始化
onMounted(() => {
fetchOrderList()
})
</script>
<style scoped>
.order-list-container {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
}
.search-form {
margin-bottom: 15px;
}
.action-bar {
display: flex;
gap: 12px;
}
.table-container {
border-radius: 8px;
}
.user-info {
padding: 4px 0;
}
.username {
font-size: 14px;
font-weight: 500;
margin-bottom: 4px;
}
.user-id {
font-size: 12px;
color: #999;
}
.amount {
color: #f56c6c;
font-weight: bold;
font-size: 14px;
}
.renew-price {
color: #409eff;
font-weight: 500;
font-size: 14px;
}
.pagination {
margin-top: 24px;
justify-content: flex-end;
}
</style>
+275
View File
@@ -0,0 +1,275 @@
<template>
<div class="product-group-container">
<!-- 操作栏 -->
<el-card class="filter-container" shadow="never">
<div class="action-bar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增商品分组
</el-button>
<el-button type="success" @click="fetchGroupList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
</div>
</el-card>
<!-- 商品分组列表 -->
<el-card class="table-container" shadow="never">
<el-table
v-loading="loading"
:data="groupList"
style="width: 100%"
>
<el-table-column prop="id" label="分组ID" width="100" />
<el-table-column prop="name" label="分组名称" min-width="200" />
<el-table-column prop="note" label="备注" min-width="250" />
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-switch
v-model="row.disable"
:active-value="false"
:inactive-value="true"
@change="(val) => handleStatusChange(row, val)"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</el-card>
<!-- 商品分组表单对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增商品分组' : '编辑商品分组'"
width="600px"
>
<el-form
ref="groupFormRef"
:model="groupForm"
:rules="groupRules"
label-width="100px"
>
<el-form-item label="分组名称" prop="name">
<el-input v-model="groupForm.name" placeholder="请输入分组名称" />
</el-form-item>
<el-form-item label="备注" prop="note">
<el-input v-model="groupForm.note" type="textarea" :rows="4" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="状态" prop="disable">
<el-radio-group v-model="groupForm.disable">
<el-radio :label="false">启用</el-radio>
<el-radio :label="true">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Refresh } from '@element-plus/icons-vue'
import {
getProductGroupList,
createProductGroup,
updateProductGroup,
deleteProductGroup,
hideProductGroup,
startProductGroup
} from '@/api/admin/product'
// 查询参数
const queryParams = reactive({
page: 1,
count: 10
})
// 商品分组表单
const groupForm = reactive({
id: undefined,
name: '',
note: '',
disable: false
})
const groupRules = {
name: [
{ required: true, message: '请输入分组名称', trigger: 'blur' }
]
}
// 状态数据
const loading = ref(false)
const groupList = ref([])
const total = ref(0)
const dialogVisible = ref(false)
const dialogType = ref('add')
const groupFormRef = ref(null)
// 获取商品分组列表
const fetchGroupList = async () => {
loading.value = true
try {
const res = await getProductGroupList(queryParams)
if (res.data.code === 200) {
groupList.value = res.data.data.data || []
total.value = res.data.data.total || 0
}
} catch (error) {
ElMessage.error('获取商品分组列表失败')
} finally {
loading.value = false
}
}
// 分页
const handleSizeChange = (size) => {
queryParams.count = size
fetchGroupList()
}
const handleCurrentChange = (page) => {
queryParams.page = page
fetchGroupList()
}
// 新增商品分组
const handleAdd = () => {
dialogType.value = 'add'
dialogVisible.value = true
Object.assign(groupForm, {
id: undefined,
name: '',
note: '',
disable: false
})
groupFormRef.value?.resetFields()
}
// 编辑商品分组
const handleEdit = (row) => {
dialogType.value = 'edit'
dialogVisible.value = true
Object.assign(groupForm, {
id: row.id,
name: row.name,
note: row.note,
disable: row.disable
})
}
// 状态变化
const handleStatusChange = async (row, disable) => {
try {
let res
if (disable === false) {
// 启用
res = await startProductGroup({ id: row.id })
} else {
// 禁用
res = await hideProductGroup({ id: row.id })
}
if (res.data.code === 200) {
ElMessage.success('状态修改成功')
}
} catch (error) {
ElMessage.error('状态修改失败')
row.disable = !disable // 恢复原状态
}
}
// 删除商品分组
const handleDelete = (row) => {
ElMessageBox.confirm(`确认删除商品分组 ${row.name} 吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deleteProductGroup({ id: row.id })
if (res.data.code === 200) {
ElMessage.success('删除成功')
fetchGroupList()
}
} catch (error) {
ElMessage.error('删除失败')
}
}).catch(() => {})
}
// 提交表单
const submitForm = () => {
groupFormRef.value?.validate(async (valid) => {
if (valid) {
try {
let res
if (dialogType.value === 'add') {
res = await createProductGroup(groupForm)
} else {
res = await updateProductGroup(groupForm)
}
if (res.data.code === 200) {
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '修改成功')
dialogVisible.value = false
fetchGroupList()
}
} catch (error) {
ElMessage.error('操作失败')
}
}
})
}
// 初始化
onMounted(() => {
fetchGroupList()
})
</script>
<style scoped>
.product-group-container {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
}
.action-bar {
display: flex;
gap: 12px;
}
.table-container {
border-radius: 8px;
}
.pagination {
margin-top: 24px;
justify-content: flex-end;
}
</style>
+497
View File
@@ -0,0 +1,497 @@
<template>
<div class="product-list-container">
<!-- 搜索和操作栏 -->
<el-card class="filter-container" shadow="never">
<el-form :inline="true" :model="queryParams" class="search-form">
<el-form-item label="商品分组">
<el-select v-model="queryParams.good_group_id" placeholder="请选择分组" clearable style="width: 200px">
<el-option
v-for="item in groupOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>查询
</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div class="action-bar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增商品
</el-button>
<el-button type="success" @click="fetchProductList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
<el-icon><Delete /></el-icon>批量删除
</el-button>
</div>
</el-card>
<!-- 商品列表 -->
<el-card class="table-container" shadow="never">
<el-table
v-loading="loading"
:data="productList"
@selection-change="handleSelectionChange"
style="width: 100%"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="id" label="商品ID" width="100" />
<el-table-column label="商品图片" width="100">
<template #default="{ row }">
<el-image
:src="row.image || '/logo.svg'"
fit="cover"
style="width: 60px; height: 60px; border-radius: 4px"
/>
</template>
</el-table-column>
<el-table-column prop="name" label="商品名称" min-width="200" />
<el-table-column prop="table" label="商品所属表" width="150" />
<el-table-column label="价格" width="120">
<template #default="{ row }">
<span class="price">¥{{ (row.price / 100).toFixed(2) }}</span>
</template>
</el-table-column>
<el-table-column label="库存控制" width="100">
<template #default="{ row }">
<el-tag :type="row.inventory_control ? 'success' : 'info'">
{{ row.inventory_control ? '已启用' : '未启用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="inventory" label="库存" width="100" />
<el-table-column prop="payNum" label="单次数量" width="100" />
<el-table-column label="推荐" width="80">
<template #default="{ row }">
<el-tag :type="row.recommend ? 'success' : 'info'" size="small">
{{ row.recommend ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<!-- <el-button type="warning" link @click="handleSpec(row)">规格</el-button> -->
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</el-card>
<!-- 商品表单对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增商品' : '编辑商品'"
width="700px"
>
<el-form
ref="productFormRef"
:model="productForm"
:rules="productRules"
label-width="120px"
>
<el-form-item label="商品名称" prop="name">
<el-input v-model="productForm.name" placeholder="请输入商品名称" />
</el-form-item>
<el-form-item label="商品分组" prop="good_group_id">
<el-select v-model="productForm.good_group_id" placeholder="请选择商品分组" style="width: 100%">
<el-option
v-for="item in groupOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="商品所属表" prop="table">
<el-input v-model="productForm.table" placeholder="请输入商品所属表" />
</el-form-item>
<el-form-item label="内容" prop="content">
<el-input v-model="productForm.content" type="textarea" :rows="4" placeholder="请输入内容" />
</el-form-item>
<el-form-item label="封面ID" prop="cover_id">
<el-input-number v-model="productForm.cover_id" :min="0" placeholder="请输入封面ID" style="width: 100%" />
</el-form-item>
<el-form-item label="库存控制" prop="inventory_control">
<el-switch v-model="productForm.inventory_control" active-text="启用" inactive-text="禁用" />
</el-form-item>
<el-form-item label="库存数量" prop="inventory">
<el-input-number v-model="productForm.inventory" :min="0" placeholder="请输入库存" style="width: 100%" />
</el-form-item>
<el-form-item label="商品价格(分)" prop="price">
<el-input-number v-model="productForm.price" :min="0" placeholder="请输入价格(分)" style="width: 100%" />
</el-form-item>
<el-form-item label="单个商品数量" prop="pay_num">
<el-input-number v-model="productForm.pay_num" :min="1" placeholder="请输入单个商品数量" style="width: 100%" />
</el-form-item>
<el-form-item label="有效期(天)" prop="expire_time">
<el-input-number v-model="productForm.expire_time" :min="0" placeholder="请输入有效期" style="width: 100%" />
</el-form-item>
<el-form-item label="推荐" prop="recommend">
<el-switch v-model="productForm.recommend" active-text="启用" inactive-text="禁用" />
</el-form-item>
<el-form-item label="推荐返还(%)" prop="recommend_rebate">
<el-input-number v-model="productForm.recommend_rebate" :min="0" :max="100" placeholder="请输入返还百分比" style="width: 100%" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { getFileDetail } from '@/api/admin/file'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Delete, Search, Refresh } from '@element-plus/icons-vue'
import { getProductList, createProduct, updateProduct, deleteProduct } from '@/api/admin/product'
import { getProductGroupList } from '@/api/admin/product'
// 查询参数
const queryParams = reactive({
good_group_id: '',
page: 1,
count: 10
})
// 商品表单
const productForm = reactive({
id: undefined,
name: '',
table: '',
content: '',
cover_id: undefined,
good_group_id: undefined, // 添加商品分组字段
inventory_control: false,
inventory: 0,
price: 0,
pay_num: 1,
expire_time: 0,
recommend: false,
recommend_rebate: 0
})
const productRules = {
name: [
{ required: true, message: '请输入商品名称', trigger: 'blur' }
],
table: [
{ required: true, message: '请输入商品所属表', trigger: 'blur' }
],
content: [
{ required: true, message: '请输入商品内容', trigger: 'blur' }
],
good_group_id: [
{ required: true, message: '请选择商品分组', trigger: 'change' }
],
price: [
{ required: true, message: '请输入商品价格', trigger: 'blur' },
{ type: 'number', message: '请输入数字', trigger: 'blur' }
]
}
// 状态数据
const loading = ref(false)
const productList = ref([])
const groupOptions = ref([])
const total = ref(0)
const selectedRows = ref([])
const dialogVisible = ref(false)
const dialogType = ref('add')
const productFormRef = ref(null)
// 获取商品列表
const fetchProductList = async () => {
loading.value = true
try {
const res = await getProductList(queryParams)
if (res.data.code === 200) {
productList.value = res.data.data.data || []
productList.value = productList.value.filter(item => item.delete == false)
total.value = res.data.data.total || 0
productList.value = productList.value.map(item => {
item.image = item.coverId ? getFileDetail({ file_id: item.coverId }).then(res => res.data.data.url) : ''
return item
})
console.log('productList', productList.value)
}
} catch (error) {
ElMessage.error('获取商品列表失败')
} finally {
loading.value = false
}
}
// 获取商品分组列表
const fetchGroupList = async () => {
try {
const res = await getProductGroupList({ page: 1, count: 100 })
if (res.data.code === 200) {
groupOptions.value = res.data.data.data || []
console.log('商品分组列表:', groupOptions.value) // 调试日志
if (groupOptions.value.length === 0) {
ElMessage.warning('暂无商品分组,请先创建商品分组')
}
}
} catch (error) {
console.error('获取分组列表失败:', error)
ElMessage.error('获取分组列表失败')
}
}
// 查询
const handleQuery = () => {
queryParams.page = 1
fetchProductList()
}
// 重置查询
const resetQuery = () => {
queryParams.good_group_id = ''
queryParams.page = 1
fetchProductList()
}
// 选择项变化
const handleSelectionChange = (selection) => {
selectedRows.value = selection
}
// 分页
const handleSizeChange = (size) => {
queryParams.count = size
fetchProductList()
}
const handleCurrentChange = (page) => {
queryParams.page = page
fetchProductList()
}
// 新增商品
const handleAdd = () => {
// 检查是否有可用的商品分组
if (!groupOptions.value || groupOptions.value.length === 0) {
ElMessage.warning('请先创建商品分组')
return
}
dialogType.value = 'add'
dialogVisible.value = true
Object.assign(productForm, {
id: undefined,
name: '',
table: '',
content: '',
cover_id: undefined,
good_group_id: undefined,
inventory_control: false,
inventory: 0,
price: 0,
pay_num: 1,
expire_time: 0,
recommend: false,
recommend_rebate: 0
})
productFormRef.value?.resetFields()
}
// 编辑商品
const handleEdit = (row) => {
dialogType.value = 'edit'
dialogVisible.value = true
Object.assign(productForm, {
id: row.id,
name: row.name,
table: row.table,
content: row.content,
cover_id: row.coverId,
good_group_id: row.goodGroupId,
inventory_control: row.inventory_control,
inventory: row.inventory,
price: row.price,
pay_num: row.payNum,
expire_time: row.expireTime,
recommend: row.recommend,
recommend_rebate: row.recommendRebate
})
}
// 规格管理
const handleSpec = (row) => {
ElMessage.info(`商品 ${row.name} 的规格管理功能待实现`)
// 可以跳转到规格管理页面或打开规格管理对话框
}
// 删除商品
const handleDelete = (row) => {
ElMessageBox.confirm(`确认删除商品 ${row.name} 吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deleteProduct({ id: row.id })
if (res.data.code === 200) {
ElMessage.success('删除成功')
fetchProductList()
}
} catch (error) {
ElMessage.error('删除失败')
}
}).catch(() => {})
}
// 批量删除
const handleBatchDelete = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请至少选择一条记录')
return
}
ElMessageBox.confirm(`确认删除选中的 ${selectedRows.value.length} 条记录吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
loading.value = true
try {
// 使用 Promise.all 并行删除所有选中的商品
const deletePromises = selectedRows.value.map(row =>
deleteProduct({ id: row.id })
)
const results = await Promise.allSettled(deletePromises)
// 统计成功和失败的数量
const successCount = results.filter(r => r.status === 'fulfilled' && r.value?.data?.code === 200).length
const failCount = results.length - successCount
if (failCount === 0) {
ElMessage.success(`批量删除成功,共删除 ${successCount} 条记录`)
} else if (successCount === 0) {
ElMessage.error(`批量删除失败,所有 ${failCount} 条记录删除失败`)
} else {
ElMessage.warning(`批量删除完成,成功 ${successCount} 条,失败 ${failCount}`)
}
// 刷新列表
fetchProductList()
} catch (error) {
console.error('批量删除失败:', error)
ElMessage.error('批量删除操作异常')
} finally {
loading.value = false
}
}).catch(() => {})
}
// 提交表单
const submitForm = () => {
productFormRef.value?.validate(async (valid) => {
if (valid) {
// 验证商品分组是否已选择
if (!productForm.good_group_id) {
ElMessage.error('请选择商品分组')
return
}
try {
let res
// 准备提交的数据,确保所有字段都有正确的值
const submitData = {
...productForm,
good_group_id: Number(productForm.good_group_id), // 确保是数字类型
cover_id: productForm.cover_id || 0,
inventory: productForm.inventory || 0,
price: productForm.price || 0,
pay_num: productForm.pay_num || 1,
expire_time: productForm.expire_time || 0,
recommend_rebate: productForm.recommend_rebate || 0
}
console.log('提交的数据:', submitData) // 调试日志
if (dialogType.value === 'add') {
res = await createProduct(submitData)
} else {
res = await updateProduct(submitData)
}
if (res.data.code === 200) {
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '修改成功')
dialogVisible.value = false
fetchProductList()
}
} catch (error) {
console.error('提交失败:', error)
ElMessage.error(error.response?.data?.message || '操作失败')
}
}
})
}
// 初始化
onMounted(() => {
fetchProductList()
fetchGroupList()
})
</script>
<style scoped>
.product-list-container {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
}
.search-form {
margin-bottom: 15px;
}
.action-bar {
display: flex;
gap: 12px;
}
.table-container {
border-radius: 8px;
}
.price {
color: #f56c6c;
font-weight: bold;
font-size: 14px;
}
.pagination {
margin-top: 24px;
justify-content: flex-end;
}
</style>
+637
View File
@@ -0,0 +1,637 @@
<template>
<div class="product-parameter-container">
<!-- 操作栏 -->
<el-card class="filter-container" shadow="never">
<el-form ref="queryFormRef" label-width="100px" :inline="true" :model="queryParams" class="search-form">
<el-form-item label="商品分组">
<el-select
v-model="queryParams.good_group_id"
placeholder="请选择商品分组"
clearable
@change="handleGroupChange"
style="width: 200px"
>
<el-option
v-for="item in groupOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="商品">
<el-select
v-model="queryParams.good_id"
placeholder="请先选择商品分组"
clearable
:disabled="!queryParams.good_group_id"
style="width: 200px"
>
<el-option
v-for="item in productOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>查询
</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div class="action-bar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增商品参数
</el-button>
<el-button type="success" @click="fetchParameterList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
</div>
</el-card>
<!-- 商品参数列表 -->
<el-card class="table-container" shadow="never">
<el-table
v-loading="loading"
:data="parameterList"
style="width: 100%"
>
<el-table-column prop="id" label="参数ID" width="100" />
<el-table-column prop="name" label="参数名称" min-width="200" />
<el-table-column prop="type" label="参数类型" width="120">
<template #default="{ row }">
<el-tag :type="getArgTypeTag(row.type)">
{{ getArgTypeText(row.type) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="250" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="success" link @click="handleViewValues(row)">查看参数值</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</el-card>
<!-- 商品参数表单对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增商品参数' : '编辑商品参数'"
width="600px"
>
<el-form
ref="parameterFormRef"
:model="parameterForm"
:rules="parameterRules"
label-width="100px"
>
<el-form-item label="参数名称" prop="arg_name">
<el-input v-model="parameterForm.arg_name" placeholder="请输入参数名称" />
</el-form-item>
<el-form-item label="参数类型" prop="arg_type">
<el-radio-group v-model="parameterForm.arg_type">
<el-radio label="string">字符串</el-radio>
<el-radio label="number">数字</el-radio>
<el-radio label="select">选择</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
<!-- 参数值管理对话框 -->
<el-dialog
v-model="valuesDialogVisible"
title="参数值管理"
width="800px"
>
<div class="values-header">
<span>参数{{ currentParameter?.arg_name }}</span>
<el-button type="primary" @click="handleAddValue">
<el-icon><Plus /></el-icon>添加参数值
</el-button>
</div>
<el-table
v-loading="valuesLoading"
:data="valuesList"
style="width: 100%; margin-top: 20px"
>
<el-table-column prop="id" label="值ID" width="100" />
<el-table-column prop="name" label="值名称" min-width="150" />
<el-table-column prop="value" label="值" min-width="150" />
<el-table-column label="价格" width="120">
<template #default="{ row }">
¥{{ (row.price / 100).toFixed(2) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEditValue(row)">编辑</el-button>
<el-button type="danger" link @click="handleDeleteValue(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
<!-- 参数值表单对话框 -->
<el-dialog
v-model="valueDialogVisible"
:title="valueDialogType === 'add' ? '添加参数值' : '编辑参数值'"
width="500px"
>
<el-form
ref="valueFormRef"
:model="valueForm"
:rules="valueRules"
label-width="100px"
>
<el-form-item label="值名称" prop="attr_name">
<el-input v-model="valueForm.attr_name" placeholder="请输入值名称" />
</el-form-item>
<el-form-item label="值" prop="attr_value">
<el-input v-model="valueForm.attr_value" placeholder="请输入值" />
</el-form-item>
<el-form-item label="价格(元)" prop="attr_price">
<el-input-number v-model="valueForm.attr_price" :min="0" :precision="2" :step="0.01" placeholder="请输入价格" style="width: 100%" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="valueDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitValueForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search, Refresh } from '@element-plus/icons-vue'
import {
getProductParameterList,
getProductParameterDetail,
createProductParameter,
updateProductParameter,
deleteProductParameter,
addProductParameterValue,
updateProductParameterValue,
deleteProductParameterValue,
getProductList,
getProductGroupList
} from '@/api/admin/product'
// 查询参数
const queryParams = reactive({
good_group_id: undefined, // 商品分组ID
good_id: undefined, // 商品ID
page: 1,
count: 10
})
// 下拉选项数据
const groupOptions = ref([]) // 商品分组选项
const productOptions = ref([]) // 商品选项
const queryFormRef = ref(null)
// 商品参数表单
const parameterForm = reactive({
good_id: undefined,
arg_id: undefined,
arg_name: '',
arg_type: 'string'
})
const parameterRules = {
arg_name: [
{ required: true, message: '请输入参数名称', trigger: 'blur' }
],
arg_type: [
{ required: true, message: '请选择参数类型', trigger: 'change' }
]
}
// 参数值表单
const valueForm = reactive({
good_id: undefined,
arg_id: undefined,
attr_id: undefined,
attr_name: '',
attr_value: '',
attr_price: 0
})
const valueRules = {
attr_name: [
{ required: true, message: '请输入值名称', trigger: 'blur' }
],
attr_value: [
{ required: true, message: '请输入值', trigger: 'blur' }
],
attr_price: [
{ required: true, message: '请输入价格', trigger: 'blur' }
]
}
// 状态数据
const loading = ref(false)
const valuesLoading = ref(false)
const parameterList = ref([])
const valuesList = ref([])
const total = ref(0)
const dialogVisible = ref(false)
const valuesDialogVisible = ref(false)
const valueDialogVisible = ref(false)
const dialogType = ref('add')
const valueDialogType = ref('add')
const currentParameter = ref(null)
const parameterFormRef = ref(null)
const valueFormRef = ref(null)
// 获取商品分组列表
const fetchGroupList = async () => {
try {
const res = await getProductGroupList({ page: 1, count: 100 })
console.log('商品分组列表:', res.data)
if (res.data.code === 200) {
groupOptions.value = res.data.data.data || []
}
} catch (error) {
console.error('获取商品分组列表失败:', error)
ElMessage.error('获取商品分组列表失败')
}
}
// 获取商品列表(根据分组ID
const fetchProductList = async (groupId) => {
try {
const res = await getProductList({ good_group_id: groupId, page: 1, count: 100 })
console.log('商品列表:', res.data)
if (res.data.code === 200) {
productOptions.value = res.data.data.data || []
productOptions.value = productOptions.value.filter(item => item.delete == false)
}
} catch (error) {
console.error('获取商品列表失败:', error)
ElMessage.error('获取商品列表失败')
}
}
// 商品分组改变时
const handleGroupChange = (groupId) => {
// 清空商品选择
queryParams.good_id = undefined
productOptions.value = []
if (groupId) {
// 获取该分组下的商品列表
fetchProductList(groupId)
}
}
// 获取商品参数列表
const fetchParameterList = async () => {
// 如果没有选择商品ID,不查询
if (!queryParams.good_id) {
ElMessage.warning('请先选择商品')
return
}
loading.value = true
try {
const res = await getProductParameterList({ good_id: queryParams.good_id })
console.log('商品参数列表:', res.data)
if (res.data.code === 200) {
parameterList.value = res.data.data || []
total.value = res.data.data.length || 0
}
} catch (error) {
console.error('获取商品参数列表失败:', error)
ElMessage.error('获取商品参数列表失败')
} finally {
loading.value = false
}
}
// 查询
const handleQuery = () => {
queryParams.page = 1
fetchParameterList()
}
// 重置查询
const resetQuery = () => {
queryParams.good_group_id = undefined
queryParams.good_id = undefined
queryParams.page = 1
productOptions.value = []
parameterList.value = []
total.value = 0
}
// 获取参数值列表
const fetchValuesList = async (goodId, argId) => {
valuesLoading.value = true
console.log('goodId', goodId)
console.log('argId', argId)
try {
const res = await getProductParameterDetail({ good_id: goodId, arg_id: argId })
console.log('参数值列表:', res.data)
if (res.data.code === 200) {
valuesList.value = res.data.data.attrs || []
}
} catch (error) {
console.error('获取参数值列表失败:', error)
ElMessage.error('获取参数值列表失败')
} finally {
valuesLoading.value = false
}
}
// 获取参数类型文本
const getArgTypeText = (type) => {
const typeMap = {
'string': '字符串',
'number': '数字',
'select': '选择'
}
return typeMap[type] || '未知'
}
// 获取参数类型标签颜色
const getArgTypeTag = (type) => {
const tagMap = {
'string': 'primary',
'number': 'success',
'select': 'warning'
}
return tagMap[type] || 'info'
}
// 分页
const handleSizeChange = (size) => {
queryParams.count = size
fetchParameterList()
}
const handleCurrentChange = (page) => {
queryParams.page = page
fetchParameterList()
}
// 新增商品参数
const handleAdd = () => {
if (!queryParams.good_id) {
ElMessage.warning('请先选择商品')
return
}
dialogType.value = 'add'
dialogVisible.value = true
Object.assign(parameterForm, {
good_id: queryParams.good_id,
arg_id: undefined,
arg_name: '',
arg_type: 'string'
})
parameterFormRef.value?.resetFields()
}
// 编辑商品参数
const handleEdit = (row) => {
dialogType.value = 'edit'
dialogVisible.value = true
Object.assign(parameterForm, {
good_id: queryParams.good_id,
arg_id: row.id,
arg_name: row.name,
arg_type: row.type
})
}
// 查看参数值
const handleViewValues = (row) => {
currentParameter.value = row
valuesDialogVisible.value = true
fetchValuesList(queryParams.good_id, row.id)
}
// 添加参数值
const handleAddValue = () => {
valueDialogType.value = 'add'
console.log('currentParameter', currentParameter.value)
valueDialogVisible.value = true
Object.assign(valueForm, {
good_id: queryParams.good_id,
arg_id: currentParameter.value.id,
attr_id: undefined,
attr_name: '',
attr_value: '',
attr_price: 0
})
valueFormRef.value?.resetFields()
}
// 编辑参数值
const handleEditValue = (row) => {
valueDialogType.value = 'edit'
valueDialogVisible.value = true
Object.assign(valueForm, {
good_id: queryParams.good_id,
arg_id: currentParameter.value.id,
attr_id: row.id,
attr_name: row.name,
attr_value: row.value,
attr_price: row.price / 100 // 分转元
})
}
// 删除参数值
const handleDeleteValue = (row) => {
ElMessageBox.confirm(`确认删除参数值 ${row.name} 吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deleteProductParameterValue({
good_id: queryParams.good_id,
attr_id: row.id
})
console.log('删除参数值响应:', res.data)
if (res.data.code === 200) {
ElMessage.success('删除成功')
fetchValuesList(queryParams.good_id, currentParameter.value.id)
}
} catch (error) {
console.error('删除失败:', error)
ElMessage.error(error.response?.data?.message || '删除失败')
}
}).catch(() => {})
}
// 删除商品参数
const handleDelete = (row) => {
ElMessageBox.confirm(`确认删除商品参数 ${row.name} 吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deleteProductParameter({
good_id: queryParams.good_id,
arg_id: row.id
})
console.log('删除参数响应:', res.data)
if (res.data.code === 200) {
ElMessage.success('删除成功')
fetchParameterList()
}
} catch (error) {
console.error('删除失败:', error)
ElMessage.error(error.response?.data?.message || '删除失败')
}
}).catch(() => {})
}
// 提交参数表单
const submitForm = () => {
parameterFormRef.value?.validate(async (valid) => {
if (valid) {
try {
const submitData = {
good_id: Number(parameterForm.good_id),
arg_name: parameterForm.arg_name,
arg_type: parameterForm.arg_type
}
if (dialogType.value === 'edit') {
submitData.arg_id = parameterForm.arg_id
}
console.log('提交参数数据:', submitData)
let res
if (dialogType.value === 'add') {
res = await createProductParameter(submitData)
} else {
res = await updateProductParameter(submitData)
}
console.log('提交参数响应:', res.data)
if (res.data.code === 200) {
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '修改成功')
dialogVisible.value = false
fetchParameterList()
}
} catch (error) {
console.error('操作失败:', error)
ElMessage.error(error.response?.data?.message || '操作失败')
}
}
})
}
// 提交参数值表单
const submitValueForm = () => {
valueFormRef.value?.validate(async (valid) => {
if (valid) {
try {
const submitData = {
good_id: Number(valueForm.good_id),
arg_id: Number(valueForm.arg_id),
attr_name: valueForm.attr_name,
attr_value: valueForm.attr_value,
attr_price: valueForm.attr_price // 元转分
}
if (valueDialogType.value === 'edit') {
submitData.attr_id = valueForm.attr_id
}
console.log('提交参数值数据:', submitData)
let res
if (valueDialogType.value === 'add') {
res = await addProductParameterValue(submitData)
} else {
res = await updateProductParameterValue(submitData)
}
console.log('提交参数值响应:', res.data)
if (res.data.code === 200) {
ElMessage.success(valueDialogType.value === 'add' ? '添加成功' : '修改成功')
valueDialogVisible.value = false
fetchValuesList(queryParams.good_id, currentParameter.value.id)
}
} catch (error) {
console.error('操作失败:', error)
ElMessage.error(error.response?.data?.message || '操作失败')
}
}
})
}
// 初始化
onMounted(() => {
// 初始化时只获取商品分组列表
fetchGroupList()
})
</script>
<style scoped>
.product-parameter-container {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
}
.search-form {
margin-bottom: 15px;
}
.action-bar {
display: flex;
gap: 12px;
}
.table-container {
border-radius: 8px;
}
.values-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.pagination {
margin-top: 24px;
justify-content: flex-end;
}
</style>
+21 -12
View File
@@ -15,6 +15,11 @@
<el-button type="primary" @click="handleAdd">
<el-icon><plus /></el-icon>新增域名
</el-button>
<el-button type="success" @click="getList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
<el-icon><delete /></el-icon>批量删除
</el-button>
@@ -30,10 +35,10 @@
style="width: 100%"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="Id" label="ID" width="80" />
<el-table-column prop="Domain" label="域名" min-width="200" >
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="domain" label="域名" min-width="200" >
<template #default="{ row }">
<el-link :href="`http://${row.Domain}`" target="_blank" type="primary">{{ row.Domain }}</el-link>
<el-link :href="`http://${row.domain}`" target="_blank" type="primary">{{ row.domain }}</el-link>
</template>
</el-table-column>
<el-table-column prop="CreatedAt" label="创建时间" width="180" :formatter="parseCreatedAt" />
@@ -223,9 +228,14 @@ const handleDelete = (row) => {
type: 'warning'
}).then(async () => {
try {
await deleteDomain(row.Id)
ElMessage.success('删除成功')
getList()
const res = await deleteDomain({domain_id: row.id})
console.log(res)
if (res.code === 200) {
ElMessage.success('删除成功')
getList()
} else {
ElMessage.error(res.data.message || '删除失败')
}
} catch (error) {
console.error('删除域名失败:', error)
ElMessage.error('删除失败')
@@ -239,10 +249,11 @@ const handleBatchDelete = () => {
ElMessage.warning('请选择要删除的域名')
return
}
const ids = selectedRows.value.map(item => item.Id)
const ids = selectedRows.value.map(item => item.id)
const domains = selectedRows.value.map(item => item.domain).join('、')
console.log('id数据:',ids)
ElMessageBox.confirm(`确认删除以下域名吗?\n${domains}`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
@@ -257,9 +268,7 @@ const handleBatchDelete = () => {
console.error('批量删除域名失败:', error)
ElMessage.error('批量删除失败')
}
await batchDeleteDomain(ids)
ElMessage.success('批量删除成功')
getList()
} catch (error) {
console.error('批量删除域名失败:', error)
ElMessage.error('批量删除失败')
+778
View File
@@ -0,0 +1,778 @@
<template>
<div class="permission-admin-container">
<!-- 搜索和操作栏 -->
<el-card class="filter-container" shadow="never">
<el-form :inline="true" :model="queryParams" class="search-form">
<el-form-item label="类型">
<el-select v-model="queryParams.owner_type" placeholder="请选择类型" clearable style="width: 150px" @change="handleOwnerTypeChange">
<el-option label="用户" value="user" />
<el-option label="组" value="group" />
</el-select>
</el-form-item>
<el-form-item label="用户" v-if="queryParams.owner_type === 'user'">
<!-- <el-select v-model="queryParams.user_id" placeholder="请选择用户" clearable filterable style="width: 200px">
<el-option v-for="item in userOptions" :key="item.UserId" :label="`${item.UserName} (ID: ${item.UserId})`" :value="item.UserId" />
</el-select> -->
<div class="user_selector-inline">
<el-tag v-if="queryParams.user_id" type="primary" closable @close="clearQueryUser" style="margin-right: 8px;">
{{ getQueryUserName() }}
</el-tag>
<el-button type="primary" plain @click="openQueryUserSelector" size="default">
<el-icon><User /></el-icon>
{{ queryParams.user_id ? '重新选择' : '选择用户' }}
</el-button>
</div>
</el-form-item>
<el-form-item label="管理员组" v-if="queryParams.owner_type === 'group'">
<el-select v-model="queryParams.admin_group_id" placeholder="请选择管理员组" clearable filterable style="width: 200px">
<el-option v-for="item in adminGroupOptions" :key="item.id" :label="`${item.name} (ID: ${item.id})`" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>查询
</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div class="action-bar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>分配权限
</el-button>
<el-button type="success" @click="fetchAdminPermissionList">
<el-icon><Refresh/></el-icon>刷新
</el-button>
</div>
</el-card>
<!-- 管理员权限列表 -->
<el-card class="table-container" shadow="never">
<el-table
v-loading="loading"
:data="adminPermissionList"
style="width: 100%"
>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column label="拥有者类型" width="120">
<template #default="{ row }">
<el-tag :type="row.ownerType === 'user' ? 'primary' : 'success'">
{{ row.ownerType === 'user' ? '用户' : '组' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="拥有者" width="180">
<template #default="{ row }">
<span v-if="row.ownerType === 'user'">用户ID: {{ row.userId }}</span>
<span v-else>管理员组ID: {{ row.groupId }}</span>
</template>
</el-table-column>
<el-table-column prop="permissionId" label="路径权限ID" width="120" />
<el-table-column label="权限路径" min-width="200" show-overflow-tooltip>
<template #default="{ row }">
{{ row.permission?.path || '-' }}
</template>
</el-table-column>
<el-table-column label="权限名称" width="150" show-overflow-tooltip>
<template #default="{ row }">
{{ row.permission?.name || '-' }}
</template>
</el-table-column>
<el-table-column prop="weight" label="权重" width="100" />
<el-table-column label="权限类型" width="120">
<template #default="{ row }">
<el-tag :type="getPermissionTypeTag(row.permissionType)">
{{ getPermissionTypeText(row.permissionType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="过期时间" width="180" show-overflow-tooltip>
<template #default="{ row }">
{{ formatDate(row.expireAt) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</el-card>
<!-- 用户选择弹窗 -->
<el-dialog
v-model="userSelectorVisible"
title="选择用户"
width="800px"
class="user-selector-dialog"
>
<!-- 搜索栏 -->
<div class="selector-search">
<el-input
v-model="userSearchParams.key"
placeholder="搜索用户名或ID"
clearable
@keyup.enter="searchUsers"
style="width: 300px; margin-right: 12px"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
<el-button type="primary" @click="searchUsers">
<el-icon><Search /></el-icon>
搜索
</el-button>
<el-button @click="resetUserSearch">重置</el-button>
</div>
<!-- 用户表格 -->
<el-table
v-loading="userSelectorLoading"
:data="userSelectorList"
highlight-current-row
@current-change="handleUserSelectChange"
style="width: 100%; margin-top: 16px"
:height="400"
>
<el-table-column type="index" label="序号" width="60" />
<el-table-column prop="UserId" label="用户ID" width="100" />
<el-table-column prop="UserName" label="用户名" min-width="150" />
<el-table-column prop="Email" label="邮箱" min-width="180" />
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.Status === 1 ? 'success' : 'danger'" size="small">
{{ row.Status === 1 ? '正常' : '禁用' }}
</el-tag>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="userSearchParams.page"
v-model:page-size="userSearchParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="userSelectorTotal"
@size-change="handleUserSelectorSizeChange"
@current-change="handleUserSelectorPageChange"
background
class="selector-pagination"
/>
<template #footer>
<el-button @click="userSelectorVisible = false">取消</el-button>
<el-button type="primary" @click="confirmUserSelection" :disabled="!selectedUserTemp">
确定选择
</el-button>
</template>
</el-dialog>
<!-- 分配权限对话框 -->
<el-dialog
v-model="dialogVisible"
title="分配管理员权限"
width="700px"
>
<el-form
ref="permissionFormRef"
:model="permissionForm"
:rules="permissionRules"
label-width="140px"
>
<el-form-item label="权限绑定类型" prop="owner_type">
<el-select v-model="permissionForm.owner_type" placeholder="请选择权限绑定类型" style="width: 100%" @change="handleFormOwnerTypeChange" :disabled="permissionForm.id">
<el-option label="用户" value="user" />
<el-option label="组" value="group" />
</el-select>
<div class="form-tip">如果是 user 则填写 user_id如果是 group 则填写 admin_group_id</div>
</el-form-item>
<el-form-item label="用户" prop="user_id" v-if="permissionForm.owner_type === 'user'" >
<div class="user_selector-inline">
<el-tag v-if="permissionForm.user_id" type="primary" closable @close="clearFormUser" style="margin-right: 8px;">
{{ getFormUserName() }}
</el-tag>
<el-button type="primary" plain @click="openFormUserSelector" size="default" :disabled="permissionForm.user_id">
<el-icon><User /></el-icon>
{{ permissionForm.user_id ? '重新选择' : '选择用户' }}
</el-button>
</div>
</el-form-item>
<el-form-item label="管理员组" prop="admin_group_id" v-if="permissionForm.owner_type === 'group'">
<el-select v-model="permissionForm.admin_group_id" placeholder="请选择管理员组" filterable style="width: 100%">
<el-option v-for="item in adminGroupOptions" :key="item.id" :label="`${item.name} (ID: ${item.id})`" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="路径权限" prop="permission_id">
<div style="display: flex; gap: 8px;">
<el-select
v-model="permissionForm.permission_id"
placeholder="请选择路径权限"
filterable
style="flex: 1"
:loading="permissionLoading"
>
<el-option
v-for="item in permissionOptions"
:key="item.id"
:value="item.id"
>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>
<el-tag v-if="item.method" :type="getMethodTag(item.method)" size="small" style="margin-right: 8px;">{{ item.method }}</el-tag>
{{ item.path }}
</span>
<span style="color: #999; font-size: 12px; margin-left: 12px;">{{ item.note || item.name || `ID: ${item.id}` }}</span>
</div>
</el-option>
</el-select>
<el-button @click="fetchPermissionList" :loading="permissionLoading" :icon="Refresh">刷新</el-button>
</div>
<div class="form-tip"> {{ permissionOptions.length }} 个路径权限可选</div>
</el-form-item>
<el-form-item label="权重" prop="weight">
<el-input-number v-model="permissionForm.weight" placeholder="请输入权重" :min="0" style="width: 100%" />
<div class="form-tip">权重默认 10</div>
</el-form-item>
<el-form-item label="权限类型" prop="permission_type">
<el-select v-model="permissionForm.permission_type" placeholder="请选择权限类型" style="width: 100%">
<el-option label="无权限 (none)" :value="0" />
<el-option label="禁止 (prohibit)" :value="1" />
<el-option label="读取 (read)" :value="2" />
<el-option label="写入 (write)" :value="3" />
<el-option label="全部 (all)" :value="4" />
</el-select>
<div class="form-tip">0 none / 1 prohibit / 2 read / 3 write / 4 all</div>
</el-form-item>
<el-form-item label="过期时间" prop="expire_at">
<el-date-picker
v-model="permissionForm.expire_at"
type="datetime"
placeholder="请选择过期时间"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
<div class="form-tip">权限过期时间 不填写则不会过期</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search, Refresh, User } from '@element-plus/icons-vue'
import {
getPermissionListByAdmin,
addPermissionAdmin,
updatePermissionAdmin,
deletePermissionAdmin,
getPermissionList
} from '@/api/admin/Permission'
import { getUserList } from '@/api/admin/user'
import { getAdminGroupList } from '@/api/admin/group'
import { formatDate ,timeToTimestamp} from '@/utils/tool'
const selectorType = ref('query')
const userSelectorVisible = ref(false)
const userSelectorList = ref([])
const userSelectorTotal = ref(0)
const userSearchParams = reactive({
key: '',
page: 1,
count: 10
})
const selectedUserTemp = ref(null)
const userSelectorLoading = ref(false)
const UserOptions = ref([])
// 查询参数
const queryParams = reactive({
owner_type: '',
user_id: undefined,
admin_group_id: undefined,
page: 1,
count: 10
})
// 清除查询用户
const clearQueryUser = () => {
queryParams.user_id = undefined
}
// 获取查询用户名称
const getQueryUserName = () => {
const user = UserOptions.value.find(u => u.UserId === queryParams.user_id)
return user ? `${user.UserName} (ID: ${user.UserId})` : `用户ID: ${queryParams.user_id}`
}
// 表单:清除用户
const clearFormUser = () => {
permissionForm.user_id = undefined
}
// 表单:获取显示名称
const getFormUserName = () => {
const user = UserOptions.value.find(u => u.UserId === permissionForm.user_id)
return user ? `${user.UserName} (ID: ${user.UserId})` : `用户ID: ${permissionForm.user_id}`
}
// 确认用户选择
const confirmUserSelection = () => {
if (!selectedUserTemp.value) {
ElMessage.warning('请选择一个用户')
return
}
if (selectorType.value === 'query') {
// 查询表单选择,仅影响查询条件
queryParams.user_id = selectedUserTemp.value.UserId
} else if (selectorType.value === 'form') {
// 表单选择,仅影响表单
permissionForm.user_id = selectedUserTemp.value.UserId
}
// 将选中的用户添加到 UserOptions 中(如果不存在)
if (!UserOptions.value.find(u => u.UserId === selectedUserTemp.value.UserId)) {
UserOptions.value.push(selectedUserTemp.value)
}
userSelectorVisible.value = false
ElMessage.success('用户选择成功')
}
// 打开查询用户选择器
const openQueryUserSelector = () => {
selectorType.value = 'query'
userSelectorVisible.value = true
selectedUserTemp.value = null
userSearchParams.key = ''
userSearchParams.page = 1
fetchUserSelectorList()
}
// 打开表单用户选择器
const openFormUserSelector = () => {
selectorType.value = 'form'
userSelectorVisible.value = true
selectedUserTemp.value = null
userSearchParams.key = ''
userSearchParams.page = 1
fetchUserSelectorList()
}
// 用户选择变化
const handleUserSelectChange = (row) => {
selectedUserTemp.value = row
}
// 搜索用户
const searchUsers = () => {
userSearchParams.page = 1
fetchUserSelectorList()
}
// 用户选择器分页
const handleUserSelectorSizeChange = (size) => {
userSearchParams.count = size
fetchUserSelectorList()
}
const handleUserSelectorPageChange = (page) => {
userSearchParams.page = page
fetchUserSelectorList()
}
// 获取用户选择器列表
const fetchUserSelectorList = async () => {
userSelectorLoading.value = true
try {
const res = await getUserList(userSearchParams)
console.log('用户选择器列表:', res.data)
if (res.data.code === 200) {
userSelectorList.value = res.data.data?.data || []
userSelectorTotal.value = res.data.data?.all_count || 0
}
} catch (error) {
console.error('获取用户列表失败:', error)
ElMessage.error('获取用户列表失败')
} finally {
userSelectorLoading.value = false
}
}
// 管理员权限表单
const permissionForm = reactive({
id: undefined,
user_id: undefined,
admin_group_id: undefined,
owner_type: '',
permission_id: undefined,
weight: 10,
permission_type: 4,
expire_at: ''
})
const permissionRules = {
owner_type: [
{ required: true, message: '请选择权限的绑定类型', trigger: 'change' }
],
permission_id: [
{ required: true, message: '请输入权限ID', trigger: 'blur' }
],
permission_type: [
{ required: true, message: '请选择权限类型', trigger: 'change' }
]
}
// 状态数据
const loading = ref(false)
const adminPermissionList = ref([])
const total = ref(0)
const dialogVisible = ref(false)
const permissionFormRef = ref(null)
// const userOptions = ref([])
// const UserOptions = ref([])
const adminGroupOptions = ref([])
const permissionOptions = ref([])
const permissionLoading = ref(false)
// 获取方法标签颜色
const getMethodTag = (method) => {
const tagMap = {
'GET': 'success',
'POST': 'primary',
'PUT': 'warning',
'DELETE': 'danger',
'PATCH': 'info'
}
return tagMap[method?.toUpperCase()] || 'info'
}
// 获取权限类型标签颜色
const getPermissionTypeTag = (type) => {
const tagMap = {
0: 'info', // none
1: 'danger', // prohibit
2: 'warning', // read
3: 'primary', // write
4: 'success' // all
}
return tagMap[type] || 'info'
}
// 获取权限类型文本
const getPermissionTypeText = (type) => {
const textMap = {
0: '无权限',
1: '禁止',
2: '读取',
3: '写入',
4: '全部'
}
return textMap[type] || '未知'
}
// 获取管理员权限列表
const fetchAdminPermissionList = async () => {
if (!queryParams.owner_type) {
ElMessage.warning('请先选择类型')
return
}
loading.value = true
try {
const params = { ...queryParams }
// 清除空参数
Object.keys(params).forEach(key => {
if (params[key] === '' || params[key] === null || params[key] === undefined) {
delete params[key]
}
})
const res = await getPermissionListByAdmin(params)
console.log('管理员权限列表数据:', res.data)
if (res.data.code === 200) {
adminPermissionList.value = res.data.data || []
total.value = res.data.data.length || 0
} else {
ElMessage.error(res.data.message || '获取管理员权限列表失败')
}
} catch (error) {
console.error('获取管理员权限列表失败:', error)
ElMessage.error('获取管理员权限列表失败')
} finally {
loading.value = false
}
}
// 查询
const handleQuery = () => {
queryParams.page = 1
fetchAdminPermissionList()
}
// 重置查询
const resetQuery = () => {
queryParams.user_id = undefined
queryParams.admin_group_id = undefined
queryParams.owner_type = ''
queryParams.page = 1
adminPermissionList.value = []
total.value = 0
}
// 类型变化时清空关联字段
const handleOwnerTypeChange = () => {
if (queryParams.owner_type === 'user') {
queryParams.admin_group_id = undefined
} else if (queryParams.owner_type === 'group') {
queryParams.user_id = undefined
}
}
// 表单类型变化时清空关联字段
const handleFormOwnerTypeChange = () => {
if (permissionForm.owner_type === 'user') {
permissionForm.admin_group_id = undefined
} else if (permissionForm.owner_type === 'group') {
permissionForm.user_id = undefined
}
}
// 分页
const handleSizeChange = (size) => {
queryParams.count = size
fetchAdminPermissionList()
}
const handleCurrentChange = (page) => {
queryParams.page = page
fetchAdminPermissionList()
}
// 分配权限
const handleAdd = () => {
dialogVisible.value = true
Object.assign(permissionForm, {
id: undefined,
user_id: undefined,
admin_group_id: undefined,
owner_type: '',
permission_id: undefined,
weight: 10,
permission_type: 4,
expire_at: ''
})
permissionFormRef.value?.resetFields()
}
// 编辑权限
const handleEdit = (row) => {
// 处理过期时间
let expireTime = ''
if (row.expireAt && row.expireAt !== '0001-01-01T00:00:00Z' && row.expireAt !== null) {
const date = new Date(row.expireAt)
if (!isNaN(date.getTime())) {
expireTime = date.toISOString().slice(0, 19).replace('T', ' ')
}
}
Object.assign(permissionForm, {
id: row.id,
user_id: row.userId,
admin_group_id: row.groupId,
owner_type: row.ownerType,
permission_id: row.permissionId,
weight: row.weight,
permission_type: row.permissionType,
expire_at: expireTime
})
dialogVisible.value = true
}
// 删除权限
const handleDelete = (row) => {
ElMessageBox.confirm(`确认删除该权限吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deletePermissionAdmin({ id: row.id })
if (res.data.code === 200) {
ElMessage.success('删除成功')
fetchAdminPermissionList()
}
} catch (error) {
console.error('删除失败:', error)
ElMessage.error(error.response?.data?.message || '删除失败')
}
}).catch(() => {})
}
// 提交表单
const submitForm = () => {
permissionFormRef.value?.validate(async (valid) => {
if (valid) {
try {
const submitData = {
owner_type: permissionForm.owner_type,
permission_id: Number(permissionForm.permission_id),
weight: Number(permissionForm.weight),
permission_type: Number(permissionForm.permission_type)
}
// 根据 owner_type 添加对应的 ID
if (permissionForm.owner_type === 'user') {
submitData.user_id = Number(permissionForm.user_id)
} else if (permissionForm.owner_type === 'group') {
submitData.admin_group_id = Number(permissionForm.admin_group_id)
}
// 将过期时间转换为时间戳(秒)
if (permissionForm.expire_at) {
submitData.expire_at = timeToTimestamp(permissionForm.expire_at)
}
// 如果是编辑,添加ID
if (permissionForm.id) {
submitData.id = permissionForm.id
}
console.log('提交管理员权限数据:', submitData)
let res
if (permissionForm.id) {
res = await updatePermissionAdmin(submitData)
} else {
res = await addPermissionAdmin(submitData)
}
if (res.data.code === 200) {
ElMessage.success(permissionForm.id ? '修改成功' : '分配成功')
dialogVisible.value = false
fetchAdminPermissionList()
} else {
ElMessage.error(res.data.message || '操作失败')
}
} catch (error) {
console.error('操作失败:', error)
ElMessage.error(error.response?.data?.message || '操作失败')
}
}
})
}
// 获取用户列表
const fetchUserList = async () => {
try {
const res = await getUserList({
page: 1,
count: 10000,
key: ''
})
if (res.data.code === 200) {
UserOptions.value = res.data.data?.data || []
}
} catch (error) {
console.error('获取用户列表失败:', error)
}
}
// 获取管理员组列表
const fetchAdminGroupList = async () => {
try {
const res = await getAdminGroupList({
page: 1,
count: 1000
})
if (res.data.code === 200) {
adminGroupOptions.value = res.data.data?.data || []
}
} catch (error) {
console.error('获取管理员组列表失败:', error)
}
}
// 获取路径权限列表
const fetchPermissionList = async () => {
permissionLoading.value = true
try {
const res = await getPermissionList({
page: 1,
count: 10000
})
if (res.data.code === 200) {
permissionOptions.value = res.data.data?.list || []
console.log('路径权限列表加载成功,共', permissionOptions.value.length, '条')
if (dialogVisible.value) {
ElMessage.success(`已加载 ${permissionOptions.value.length} 个路径权限`)
}
} else {
ElMessage.error(res.data.message || '获取路径权限列表失败')
}
} catch (error) {
console.error('获取路径权限列表失败:', error)
ElMessage.error('获取路径权限列表失败')
} finally {
permissionLoading.value = false
}
}
// 初始化
onMounted(() => {
fetchUserList()
fetchAdminGroupList()
fetchPermissionList()
})
</script>
<style scoped>
.permission-admin-container {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
}
.search-form {
margin-bottom: 15px;
}
.action-bar {
display: flex;
gap: 12px;
}
.table-container {
border-radius: 8px;
}
.pagination {
margin-top: 24px;
justify-content: flex-end;
}
.form-tip {
font-size: 12px;
color: #999;
margin-top: 4px;
line-height: 1.5;
}
</style>
+281
View File
@@ -0,0 +1,281 @@
<template>
<div class="permission-route-container">
<!-- 搜索和操作栏 -->
<el-card class="filter-container" shadow="never">
<el-form :inline="true" :model="queryParams" class="search-form">
<el-form-item label="关键词">
<el-input v-model="queryParams.key" placeholder="请输入关键词搜索" clearable style="width: 250px" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>查询
</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div class="action-bar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增路由权限
</el-button>
<el-button type="success" @click="fetchPermissionList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
</div>
</el-card>
<!-- 路由权限列表 -->
<el-card class="table-container" shadow="never">
<el-table
v-loading="loading"
:data="permissionList"
style="width: 100%"
>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="权限名称" min-width="200" />
<el-table-column prop="path" label="路由路径" min-width="300" />
<el-table-column prop="note" label="说明" min-width="250" show-overflow-tooltip />
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</el-card>
<!-- 路由权限表单对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增路由权限' : '编辑路由权限'"
width="600px"
>
<el-form
ref="permissionFormRef"
:model="permissionForm"
:rules="permissionRules"
label-width="120px"
>
<el-form-item label="权限名称" prop="name">
<el-input v-model="permissionForm.name" placeholder="请输入权限名称" />
</el-form-item>
<el-form-item label="路由路径" prop="path">
<el-input v-model="permissionForm.path" placeholder="请输入路由路径,如: /api/v1/admin/..." />
</el-form-item>
<el-form-item label="说明" prop="note">
<el-input v-model="permissionForm.note" type="textarea" :rows="3" placeholder="请输入说明" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search } from '@element-plus/icons-vue'
import {
getPermissionList,
addPermissionInfo,
updatePermissionInfo,
deletePermissionInfo
} from '@/api/admin/Permission'
// 查询参数
const queryParams = reactive({
key: '',
page: 1,
count: 10
})
// 路由权限表单
const permissionForm = reactive({
id: undefined,
path: '',
name: '',
note: ''
})
const permissionRules = {
path: [
{ required: true, message: '请输入路由路径', trigger: 'blur' }
],
name: [
{ required: true, message: '请输入权限名称', trigger: 'blur' }
]
}
// 状态数据
const loading = ref(false)
const permissionList = ref([])
const total = ref(0)
const dialogVisible = ref(false)
const dialogType = ref('add')
const permissionFormRef = ref(null)
// 获取路由权限列表
const fetchPermissionList = async () => {
loading.value = true
try {
const res = await getPermissionList(queryParams)
console.log('路由权限列表数据:', res.data)
if (res.data.code === 200) {
permissionList.value = res.data.data.list || []
total.value = res.data.data.all_count || 0
}
} catch (error) {
console.error('获取路由权限列表失败:', error)
ElMessage.error('获取路由权限列表失败')
} finally {
loading.value = false
}
}
// 查询
const handleQuery = () => {
queryParams.page = 1
fetchPermissionList()
}
// 重置查询
const resetQuery = () => {
queryParams.key = ''
queryParams.page = 1
fetchPermissionList()
}
// 分页
const handleSizeChange = (size) => {
queryParams.count = size
fetchPermissionList()
}
const handleCurrentChange = (page) => {
queryParams.page = page
fetchPermissionList()
}
// 新增路由权限
const handleAdd = () => {
dialogType.value = 'add'
dialogVisible.value = true
Object.assign(permissionForm, {
id: undefined,
path: '',
name: '',
note: ''
})
permissionFormRef.value?.resetFields()
}
// 编辑路由权限
const handleEdit = (row) => {
dialogType.value = 'edit'
dialogVisible.value = true
Object.assign(permissionForm, {
id: row.id,
path: row.path,
name: row.name,
note: row.note || ''
})
}
// 删除路由权限
const handleDelete = (row) => {
ElMessageBox.confirm(`确认删除路由权限 ${row.name} 吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deletePermissionInfo({ id: row.id })
if (res.data.code === 200) {
ElMessage.success('删除成功')
fetchPermissionList()
}
} catch (error) {
console.error('删除失败:', error)
ElMessage.error(error.response?.data?.message || '删除失败')
}
}).catch(() => {})
}
// 提交表单
const submitForm = () => {
permissionFormRef.value?.validate(async (valid) => {
if (valid) {
try {
console.log('提交路由权限数据:', permissionForm)
let res
if (dialogType.value === 'add') {
res = await addPermissionInfo(permissionForm)
} else {
res = await updatePermissionInfo(permissionForm)
}
if (res.data.code === 200) {
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '修改成功')
dialogVisible.value = false
fetchPermissionList()
}
} catch (error) {
console.error('操作失败:', error)
ElMessage.error(error.response?.data?.message || '操作失败')
}
}
})
}
// 初始化
onMounted(() => {
fetchPermissionList()
})
</script>
<style scoped>
.permission-route-container {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
}
.search-form {
margin-bottom: 15px;
}
.action-bar {
display: flex;
gap: 12px;
}
.table-container {
border-radius: 8px;
}
.pagination {
margin-top: 24px;
justify-content: flex-end;
}
</style>
+535
View File
@@ -0,0 +1,535 @@
<template>
<div class="setting-container">
<!-- 搜索和操作栏 -->
<el-card class="filter-container" shadow="never">
<el-form :inline="true" :model="queryParams" class="search-form">
<el-form-item label="配置组">
<el-select v-model="queryParams.group_id" placeholder="请选择配置组" clearable style="width: 200px" @change="handleQuery">
<el-option
v-for="group in groupList"
:key="group.id"
:label="group.name"
:value="group.id"
/>
</el-select>
</el-form-item>
<el-form-item label="关键词筛选">
<el-input v-model="queryParams.key" placeholder="请输入关键词" clearable style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>查询
</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div class="action-bar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增配置
</el-button>
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
<el-icon><Delete /></el-icon>批量删除
</el-button>
</div>
</el-card>
<!-- 配置列表 -->
<el-card class="table-container" shadow="never">
<el-table
v-loading="loading"
:data="settingList"
@selection-change="handleSelectionChange"
style="width: 100%"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="名称" min-width="150" />
<el-table-column prop="value" label="值" min-width="200" show-overflow-tooltip>
<template #default="{ row }">
<span v-if="row.type === 'bool'">{{ row.value ? '' : '' }}</span>
<span v-else>{{ row.value }}</span>
</template>
</el-table-column>
<el-table-column prop="type" label="类型" width="100">
<template #default="{ row }">
<el-tag :type="getTypeColor(row.type)">
{{ row.type || '未知' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="settingGroupID" label="配置组" width="150" />
<el-table-column label="是否开放" width="100">
<template #default="{ row }">
<el-switch
v-model="row.open"
@change="handleToggleOpen(row)"
:disabled="toggleLoading === row.id"
/>
</template>
</el-table-column>
<el-table-column prop="note" label="备注" min-width="200" show-overflow-tooltip />
<el-table-column label="创建时间" width="180">
<template #default="{ row }">
{{ formatDate(row.CreatedAt) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</el-card>
<!-- 配置表单对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="600px"
destroy-on-close
>
<el-form
ref="settingFormRef"
:model="settingForm"
:rules="settingRules"
label-width="120px"
>
<el-form-item label="配置组" prop="settingGroupID">
<el-select v-model="settingForm.settingGroupID" placeholder="请选择配置组" style="width: 100%">
<el-option
v-for="group in groupList"
:key="group.id"
:label="group.name"
:value="group.id"
/>
</el-select>
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="settingForm.name" placeholder="请输入配置名称" />
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select v-model="settingForm.type" placeholder="请选择类型" style="width: 100%" @change="handleTypeChange">
<el-option label="字符串 (string)" value="string" />
<el-option label="整数 (int)" value="int" />
<el-option label="浮点数 (float)" value="float" />
<el-option label="布尔值 (bool)" value="bool" />
</el-select>
</el-form-item>
<el-form-item label="值" prop="value">
<el-input
v-if="settingForm.type === 'string'"
v-model="settingForm.value"
type="textarea"
:rows="3"
placeholder="请输入配置值"
/>
<el-input-number
v-else-if="settingForm.type === 'int'"
v-model="settingForm.value"
:controls="false"
placeholder="请输入整数"
style="width: 100%"
/>
<el-input-number
v-else-if="settingForm.type === 'float'"
v-model="settingForm.value"
:controls="false"
:precision="2"
placeholder="请输入浮点数"
style="width: 100%"
/>
<el-switch
v-else-if="settingForm.type === 'bool'"
v-model="settingForm.value"
/>
<el-input
v-else
v-model="settingForm.value"
placeholder="请输入配置值"
/>
</el-form-item>
<el-form-item label="是否开放访问">
<el-switch v-model="settingForm.open" />
<span style="margin-left: 10px; color: #909399; font-size: 12px;">
开启后允许公开访问
</span>
</el-form-item>
<el-form-item label="备注" prop="note">
<el-input
v-model="settingForm.note"
type="textarea"
:rows="3"
placeholder="请输入备注信息"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Plus, Delete } from '@element-plus/icons-vue'
import {
getSettingList,
getSettingInfo,
createSetting,
updateSetting,
setSettingOpen,
deleteSetting
} from '@/api/admin/setting'
import { getSettingGroupList } from '@/api/admin/setting'
// 查询参数
const queryParams = reactive({
group_id: undefined,
key: '',
page: 1,
count: 10
})
// 配置表单
const settingForm = reactive({
id: undefined,
name: '',
value: '',
type: 'string',
settingGroupID: undefined,
open: false,
note: ''
})
const settingRules = {
name: [
{ required: true, message: '请输入配置名称', trigger: 'blur' }
],
value: [
{ required: true, message: '请输入配置值', trigger: 'blur' }
],
type: [
{ required: true, message: '请选择配置类型', trigger: 'change' }
],
settingGroupID: [
{ required: true, message: '请选择配置组', trigger: 'change' }
]
}
// 状态数据
const loading = ref(false)
const settingList = ref([])
const groupList = ref([])
const total = ref(0)
const selectedRows = ref([])
const dialogVisible = ref(false)
const dialogTitle = ref('新增配置')
const settingFormRef = ref(null)
const toggleLoading = ref(null)
// 格式化日期时间
const formatDate = (dateString) => {
if (!dateString) return '-'
const date = new Date(dateString)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
// 获取类型颜色
const getTypeColor = (type) => {
const colorMap = {
'string': 'primary',
'int': 'success',
'float': 'warning',
'bool': 'info'
}
return colorMap[type] || ''
}
// 获取配置组列表
const fetchGroupList = async () => {
try {
const res = await getSettingGroupList({ page: 1, count: 1000 })
if (res.data.code === 200) {
groupList.value = res.data.data.data || []
}
} catch (error) {
console.error('获取配置组列表失败:', error)
}
}
// 获取配置列表
const fetchSettingList = async () => {
loading.value = true
try {
const params = { ...queryParams }
if (!params.group_id) {
delete params.group_id
}
const res = await getSettingList(params)
console.log('配置列表数据:', res.data)
if (res.data.code === 200) {
settingList.value = res.data.data.data || []
total.value = res.data.data.all_count || 0
}
} catch (error) {
console.error('获取配置列表失败:', error)
ElMessage.error('获取配置列表失败')
} finally {
loading.value = false
}
}
// 查询
const handleQuery = () => {
queryParams.page = 1
fetchSettingList()
}
// 重置查询
const resetQuery = () => {
queryParams.group_id = undefined
queryParams.key = ''
queryParams.page = 1
fetchSettingList()
}
// 选择项变化
const handleSelectionChange = (selection) => {
selectedRows.value = selection
}
// 分页
const handleSizeChange = (size) => {
queryParams.count = size
fetchSettingList()
}
const handleCurrentChange = (page) => {
queryParams.page = page
fetchSettingList()
}
// 类型变化
const handleTypeChange = (type) => {
// 根据类型重置值
if (type === 'bool') {
settingForm.value = false
} else if (type === 'int' || type === 'float') {
settingForm.value = 0
} else {
settingForm.value = ''
}
}
// 新增配置
const handleAdd = () => {
dialogTitle.value = '新增配置'
Object.assign(settingForm, {
id: undefined,
name: '',
value: '',
type: 'string',
setting_group_id: undefined,
open: false,
note: ''
})
dialogVisible.value = true
}
// 编辑配置
const handleEdit = async (row) => {
dialogTitle.value = '编辑配置'
try {
const res = await getSettingInfo({ id: row.id })
console.log('配置详情数据:', res)
if (res.data.code === 200) {
const data = res.data.data
Object.assign(settingForm, {
id: data.id,
name: data.name || '',
value: data.value,
type: data.type || 'string',
settingGroupID: data.settingGroupID,
open: data.open || false,
note: data.note || ''
})
console.log('配置详情数据:', settingForm)
// 根据类型转换值
if (data.type === 'bool') {
settingForm.value = data.value === true || data.value === 'true' || data.value === 1
} else if (data.type === 'int') {
settingForm.value = parseInt(data.value) || 0
} else if (data.type === 'float') {
settingForm.value = parseFloat(data.value) || 0
}
dialogVisible.value = true
}
} catch (error) {
console.error('获取配置详情失败:', error)
ElMessage.error('获取配置详情失败')
}
}
// 切换开放状态
const handleToggleOpen = async (row) => {
toggleLoading.value = row.id
try {
const res = await setSettingOpen({
id: row.id,
open: row.open
})
if (res.data.code === 200) {
ElMessage.success('修改成功')
} else {
// 恢复原状态
row.open = !row.open
ElMessage.error(res.data.message || '修改失败')
}
} catch (error) {
// 恢复原状态
row.open = !row.open
console.error('修改失败:', error)
ElMessage.error(error.response?.data?.message || '修改失败')
} finally {
toggleLoading.value = null
}
}
// 删除配置
const handleDelete = (row) => {
ElMessageBox.confirm(`确认删除配置 "${row.name}" 吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deleteSetting({ id: row.id })
console.log('删除配置响应:', res.data)
if (res.data.code === 200) {
ElMessage.success('删除成功')
fetchSettingList()
}
} catch (error) {
console.error('删除失败:', error)
ElMessage.error(error.response?.data?.message || '删除失败')
}
}).catch(() => {})
}
// 批量删除
const handleBatchDelete = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请至少选择一条记录')
return
}
ElMessageBox.confirm(`确认删除选中的 ${selectedRows.value.length} 条记录吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const deletePromises = selectedRows.value.map(row =>
deleteSetting({ id: row.id })
)
await Promise.all(deletePromises)
ElMessage.success('批量删除成功')
fetchSettingList()
} catch (error) {
console.error('批量删除失败:', error)
ElMessage.error('批量删除失败')
}
}).catch(() => {})
}
// 提交表单
const submitForm = () => {
settingFormRef.value?.validate(async (valid) => {
if (valid) {
try {
const submitData = {
name: settingForm.name,
value: String(settingForm.value),
type: settingForm.type,
setting_group_id: settingForm.settingGroupID,
open: settingForm.open,
note: settingForm.note
}
if (settingForm.id) {
submitData.id = settingForm.id
}
console.log('提交配置数据:', submitData)
const res = settingForm.id
? await updateSetting(submitData)
: await createSetting(submitData)
if (res.data.code === 200) {
ElMessage.success(settingForm.id ? '修改成功' : '创建成功')
dialogVisible.value = false
fetchSettingList()
}
} catch (error) {
console.error('提交失败:', error)
ElMessage.error(error.response?.data?.message || '提交失败')
}
}
})
}
// 初始化
onMounted(() => {
fetchGroupList()
fetchSettingList()
})
</script>
<style scoped>
.setting-container {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
}
.search-form {
margin-bottom: 15px;
}
.action-bar {
display: flex;
gap: 12px;
}
.table-container {
border-radius: 8px;
}
.pagination {
margin-top: 24px;
justify-content: flex-end;
}
</style>
+344
View File
@@ -0,0 +1,344 @@
<template>
<div class="setting-group-container">
<!-- 搜索和操作栏 -->
<el-card class="filter-container" shadow="never">
<el-form :inline="true" :model="queryParams" class="search-form">
<el-form-item label="关键词筛选">
<el-input v-model="queryParams.key" placeholder="请输入关键词" clearable style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>查询
</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div class="action-bar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增配置组
</el-button>
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
<el-icon><Delete /></el-icon>批量删除
</el-button>
</div>
</el-card>
<!-- 配置组列表 -->
<el-card class="table-container" shadow="never">
<el-table
v-loading="loading"
:data="groupList"
@selection-change="handleSelectionChange"
style="width: 100%"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="名称" min-width="200" />
<el-table-column prop="note" label="备注" min-width="250" show-overflow-tooltip />
<el-table-column label="创建时间" width="180">
<template #default="{ row }">
{{ formatDate(row.CreatedAt) }}
</template>
</el-table-column>
<el-table-column label="更新时间" width="180">
<template #default="{ row }">
{{ formatDate(row.UpdatedAt) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</el-card>
<!-- 配置组表单对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="500px"
destroy-on-close
>
<el-form
ref="groupFormRef"
:model="groupForm"
:rules="groupRules"
label-width="100px"
>
<el-form-item label="名称" prop="name">
<el-input v-model="groupForm.name" placeholder="请输入配置组名称" />
</el-form-item>
<el-form-item label="备注" prop="note">
<el-input
v-model="groupForm.note"
type="textarea"
:rows="3"
placeholder="请输入备注信息"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Plus, Delete } from '@element-plus/icons-vue'
import {
getSettingGroupList,
getSettingGroupInfo,
createSettingGroup,
updateSettingGroup,
deleteSettingGroup
} from '@/api/admin/setting'
// 查询参数
const queryParams = reactive({
key: '',
page: 1,
count: 10
})
// 配置组表单
const groupForm = reactive({
id: undefined,
name: '',
note: ''
})
const groupRules = {
name: [
{ required: true, message: '请输入配置组名称', trigger: 'blur' }
]
}
// 状态数据
const loading = ref(false)
const groupList = ref([])
const total = ref(0)
const selectedRows = ref([])
const dialogVisible = ref(false)
const dialogTitle = ref('新增配置组')
const groupFormRef = ref(null)
// 格式化日期时间
const formatDate = (dateString) => {
if (!dateString) return '-'
const date = new Date(dateString)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
// 获取配置组列表
const fetchGroupList = async () => {
loading.value = true
try {
const res = await getSettingGroupList(queryParams)
console.log('配置组列表数据:', res.data)
if (res.data.code === 200) {
groupList.value = res.data.data.data || []
total.value = res.data.data.all_count || 0
}
} catch (error) {
console.error('获取配置组列表失败:', error)
ElMessage.error('获取配置组列表失败')
} finally {
loading.value = false
}
}
// 查询
const handleQuery = () => {
queryParams.page = 1
fetchGroupList()
}
// 重置查询
const resetQuery = () => {
queryParams.key = ''
queryParams.page = 1
fetchGroupList()
}
// 选择项变化
const handleSelectionChange = (selection) => {
selectedRows.value = selection
}
// 分页
const handleSizeChange = (size) => {
queryParams.count = size
fetchGroupList()
}
const handleCurrentChange = (page) => {
queryParams.page = page
fetchGroupList()
}
// 新增配置组
const handleAdd = () => {
dialogTitle.value = '新增配置组'
Object.assign(groupForm, {
id: undefined,
name: '',
note: ''
})
dialogVisible.value = true
}
// 编辑配置组
const handleEdit = async (row) => {
dialogTitle.value = '编辑配置组'
try {
const res = await getSettingGroupInfo({ setting_group_id: row.id })
console.log('配置组详情数据:', res.data)
if (res.data.code === 200) {
Object.assign(groupForm, {
id: res.data.data.id,
name: res.data.data.name || '',
note: res.data.data.note || ''
})
dialogVisible.value = true
}
} catch (error) {
console.error('获取配置组详情失败:', error)
ElMessage.error('获取配置组详情失败')
}
}
// 删除配置组
const handleDelete = (row) => {
ElMessageBox.confirm(`确认删除配置组 "${row.name}" 吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deleteSettingGroup({ setting_group_id: row.id })
console.log('删除配置组响应:', res.data)
if (res.data.code === 200) {
ElMessage.success('删除成功')
fetchGroupList()
}
} catch (error) {
console.error('删除失败:', error)
ElMessage.error(error.response?.data?.message || '删除失败')
}
}).catch(() => {})
}
// 批量删除
const handleBatchDelete = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请至少选择一条记录')
return
}
ElMessageBox.confirm(`确认删除选中的 ${selectedRows.value.length} 条记录吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const deletePromises = selectedRows.value.map(row =>
deleteSettingGroup({ setting_group_id: row.id })
)
await Promise.all(deletePromises)
ElMessage.success('批量删除成功')
fetchGroupList()
} catch (error) {
console.error('批量删除失败:', error)
ElMessage.error('批量删除失败')
}
}).catch(() => {})
}
// 提交表单
const submitForm = () => {
groupFormRef.value?.validate(async (valid) => {
if (valid) {
try {
const submitData = {
name: groupForm.name,
note: groupForm.note
}
if (groupForm.id) {
submitData.id = groupForm.id
}
console.log('提交配置组数据:', submitData)
const res = groupForm.id
? await updateSettingGroup(submitData)
: await createSettingGroup(submitData)
if (res.data.code === 200) {
ElMessage.success(groupForm.id ? '修改成功' : '创建成功')
dialogVisible.value = false
fetchGroupList()
}
} catch (error) {
console.error('提交失败:', error)
ElMessage.error(error.response?.data?.message || '提交失败')
}
}
})
}
// 初始化
onMounted(() => {
fetchGroupList()
})
</script>
<style scoped>
.setting-group-container {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
}
.search-form {
margin-bottom: 15px;
}
.action-bar {
display: flex;
gap: 12px;
}
.table-container {
border-radius: 8px;
}
.pagination {
margin-top: 24px;
justify-content: flex-end;
}
</style>
+771
View File
@@ -0,0 +1,771 @@
<template>
<div class="system-file-container">
<!-- 搜索和操作栏 -->
<el-card class="filter-container" shadow="never">
<el-form :inline="true" :model="queryParams" class="search-form">
<el-form-item label="关键词筛选">
<el-input v-model="queryParams.key" placeholder="请输入关键词" clearable style="width: 200px" />
</el-form-item>
<el-form-item label="筛选用户">
<el-input-number v-model="queryParams.user_id" placeholder="请输入用户ID" :controls="false" clearable style="width: 150px" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>查询
</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div class="action-bar">
<el-button type="primary" @click="handleUpload">
<el-icon><Upload /></el-icon>上传文件
</el-button>
<el-button type="success" @click="fetchFileList">
<el-icon><Refresh/></el-icon>刷新
</el-button>
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
<el-icon><Delete /></el-icon>批量删除
</el-button>
</div>
</el-card>
<!-- 文件列表 -->
<el-card class="table-container" shadow="never">
<el-table
v-loading="loading"
:data="fileList"
@selection-change="handleSelectionChange"
style="width: 100%"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="realName" label="真实文件名" min-width="200" />
<el-table-column prop="saveName" label="保存名称" min-width="150" />
<el-table-column prop="savePath" label="保存路径" min-width="250" show-overflow-tooltip />
<el-table-column prop="size" label="文件大小" width="120">
<template #default="{ row }">
{{ formatFileSize(row.size) }}
</template>
</el-table-column>
<el-table-column prop="type" label="文件类型" width="120">
<template #default="{ row }">
<el-tag :type="getFileTypeColor(row.type)">
{{ row.type || '未知' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="userId" label="用户ID" width="100" />
<el-table-column label="是否公开" width="100">
<template #default="{ row }">
<el-tag :type="row.openDow ? 'success' : 'info'">
{{ row.openDow ? '公开' : '私有' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="创建时间" width="180">
<template #default="{ row }">
{{ formatDate(row.CreatedAt) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleView(row)">查看</el-button>
<el-button type="success" link @click="handleDownload(row)">下载</el-button>
<el-button type="warning" link @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</el-card>
<!-- 文件详情对话框 -->
<el-dialog
v-model="detailDialogVisible"
title="文件详情"
width="700px"
destroy-on-close
>
<div v-if="fileDetail" class="file-detail-container">
<!-- 文件预览区域 -->
<div class="file-preview-section">
<div class="preview-label">文件预览</div>
<div class="preview-content">
<el-image
v-if="isImageFile(fileDetail.type) && fileDetail.url"
:src="fileDetail.url"
fit="contain"
style="max-width: 100%; max-height: 400px; border-radius: 8px;"
:preview-src-list="[fileDetail.url]"
:initial-index="0"
>
<template #error>
<div class="image-error">
<el-icon size="40"><Picture /></el-icon>
<div>图片加载失败</div>
</div>
</template>
</el-image>
<div v-else class="file-icon-large">
<el-icon size="80"><Document /></el-icon>
<div class="file-type-text">{{ fileDetail.type || '未知类型' }}</div>
</div>
</div>
</div>
<!-- 文件信息 -->
<el-descriptions :column="2" border class="file-info-descriptions">
<el-descriptions-item label="文件ID" label-align="right">{{ fileDetail.id }}</el-descriptions-item>
<el-descriptions-item label="用户ID" label-align="right">{{ fileDetail.userId }}</el-descriptions-item>
<el-descriptions-item label="真实文件名" label-align="right" :span="2">{{ fileDetail.realName }}</el-descriptions-item>
<el-descriptions-item label="保存名称" label-align="right">{{ fileDetail.saveName }}</el-descriptions-item>
<el-descriptions-item label="文件类型" label-align="right">
<el-tag :type="getFileTypeColor(fileDetail.type)">{{ fileDetail.type || '未知' }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="文件大小" label-align="right">{{ formatFileSize(fileDetail.size) }}</el-descriptions-item>
<el-descriptions-item label="是否公开" label-align="right">
<el-tag :type="fileDetail.openDow ? 'success' : 'info'">
{{ fileDetail.openDow ? '公开访问' : '私有' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="保存路径" label-align="right" :span="2">
<span class="file-path">{{ fileDetail.savePath }}</span>
</el-descriptions-item>
<el-descriptions-item label="文件URL" label-align="right" :span="2">
<el-link :href="fileDetail.url" target="_blank" type="primary" v-if="fileDetail.url">
点击查看文件
</el-link>
<span v-else style="color: #909399;">无URL</span>
</el-descriptions-item>
<el-descriptions-item label="创建时间" label-align="right">{{ formatDate(fileDetail.CreatedAt) }}</el-descriptions-item>
<el-descriptions-item label="更新时间" label-align="right">{{ formatDate(fileDetail.UpdatedAt) }}</el-descriptions-item>
<el-descriptions-item label="备注" label-align="right" :span="2">{{ fileDetail.content || '无' }}</el-descriptions-item>
</el-descriptions>
</div>
</el-dialog>
<!-- 文件编辑对话框 -->
<el-dialog
v-model="editDialogVisible"
title="编辑文件信息"
width="500px"
>
<el-form
ref="fileFormRef"
:model="fileForm"
:rules="fileRules"
label-width="120px"
>
<el-form-item label="文件ID">
<el-input v-model="fileForm.file_id" disabled />
</el-form-item>
<el-form-item label="用户ID" prop="user_id">
<el-input-number v-model="fileForm.user_id" placeholder="请输入用户ID" :controls="false" style="width: 100%" />
</el-form-item>
<el-form-item label="是否允许公开">
<el-switch v-model="fileForm.open_dow" />
<span style="margin-left: 10px; color: #909399; font-size: 12px;">
开启后允许公开访问
</span>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="editDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitEditForm">确定</el-button>
</template>
</el-dialog>
<!-- 文件上传对话框 -->
<el-dialog
v-model="uploadDialogVisible"
title="上传文件"
width="600px"
>
<el-form
ref="uploadFormRef"
:model="uploadForm"
label-width="120px"
>
<el-form-item label="上传类型" prop="update_type">
<el-select v-model="uploadForm.update_type" placeholder="请选择上传类型" style="width: 100%">
<el-option label="工单文件" value="work_order" />
<el-option label="封面" value="cover" />
</el-select>
</el-form-item>
<el-form-item label="是否开放下载">
<el-switch v-model="uploadForm.open_down" />
<span style="margin-left: 10px; color: #909399; font-size: 12px;">
开启后允许公开下载
</span>
</el-form-item>
<el-form-item label="上传文件">
<el-upload
ref="uploadRef"
:http-request="handleCustomUpload"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:on-remove="handleRemoveFile"
:on-change="handleFileChange"
:before-upload="beforeUpload"
:file-list="uploadFileList"
:auto-upload="false"
drag
multiple
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
将文件拖到此处<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
支持 jpg/png/gif/pdf/doc/docx 文件且不超过 10MB
</div>
</template>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleCloseUpload">取消</el-button>
<el-button type="primary" @click="handleSubmitUpload">确定上传</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Upload, Delete, Search, Document, VideoPlay, Folder, UploadFilled, Picture } from '@element-plus/icons-vue'
import { getFileList, getFileDetail, updateFile, deleteFile, uploadFile } from '@/api/admin/file'
// 查询参数
const queryParams = reactive({
key: '',
user_id: undefined,
page: 1,
count: 10
})
// 文件表单
const fileForm = reactive({
file_id: undefined,
user_id: undefined,
open_dow: false
})
const fileRules = {
user_id: [
{ required: true, message: '请输入用户ID', trigger: 'blur' }
]
}
// 状态数据
const loading = ref(false)
const fileList = ref([])
const fileDetail = ref(null)
const total = ref(0)
const selectedRows = ref([])
const detailDialogVisible = ref(false)
const editDialogVisible = ref(false)
const uploadDialogVisible = ref(false)
const fileFormRef = ref(null)
const uploadRef = ref(null)
const uploadFormRef = ref(null)
// 上传表单
const uploadForm = reactive({
update_type: 'work_order',
open_down: false
})
// 上传文件列表
const uploadFileList = ref([])
// 判断是否为图片文件
const isImageFile = (type) => {
const imageTypes = ['cover', 'image', 'avatar', 'photo', 'picture']
return imageTypes.includes(type?.toLowerCase())
}
// 获取文件类型颜色
const getFileTypeColor = (type) => {
if (isImageFile(type)) return 'success'
const colorMap = {
'document': 'primary',
'video': 'warning',
'audio': 'info',
'file': ''
}
return colorMap[type?.toLowerCase()] || 'info'
}
// 格式化日期时间
const formatDate = (dateString) => {
if (!dateString) return '-'
const date = new Date(dateString)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
// 格式化文件大小
const formatFileSize = (size) => {
if (size < 1024) return size + ' B'
if (size < 1024 * 1024) return (size / 1024).toFixed(2) + ' KB'
if (size < 1024 * 1024 * 1024) return (size / (1024 * 1024)).toFixed(2) + ' MB'
return (size / (1024 * 1024 * 1024)).toFixed(2) + ' GB'
}
// 获取文件列表
const fetchFileList = async () => {
loading.value = true
try {
const res = await getFileList(queryParams)
console.log('文件列表数据:', res.data)
if (res.data.code === 200) {
fileList.value = res.data.data.list || []
total.value = res.data.data.all_count || 0
}
} catch (error) {
console.error('获取文件列表失败:', error)
ElMessage.error('获取文件列表失败')
} finally {
loading.value = false
}
}
// 查询
const handleQuery = () => {
queryParams.page = 1
fetchFileList()
}
// 重置查询
const resetQuery = () => {
queryParams.key = ''
queryParams.user_id = undefined
queryParams.page = 1
fetchFileList()
}
// 选择项变化
const handleSelectionChange = (selection) => {
selectedRows.value = selection
}
// 分页
const handleSizeChange = (size) => {
queryParams.count = size
fetchFileList()
}
const handleCurrentChange = (page) => {
queryParams.page = page
fetchFileList()
}
// 查看文件详情
const handleView = async (row) => {
try {
const res = await getFileDetail({ file_id: row.id })
console.log('文件详情数据:', res.data)
if (res.data.code === 200) {
fileDetail.value = res.data.data.data
fileDetail.value.url = res.data.data.url
detailDialogVisible.value = true
}
} catch (error) {
console.error('获取文件详情失败:', error)
ElMessage.error('获取文件详情失败')
}
}
// 下载文件
const handleDownload = async (row) => {
try {
// 先获取文件详情以获取完整URL
const res = await getFileDetail({ file_id: row.id })
if (res.data.code === 200 && res.data.data.url) {
const link = document.createElement('a')
link.href = res.data.data.url
link.download = row.realName
link.target = '_blank'
link.click()
ElMessage.success('开始下载文件')
} else {
ElMessage.error('获取文件下载链接失败')
}
} catch (error) {
console.error('下载文件失败:', error)
ElMessage.error('下载文件失败')
}
}
// 编辑文件
const handleEdit = (row) => {
Object.assign(fileForm, {
file_id: row.id,
user_id: row.userId || undefined,
open_dow: row.openDow || false
})
editDialogVisible.value = true
}
// 删除文件
const handleDelete = (row) => {
ElMessageBox.confirm(`确认删除文件 ${row.realName} 吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deleteFile({ file_id: row.id })
console.log('删除文件响应:', res.data)
if (res.data.code === 200) {
ElMessage.success('删除成功')
fetchFileList()
}
} catch (error) {
console.error('删除失败:', error)
ElMessage.error(error.response?.data?.message || '删除失败')
}
}).catch(() => {})
}
// 批量删除
const handleBatchDelete = () => {
console.log("批量选择的值:",selectedRows.value)
if (selectedRows.value.length === 0) {
ElMessage.warning('请至少选择一条记录')
return
}
ElMessageBox.confirm(`确认删除选中的 ${selectedRows.value.length} 条记录吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
try{
const deleteMap = selectedRows.value.map(f => deleteFile({file_id:f.id}))
//等待所有删除完毕
await Promise.all(deleteMap)
ElMessage.success('批量删除成功')
//刷新文件列表
fetchFileList()
}catch(error){
console.error('删除失败:', error)
ElMessage.error(error.response?.data?.message || '删除失败')
}
}).catch(() => {})
}
// 上传文件
const handleUpload = () => {
uploadForm.update_type = 'work_order'
uploadForm.open_down = false
uploadFileList.value = []
uploadDialogVisible.value = true
}
// 关闭上传对话框
const handleCloseUpload = () => {
uploadDialogVisible.value = false
uploadFileList.value = []
}
// 文件列表变化
const handleFileChange = (file, fileList) => {
console.log('文件列表变化:', file, fileList)
uploadFileList.value = fileList
}
// 移除文件
const handleRemoveFile = (file, fileList) => {
console.log('移除文件:', file, fileList)
uploadFileList.value = fileList
}
// 提交上传
const handleSubmitUpload = () => {
if (uploadFileList.value.length === 0) {
ElMessage.warning('请至少选择一个文件')
return
}
// 触发所有待上传文件的上传
const filesToUpload = uploadFileList.value.filter(file =>
file.status !== 'success' && file.status !== 'uploading'
)
if (filesToUpload.length === 0) {
ElMessage.info('所有文件已上传完成')
return
}
// 逐个提交文件
uploadRef.value?.submit()
}
// 上传前检查(只做提示,不阻止文件添加到列表)
const beforeUpload = (file) => {
const isValidType = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'].includes(file.type)
const isLt10M = file.size / 1024 / 1024 < 10
console.log('beforeUpload', file)
if (!isValidType) {
ElMessage.warning(`文件 ${file.name} 格式不符合要求(仅支持 JPG/PNG/GIF/PDF/DOC/DOCX`)
}
if (!isLt10M) {
ElMessage.warning(`文件 ${file.name} 大小超过 10MB`)
}
// 允许文件添加到列表,在上传时再进行验证
return true
}
// 自定义上传方法
const handleCustomUpload = async (options) => {
const { file, onSuccess, onError } = options
console.log('开始上传文件:', file)
// 在上传前进行验证
const isValidType = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'].includes(file.type)
const isLt10M = file.size / 1024 / 1024 < 10
if (!isValidType) {
const error = new Error(`文件 ${file.name} 格式不符合要求(仅支持 JPG/PNG/GIF/PDF/DOC/DOCX`)
// 标记为校验类错误,on-error 中不再弹 error 提示
error.isValidation = true
onError(error, file)
return
}
if (!isLt10M) {
const error = new Error(`文件 ${file.name} 大小超过 10MB`)
error.isValidation = true
onError(error, file)
return
}
try {
const formData = new FormData()
// 根据 API 文档,字段名应该是 files(复数)
formData.append('files', file)
// 添加文件名列表(虽然 API 文档说是数组,但实际传递时直接传字符串)
formData.append('file_names', file.name)
// 添加上传类型
if (uploadForm.update_type) {
formData.append('update_type', uploadForm.update_type)
}
// 添加是否开放下载
formData.append('open_down', uploadForm.open_down ? '1' : '0')
console.log('上传参数:', {
files: file.name,
file_names: [file.name],
update_type: uploadForm.update_type,
open_down: uploadForm.open_down
})
const res = await uploadFile(formData)
console.log('上传响应:', res)
// 根据返回码严格区分成功和失败
if (res && res.data && res.data.code === 200) {
onSuccess(res.data.data, file)
} else {
const errorMsg = res?.data?.message || res?.data?.msg || '上传失败'
const error = new Error(errorMsg)
onError(error, file)
}
} catch (error) {
console.error('上传文件失败:', error)
const err = new Error(error?.response?.data?.message || error?.message || '上传失败')
onError(err, file)
}
}
// 上传成功
const handleUploadSuccess = (response, file, fileList) => {
console.log('上传成功文件:', file)
console.log('上传成功文件列表:',fileList)
// 成功回调只会在 code === 200 时触发
// ElMessage.success(`文件 ${file.name} 上传成功`)
// 更新文件列表状态
uploadFileList.value = fileList
// 如果所有文件都上传成功,关闭对话框并刷新列表
const allSuccess = fileList.every(f => f.status === 'success')
const uploadList = fileList.some(f => f.status === 'uploading')
if (allSuccess && !uploadList && fileList.length > 0) {
ElMessage.success(`已成功上传${fileList.length}个文件`)
setTimeout(() => {
uploadDialogVisible.value = false
uploadFileList.value = []
fetchFileList()
}, 1000)
}
}
// 上传失败
const handleUploadError = (error, file, fileList) => {
console.error('上传失败:', error, file, fileList)
// 对校验类错误仅在 beforeUpload 中提示过一次 warning,这里不再重复报错
if (error?.isValidation) return
ElMessage.error(error?.message || '上传失败,请检查网络连接或联系管理员')
}
// 提交编辑表单
const submitEditForm = () => {
fileFormRef.value?.validate(async (valid) => {
if (valid) {
try {
const submitData = {
file_id: fileForm.file_id,
user_id: Number(fileForm.user_id),
open_dow: fileForm.open_dow
}
console.log('提交文件信息数据:', submitData)
const res = await updateFile(submitData)
if (res.data.code === 200) {
ElMessage.success('修改成功')
editDialogVisible.value = false
fetchFileList()
}
} catch (error) {
console.error('修改失败:', error)
ElMessage.error(error.response?.data?.message || '修改失败')
}
}
})
}
// 初始化
onMounted(() => {
fetchFileList()
})
</script>
<style scoped>
.system-file-container {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
}
.search-form {
margin-bottom: 15px;
}
.action-bar {
display: flex;
gap: 12px;
}
.table-container {
border-radius: 8px;
}
.file-icon {
display: flex;
align-items: center;
justify-content: center;
width: 60px;
height: 60px;
background-color: #f5f7fa;
border-radius: 4px;
}
.pagination {
margin-top: 24px;
justify-content: flex-end;
}
.file-detail-container {
padding: 10px 0;
}
.file-preview-section {
margin-bottom: 24px;
}
.preview-label {
font-size: 14px;
font-weight: 500;
color: #303133;
margin-bottom: 12px;
}
.preview-content {
display: flex;
justify-content: center;
align-items: center;
background-color: #f5f7fa;
border-radius: 8px;
padding: 20px;
min-height: 200px;
}
.file-icon-large {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #909399;
gap: 12px;
}
.file-type-text {
font-size: 14px;
color: #606266;
}
.image-error {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #909399;
gap: 8px;
}
.file-info-descriptions {
margin-top: 16px;
}
.file-path {
font-family: 'Courier New', monospace;
font-size: 12px;
color: #606266;
word-break: break-all;
}
:deep(.el-descriptions__label) {
width: 120px;
}
</style>
+358
View File
@@ -0,0 +1,358 @@
<template>
<div class="admin-group-container">
<!-- 操作栏 -->
<el-card class="filter-container" shadow="never">
<el-row :gutter="20">
<el-col :span="8">
<el-input
v-model="queryParams.key"
placeholder="搜索关键词"
clearable
@clear="fetchGroupList"
@keyup.enter="fetchGroupList"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</el-col>
<el-col :span="16">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增管理员组
</el-button>
<el-button type="success" @click="fetchGroupList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
</el-col>
</el-row>
</el-card>
<!-- 管理员组列表 -->
<el-card class="table-container" shadow="never">
<el-table
v-loading="loading"
:data="groupList"
style="width: 100%"
>
<el-table-column prop="id" label="组ID" width="100" />
<el-table-column prop="name" label="组名称" min-width="200" />
<el-table-column prop="note" label="备注" min-width="250" />
<!-- <el-table-column prop="member_count" label="成员数量" width="120" /> -->
<el-table-column prop="CreatedAt" label="创建时间" width="180" />
<el-table-column label="操作" width="250" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="success" link @click="handleViewMembers(row)">查看成员</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</el-card>
<!-- 管理员组表单对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增管理员组' : '编辑管理员组'"
width="600px"
>
<el-form
ref="groupFormRef"
:model="groupForm"
:rules="groupRules"
label-width="120px"
>
<el-form-item label="组名称" prop="name">
<el-input v-model="groupForm.name" placeholder="请输入组名称" />
</el-form-item>
<el-form-item label="备注" prop="note">
<el-input v-model="groupForm.note" type="textarea" :rows="4" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
<!-- 成员列表对话框 -->
<el-dialog
v-model="memberDialogVisible"
title="管理员组成员"
width="900px"
>
<el-input
v-model="memberParams.key"
placeholder="搜索成员"
clearable
@clear="fetchMemberList"
@keyup.enter="fetchMemberList"
style="margin-bottom: 16px"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
<el-table
v-loading="memberLoading"
:data="memberList"
style="width: 100%"
>
<el-table-column prop="UserId" label="用户ID" width="100" />
<el-table-column prop="UserName" label="用户名" min-width="150" />
<el-table-column prop="Email" label="邮箱" min-width="200" />
<el-table-column prop="Phone" label="手机号" width="130" />
<el-table-column label="性别" width="80">
<template #default="{ row }">
{{ row.Sex ? '男' : '女' }}
</template>
</el-table-column>
<el-table-column prop="Age" label="年龄" width="80" />
<el-table-column prop="UserGroupId" label="用户组" width="120" />
<el-table-column label="加入时间" width="180">
<template #default="{ row }">
{{ formatTime(row.CreatedAt) }}
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="memberParams.page"
v-model:page-size="memberParams.count"
:page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next"
:total="memberTotal"
@size-change="handleMemberSizeChange"
@current-change="handleMemberCurrentChange"
background
class="pagination"
/>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Refresh, Search } from '@element-plus/icons-vue'
import {
getAdminGroupList,
getAdminGroupMemberList,
addAdminGroup,
updateAdminGroupInfo,
deleteAdminGroup
} from '@/api/admin/group'
import { formatTime } from '@/utils/tool'
// 查询参数
const queryParams = reactive({
key: '',
page: 1,
count: 10
})
// 成员查询参数
const memberParams = reactive({
key: '',
group_id: '',
page: 1,
count: 10
})
// 管理员组表单
const groupForm = reactive({
group_id: undefined,
name: '',
note: ''
})
const groupRules = {
name: [
{ required: true, message: '请输入组名称', trigger: 'blur' }
]
}
// 状态数据
const loading = ref(false)
const memberLoading = ref(false)
const groupList = ref([])
const memberList = ref([])
const total = ref(0)
const memberTotal = ref(0)
const dialogVisible = ref(false)
const memberDialogVisible = ref(false)
const dialogType = ref('add')
const groupFormRef = ref(null)
// 获取管理员组列表
const fetchGroupList = async () => {
loading.value = true
try {
const res = await getAdminGroupList(queryParams)
if (res.data.code === 200) {
let responseData = res.data.data.data || []
responseData.forEach(item => {
item.CreatedAt = formatTime(item.CreatedAt)
})
groupList.value = responseData
total.value = res.data.data.total || 0
}
} catch (error) {
ElMessage.error('获取管理员组列表失败')
} finally {
loading.value = false
}
}
// 获取成员列表
const fetchMemberList = async () => {
memberLoading.value = true
try {
const res = await getAdminGroupMemberList(memberParams)
if (res.data.code === 200) {
let responseData = res.data.data.data || []
// 数据已经包含所有需要的字段,不需要额外处理
memberList.value = responseData
memberTotal.value = res.data.data.total || 0
}
} catch (error) {
ElMessage.error('获取成员列表失败')
} finally {
memberLoading.value = false
}
}
// 分页
const handleSizeChange = (size) => {
queryParams.count = size
fetchGroupList()
}
const handleCurrentChange = (page) => {
queryParams.page = page
fetchGroupList()
}
const handleMemberSizeChange = (size) => {
memberParams.count = size
fetchMemberList()
}
const handleMemberCurrentChange = (page) => {
memberParams.page = page
fetchMemberList()
}
// 新增管理员组
const handleAdd = () => {
dialogType.value = 'add'
dialogVisible.value = true
Object.assign(groupForm, {
group_id: undefined,
name: '',
note: ''
})
groupFormRef.value?.resetFields()
}
// 编辑管理员组
const handleEdit = (row) => {
console.log("编辑管理员组",row)
dialogType.value = 'edit'
dialogVisible.value = true
Object.assign(groupForm, {
group_id: row.id,
name: row.name,
note: row.note
})
}
// 查看成员
const handleViewMembers = (row) => {
memberParams.group_id = row.id
memberParams.key = ''
memberParams.page = 1
memberDialogVisible.value = true
fetchMemberList()
}
// 删除管理员组
const handleDelete = (row) => {
ElMessageBox.confirm(`确认删除管理员组 ${row.name} 吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deleteAdminGroup({ group_id: row.id })
if (res.data.code === 200) {
ElMessage.success('删除成功')
fetchGroupList()
}
} catch (error) {
ElMessage.error('删除失败')
}
}).catch(() => {})
}
// 提交表单
const submitForm = () => {
groupFormRef.value?.validate(async (valid) => {
if (valid) {
try {
let res
if (dialogType.value === 'add') {
res = await addAdminGroup(groupForm)
} else {
res = await updateAdminGroupInfo(groupForm)
}
if (res.data.code === 200) {
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '修改成功')
dialogVisible.value = false
fetchGroupList()
}
} catch (error) {
ElMessage.error('操作失败')
}
}
})
}
// 初始化
onMounted(() => {
fetchGroupList()
})
</script>
<style scoped>
.admin-group-container {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
}
.table-container {
border-radius: 8px;
}
.pagination {
margin-top: 24px;
justify-content: flex-end;
}
</style>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+632
View File
@@ -0,0 +1,632 @@
<template>
<div class="user-group-container">
<!-- 操作栏 -->
<el-card class="filter-container" shadow="never">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增用户组
</el-button>
<el-button type="success" @click="fetchGroupList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
</el-card>
<!-- 用户组列表 -->
<el-card class="table-container" shadow="never">
<el-table
v-loading="loading"
:data="groupList"
style="width: 100%"
>
<el-table-column label="组ID" width="100">
<template #default="{ row }">
{{ row.group_id || row.GroupId || row.id || row.Id }}
</template>
</el-table-column>
<el-table-column label="组名称" min-width="200">
<template #default="{ row }">
{{ row.group_name || row.name || row.Name }}
</template>
</el-table-column>
<el-table-column label="权限" min-width="200" show-overflow-tooltip>
<template #default="{ row }">
<el-tag type="info">{{ row.auth || row.Auth || '-' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="升级金额" width="120">
<template #default="{ row }">
<span v-if="row.floor_price || row.FloorPrice">¥{{ row.floor_price || row.FloorPrice }}</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="下一级组ID" width="120">
<template #default="{ row }">
{{ row.higher_level_id || row.HigherLevelId || '-' }}
</template>
</el-table-column>
<el-table-column label="类型" width="100" align="center">
<template #default="{ row }">
<el-tag :type="(row.fixed || row.Fixed) ? 'warning' : 'success'" size="small">
{{ (row.fixed || row.Fixed) ? '固定' : '可升级' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="成员数量" width="100" align="center">
<template #default="{ row }">
{{ row.member_count || row.MemberCount || 0 }}
</template>
</el-table-column>
<el-table-column label="创建时间" width="160" show-overflow-tooltip>
<template #default="{ row }">
{{ row.create_time || row.CreateTime || row.CreatedAt || '-' }}
</template>
</el-table-column>
<el-table-column label="操作" width="280" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="success" link @click="handleViewMembers(row)">查看成员</el-button>
<!-- <el-button type="warning" link @click="handleAddMember(row)">添加成员</el-button> -->
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</el-card>
<!-- 用户组表单对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增用户组' : '编辑用户组'"
width="650px"
destroy-on-close
>
<el-form
ref="groupFormRef"
:model="groupForm"
:rules="groupRules"
label-width="140px"
>
<el-form-item v-if="dialogType === 'edit'" label="组ID">
<el-input v-model="groupForm.group_id" disabled />
</el-form-item>
<el-form-item label="组名称" prop="name">
<el-input v-model="groupForm.name" placeholder="请输入组名称" />
</el-form-item>
<el-form-item label="权限" prop="auth">
<el-input v-model="groupForm.auth" type="textarea" :rows="4" placeholder="请输入权限配置(JSON格式)" />
</el-form-item>
<el-form-item label="下一级用户组ID">
<el-input-number
v-model="groupForm.higher_level_id"
:min="0"
placeholder="请输入下一级用户组ID"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="升级所需消费金额">
<el-input-number
v-model="groupForm.floor_price"
:min="0"
:precision="2"
placeholder="请输入升级所需消费金额"
style="width: 100%"
>
<template #prepend>¥</template>
</el-input-number>
</el-form-item>
<el-form-item label="是否为固定用户组">
<el-switch
v-model="groupForm.fixed"
active-text="固定不可升级"
inactive-text="可升级"
/>
<div style="color: #909399; font-size: 12px; margin-top: 4px;">
固定用户组不能通过消费金额自动升级
</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
<!-- 成员列表对话框 -->
<el-dialog
v-model="memberDialogVisible"
title="用户组成员"
width="800px"
>
<el-table
v-loading="memberLoading"
:data="memberList"
style="width: 100%"
>
<el-table-column label="用户ID" width="100">
<template #default="{ row }">
{{ row.user_id || row.UserId || row.id || row.Id }}
</template>
</el-table-column>
<el-table-column label="用户名" min-width="150">
<template #default="{ row }">
{{ row.username || row.Username || row.UserName || row.name || row.Name }}
</template>
</el-table-column>
<el-table-column label="邮箱" min-width="200">
<template #default="{ row }">
{{ row.email || row.Email || '-' }}
</template>
</el-table-column>
<el-table-column label="加入时间" width="180">
<template #default="{ row }">
{{ row.join_time || row.JoinTime || row.CreatedAt || '-' }}
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="memberParams.page"
v-model:page-size="memberParams.count"
:page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next"
:total="memberTotal"
@size-change="handleMemberSizeChange"
@current-change="handleMemberCurrentChange"
background
class="pagination"
/>
</el-dialog>
<!-- 添加成员对话框 -->
<el-dialog
v-model="addMemberDialogVisible"
title="添加用户组成员"
width="500px"
>
<el-form
ref="memberFormRef"
:model="memberForm"
:rules="memberRules"
label-width="100px"
>
<el-form-item label="用户ID" prop="user_ids">
<el-input v-model="memberForm.user_ids" placeholder="请输入用户ID,多个用逗号分隔" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="addMemberDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitAddMember">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Refresh } from '@element-plus/icons-vue'
import {
getUserGroupList,
getUserGroupMemberList,
createUserGroup,
updateUserGroupInfo,
deleteUserGroup,
addUserGroupMember
} from '@/api/admin/user'
import { formatTime } from '@/utils/tool'
// 查询参数
const queryParams = reactive({
page: 1,
count: 10
})
// 成员查询参数
const memberParams = reactive({
group_id: '',
page: 1,
count: 10
})
// 用户组表单
const groupForm = reactive({
group_id: undefined,
name: '',
auth: '',
higher_level_id: undefined,
floor_price: undefined,
fixed: false
})
const groupRules = {
name: [
{ required: true, message: '请输入组名称', trigger: 'blur' }
],
auth: [
{ required: true, message: '请输入权限', trigger: 'blur' }
]
}
// 成员表单
const memberForm = reactive({
group_id: '',
user_ids: ''
})
const memberRules = {
user_ids: [
{ required: true, message: '请输入用户ID', trigger: 'blur' }
]
}
// 状态数据
const loading = ref(false)
const memberLoading = ref(false)
const groupList = ref([])
const memberList = ref([])
const total = ref(0)
const memberTotal = ref(0)
const dialogVisible = ref(false)
const memberDialogVisible = ref(false)
const addMemberDialogVisible = ref(false)
const dialogType = ref('add')
const groupFormRef = ref(null)
const memberFormRef = ref(null)
// 获取用户组列表
const fetchGroupList = async () => {
console.log('=== 获取用户组列表 ===')
console.log('请求参数:', queryParams)
loading.value = true
try {
const res = await getUserGroupList(queryParams)
console.log('用户组列表响应:', res)
console.log('响应状态码:', res.data?.code || res.code)
console.log('响应数据:', res.data?.data || res.data)
const code = res.data?.code || res.code
if (code === 200) {
let responseData = res.data?.data || res.data
responseData.data.forEach(item => {
item.CreatedAt = formatTime(item.CreatedAt)
})
// 处理不同的数据结构
if (Array.isArray(responseData)) {
groupList.value = responseData
total.value = responseData.length
} else if (responseData.list) {
groupList.value = responseData.list || []
total.value = responseData.total || responseData.all_count || 0
} else if (responseData.data && Array.isArray(responseData.data)) {
groupList.value = responseData.data
total.value = responseData.all_count || responseData.data.length
} else {
groupList.value = []
total.value = 0
}
console.log('用户组列表数据:', groupList.value)
console.log('总数:', total.value)
if (groupList.value.length > 0) {
console.log('第一个用户组示例:', groupList.value[0])
}
} else {
console.error('获取用户组列表失败:', res.data)
ElMessage.error(res.data?.message || '获取用户组列表失败')
}
} catch (error) {
console.error('获取用户组列表错误:', error)
console.error('错误详情:', error.response || error.message)
ElMessage.error('获取用户组列表失败')
} finally {
loading.value = false
}
}
// 获取成员列表
const fetchMemberList = async () => {
console.log('=== 获取用户组成员列表 ===')
console.log('请求参数:', memberParams)
memberLoading.value = true
try {
const res = await getUserGroupMemberList(memberParams)
console.log('成员列表响应:', res)
console.log('响应状态码:', res.data?.code || res.code)
console.log('响应数据:', res.data?.data || res.data)
const code = res.data?.code || res.code
if (code === 200) {
const responseData = res.data?.data || res.data
// 处理不同的数据结构
if (Array.isArray(responseData)) {
memberList.value = responseData
memberTotal.value = responseData.length
} else if (responseData.list) {
memberList.value = responseData.list || []
memberTotal.value = responseData.total || responseData.all_count || 0
} else if (responseData.data && Array.isArray(responseData.data)) {
memberList.value = responseData.data
memberTotal.value = responseData.all_count || responseData.data.length
} else {
memberList.value = []
memberTotal.value = 0
}
console.log('成员列表数据:', memberList.value)
console.log('成员总数:', memberTotal.value)
if (memberList.value.length > 0) {
console.log('第一个成员示例:', memberList.value[0])
}
} else {
console.error('获取成员列表失败:', res.data)
ElMessage.error(res.data?.message || '获取成员列表失败')
}
} catch (error) {
console.error('获取成员列表错误:', error)
console.error('错误详情:', error.response || error.message)
ElMessage.error('获取成员列表失败')
} finally {
memberLoading.value = false
}
}
// 分页
const handleSizeChange = (size) => {
queryParams.count = size
fetchGroupList()
}
const handleCurrentChange = (page) => {
queryParams.page = page
fetchGroupList()
}
const handleMemberSizeChange = (size) => {
memberParams.count = size
fetchMemberList()
}
const handleMemberCurrentChange = (page) => {
memberParams.page = page
fetchMemberList()
}
// 新增用户组
const handleAdd = () => {
console.log('=== 打开新增用户组对话框 ===')
dialogType.value = 'add'
dialogVisible.value = true
Object.assign(groupForm, {
group_id: undefined,
name: '',
auth: '',
higher_level_id: undefined,
floor_price: undefined,
fixed: false
})
groupFormRef.value?.resetFields()
}
// 编辑用户组
const handleEdit = (row) => {
console.log('=== 打开编辑用户组对话框 ===')
console.log('用户组数据:', row)
dialogType.value = 'edit'
dialogVisible.value = true
// 适配不同的字段名
const groupId = row.group_id || row.GroupId || row.id || row.Id
const groupName = row.group_name || row.name || row.Name
const groupAuth = row.auth || row.Auth || ''
const higherLevelId = row.higher_level_id || row.HigherLevelId
const floorPrice = row.floor_price || row.FloorPrice
const fixed = row.fixed || row.Fixed || false
Object.assign(groupForm, {
group_id: groupId,
name: groupName,
auth: typeof groupAuth === 'object' ? JSON.stringify(groupAuth, null, 2) : groupAuth,
higher_level_id: higherLevelId,
floor_price: floorPrice,
fixed: fixed
})
console.log('表单数据:', groupForm)
}
// 查看成员
const handleViewMembers = (row) => {
// 适配不同的字段名
const groupId = row.group_id || row.GroupId || row.id || row.Id
console.log('=== 查看用户组成员 ===')
console.log('用户组数据:', row)
console.log('组ID:', groupId)
memberParams.group_id = groupId
memberParams.page = 1
memberDialogVisible.value = true
fetchMemberList()
}
// 添加成员
const handleAddMember = (row) => {
// 适配不同的字段名
const groupId = row.group_id || row.GroupId || row.id || row.Id
console.log('=== 打开添加成员对话框 ===')
console.log('用户组数据:', row)
console.log('组ID:', groupId)
memberForm.group_id = groupId
memberForm.user_ids = ''
addMemberDialogVisible.value = true
}
// 删除用户组
const handleDelete = (row) => {
// 适配不同的字段名
const groupId = row.group_id || row.GroupId || row.id || row.Id
const groupName = row.group_name || row.name || row.Name
console.log('=== 删除用户组 ===')
console.log('用户组数据:', row)
console.log('组ID:', groupId)
console.log('组名称:', groupName)
ElMessageBox.confirm(`确认删除用户组 ${groupName} 吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deleteUserGroup({ group_id: groupId })
console.log('删除响应:', res)
console.log('响应状态码:', res.data?.code || res.code)
const code = res.data?.code || res.code
if (code === 200) {
ElMessage.success('删除成功')
fetchGroupList()
} else {
console.error('删除失败:', res.data)
ElMessage.error(res.data?.message || '删除失败')
}
} catch (error) {
console.error('删除错误:', error)
console.error('错误详情:', error.response || error.message)
ElMessage.error('删除失败')
}
}).catch(() => {})
}
// 提交用户组表单
const submitForm = () => {
groupFormRef.value?.validate(async (valid) => {
if (valid) {
console.log('=== 提交用户组表单 ===')
console.log('操作类型:', dialogType.value)
console.log('表单数据:', groupForm)
try {
let res
const submitData = {
name: groupForm.name,
auth: groupForm.auth
}
// 添加可选字段(如果有值才添加)
if (groupForm.higher_level_id !== undefined && groupForm.higher_level_id !== null) {
submitData.higher_level_id = groupForm.higher_level_id
}
if (groupForm.floor_price !== undefined && groupForm.floor_price !== null) {
submitData.floor_price = groupForm.floor_price
}
// fixed 字段总是传递(boolean 值)
submitData.fixed = groupForm.fixed
if (dialogType.value === 'add') {
console.log('新增用户组,提交数据:', submitData)
res = await createUserGroup(submitData)
} else {
submitData.group_id = groupForm.group_id
console.log('更新用户组,提交数据:', submitData)
res = await updateUserGroupInfo(submitData)
}
console.log('提交响应:', res)
console.log('响应状态码:', res.data?.code || res.code)
const code = res.data?.code || res.code
if (code === 200) {
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '修改成功')
dialogVisible.value = false
fetchGroupList()
} else {
console.error('操作失败:', res.data)
ElMessage.error(res.data?.message || '操作失败')
}
} catch (error) {
console.error('提交失败:', error)
console.error('错误详情:', error.response || error.message)
ElMessage.error('操作失败')
}
}
})
}
// 提交添加成员
const submitAddMember = () => {
memberFormRef.value?.validate(async (valid) => {
if (valid) {
console.log('=== 提交添加成员 ===')
console.log('表单数据:', memberForm)
try {
const res = await addUserGroupMember(memberForm)
console.log('添加成员响应:', res)
console.log('响应状态码:', res.data?.code || res.code)
const code = res.data?.code || res.code
if (code === 200) {
ElMessage.success('添加成功')
addMemberDialogVisible.value = false
fetchGroupList()
} else {
console.error('添加失败:', res.data)
ElMessage.error(res.data?.message || '添加失败')
}
} catch (error) {
console.error('添加错误:', error)
console.error('错误详情:', error.response || error.message)
ElMessage.error('添加失败')
}
}
})
}
// 初始化
onMounted(() => {
console.log('=== 用户组管理页面初始化 ===')
fetchGroupList()
})
</script>
<style scoped>
.user-group-container {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
}
.table-container {
border-radius: 8px;
}
.pagination {
margin-top: 24px;
justify-content: flex-end;
}
</style>
File diff suppressed because it is too large Load Diff
+687
View File
@@ -0,0 +1,687 @@
{
"openapi": "3.0.1",
"info": {
"title": "默认模块",
"description": "",
"version": "1.0.0"
},
"tags": [],
"paths": {
"/api/v1/admin/server/setting/group/list": {
"get": {
"summary": "获取配置分组列表",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [
{
"name": "page",
"in": "query",
"description": "获取页码 默认 1",
"required": false,
"schema": {
"type": "integer"
}
},
{
"name": "count",
"in": "query",
"description": "获取条数 默认 10",
"required": false,
"schema": {
"type": "integer"
}
},
{
"name": "key",
"in": "query",
"description": "关键词筛选",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "Authorization",
"in": "header",
"description": "",
"example": "Bearer {{token}}",
"schema": {
"type": "string",
"default": "Bearer {{token}}"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {}
}
}
},
"headers": {}
}
},
"security": []
}
},
"/api/v1/admin/server/setting/group/info": {
"get": {
"summary": "获取配置分组信息",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [
{
"name": "setting_group_id",
"in": "query",
"description": "",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "Authorization",
"in": "header",
"description": "",
"example": "Bearer {{token}}",
"schema": {
"type": "string",
"default": "Bearer {{token}}"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {}
}
}
},
"headers": {}
}
},
"security": []
}
},
"/api/v1/admin/server/setting/group/create": {
"post": {
"summary": "创建配置分组",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [
{
"name": "Authorization",
"in": "header",
"description": "",
"example": "Bearer {{token}}",
"schema": {
"type": "string",
"default": "Bearer {{token}}"
}
}
],
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"properties": {
"name": {
"description": "名称",
"example": "",
"type": "string"
},
"note": {
"description": "备注",
"example": "",
"type": "string"
}
}
}
}
}
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {}
},
"example": {
"code": 200,
"message": "Success"
}
}
},
"headers": {}
}
},
"security": []
}
},
"/api/v1/admin/server/setting/group/update": {
"post": {
"summary": "修改配置分组",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [
{
"name": "Authorization",
"in": "header",
"description": "",
"example": "Bearer {{token}}",
"schema": {
"type": "string",
"default": "Bearer {{token}}"
}
}
],
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"properties": {
"id": {
"description": "ID",
"example": "",
"type": "string"
},
"name": {
"description": "名称",
"example": "",
"type": "string"
},
"note": {
"description": "备注",
"example": "",
"type": "string"
}
}
}
}
}
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {}
}
}
},
"headers": {}
}
},
"security": []
}
},
"/api/v1/admin/server/setting/group/delete": {
"delete": {
"summary": "删除配置分组",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [
{
"name": "setting_group_id",
"in": "query",
"description": "",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "Authorization",
"in": "header",
"description": "",
"example": "Bearer {{token}}",
"schema": {
"type": "string",
"default": "Bearer {{token}}"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {}
}
}
},
"headers": {}
}
},
"security": []
}
},
"/api/v1/admin/server/setting/list": {
"get": {
"summary": "获取配置列表",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [
{
"name": "page",
"in": "query",
"description": "获取页码 默认 1",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "count",
"in": "query",
"description": "获取条数 默认 10",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "group_id",
"in": "query",
"description": "组id(与组名称二选一)",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "group_name",
"in": "query",
"description": "组名称(与组id二选一)",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "key",
"in": "query",
"description": "关键词筛选",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "Authorization",
"in": "header",
"description": "",
"example": "Bearer {{token}}",
"schema": {
"type": "string",
"default": "Bearer {{token}}"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {}
}
}
},
"headers": {}
}
},
"security": []
}
},
"/api/v1/admin/server/setting/info": {
"get": {
"summary": "获取配置信息",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [
{
"name": "id",
"in": "query",
"description": "配置id (与name二选一)",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "name",
"in": "query",
"description": "配置名称 (与id二选一)",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "Authorization",
"in": "header",
"description": "",
"example": "Bearer {{token}}",
"schema": {
"type": "string",
"default": "Bearer {{token}}"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {}
}
}
},
"headers": {}
}
},
"security": []
}
},
"/api/v1/admin/server/setting/create": {
"post": {
"summary": "创建配置",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [
{
"name": "Authorization",
"in": "header",
"description": "",
"example": "Bearer {{token}}",
"schema": {
"type": "string",
"default": "Bearer {{token}}"
}
}
],
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"properties": {
"name": {
"description": "名称",
"example": "",
"type": "string"
},
"value": {
"example": "",
"type": "string"
},
"note": {
"description": "备注",
"example": "",
"type": "string"
},
"type": {
"description": "类型 string/int/float/bool/",
"example": "",
"type": "string"
},
"setting_group_id": {
"description": "配置组id",
"example": 0,
"type": "integer"
},
"open": {
"description": "是否开放访问",
"example": "",
"type": "boolean"
}
},
"required": [
"name",
"value",
"type"
]
}
}
}
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {}
}
}
},
"headers": {}
}
},
"security": []
}
},
"/api/v1/admin/server/setting/update": {
"post": {
"summary": "修改配置",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [
{
"name": "Authorization",
"in": "header",
"description": "",
"example": "Bearer {{token}}",
"schema": {
"type": "string",
"default": "Bearer {{token}}"
}
}
],
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"properties": {
"id": {
"example": 0,
"type": "integer"
},
"name": {
"description": "名称",
"example": "",
"type": "string"
},
"value": {
"example": "",
"type": "string"
},
"note": {
"description": "备注",
"example": "",
"type": "string"
},
"type": {
"description": "类型 string/int/float/bool/",
"example": "",
"type": "string"
},
"setting_group_id": {
"description": "配置组id",
"example": "",
"type": "string"
}
},
"required": [
"id"
]
}
}
}
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {}
}
}
},
"headers": {}
}
},
"security": []
}
},
"/api/v1/admin/server/setting/set_open": {
"post": {
"summary": "修改配置是否开放访问",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [
{
"name": "Authorization",
"in": "header",
"description": "",
"example": "Bearer {{token}}",
"schema": {
"type": "string",
"default": "Bearer {{token}}"
}
}
],
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"properties": {
"id": {
"example": 0,
"type": "integer"
},
"open": {
"description": "是否开放",
"example": "",
"type": "boolean"
}
},
"required": [
"id",
"open"
]
}
}
}
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {}
}
}
},
"headers": {}
}
},
"security": []
}
},
"/api/v1/admin/server/setting/delete": {
"delete": {
"summary": "删除配置",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [
{
"name": "Authorization",
"in": "header",
"description": "",
"example": "Bearer {{token}}",
"schema": {
"type": "string",
"default": "Bearer {{token}}"
}
}
],
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"properties": {
"id": {
"example": "",
"type": "string"
}
}
}
}
}
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {}
}
}
},
"headers": {}
}
},
"security": []
}
}
},
"components": {
"schemas": {},
"securitySchemes": {}
},
"servers": [],
"security": []
}