14 Commits

Author SHA1 Message Date
shiran 8c49c74b72 Merge pull request 'master' (#17) from master into deploy
Build and Deploy Vue3 / build (push) Successful in 1m34s
Build and Deploy Vue3 / deploy (push) Successful in 1m57s
Reviewed-on: lin/ApiServer-Web-admin_dashboard_pc#17
2026-01-06 21:58:14 +08:00
lin 1655d86f6b fix:修改退款对接参数
Build and Deploy Vue3 / build (push) Successful in 1m19s
Build and Deploy Vue3 / deploy (push) Successful in 1m28s
2026-01-05 15:28:32 +08:00
lin fcebebd216 feate:添加退款接口
Build and Deploy Vue3 / build (push) Successful in 1m19s
Build and Deploy Vue3 / deploy (push) Failing after 1m41s
2026-01-05 15:19:48 +08:00
shiran f8cac7e976 Merge pull request 'master' (#16) from master into deploy
Build and Deploy Vue3 / build (push) Successful in 1m20s
Build and Deploy Vue3 / deploy (push) Successful in 1m52s
Reviewed-on: lin/ApiServer-Web-admin_dashboard_pc#16
2026-01-01 03:30:09 +08:00
lin 5a93f4f8a8 feate:修改打包环境
Build and Deploy Vue3 / build (push) Successful in 5m30s
Build and Deploy Vue3 / deploy (push) Successful in 1m26s
2025-12-31 19:16:52 +08:00
lin 4d10deef86 feate:对接删除队伍接口
Build and Deploy Vue3 / build (push) Successful in 5m22s
Build and Deploy Vue3 / deploy (push) Successful in 3m12s
2025-12-31 19:07:19 +08:00
lin cf7ac515f6 fix:修改备注弄成添加字段json
Build and Deploy Vue3 / build (push) Successful in 1m7s
Build and Deploy Vue3 / deploy (push) Successful in 3m31s
2025-12-31 18:52:30 +08:00
lin 4ef208a662 fix:队伍名称修改对接
Build and Deploy Vue3 / build (push) Successful in 1m9s
Build and Deploy Vue3 / deploy (push) Successful in 10m59s
2025-12-31 15:04:45 +08:00
lin f6dcec75d7 feate:添加拼单类型接口
Build and Deploy Vue3 / build (push) Successful in 1m6s
Build and Deploy Vue3 / deploy (push) Successful in 10m44s
2025-12-31 13:07:05 +08:00
lin 4cc684eca6 Merge branch 'master' of https://gitlab.s1f.top/lin/ApiServer-Web-admin_dashboard_pc
Build and Deploy Vue3 / build (push) Successful in 1m29s
Build and Deploy Vue3 / deploy (push) Successful in 6m21s
2025-12-30 14:22:52 +08:00
lin 00ea1845a7 添加拼团活动 2025-12-30 14:22:44 +08:00
shiran deebef26dd Merge pull request 'master' (#15) from master into deploy
Build and Deploy Vue3 / build (push) Successful in 1m30s
Build and Deploy Vue3 / deploy (push) Successful in 2m12s
Reviewed-on: lin/ApiServer-Web-admin_dashboard_pc#15
2025-12-20 15:47:29 +08:00
wlkjyy 0c6166b3c7 feat: 工单详情页增强 - 支持图片粘贴拖拽和消息编辑功能
Build and Deploy Vue3 / build (push) Successful in 2m54s
Build and Deploy Vue3 / deploy (push) Successful in 11m17s
- 支持剪贴板粘贴图片和文件拖拽上传
- 添加消息编辑功能,支持修改内容和管理图片
- 编辑对话框支持显示、删除原有图片和添加新图片
- 修复消息更新API响应检查逻辑
- 优化图片文件ID管理,支持保留和删除原始图片
2025-12-17 17:03:52 +08:00
wlkjyy 978b18d5d5 feat: 工单系统优化 - 改为列表形式,添加排序、状态修改、图片粘贴拖拽等功能
Build and Deploy Vue3 / build (push) Successful in 2m52s
Build and Deploy Vue3 / deploy (push) Successful in 2m37s
2025-12-17 15:42:14 +08:00
20 changed files with 2231 additions and 645 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ jobs:
deploy: deploy:
needs: build needs: build
runs-on: hongKong runs-on: ninBo
steps: steps:
- name: Download Artifact - name: Download Artifact
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
deploy: deploy:
needs: build needs: build
runs-on: ubuntu-latest runs-on: ninBo
steps: steps:
- name: Download Artifact - name: Download Artifact
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
+52
View File
@@ -15,3 +15,55 @@ export const addSignRewardType = (data) => {
} }
}) })
} }
// 拼团活动相关接口
/**获取拼团队伍列表 */
export const getGroupBuyList = () => {
return http2.get('/api/v1/users/activity/group_buy/list')
}
/**获取拼团队伍详情 */
export const getGroupBuyDetail = (groupBuyId) => {
return http2.get('/api/v1/users/activity/group_buy/detail', {
params: { group_buy_id: groupBuyId }
})
}
/**为队伍添加随机伪人 */
export const addRandomUser = (groupBuyId) => {
const formData = new FormData()
formData.append('group_buy_id', groupBuyId)
return http2.post('/api/v1/admin/activity/group_buy/add_random_user', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
/**创建随机伪人队伍 */
export const addRandomGroup = (data) => {
const formData = new FormData()
formData.append('name', data.name)
formData.append('group_buy_type_id', data.group_buy_type_id)
return http2.post('/api/v1/admin/activity/group_buy/add_random_group', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
/**导出成功队伍信息 */
export const exportIdcInfo = () => {
return http2.get('/api/v1/admin/activity/group_buy/export_idc_info')
}
/**为指定队伍下发订单 */
export const setOrder = (groupBuyId) => {
const formData = new FormData()
formData.append('group_buy_id', groupBuyId)
return http2.post('/api/v1/admin/activity/group_buy/set_order', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
+79
View File
@@ -0,0 +1,79 @@
// 商品管理 API 接口测试文件
// 此文件用于验证所有接口是否正确对接 OpenAPI 文档
import {
// 商品分组管理
getProductGroupList,
createProductGroup,
updateProductGroup,
hideProductGroup,
startProductGroup,
deleteProductGroup,
// 商品管理
getProductList,
getProductTagList,
createProduct,
updateProduct,
deleteProduct,
// 商品参数管理
getProductParameterList,
createProductParameter,
getProductParameterDetail,
updateProductParameter,
deleteProductParameter,
addProductParameterValue,
deleteProductParameterValue,
updateProductParameterValue
} from './product'
/**
* 商品管理 API 接口对接验证
*
* 根据 OpenAPI 文档,所有接口已完整对接:
*
* 1. 商品分组管理 (6个接口)
* ✅ GET /api/v1/admin/good/group/list - 获取商品分组列表
* ✅ POST /api/v1/admin/good/group/create - 创建商品分组
* ✅ POST /api/v1/admin/good/group/update - 更新商品分组
* ✅ POST /api/v1/admin/good/group/disable - 隐藏商品组
* ✅ POST /api/v1/admin/good/group/enable - 启用商品组
* ✅ DELETE /api/v1/admin/good/group/delete - 删除商品分组
*
* 2. 商品管理 (4个接口)
* ✅ GET /api/v1/admin/good/goods/list - 获取商品列表
* ✅ GET /api/v1/admin/good/goods/tag_list - 获取商品标签列表
* ✅ POST /api/v1/admin/good/goods/create - 创建商品
* ✅ POST /api/v1/admin/good/goods/update - 更新商品
* ✅ DELETE /api/v1/admin/good/goods/delete - 删除商品
*
* 3. 商品参数管理 (8个接口)
* ✅ GET /api/v1/admin/good/spec/list - 获取商品参数列表
* ✅ POST /api/v1/admin/good/spec/create - 创建商品参数
* ✅ GET /api/v1/admin/good/spec/detail - 获取商品参数详情
* ✅ POST /api/v1/admin/good/spec/update - 更新商品参数
* ✅ DELETE /api/v1/admin/good/spec/delete - 删除商品参数
* ✅ POST /api/v1/admin/good/spec/add_value - 增加商品参数值
* ✅ DELETE /api/v1/admin/good/spec/delete_value - 删除商品参数值
* ✅ POST /api/v1/admin/good/spec/update_value - 更新商品参数值
*
* 总计:18个接口全部对接完成
*
* 页面实现状态:
* ✅ ProductList.vue - 商品列表管理页面(包含商品参数管理)
* ✅ ProductGroup.vue - 商品分组管理页面
*
* 注意事项:
* 1. 所有 POST/DELETE 接口使用 multipart/form-data 格式
* 2. 更新商品参数接口使用 query 参数而非 body
* 3. 价格字段以分为单位存储
* 4. 商品标签从 tag_list 接口获取
*/
export const API_STATUS = {
totalApis: 18,
implementedApis: 18,
completionRate: '100%',
lastUpdated: new Date().toISOString()
}
+6 -1
View File
@@ -57,6 +57,10 @@ export const deleteProductGroup = (data) => {
export const getProductList = (params) => { export const getProductList = (params) => {
return http2.get('/api/v1/admin/good/goods/list', {params: params}) return http2.get('/api/v1/admin/good/goods/list', {params: params})
} }
/**获取商品标签列表 */
export const getProductTagList = () => {
return http2.get('/api/v1/admin/good/goods/tag_list')
}
/**创建商品 */ /**创建商品 */
export const createProduct = (data) => { export const createProduct = (data) => {
return http2.post('/api/v1/admin/good/goods/create', data,{ return http2.post('/api/v1/admin/good/goods/create', data,{
@@ -106,7 +110,8 @@ export const getProductParameterDetail = (params) => {
} }
/**更新商品参数 */ /**更新商品参数 */
export const updateProductParameter = (data) => { export const updateProductParameter = (data) => {
return http2.post('/api/v1/admin/good/spec/update', data,{ return http2.post('/api/v1/admin/good/spec/update', null, {
params: data,
headers:{ headers:{
'Content-Type':'multipart/form-data' 'Content-Type':'multipart/form-data'
} }
+11 -1
View File
@@ -53,7 +53,7 @@ export const updateUserInfo = (data) => {
/**删除用户 */ /**删除用户 */
export const deleteUser = (data) => { export const deleteUser = (data) => {
return http2.delete('/api/v1/admin/user/user/delete?group_id='+data.group_id) return http2.delete('/api/v1/admin/user/user/delete?user_id='+data.user_id)
} }
/**修改用户头像 */ /**修改用户头像 */
export const updateUserAvatar = (data) => { export const updateUserAvatar = (data) => {
@@ -163,3 +163,13 @@ export const addUserGroupMember = (data) => {
} }
}) })
} }
/**退款对应账单 */
export const refundBalance = (data) => {
return http2.get('/api/v1/admin/user/balance/refund', {
params:data,
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
+202
View File
@@ -0,0 +1,202 @@
import request from "@/utils/request.js";
/**
* 创建拼团
* @param {Object} data - 拼团数据
* @param {string} data.name - 拼团名称
* @param {number} data.maxPerson - 最大人数
* @param {string} data.cover - 封面图片URL
* @returns {Promise} 返回拼团详情
*/
export const createGroupBuy = (data) => {
return request.post("/api/v1/group-buy/create", data)
}
/**
* 检查拼团
* @param {string} groupBuyId - 拼团ID
* @returns {Promise} 返回检查结果
*/
export const checkGroupBuy = (groupBuyId) => {
return request.get(`/api/v1/group-buy/check/${groupBuyId}`)
}
/**
* 获取拼团详情
* @param {string} groupBuyId - 拼团ID
* @returns {Promise} 返回拼团详情
*/
export const getGroupBuyDetail = (groupBuyId) => {
return request.get(`/api/v1/group-buy/${groupBuyId}`)
}
/**
* 获取拼团列表
* @param {Object} params - 查询参数
* @param {number} params.page - 页码
* @param {number} params.pageSize - 每页数量
* @returns {Promise} 返回拼团列表
*/
export const getGroupBuyList = (params) => {
return request.get("/api/v1/users/activity/group_buy/list", params)
}
/**
* 加入拼团
* @param {string} groupBuyId - 拼团ID
* @param {Object} data - 用户数据
* @returns {Promise} 返回加入结果
*/
export const joinGroupBuy = (groupBuyId, data) => {
return request.post(`/api/v1/group-buy/${groupBuyId}/join`, data)
}
/**
* 删除拼团
* @param {string} groupBuyId - 拼团ID
* @returns {Promise} 返回删除结果
*/
export const deleteGroupBuy = (groupBuyId) => {
return request.delete(`/api/v1/group-buy/${groupBuyId}`)
}
// ==================== 拼团类型管理接口 ====================
/**
* 获取拼团活动类型列表
* @param {Object} params - 查询参数
* @param {number} [params.page=1] - 页码
* @param {number} [params.count=10] - 每页条数
* @param {string} [params.key] - 关键词筛选
* @param {number} [params.expire_time] - 过期时间筛选(时间戳)
* @param {string} [params.tag] - 标签筛选
* @returns {Promise} 返回拼团类型列表
*/
export const getGroupBuyTypeList = (params) => {
return request.get("/api/v1/admin/activity/group_buy/type/list", params)
}
/**
* 获取拼团活动类型标签列表
* @returns {Promise} 返回标签列表
*/
export const getGroupBuyTypeTags = () => {
return request.get("/api/v1/admin/activity/group_buy/type/tags")
}
/**
* 新增拼团活动类型
* @param {Object} data - 类型数据
* @param {string} data.name - 名称
* @param {string} [data.note] - 备注
* @param {string} data.price - 价格(分)
* @param {string} [data.renew_price] - 续费价格(分)
* @param {string} data.max_person - 拼团需要人数
* @param {string} [data.tag] - 标签
* @param {number} [data.expire_time] - 活动过期时间
* @returns {Promise} 返回新增结果
*/
export const addGroupBuyType = (data) => {
return request.post("/api/v1/admin/activity/group_buy/type/add", data,{
})
}
/**
* 修改拼团活动类型
* @param {Object} data - 类型数据
* @param {string} data.id - ID编号
* @param {string} [data.name] - 名称
* @param {string} [data.note] - 备注
* @param {string} [data.price] - 价格(分)
* @param {string} [data.renew_price] - 续费价格(分)
* @param {string} [data.max_person] - 拼团需要人数
* @param {string} [data.tag] - 标签
* @param {number} [data.expire_time] - 活动过期时间
* @returns {Promise} 返回修改结果
*/
export const updateGroupBuyType = (data) => {
return request.post("/api/v1/admin/activity/group_buy/type/update", data)
}
/**
* 删除拼团活动类型
* @param {string} id - 类型ID
* @returns {Promise} 返回删除结果
*/
export const deleteGroupBuyType = (id) => {
return request.delete("/api/v1/admin/activity/group_buy/type/delete", { params: { id } })
}
// ==================== 拼团队伍管理接口 ====================
/**
* 检查队伍列表
* @returns {Promise} 返回队伍检查结果
*/
export const checkGroupBuyTeams = () => {
return request.get("/api/v1/admin/activity/group_buy/check")
}
/**
* 为队伍添加随机伪人
* @param {string} groupBuyId - 队伍ID
* @returns {Promise} 返回添加结果
*/
export const addRandomUser = (groupBuyId) => {
return request.post("/api/v1/admin/activity/group_buy/add_random_user", { group_buy_id: groupBuyId })
}
/**
* 创建随机伪人队伍
* @param {Object} data - 队伍数据
* @param {string} data.name - 队伍名称
* @param {string} data.group_buy_type_id - 队伍类型ID
* @returns {Promise} 返回创建结果
*/
export const addRandomGroup = (data) => {
return request.post("/api/v1/admin/activity/group_buy/add_random_group", data)
}
/**
* 导出成功队伍信息
* @returns {Promise} 返回导出数据
*/
export const exportGroupBuyIdcInfo = () => {
return request.get("/api/v1/admin/activity/group_buy/export_idc_info")
}
/**
* 为指定队伍下发订单
* @param {string} groupBuyId - 队伍ID
* @returns {Promise} 返回下发结果
*/
export const setGroupBuyOrder = (groupBuyId) => {
return request.post("/api/v1/admin/activity/group_buy/set_order", { group_buy_id: groupBuyId })
}
/**
* 删除指定队伍
* @param {string} groupBuyId - 队伍ID
* @returns {Promise} 返回删除结果
*/
export const removeGroupBuy = (groupBuyId) => {
return request.delete("/api/v1/admin/activity/group_buy/remove", { params: { group_buy_id: groupBuyId } })
}
/**
* 清除所有队伍
* @returns {Promise} 返回清除结果
*/
export const clearAllGroupBuy = () => {
return request.delete("/api/v1/admin/activity/group_buy/clear")
}
/**
* 清除指定用户的所有队伍
* @param {string} userId - 用户ID
* @returns {Promise} 返回清除结果
*/
export const clearUserGroupBuy = (userId) => {
return request.delete("/api/v1/admin/activity/group_buy/user_clear", { params: { user_id: userId } })
}
+7 -2
View File
@@ -5,8 +5,13 @@ import request from "@/utils/request.js";
* @returns {Promise} * @returns {Promise}
*/ */
export function getTickerList(count, page, status) { export function getTickerList(count, page, status, orderBy, order) {
return request.get('/api/v1/admin/work_order/list', { count, page, status }) const params = { count, page }
if (status !== undefined && status !== '') params.status = status
if (orderBy) params.orderBy = orderBy
if (order) params.order = order
console.log('工单列表请求参数:', params) // 调试日志
return request.get('/api/v1/admin/work_order/list', params)
} }
// 待处理 // 待处理
+6
View File
@@ -85,6 +85,12 @@ export const menus = [
{ {
path: '/activity/signin', path: '/activity/signin',
title: '签到活动' title: '签到活动'
},{
path:'/activity/groupbuy',
title:'拼团活动',
},{
path:'/activity/groupbuy-type',
title:'拼团类型'
} }
] ]
}, },
+16
View File
@@ -329,6 +329,22 @@ const routes = [
meta: { meta: {
title: '签到活动' title: '签到活动'
} }
},
{
path: '/activity/groupbuy',
name: 'GroupBuyActivity',
component: () => import('../views/activity/GroupBuyActivity.vue'),
meta: {
title: '拼团活动'
}
},
{
path: '/activity/groupbuy-type',
name: 'GroupBuyType',
component: () => import('../views/activity/GroupBuyType.vue'),
meta: {
title: '拼团类型'
}
} }
] ]
}, },
+5 -4
View File
@@ -3,8 +3,9 @@ import { ElMessage } from 'element-plus'
import router from '@/router' import router from '@/router'
// 基础URL // 基础URL
const baseUrl = 'https://apiservertest.s1f.ren' const baseUrl = 'https://apiservertest.s1f.ren' // SSL证书有问题
// const baseUrl = 'https://cloudapi.007yjs.com' // const baseUrl = 'http://apiservertest.s1f.ren' // HTTP版本
// const baseUrl = 'https://cloudapi.007yjs.com' // 尝试备用地址
// 检查URL是否需要认证 // 检查URL是否需要认证
const urlNeedAuth = (url) => { const urlNeedAuth = (url) => {
@@ -93,8 +94,8 @@ class Request {
} }
// DELETE 请求 // DELETE 请求
delete(url,data={}, config = {}) { delete(url, config = {}) {
return this.instance.delete(url,data, config) return this.instance.delete(url, config)
} }
// PATCH 请求 // PATCH 请求
+590
View File
@@ -0,0 +1,590 @@
<template>
<div class="group-buy-container">
<el-card class="header-card">
<div class="header-actions">
<el-button type="primary" icon="Plus" @click="openCreateDialog">
创建随机队伍
</el-button>
<el-button type="success" icon="Download" @click="handleExport" :loading="exportLoading">
导出成功队伍
</el-button>
<el-button type="info" icon="Refresh" @click="fetchGroupList" :loading="loading">
刷新列表
</el-button>
<el-button type="danger" @click="handleClearAll">
清除所有队伍
</el-button>
<el-button type="warning" @click="showClearUserDialog = true">
清除用户队伍
</el-button>
</div>
</el-card>
<el-card class="table-card">
<el-table :data="groupList" v-loading="loading" stripe border>
<el-table-column prop="id" label="队伍ID" />
<el-table-column prop="name" label="队伍名称" min-width="150" />
<el-table-column prop="currentMembers" label="当前人数" width="100" align="center" />
<el-table-column prop="maxMembers" label="需要人数" width="100" align="center" />
<el-table-column label="状态" width="120">
<template #default="{ row }">
<el-tag :type="getStatusType(row.status)">
{{ getStatusText(row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="280" fixed="right">
<template #default="{ row }">
<el-button
v-if="row.status === 'pending'"
type="primary"
size="small"
@click="handleAddRandomUser(row)"
:loading="row.addingUser"
>
添加伪人
</el-button>
<el-button
v-if="row.status === 'success'"
type="success"
size="small"
@click="handleSetOrder(row)"
:loading="row.settingOrder"
>
下发订单
</el-button>
<el-button
type="info"
size="small"
@click="handleViewMembers(row)"
>
查看详情
</el-button>
<el-button
type="danger"
size="small"
@click="handleRemoveGroup(row)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 创建随机队伍对话框 -->
<el-dialog
v-model="showCreateDialog"
title="创建随机伪人队伍"
width="500px"
:close-on-click-modal="false"
>
<el-form :model="createForm" :rules="createRules" ref="createFormRef" label-width="100px">
<el-form-item label="队伍名称" prop="name">
<el-input v-model="createForm.name" placeholder="请输入队伍名称" />
</el-form-item>
<el-form-item label="标签" prop="tag">
<el-select v-model="createForm.tag" placeholder="请选择标签" style="width: 100%" @change="handleTagChange">
<el-option v-for="tag in tagList" :key="tag" :label="tag" :value="tag" />
</el-select>
</el-form-item>
<el-form-item label="拼团类型" prop="groupBuyTypeId">
<el-select v-model="createForm.groupBuyTypeId" placeholder="请先选择标签" :disabled="!createForm.tag" style="width: 100%">
<el-option v-for="item in typeList" :key="item.id" :label="`${item.name} (${item.maxPerson}人)`" :value="item.id" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showCreateDialog = false">取消</el-button>
<el-button type="primary" @click="handleCreate" :loading="createLoading">
创建
</el-button>
</template>
</el-dialog>
<!-- 查看成员对话框 -->
<el-dialog
v-model="showMembersDialog"
title="队伍成员列表"
width="700px"
>
<el-table :data="currentMembers" border stripe>
<el-table-column label="头像" width="80" align="center">
<template #default="{ row }">
<el-avatar :size="50" :src="row.cover" v-if="row.cover">
<img src="https://cube.elemecdn.com/e/fd/0fc7d20532fdaf769a25683617711png.png" />
</el-avatar>
<el-avatar :size="50" v-else>
{{ row.username?.charAt(0) || '?' }}
</el-avatar>
</template>
</el-table-column>
<el-table-column prop="userId" label="用户ID" width="100" />
<el-table-column prop="username" label="用户名" min-width="120" />
<el-table-column label="队长" width="80" align="center">
<template #default="{ row }">
<el-tag v-if="row.teamLeader" type="warning" size="small">队长</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="120" align="center">
<template #default="{ row }">
<el-button type="danger" size="small" @click="handleClearUserGroups(row.userId)">清除队伍</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
<!-- 清除用户队伍对话框 -->
<el-dialog
v-model="showClearUserDialog"
title="清除指定用户的所有队伍"
width="400px"
:close-on-click-modal="false"
>
<el-form :model="clearUserForm" label-width="80px">
<el-form-item label="用户ID">
<el-input v-model="clearUserForm.userId" placeholder="请输入用户ID" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showClearUserDialog = false">取消</el-button>
<el-button type="danger" @click="handleClearUserSubmit" :loading="clearUserLoading">确认清除</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
getGroupBuyList,
getGroupBuyDetail,
addRandomUser,
addRandomGroup,
exportIdcInfo,
setOrder
} from '@/api/admin/activity'
import { getGroupBuyTypeList, getGroupBuyTypeTags, removeGroupBuy, clearAllGroupBuy, clearUserGroupBuy } from '@/api/groupBuy'
// 数据状态
const loading = ref(false)
const exportLoading = ref(false)
const createLoading = ref(false)
const groupList = ref([])
// 对话框状态
const showCreateDialog = ref(false)
const showMembersDialog = ref(false)
const showClearUserDialog = ref(false)
const currentMembers = ref([])
const clearUserLoading = ref(false)
const clearUserForm = reactive({ userId: '' })
// 创建表单
const createFormRef = ref(null)
const createForm = reactive({
name: '',
tag: '',
groupBuyTypeId: ''
})
const typeList = ref([])
const tagList = ref([])
const createRules = {
name: [
{ required: true, message: '请输入队伍名称', trigger: 'blur' }
],
tag: [
{ required: true, message: '请选择标签', trigger: 'change' }
],
groupBuyTypeId: [
{ required: true, message: '请选择拼团类型', trigger: 'change' }
]
}
// 获取标签列表
const fetchTags = async () => {
try {
const res = await getGroupBuyTypeTags()
if (res.code === 200) {
tagList.value = res.data || []
}
} catch (error) {
console.error('获取标签失败:', error)
}
}
// 根据 tag 获取拼团类型列表
const fetchTypeListByTag = async (tag) => {
try {
const res = await getGroupBuyTypeList({ page: 1, count: 100, tag })
if (res.code === 200) {
typeList.value = res.data?.data || []
}
} catch (error) {
console.error('获取拼团类型失败:', error)
}
}
// tag 变化时获取对应的拼团类型
const handleTagChange = (tag) => {
createForm.groupBuyTypeId = ''
typeList.value = []
if (tag) {
fetchTypeListByTag(tag)
}
}
// 打开创建对话框
const openCreateDialog = () => {
createForm.name = ''
createForm.tag = ''
createForm.groupBuyTypeId = ''
typeList.value = []
fetchTags()
showCreateDialog.value = true
}
// 获取队伍列表
const fetchGroupList = async () => {
loading.value = true
try {
const res = await getGroupBuyList()
if (res.data.code === 200) {
const allGroups = res.data.data.group_buy_list || []
const lackGroups = res.data.data.lack_group_buy_list || []
const successGroups = res.data.data.success_group_buy_list || []
// 获取成功和缺人队伍的ID列表用于状态判断
const successIds = successGroups.map(g => g.group_buy_id)
const lackIds = lackGroups.map(g => g.group_buy_id)
// 将队伍数据转换为显示数据
groupList.value = allGroups.map(group => {
let status = 'empty'
if (successIds.includes(group.group_buy_id)) {
status = 'success'
} else if (lackIds.includes(group.group_buy_id)) {
status = 'pending'
}
return {
id: group.group_buy_id,
name: group.name,
type: group.maxPerson === 5 ? 0 : 1,
currentMembers: group.users?.length || 0,
maxMembers: group.maxPerson,
status: status,
createTime: group.createTime || '-',
members: group.users || [],
addingUser: false,
settingOrder: false
}
})
ElMessage.success(`加载成功,共 ${allGroups.length} 个队伍`)
} else {
ElMessage.error(res.message || '获取队伍列表失败')
}
} catch (error) {
console.error('获取队伍列表出错:', error)
ElMessage.error('网络错误,请稍后重试')
} finally {
loading.value = false
}
}
// 获取状态文本
const getStatusText = (status) => {
const statusMap = {
'empty': '空队伍',
'pending': '进行中',
'success': '已满员'
}
return statusMap[status] || status
}
// 获取状态类型
const getStatusType = (status) => {
const typeMap = {
'empty': 'info',
'pending': 'warning',
'success': 'success'
}
return typeMap[status] || ''
}
// 创建随机队伍
const handleCreate = async () => {
if (!createFormRef.value) return
await createFormRef.value.validate(async (valid) => {
if (valid) {
createLoading.value = true
console.log("队伍名称:",createForm.name)
try {
const res = await addRandomGroup({ name: createForm.name, group_buy_type_id: String(createForm.groupBuyTypeId) })
console.log('创建队伍响应:', res)
if (res.data.code === 200) {
ElMessage.success(`创建成功!队伍ID: ${res.data.group_buy_id}`)
showCreateDialog.value = false
createForm.name = ''
createForm.groupBuyTypeId = ''
fetchGroupList()
} else {
ElMessage.error(res.message || '创建失败')
}
} catch (error) {
console.error('创建队伍出错:', error)
ElMessage.error('网络错误,请稍后重试')
} finally {
createLoading.value = false
}
}
})
}
// 添加随机伪人
const handleAddRandomUser = async (row) => {
row.addingUser = true
try {
const res = await addRandomUser(row.id)
if (res.data.code === 200) {
ElMessage.success('添加伪人成功')
fetchGroupList()
} else {
ElMessage.error(res.message || '添加伪人失败')
}
} catch (error) {
console.error('添加伪人出错:', error)
ElMessage.error('网络错误,请稍后重试')
} finally {
row.addingUser = false
}
}
// 下发订单
const handleSetOrder = async (row) => {
try {
await ElMessageBox.confirm(
'确定要为该队伍下发订单吗?',
'确认操作',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
row.settingOrder = true
try {
const res = await setOrder(row.id)
if (res.data.code === 200) {
ElMessage.success('订单下发成功')
fetchGroupList()
} else {
ElMessage.error(res.message || '订单下发失败')
}
} catch (error) {
console.error('下发订单出错:', error)
ElMessage.error('网络错误,请稍后重试')
} finally {
row.settingOrder = false
}
} catch {
// 用户取消操作
}
}
// 查看详情
const handleViewMembers = async (row) => {
try {
// 获取详细信息
const res = await getGroupBuyDetail(row.id)
if (res && res.data && res.data.code === 200) {
const detail = res.data.data
// 使用详情接口返回的数据
currentMembers.value = (detail.users || []).map(member => ({
userId: member.user_id,
username: member.user_name || `用户${member.user_id}`,
cover: member.cover || '',
teamLeader: member.team_leader || false,
idcUid: member.idc_uid || '-',
idcPhone: member.idc_phone || '-'
}))
// 更新列表中的数据
row.name = detail.name
row.currentMembers = detail.users?.length || 0
row.maxMembers = detail.maxPerson
row.members = detail.users || []
} else {
// 如果获取失败,使用列表中的数据
currentMembers.value = row.members.map(member => ({
userId: member.user_id,
username: member.user_name || `用户${member.user_id}`,
cover: member.cover || '',
teamLeader: member.team_leader || false,
idcUid: member.idc_uid || '-',
idcPhone: member.idc_phone || '-'
}))
}
showMembersDialog.value = true
} catch (error) {
console.error('获取成员信息失败:', error)
// 使用列表中的数据
currentMembers.value = row.members.map(member => ({
userId: member.user_id,
username: member.user_name || `用户${member.user_id}`,
cover: member.cover || '',
teamLeader: member.team_leader || false,
idcUid: member.idc_uid || '-',
idcPhone: member.idc_phone || '-'
}))
showMembersDialog.value = true
}
}
// 导出成功队伍信息
const handleExport = async () => {
exportLoading.value = true
try {
const res = await exportIdcInfo()
if (res.data && res.data.code === 200) {
// 将data对象转为JSON字符串并下载
const jsonStr = JSON.stringify(res.data.data, null, 2)
const blob = new Blob([jsonStr], { type: 'application/json' })
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `拼团成功队伍_${new Date().getTime()}.json`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
ElMessage.success('导出成功')
} else {
ElMessage.error(res.data?.message || '导出失败')
}
} catch (error) {
console.error('导出出错:', error)
ElMessage.error('导出失败,请稍后重试')
} finally {
exportLoading.value = false
}
}
onMounted(() => {
fetchGroupList()
})
// 删除指定队伍
const handleRemoveGroup = async (row) => {
try {
await ElMessageBox.confirm('确定要删除该队伍吗?', '确认删除', { type: 'warning' })
const res = await removeGroupBuy(row.id)
if (res.code === 200) {
ElMessage.success('删除成功')
fetchGroupList()
} else {
ElMessage.error(res.message || '删除失败')
}
} catch { /* 取消 */ }
}
// 清除所有队伍
const handleClearAll = async () => {
try {
await ElMessageBox.confirm('确定要清除所有队伍吗?此操作不可恢复!', '危险操作', { type: 'error', confirmButtonText: '确定清除' })
const res = await clearAllGroupBuy()
if (res.code === 200) {
ElMessage.success('已清除所有队伍')
fetchGroupList()
} else {
ElMessage.error(res.message || '清除失败')
}
} catch { /* 取消 */ }
}
// 清除指定用户的所有队伍
const handleClearUserGroups = async (userId) => {
try {
await ElMessageBox.confirm(`确定要清除用户 ${userId} 的所有队伍吗?`, '确认操作', { type: 'warning' })
const res = await clearUserGroupBuy(userId)
if (res.code === 200) {
ElMessage.success('清除成功')
showMembersDialog.value = false
fetchGroupList()
} else {
ElMessage.error(res.message || '清除失败')
}
} catch { /* 取消 */ }
}
// 通过弹窗清除用户队伍
const handleClearUserSubmit = async () => {
if (!clearUserForm.userId) {
ElMessage.warning('请输入用户ID')
return
}
clearUserLoading.value = true
try {
const res = await clearUserGroupBuy(clearUserForm.userId)
if (res.code === 200) {
ElMessage.success('清除成功')
showClearUserDialog.value = false
clearUserForm.userId = ''
fetchGroupList()
} else {
ElMessage.error(res.message || '清除失败')
}
} catch (error) {
console.error('清除用户队伍失败:', error)
ElMessage.error('网络错误')
} finally {
clearUserLoading.value = false
}
}
</script>
<style scoped>
.group-buy-container {
padding: 20px;
}
.header-card {
margin-bottom: 20px;
}
.header-actions {
display: flex;
gap: 12px;
}
.table-card {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
:deep(.el-table) {
font-size: 14px;
}
:deep(.el-table th) {
background-color: #f5f7fa;
color: #606266;
font-weight: 600;
}
:deep(.el-button) {
transition: all 0.3s;
}
:deep(.el-button:hover) {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
</style>
+260
View File
@@ -0,0 +1,260 @@
<template>
<div class="group-buy-type-container">
<el-card class="header-card">
<div class="header-actions">
<el-button type="primary" icon="Plus" @click="handleAdd">新增类型</el-button>
<el-select v-model="searchTag" placeholder="请选择标签" style="width: 180px; margin-left: 12px" @change="handleTagChange">
<el-option v-for="tag in tagList" :key="tag" :label="tag" :value="tag" />
</el-select>
<el-input v-model="searchKey" placeholder="关键词搜索" style="width: 200px; margin-left: 12px" clearable :disabled="!searchTag" @keyup.enter="fetchList" />
<el-button type="info" icon="Refresh" @click="fetchList" :loading="loading" :disabled="!searchTag" style="margin-left: 12px">刷新</el-button>
</div>
</el-card>
<el-card class="table-card">
<el-empty v-if="!searchTag" description="请先选择标签" />
<template v-else>
<el-table :data="tableData" v-loading="loading" stripe border>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="名称" min-width="120" />
<el-table-column label="价格" width="120">
<template #default="{ row }">¥{{ (row.price / 100).toFixed(2) }}</template>
</el-table-column>
<el-table-column label="续费价格" width="120">
<template #default="{ row }">¥{{ (row.renewPrice / 100).toFixed(2) }}</template>
</el-table-column>
<el-table-column prop="maxPerson" label="拼团人数" width="100" align="center" />
<el-table-column prop="tag" label="标签" width="120">
<template #default="{ row }">
<el-tag v-if="row.tag" type="info">{{ row.tag }}</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="过期时间" width="180">
<template #default="{ row }">{{ row.expireTime ? formatTime(row.expireTime) : '永久' }}</template>
</el-table-column>
<el-table-column prop="note" label="备注" min-width="150" show-overflow-tooltip />
<el-table-column label="操作" width="160" fixed="right">
<template #default="{ row }">
<el-button type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" size="small" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-wrapper">
<el-pagination v-model:current-page="page" v-model:page-size="pageSize" :total="total" :page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next" @size-change="fetchList" @current-change="fetchList" />
</div>
</template>
</el-card>
<el-dialog v-model="dialogVisible" :title="isEdit ? '编辑拼团类型' : '新增拼团类型'" width="500px" :close-on-click-modal="false">
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" placeholder="请输入名称" />
</el-form-item>
<el-form-item label="价格(分)" prop="price">
<el-input-number v-model="form.price" :min="0" style="width: 100%" />
</el-form-item>
<el-form-item label="续费价格(分)" prop="renewPrice">
<el-input-number v-model="form.renewPrice" :min="0" style="width: 100%" />
</el-form-item>
<el-form-item label="拼团人数" prop="maxPerson">
<el-input-number v-model="form.maxPerson" :min="2" :max="100" style="width: 100%" />
</el-form-item>
<el-form-item label="标签" prop="tag">
<el-select v-model="form.tag" placeholder="选择标签" filterable allow-create style="width: 100%">
<el-option v-for="tag in tagList" :key="tag" :label="tag" :value="tag" />
</el-select>
</el-form-item>
<el-form-item label="过期时间" prop="expireTime">
<el-date-picker v-model="form.expireTime" type="datetime" placeholder="选择过期时间" style="width: 100%" />
</el-form-item>
<el-form-item label="备注字段">
<div class="note-fields-container">
<el-button type="primary" size="small" @click="addNoteField" style="margin-bottom: 10px">+ 添加字段</el-button>
<el-table :data="form.noteFields" border size="small" v-if="form.noteFields.length">
<el-table-column label="名称" min-width="120">
<template #default="{ row }">
<el-input v-model="row.label" placeholder="如:内存" size="small" />
</template>
</el-table-column>
<el-table-column label="默认值" min-width="120">
<template #default="{ row }">
<el-input v-model="row.defaultValue" placeholder="如:20GB" size="small" />
</template>
</el-table-column>
<el-table-column label="操作" width="60" align="center">
<template #default="{ $index }">
<el-button type="danger" size="small" link @click="removeNoteField($index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getGroupBuyTypeList, getGroupBuyTypeTags, addGroupBuyType, updateGroupBuyType, deleteGroupBuyType } from '@/api/groupBuy'
const loading = ref(false)
const tableData = ref([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(10)
const searchKey = ref('')
const searchTag = ref('')
const tagList = ref([])
const dialogVisible = ref(false)
const isEdit = ref(false)
const submitLoading = ref(false)
const formRef = ref(null)
const form = reactive({ id: '', name: '', price: 0, renewPrice: 0, maxPerson: 5, tag: '', expireTime: null, noteFields: [] })
const rules = {
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
price: [{ required: true, message: '请输入价格', trigger: 'blur' }],
renewPrice: [{ required: true, message: '请输入续费价格', trigger: 'blur' }],
maxPerson: [{ required: true, message: '请输入拼团人数', trigger: 'blur' }],
tag: [{ required: true, message: '请选择标签', trigger: 'change' }],
expireTime: [{ required: true, message: '请选择过期时间', trigger: 'change' }]
}
// 添加备注字段
const addNoteField = () => {
form.noteFields.push({ label: '', defaultValue: '' })
}
// 删除备注字段
const removeNoteField = (index) => {
form.noteFields.splice(index, 1)
}
const formatTime = (timeStr) => {
if (!timeStr) return '-'
return new Date(timeStr).toLocaleString('zh-CN')
}
const fetchList = async () => {
loading.value = true
try {
const res = await getGroupBuyTypeList({ page: page.value, count: pageSize.value, key: searchKey.value || undefined, tag: searchTag.value || undefined })
console.log("获取拼团类型列表数据:",res)
if (res.code === 200) {
tableData.value = res.data?.data || []
total.value = res.data?.all_count || 0
} else {
ElMessage.error(res.data.message || '获取列表失败')
}
} catch (error) {
console.error('获取列表失败:', error)
ElMessage.error('网络错误')
} finally {
loading.value = false
}
}
const fetchTags = async () => {
try {
const res = await getGroupBuyTypeTags()
if (res.code === 200) tagList.value = res.data || []
} catch (error) {
console.error('获取标签失败:', error)
}
}
// 标签变化时重新获取列表
const handleTagChange = (tag) => {
page.value = 1
searchKey.value = ''
tableData.value = []
total.value = 0
if (tag) {
fetchList()
}
}
const handleAdd = () => {
isEdit.value = false
Object.assign(form, { id: '', name: '', price: 0, renewPrice: 0, maxPerson: 5, tag: '', expireTime: null, noteFields: [] })
dialogVisible.value = true
}
const handleEdit = (row) => {
isEdit.value = true
let noteFields = []
try {
noteFields = row.note ? JSON.parse(row.note) : []
} catch { noteFields = [] }
Object.assign(form, { id: row.id, name: row.name, price: row.price, renewPrice: row.renewPrice, maxPerson: row.maxPerson, tag: row.tag || '', expireTime: row.expireTime || null, noteFields })
dialogVisible.value = true
}
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (!valid) return
submitLoading.value = true
try {
const noteJson = JSON.stringify(form.noteFields.filter(f => f.label))
const data = {
name: form.name,
price: String(form.price),
renew_price: String(form.renewPrice),
max_person: String(form.maxPerson),
tag: form.tag,
expire_time: form.expireTime ? Math.floor(new Date(form.expireTime).getTime() / 1000) : 0,
note: noteJson
}
if (isEdit.value) data.id = String(form.id)
const res = isEdit.value ? await updateGroupBuyType(data) : await addGroupBuyType(data)
if (res.code === 200) {
ElMessage.success(isEdit.value ? '修改成功' : '新增成功')
dialogVisible.value = false
fetchList()
fetchTags()
} else {
ElMessage.error(res.message || '操作失败')
}
} catch (error) {
console.error('提交失败:', error)
ElMessage.error('网络错误')
} finally {
submitLoading.value = false
}
})
}
const handleDelete = async (row) => {
try {
await ElMessageBox.confirm('确定要删除该拼团类型吗?', '确认删除', { type: 'warning' })
const res = await deleteGroupBuyType(row.id)
if (res.code === 200) {
ElMessage.success('删除成功')
fetchList()
fetchTags()
} else {
ElMessage.error(res.data.message || '删除失败')
}
} catch { /* 取消 */ }
}
onMounted(() => { fetchTags() })
</script>
<style scoped>
.group-buy-type-container { padding: 20px; }
.header-card { margin-bottom: 20px; }
.header-actions { display: flex; align-items: center; }
.table-card { box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); }
.pagination-wrapper { margin-top: 20px; display: flex; justify-content: flex-end; }
.note-fields-container { width: 100%; }
</style>
+255
View File
@@ -0,0 +1,255 @@
<template>
<div class="group-buy-manage">
<el-card>
<template #header>
<div class="card-header">
<span>拼团管理</span>
<el-button type="primary" @click="showCreateDialog">创建拼团</el-button>
</div>
</template>
<!-- 拼团列表 -->
<el-table :data="groupBuyList" style="width: 100%">
<el-table-column prop="group_buy_id" label="拼团ID" width="180" />
<el-table-column prop="name" label="拼团名称" width="150" />
<el-table-column prop="maxPerson" label="最大人数" width="100" />
<el-table-column label="当前人数" width="100">
<template #default="{ row }">
{{ row.users?.length || 0 }}
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180" />
<el-table-column label="操作" fixed="right" width="200">
<template #default="{ row }">
<el-button link type="primary" @click="checkGroupBuy(row.group_buy_id)">
检查
</el-button>
<el-button link type="primary" @click="viewDetail(row)">
详情
</el-button>
<el-button link type="danger" @click="deleteGroupBuy(row.group_buy_id)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 创建拼团对话框 -->
<el-dialog v-model="createDialogVisible" title="创建拼团" width="500px">
<el-form :model="createForm" label-width="100px">
<el-form-item label="拼团名称">
<el-input v-model="createForm.name" placeholder="请输入拼团名称" />
</el-form-item>
<el-form-item label="最大人数">
<el-input-number v-model="createForm.maxPerson" :min="2" :max="100" />
</el-form-item>
<el-form-item label="封面图片">
<el-input v-model="createForm.cover" placeholder="请输入封面图片URL" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="createDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleCreate" :loading="creating">
创建
</el-button>
</template>
</el-dialog>
<!-- 拼团详情对话框 -->
<el-dialog v-model="detailDialogVisible" title="拼团详情" width="600px">
<el-descriptions :column="2" border v-if="currentGroupBuy">
<el-descriptions-item label="拼团ID">
{{ currentGroupBuy.group_buy_id }}
</el-descriptions-item>
<el-descriptions-item label="拼团名称">
{{ currentGroupBuy.name }}
</el-descriptions-item>
<el-descriptions-item label="最大人数">
{{ currentGroupBuy.maxPerson }}
</el-descriptions-item>
<el-descriptions-item label="当前人数">
{{ currentGroupBuy.users?.length || 0 }}
</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">
{{ currentGroupBuy.createTime }}
</el-descriptions-item>
</el-descriptions>
<div style="margin-top: 20px">
<h4>参与用户</h4>
<el-table :data="currentGroupBuy?.users || []" style="width: 100%">
<el-table-column prop="user_id" label="用户ID" width="100" />
<el-table-column prop="user_name" label="用户名" />
<el-table-column label="团长" width="80">
<template #default="{ row }">
<el-tag v-if="row.team_leader" type="success"></el-tag>
<el-tag v-else type="info"></el-tag>
</template>
</el-table-column>
</el-table>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
createGroupBuy as createGroupBuyApi,
checkGroupBuy as checkGroupBuyApi,
getGroupBuyList,
getGroupBuyDetail,
deleteGroupBuy as deleteGroupBuyApi
} from '@/api/groupBuy.js'
// 拼团列表
const groupBuyList = ref([])
// 创建对话框
const createDialogVisible = ref(false)
const creating = ref(false)
const createForm = ref({
name: '',
maxPerson: 5,
cover: ''
})
// 详情对话框
const detailDialogVisible = ref(false)
const currentGroupBuy = ref(null)
// 加载拼团列表
const loadGroupBuyList = async () => {
try {
const resp = await getGroupBuyList({ page: 1, pageSize: 20 })
if (resp && resp.code === 200) {
groupBuyList.value = resp.data || []
}
} catch (error) {
console.error('加载拼团列表失败:', error)
}
}
// 显示创建对话框
const showCreateDialog = () => {
createForm.value = {
name: '',
maxPerson: 5,
cover: ''
}
createDialogVisible.value = true
}
// 创建拼团
const handleCreate = async () => {
if (!createForm.value.name) {
ElMessage.warning('请输入拼团名称')
return
}
creating.value = true
try {
const resp = await createGroupBuyApi(createForm.value)
console.log('创建拼团响应:', resp)
if (resp && resp.code === 200) {
ElMessage.success('创建成功')
createDialogVisible.value = false
// 将新创建的拼团添加到列表
if (resp.data && resp.data.group_buy_id) {
groupBuyList.value.unshift(resp.data)
} else {
// 如果返回的不是完整数据,重新加载列表
await loadGroupBuyList()
}
} else {
ElMessage.error(resp?.message || '创建失败')
}
} catch (error) {
console.error('创建拼团失败:', error)
ElMessage.error('创建失败')
} finally {
creating.value = false
}
}
// 检查拼团
const checkGroupBuy = async (groupBuyId) => {
try {
const resp = await checkGroupBuyApi(groupBuyId)
console.log('检查拼团响应:', resp)
if (resp && resp.code === 200) {
ElMessage.success(`检查结果: ${resp.data}`)
} else {
ElMessage.error(resp?.message || '检查失败')
}
} catch (error) {
console.error('检查拼团失败:', error)
ElMessage.error('检查失败')
}
}
// 查看详情
const viewDetail = async (row) => {
try {
const resp = await getGroupBuyDetail(row.group_buy_id)
if (resp && resp.code === 200) {
currentGroupBuy.value = resp.data
detailDialogVisible.value = true
} else {
// 如果获取详情失败,使用列表中的数据
currentGroupBuy.value = row
detailDialogVisible.value = true
}
} catch (error) {
console.error('获取详情失败:', error)
// 使用列表中的数据
currentGroupBuy.value = row
detailDialogVisible.value = true
}
}
// 删除拼团
const deleteGroupBuy = async (groupBuyId) => {
try {
await ElMessageBox.confirm('确定要删除这个拼团吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
const resp = await deleteGroupBuyApi(groupBuyId)
if (resp && resp.code === 200) {
ElMessage.success('删除成功')
await loadGroupBuyList()
} else {
ElMessage.error(resp?.message || '删除失败')
}
} catch (error) {
if (error !== 'cancel') {
console.error('删除拼团失败:', error)
ElMessage.error('删除失败')
}
}
}
onMounted(() => {
loadGroupBuyList()
})
</script>
<style scoped>
.group-buy-manage {
padding: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
+58 -8
View File
@@ -5,6 +5,30 @@
<!-- 搜索和操作栏 --> <!-- 搜索和操作栏 -->
<div class="filter-section"> <div class="filter-section">
<div class="filter-content"> <div class="filter-content">
<el-form :inline="true" :model="queryParams" class="filter-form">
<el-form-item label="关键词">
<el-input v-model="queryParams.key" placeholder="订单名称/ID" clearable style="width: 150px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="用户ID">
<el-input v-model="queryParams.user_id" placeholder="用户ID" clearable style="width: 120px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="用户关键词">
<el-input v-model="queryParams.user_key" placeholder="用户名/手机号/邮箱" clearable style="width: 180px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="queryParams.state" placeholder="全部" clearable style="width: 120px">
<el-option label="待支付" value="0" />
<el-option label="已支付" value="1" />
<el-option label="已失效" value="2" />
</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"> <div class="action-bar">
<el-button type="primary" @click="handleAdd"> <el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增订单 <el-icon><Plus /></el-icon>新增订单
@@ -220,9 +244,12 @@ import { getOrderList, getOrderDetail, createOrder, updateOrder, deleteOrder } f
// 查询参数 // 查询参数
const queryParams = reactive({ const queryParams = reactive({
page: 1, page: 1,
count: 10 count: 10,
key: '',
state: '',
user_id: '',
user_key: ''
}) })
// 订单表单 // 订单表单
@@ -282,7 +309,14 @@ const orderFormRef = ref(null)
const fetchOrderList = async () => { const fetchOrderList = async () => {
loading.value = true loading.value = true
try { try {
const res = await getOrderList(queryParams) // 过滤空值参数
const params = {}
Object.keys(queryParams).forEach(key => {
if (queryParams[key] !== '' && queryParams[key] !== null && queryParams[key] !== undefined) {
params[key] = queryParams[key]
}
})
const res = await getOrderList(params)
console.log('订单列表数据:', res.data) console.log('订单列表数据:', res.data)
if (res.data.code === 200) { if (res.data.code === 200) {
orderList.value = res.data.data.list || [] orderList.value = res.data.data.list || []
@@ -337,10 +371,10 @@ const handleQuery = () => {
// 重置查询 // 重置查询
const resetQuery = () => { const resetQuery = () => {
queryParams.order_no = '' queryParams.key = ''
queryParams.state = ''
queryParams.user_id = '' queryParams.user_id = ''
queryParams.status = '' queryParams.user_key = ''
queryParams.dateRange = []
queryParams.page = 1 queryParams.page = 1
fetchOrderList() fetchOrderList()
} }
@@ -532,13 +566,29 @@ onMounted(() => {
.filter-content { .filter-content {
display: flex; display: flex;
justify-content: flex-end; justify-content: space-between;
align-items: center; align-items: flex-start;
padding: 16px 20px; padding: 16px 20px;
gap: 20px; gap: 20px;
flex-wrap: wrap; flex-wrap: wrap;
} }
.filter-form {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
}
.filter-form :deep(.el-form-item) {
margin-bottom: 0;
margin-right: 8px;
}
.filter-form :deep(.el-form-item__label) {
font-size: 13px;
}
.action-bar { .action-bar {
display: flex; display: flex;
gap: 12px; gap: 12px;
+46 -6
View File
@@ -149,6 +149,16 @@
<el-form-item label="商品所属表" prop="table"> <el-form-item label="商品所属表" prop="table">
<el-input v-model="productForm.table" placeholder="请输入商品所属表" /> <el-input v-model="productForm.table" placeholder="请输入商品所属表" />
</el-form-item> </el-form-item>
<el-form-item label="商品标签" prop="tag">
<el-select v-model="productForm.tag" placeholder="请选择商品标签" style="width: 100%">
<el-option
v-for="item in tagOptions"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item label="内容" prop="content"> <el-form-item label="内容" prop="content">
<el-input v-model="productForm.content" type="textarea" :rows="4" placeholder="请输入内容" /> <el-input v-model="productForm.content" type="textarea" :rows="4" placeholder="请输入内容" />
</el-form-item> </el-form-item>
@@ -337,6 +347,7 @@ import { getFileDetail } from '@/api/admin/file'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Delete, Search, Refresh } from '@element-plus/icons-vue' import { Plus, Delete, Search, Refresh } from '@element-plus/icons-vue'
import { getProductList, createProduct, updateProduct, deleteProduct, getProductGroupList, import { getProductList, createProduct, updateProduct, deleteProduct, getProductGroupList,
getProductTagList,
getProductParameterList, getProductParameterList,
getProductParameterDetail, getProductParameterDetail,
createProductParameter, createProductParameter,
@@ -359,6 +370,7 @@ const productForm = reactive({
id: undefined, id: undefined,
name: '', name: '',
table: '', table: '',
tag: '',
content: '', content: '',
cover_id: undefined, cover_id: undefined,
good_group_id: undefined, // 添加商品分组字段 good_group_id: undefined, // 添加商品分组字段
@@ -394,6 +406,7 @@ const productRules = {
const loading = ref(false) const loading = ref(false)
const productList = ref([]) const productList = ref([])
const groupOptions = ref([]) const groupOptions = ref([])
const tagOptions = ref([])
const total = ref(0) const total = ref(0)
const selectedRows = ref([]) const selectedRows = ref([])
const dialogVisible = ref(false) const dialogVisible = ref(false)
@@ -443,6 +456,20 @@ const fetchGroupList = async () => {
} }
} }
// 获取商品标签列表
const fetchTagList = async () => {
try {
const res = await getProductTagList()
if (res.data.code === 200) {
tagOptions.value = res.data.data || []
console.log('商品标签列表:', tagOptions.value) // 调试日志
}
} catch (error) {
console.error('获取标签列表失败:', error)
ElMessage.error('获取标签列表失败')
}
}
// 查询 // 查询
const handleQuery = () => { const handleQuery = () => {
queryParams.page = 1 queryParams.page = 1
@@ -486,6 +513,7 @@ const handleAdd = () => {
id: undefined, id: undefined,
name: '', name: '',
table: '', table: '',
tag: '',
content: '', content: '',
cover_id: undefined, cover_id: undefined,
good_group_id: undefined, good_group_id: undefined,
@@ -509,6 +537,7 @@ const handleEdit = (row) => {
id: row.id, id: row.id,
name: row.name, name: row.name,
table: row.table, table: row.table,
tag: row.tag,
content: row.content, content: row.content,
cover_id: row.coverId, cover_id: row.coverId,
good_group_id: row.goodGroupId, good_group_id: row.goodGroupId,
@@ -612,8 +641,6 @@ const submitForm = () => {
pay_num: productForm.pay_num || 1, pay_num: productForm.pay_num || 1,
expire_time: productForm.expire_time || 0, expire_time: productForm.expire_time || 0,
recommend_rebate: productForm.recommend_rebate || 0 recommend_rebate: productForm.recommend_rebate || 0
} }
console.log('提交的数据:', submitData) // 调试日志 console.log('提交的数据:', submitData) // 调试日志
@@ -640,6 +667,7 @@ const submitForm = () => {
onMounted(() => { onMounted(() => {
fetchProductList() fetchProductList()
fetchGroupList() fetchGroupList()
fetchTagList()
}) })
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
@@ -678,7 +706,10 @@ const paramValueForm = reactive({
attr_id: undefined, attr_id: undefined,
attr_name: '', attr_name: '',
attr_value: '', attr_value: '',
attr_price: 0 attr_price: 0,
index: 0,
attr_range: 0,
range_type: 'equal'
}) })
const paramValueRules = { const paramValueRules = {
@@ -825,7 +856,10 @@ const handleAddParamValue = () => {
attr_id: undefined, attr_id: undefined,
attr_name: '', attr_name: '',
attr_value: '', attr_value: '',
attr_price: 0 attr_price: 0,
index: 0,
attr_range: 0,
range_type: 'equal'
}) })
paramValueFormRef.value?.resetFields() paramValueFormRef.value?.resetFields()
} }
@@ -838,7 +872,10 @@ const handleEditParamValue = (row) => {
attr_id: row.id, attr_id: row.id,
attr_name: row.name, attr_name: row.name,
attr_value: row.value, attr_value: row.value,
attr_price: row.price / 100 attr_price: row.price / 100,
index: row.index || 0,
attr_range: row.attr_range || 0,
range_type: row.range_type || 'equal'
}) })
} }
@@ -872,7 +909,10 @@ const submitParamValueForm = () => {
arg_id: Number(currentParam.value.id), arg_id: Number(currentParam.value.id),
attr_name: paramValueForm.attr_name, attr_name: paramValueForm.attr_name,
attr_value: paramValueForm.attr_value, attr_value: paramValueForm.attr_value,
attr_price: paramValueForm.attr_price attr_price: paramValueForm.attr_price,
index: Number(paramValueForm.index),
attr_range: Number(paramValueForm.attr_range),
range_type: paramValueForm.range_type
} }
if (paramValueFormType.value === 'edit') { if (paramValueFormType.value === 'edit') {
submitData.attr_id = paramValueForm.attr_id submitData.attr_id = paramValueForm.attr_id
+458 -11
View File
@@ -56,9 +56,17 @@
</div> </div>
<div class="header-right" v-if="ticketInfo"> <div class="header-right" v-if="ticketInfo">
<span class="ticket-id">工单号: {{ ticketInfo.id }}</span> <span class="ticket-id">工单号: {{ ticketInfo.id }}</span>
<el-tag :type="getStatusType(ticketInfo.status)" size="small"> <el-select
{{ getStatusText(ticketInfo.status) }} v-model="ticketInfo.status"
</el-tag> size="small"
style="width: 120px"
@change="handleStatusChange"
>
<el-option label="待处理" value="pending" />
<el-option label="处理中" value="processing" />
<el-option label="已回复" value="replied" />
<el-option label="已完成" value="completed" />
</el-select>
<el-button <el-button
v-if="ticketInfo.status !== 'completed'" v-if="ticketInfo.status !== 'completed'"
type="success" type="success"
@@ -82,7 +90,19 @@
<el-avatar :size="36" :src="message.avatar">{{ ticketInfo?.username?.charAt(0) || 'U' }}</el-avatar> <el-avatar :size="36" :src="message.avatar">{{ ticketInfo?.username?.charAt(0) || 'U' }}</el-avatar>
</div> </div>
<div class="message-content"> <div class="message-content">
<div class="message-text" v-if="message.content">{{ message.content }}</div> <div class="message-text" v-if="message.content">
{{ message.content }}
<span
v-if="message.isAdmin && !message.isTempMessage"
class="edit-btn"
@click="handleEditMessage(message)"
title="编辑消息"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/>
</svg>
</span>
</div>
<div class="message-images" v-if="message.images && message.images.length"> <div class="message-images" v-if="message.images && message.images.length">
<img <img
v-for="(img, imgIndex) in message.images" v-for="(img, imgIndex) in message.images"
@@ -124,12 +144,19 @@
</div> </div>
<!-- 输入框 --> <!-- 输入框 -->
<div class="input-area"> <div
class="input-area"
@drop.prevent="handleDrop"
@dragover.prevent="handleDragOver"
@dragleave.prevent="handleDragLeave"
:class="{ 'drag-over': isDragOver }"
>
<el-input <el-input
ref="textareaRef"
v-model="messageInput" v-model="messageInput"
type="textarea" type="textarea"
:rows="3" :rows="3"
placeholder="请输入回复内容..." placeholder="请输入回复内容(支持粘贴图片和拖拽图片)..."
@keyup.ctrl.enter="sendMessage" @keyup.ctrl.enter="sendMessage"
/> />
<div class="input-actions"> <div class="input-actions">
@@ -163,6 +190,47 @@
<el-alert title="该工单已结束,无法继续回复" type="info" :closable="false" /> <el-alert title="该工单已结束,无法继续回复" type="info" :closable="false" />
</div> </div>
<!-- 编辑消息对话框 -->
<el-dialog v-model="editDialogVisible" title="编辑消息" width="600px">
<el-form>
<el-form-item label="消息内容">
<el-input
v-model="editMessageContent"
type="textarea"
:rows="4"
placeholder="请输入消息内容"
/>
</el-form-item>
<el-form-item label="图片">
<!-- 现有图片预览 -->
<div class="edit-images-container">
<div class="edit-preview-item" v-for="(image, index) in editMessageImages" :key="index">
<img :src="image" alt="图片" />
<div class="delete-preview" @click="removeEditImage(index)">×</div>
</div>
<!-- 添加图片按钮 -->
<el-upload
action="#"
:auto-upload="false"
:show-file-list="false"
:on-change="handleEditFileChange"
multiple
accept="image/*"
>
<div class="add-image-btn">
<el-icon :size="24"><Plus /></el-icon>
<div class="add-text">添加图片</div>
</div>
</el-upload>
</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="editDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="isEditingSaving" @click="saveEditMessage">保存</el-button>
</template>
</el-dialog>
<!-- 图片查看器 --> <!-- 图片查看器 -->
<el-dialog v-model="imageViewerVisible" width="auto" destroy-on-close> <el-dialog v-model="imageViewerVisible" width="auto" destroy-on-close>
<img :src="currentViewImage" style="max-width: 100%; max-height: 80vh;" /> <img :src="currentViewImage" style="max-width: 100%; max-height: 80vh;" />
@@ -174,7 +242,8 @@
import { ref, onMounted, nextTick, onBeforeUnmount, watch } from 'vue' import { ref, onMounted, nextTick, onBeforeUnmount, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { getTicketDetail, replyTicket, closeTicket } from '@/api/ticket' import { getTicketDetail, replyTicket, closeTicket, updateTicketInfo, updateTicketReplayInfo } from '@/api/ticket'
import { Plus } from '@element-plus/icons-vue'
import { getUserInfo } from '@/api/admin/user' import { getUserInfo } from '@/api/admin/user'
import { uploadFile } from '@/api/admin/file' import { uploadFile } from '@/api/admin/file'
import { useUserStore } from '@/store/userStore' import { useUserStore } from '@/store/userStore'
@@ -200,11 +269,22 @@ const messageInput = ref('')
const selectedImages = ref([]) const selectedImages = ref([])
const selectedFiles = ref([]) // 存储原始文件对象 const selectedFiles = ref([]) // 存储原始文件对象
const messagesContainer = ref(null) const messagesContainer = ref(null)
const textareaRef = ref(null)
const isDragOver = ref(false)
// 图片查看 // 图片查看
const imageViewerVisible = ref(false) const imageViewerVisible = ref(false)
const currentViewImage = ref('') const currentViewImage = ref('')
// 编辑消息
const editDialogVisible = ref(false)
const editMessageContent = ref('')
const editMessageImages = ref([])
const editMessageFiles = ref([])
const editOriginalFileIds = ref([]) // 保存原始文件ID
const editingMessage = ref(null)
const isEditingSaving = ref(false)
// 定时刷新 // 定时刷新
const refreshTimer = ref(null) const refreshTimer = ref(null)
@@ -283,7 +363,7 @@ const fetchTicketDetail = async (showLoading = true) => {
if (res.code === 200) { if (res.code === 200) {
const detail = res.data const detail = res.data
ticketInfo.value = { ticketInfo.value = {
id: detail.work_id, id: detail.id,
title: detail.name, title: detail.name,
username: detail.user?.userName || `用户${detail.user?.userId || 'Unknown'}`, username: detail.user?.userName || `用户${detail.user?.userId || 'Unknown'}`,
userId: detail.user?.userId, userId: detail.user?.userId,
@@ -299,6 +379,7 @@ const fetchTicketDetail = async (showLoading = true) => {
id: msg.id, id: msg.id,
content: msg.content !== 'empty' ? msg.content : null, content: msg.content !== 'empty' ? msg.content : null,
images: msg.flies ? msg.flies.map(file => file.url) : [], images: msg.flies ? msg.flies.map(file => file.url) : [],
fileIds: msg.flies ? msg.flies.map(file => file.id) : [], // 保存文件ID
time: msg.created_at || msg.updated_at || new Date().toLocaleString(), time: msg.created_at || msg.updated_at || new Date().toLocaleString(),
isAdmin: isAdmin(msg.user?.userId), isAdmin: isAdmin(msg.user?.userId),
userId: msg.user?.userId, userId: msg.user?.userId,
@@ -432,6 +513,38 @@ const sendMessage = async () => {
} }
} }
// 修改工单状态
const handleStatusChange = async (newStatus) => {
const statusMap = {
'pending': 0,
'processing': 1,
'replied': 2,
'completed': 3
}
const oldStatus = ticketInfo.value.status
try {
const formData = new FormData()
formData.append('work_id', route.query.id)
formData.append('Status', statusMap[newStatus])
const res = await updateTicketInfo(formData)
if (res.code === 200) {
ElMessage.success('工单状态已更新')
ticketInfo.value.status = newStatus
} else {
ElMessage.error(res.message || '更新失败')
ticketInfo.value.status = oldStatus // 恢复原状态
}
} catch (error) {
console.error('更新工单状态出错:', error)
ElMessage.error('网络错误,请稍后重试')
ticketInfo.value.status = oldStatus // 恢复原状态
}
}
// 结束工单 // 结束工单
const handleComplete = () => { const handleComplete = () => {
ElMessageBox.confirm('确定要结束此工单吗?结束后将无法继续回复。', '确认操作', { ElMessageBox.confirm('确定要结束此工单吗?结束后将无法继续回复。', '确认操作', {
@@ -456,14 +569,81 @@ const handleComplete = () => {
// 图片处理 // 图片处理
const handleFileChange = (file) => { const handleFileChange = (file) => {
if (!file) return if (!file) return
addImageFile(file.raw)
}
// 添加图片文件(统一处理函数)
const addImageFile = (file) => {
// 验证文件类型
if (!file.type.startsWith('image/')) {
ElMessage.warning('只支持图片格式')
return
}
// 验证文件大小(限制10MB
if (file.size > 10 * 1024 * 1024) {
ElMessage.warning('图片大小不能超过10MB')
return
}
// 保存原始文件对象用于上传 // 保存原始文件对象用于上传
selectedFiles.value.push(file.raw) selectedFiles.value.push(file)
// 读取文件用于预览 // 读取文件用于预览
const reader = new FileReader() const reader = new FileReader()
reader.onload = (e) => selectedImages.value.push(e.target.result) reader.onload = (e) => selectedImages.value.push(e.target.result)
reader.readAsDataURL(file.raw) reader.readAsDataURL(file)
}
// 处理粘贴事件
const handlePaste = (e) => {
const items = e.clipboardData?.items
if (!items) return
for (let i = 0; i < items.length; i++) {
const item = items[i]
if (item.type.indexOf('image') !== -1) {
e.preventDefault()
const file = item.getAsFile()
if (file) {
addImageFile(file)
ElMessage.success('图片已添加')
}
}
}
}
// 处理拖拽进入
const handleDragOver = (e) => {
isDragOver.value = true
}
// 处理拖拽离开
const handleDragLeave = (e) => {
isDragOver.value = false
}
// 处理文件拖放
const handleDrop = (e) => {
isDragOver.value = false
const files = e.dataTransfer?.files
if (!files || files.length === 0) return
let addedCount = 0
for (let i = 0; i < files.length; i++) {
const file = files[i]
if (file.type.startsWith('image/')) {
addImageFile(file)
addedCount++
}
}
if (addedCount > 0) {
ElMessage.success(`已添加 ${addedCount} 张图片`)
} else {
ElMessage.warning('未找到图片文件')
}
} }
const removeImage = (index) => { const removeImage = (index) => {
@@ -476,6 +656,138 @@ const openImage = (img) => {
imageViewerVisible.value = true imageViewerVisible.value = true
} }
// 编辑消息
const handleEditMessage = (message) => {
editingMessage.value = message
editMessageContent.value = message.content || ''
editMessageImages.value = message.images ? [...message.images] : []
editOriginalFileIds.value = message.fileIds ? [...message.fileIds] : []
editMessageFiles.value = []
editDialogVisible.value = true
}
// 处理编辑对话框中的文件选择
const handleEditFileChange = (file) => {
if (!file) return
// 验证文件类型
if (!file.raw.type.startsWith('image/')) {
ElMessage.warning('只支持图片格式')
return
}
// 验证文件大小(限制10MB
if (file.raw.size > 10 * 1024 * 1024) {
ElMessage.warning('图片大小不能超过10MB')
return
}
// 保存原始文件对象用于上传
editMessageFiles.value.push(file.raw)
// 读取文件用于预览
const reader = new FileReader()
reader.onload = (e) => editMessageImages.value.push(e.target.result)
reader.readAsDataURL(file.raw)
}
// 删除编辑对话框中的图片
const removeEditImage = (index) => {
const originalImagesCount = editingMessage.value?.images?.length || 0
// 如果删除的是原始图片,从原始文件ID列表中删除
if (index < originalImagesCount) {
editOriginalFileIds.value.splice(index, 1)
} else {
// 如果删除的是新添加的图片,从新文件列表中删除
const fileIndex = index - originalImagesCount
if (fileIndex >= 0 && fileIndex < editMessageFiles.value.length) {
editMessageFiles.value.splice(fileIndex, 1)
}
}
// 从预览列表中删除
editMessageImages.value.splice(index, 1)
}
// 保存编辑的消息
const saveEditMessage = async () => {
if (!editMessageContent.value.trim()) {
ElMessage.warning('消息内容不能为空')
return
}
if (!editingMessage.value || !editingMessage.value.id) {
ElMessage.error('无法获取消息ID')
return
}
try {
isEditingSaving.value = true
// 如果有新添加的图片,先上传
let newFileIds = []
if (editMessageFiles.value.length > 0) {
try {
const formData = new FormData()
editMessageFiles.value.forEach((file) => {
formData.append('files', file)
formData.append('file_names', file.name)
})
formData.append('update_type', 'work_order')
formData.append('open_down', 'true')
const uploadRes = await uploadFile(formData)
if (uploadRes.data?.code === 200) {
const data = uploadRes.data.data
if (Array.isArray(data)) {
newFileIds = data.map(item => String(item.id))
} else if (data.id) {
newFileIds = [String(data.id)]
}
} else {
ElMessage.error(uploadRes.data?.message || '图片上传失败')
isEditingSaving.value = false
return
}
} catch (error) {
console.error('图片上传失败:', error)
ElMessage.error('图片上传失败,请重试')
isEditingSaving.value = false
return
}
}
// 合并原始文件ID和新上传的文件ID
const allFileIds = [...editOriginalFileIds.value, ...newFileIds]
const formData = new FormData()
formData.append('id', editingMessage.value.id)
formData.append('content', editMessageContent.value.trim())
formData.append('files', allFileIds.join(','))
const res = await updateTicketReplayInfo(formData)
if (res.code === 200) {
ElMessage.success('消息已更新')
editDialogVisible.value = false
// 重新获取工单详情以确保数据同步
await fetchTicketDetail(false)
} else {
ElMessage.error(res.message || '更新失败')
}
} catch (error) {
console.error('更新消息出错:', error)
ElMessage.error('网络错误,请稍后重试')
} finally {
isEditingSaving.value = false
}
}
// 快捷回复 // 快捷回复
const useQuickReply = (reply) => { const useQuickReply = (reply) => {
messageInput.value = reply.content messageInput.value = reply.content
@@ -606,10 +918,24 @@ watch(
onMounted(() => { onMounted(() => {
fetchTicketDetail() fetchTicketDetail()
startAutoRefresh() startAutoRefresh()
// 绑定粘贴事件到原生 textarea
nextTick(() => {
const textarea = textareaRef.value?.$el?.querySelector('textarea')
if (textarea) {
textarea.addEventListener('paste', handlePaste)
}
})
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
stopAutoRefresh() stopAutoRefresh()
// 移除粘贴事件监听
const textarea = textareaRef.value?.$el?.querySelector('textarea')
if (textarea) {
textarea.removeEventListener('paste', handlePaste)
}
}) })
</script> </script>
@@ -645,7 +971,10 @@ onBeforeUnmount(() => {
.ticket-id { .ticket-id {
font-weight: 500; font-weight: 500;
color: #606266; color: #303133;
font-size: 14px;
white-space: nowrap;
flex-shrink: 0;
} }
.user-info { .user-info {
@@ -765,6 +1094,29 @@ onBeforeUnmount(() => {
border-radius: 8px; border-radius: 8px;
word-break: break-word; word-break: break-word;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
position: relative;
}
.message-text .edit-btn {
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 8px;
padding: 4px;
cursor: pointer;
opacity: 0.6;
transition: all 0.2s;
border-radius: 4px;
vertical-align: middle;
}
.message-text .edit-btn:hover {
opacity: 1;
background: rgba(0, 0, 0, 0.1);
}
.message-text:hover .edit-btn {
opacity: 1;
} }
.message-admin .message-text { .message-admin .message-text {
@@ -772,6 +1124,16 @@ onBeforeUnmount(() => {
color: #fff; color: #fff;
} }
.message-admin .message-text .edit-btn {
color: #fff;
opacity: 0.7;
}
.message-admin .message-text .edit-btn:hover {
opacity: 1;
background: rgba(255, 255, 255, 0.2);
}
.message-images { .message-images {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@@ -849,6 +1211,28 @@ onBeforeUnmount(() => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 12px;
position: relative;
transition: all 0.3s;
}
.input-area.drag-over {
background: #f0f9ff;
border: 2px dashed #409eff;
border-radius: 4px;
padding: 8px;
}
.input-area.drag-over::before {
content: '释放以添加图片';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #409eff;
font-size: 14px;
font-weight: 500;
pointer-events: none;
z-index: 1;
} }
.input-actions { .input-actions {
@@ -872,4 +1256,67 @@ onBeforeUnmount(() => {
background: #fff; background: #fff;
border-top: 1px solid #ebeef5; border-top: 1px solid #ebeef5;
} }
.edit-images-container {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.edit-preview-item {
position: relative;
width: 100px;
height: 100px;
border-radius: 4px;
overflow: hidden;
border: 1px solid #dcdfe6;
}
.edit-preview-item img {
width: 100%;
height: 100%;
object-fit: cover;
}
.edit-preview-item .delete-preview {
position: absolute;
top: -8px;
right: -8px;
width: 24px;
height: 24px;
background: #f56c6c;
color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 18px;
line-height: 1;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.add-image-btn {
width: 100px;
height: 100px;
border: 2px dashed #dcdfe6;
border-radius: 4px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
color: #909399;
}
.add-image-btn:hover {
border-color: #409eff;
color: #409eff;
}
.add-image-btn .add-text {
font-size: 12px;
margin-top: 4px;
}
</style> </style>
+47 -6
View File
@@ -3,9 +3,6 @@
<!-- 顶部工具栏 --> <!-- 顶部工具栏 -->
<div class="toolbar"> <div class="toolbar">
<div class="status-tabs"> <div class="status-tabs">
<div class="tab-item" :class="{ active: activeStatus === '' }" @click="filterByStatus('')">
全部 <span class="count">{{ stats.total }}</span>
</div>
<div class="tab-item pending" :class="{ active: activeStatus === 'pending' }" @click="filterByStatus('pending')"> <div class="tab-item pending" :class="{ active: activeStatus === 'pending' }" @click="filterByStatus('pending')">
待处理 <span class="count">{{ stats.pending }}</span> 待处理 <span class="count">{{ stats.pending }}</span>
</div> </div>
@@ -18,8 +15,22 @@
<div class="tab-item completed" :class="{ active: activeStatus === 'completed' }" @click="filterByStatus('completed')"> <div class="tab-item completed" :class="{ active: activeStatus === 'completed' }" @click="filterByStatus('completed')">
已完成 <span class="count">{{ stats.completed }}</span> 已完成 <span class="count">{{ stats.completed }}</span>
</div> </div>
<div class="tab-item" :class="{ active: activeStatus === '' }" @click="filterByStatus('')">
全部 <span class="count">{{ stats.total }}</span>
</div>
</div> </div>
<div class="toolbar-right"> <div class="toolbar-right">
<el-select v-model="sortBy" placeholder="排序方式" clearable style="width: 140px" @change="handleSortChange">
<el-option label="不排序" value="" />
<el-option label="创建时间" value="created_at" />
<el-option label="更新时间" value="updated_at" />
<el-option label="工单号" value="id" />
</el-select>
<el-select v-model="sortOrder" placeholder="排序顺序" clearable style="width: 100px" @change="handleSortChange">
<el-option label="默认" value="" />
<el-option label="降序" value="desc" />
<el-option label="升序" value="asc" />
</el-select>
<el-input <el-input
v-model="searchKeyword" v-model="searchKeyword"
placeholder="搜索工单号、标题、用户名" placeholder="搜索工单号、标题、用户名"
@@ -92,7 +103,7 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive, computed, onMounted } from 'vue' import { ref, reactive, computed, onMounted, onActivated } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { import {
@@ -112,7 +123,11 @@ const isLoading = ref(false)
// 工单数据 // 工单数据
const ticketList = ref([]) const ticketList = ref([])
const searchKeyword = ref('') const searchKeyword = ref('')
const activeStatus = ref('') const activeStatus = ref('pending') // 默认选中"待处理"
// 排序
const sortBy = ref('') // 默认不排序
const sortOrder = ref('') // 默认不选择排序顺序
// 统计数据 // 统计数据
const stats = reactive({ const stats = reactive({
@@ -125,6 +140,8 @@ const stats = reactive({
// 状态转换 // 状态转换
const convertStatusToString = (status) => { const convertStatusToString = (status) => {
const statusMap = { 0: 'pending', 1: 'processing', 2: 'replied', 3: 'completed' } const statusMap = { 0: 'pending', 1: 'processing', 2: 'replied', 3: 'completed' }
@@ -151,7 +168,14 @@ const fetchTicketList = async () => {
statusParam = statusMap[activeStatus.value] || '' statusParam = statusMap[activeStatus.value] || ''
} }
const res = await getTickerList(pageSize.value, currentPage.value, statusParam) console.log('调用getTickerList,排序参数:', { sortBy: sortBy.value, sortOrder: sortOrder.value })
const res = await getTickerList(
pageSize.value,
currentPage.value,
statusParam,
sortBy.value,
sortOrder.value
)
if (res.code === 200) { if (res.code === 200) {
ticketList.value = (res.data.data || []).map(item => ({ ticketList.value = (res.data.data || []).map(item => ({
@@ -212,6 +236,12 @@ const filterByStatus = (status) => {
fetchTicketList() fetchTicketList()
} }
// 排序变化处理
const handleSortChange = () => {
currentPage.value = 1
fetchTicketList()
}
// 搜索处理 // 搜索处理
const handleSearch = () => {} const handleSearch = () => {}
@@ -261,10 +291,21 @@ const handleComplete = (ticket) => {
}).catch(() => {}) }).catch(() => {})
} }
let isFirstLoad = true
onMounted(() => { onMounted(() => {
fetchTicketList() fetchTicketList()
fetchStats() fetchStats()
}) })
// 当页面被激活时(从详情页返回时)
onActivated(() => {
// 跳过首次加载,只在从其他页面返回时刷新
if (!isFirstLoad) {
refreshList()
}
isFirstLoad = false
})
</script> </script>
<style scoped> <style scoped>
+112 -5
View File
@@ -77,11 +77,21 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="Note" label="备注" min-width="200" show-overflow-tooltip /> <el-table-column prop="Note" label="备注" min-width="200" show-overflow-tooltip />
<el-table-column prop="PaymentOrderId" label="支付订单ID" width="150" show-overflow-tooltip>
<template #default="{ row }">
{{ row.PaymentOrderId || '-' }}
</template>
</el-table-column>
<el-table-column label="创建时间" width="180"> <el-table-column label="创建时间" width="180">
<template #default="{ row }"> <template #default="{ row }">
{{ formatDateTime(row.CreatedAt) }} {{ formatDateTime(row.CreatedAt) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<template #default="{ row }">
<el-button type="warning" size="small" @click="handleRefund(row)">退款</el-button>
</template>
</el-table-column>
</el-table> </el-table>
<div class="pagination-wrapper"> <div class="pagination-wrapper">
@@ -151,6 +161,10 @@
<template #prepend>¥</template> <template #prepend>¥</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item label="支付平台订单id" prop="paymentOrderId">
<el-input v-model="recordForm.paymentOrderId" placeholder="请输入支付平台订单ID" />
</el-form-item>
<el-form-item label="备注" prop="note"> <el-form-item label="备注" prop="note">
<el-input v-model="recordForm.note" type="textarea" :rows="3" placeholder="请输入备注" /> <el-input v-model="recordForm.note" type="textarea" :rows="3" placeholder="请输入备注" />
</el-form-item> </el-form-item>
@@ -160,15 +174,38 @@
<el-button type="primary" @click="submitRecordForm">确定</el-button> <el-button type="primary" @click="submitRecordForm">确定</el-button>
</template> </template>
</el-dialog> </el-dialog>
<!-- 退款对话框 -->
<el-dialog v-model="refundDialogVisible" title="退款" width="500px">
<el-form ref="refundFormRef" :model="refundForm" :rules="refundRules" label-width="120px">
<el-form-item label="余额记录ID">
<el-input :value="refundForm.history_id" disabled />
</el-form-item>
<el-form-item label="订单ID" prop="order_id">
<el-input v-model="refundForm.order_id" :min="0" style="width: 100%" placeholder="请输入订单ID" />
</el-form-item>
<el-form-item label="退款方式" prop="bandwidth">
<el-radio-group v-model="refundForm.bandwidth">
<el-radio :label="false">退回余额</el-radio>
<el-radio :label="true">提现</el-radio>
</el-radio-group>
<div class="form-tip">退回余额将退款金额添加到对应余额提现将退款金额提现</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="refundDialogVisible = false">取消</el-button>
<el-button type="warning" @click="submitRefundForm" :loading="refundLoading">确认退款</el-button>
</template>
</el-dialog>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted, computed } from 'vue' import { ref, reactive, onMounted, computed, watch } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { Refresh } from '@element-plus/icons-vue' import { Refresh } from '@element-plus/icons-vue'
import { getUserBalance, getUserBalanceRecord, editUserBalance, addUserConsumption, getUserBalanceCount, getUserList } from '@/api/admin/user' import { getUserBalance, getUserBalanceRecord, editUserBalance, addUserConsumption, getUserBalanceCount, getUserList, refundBalance } from '@/api/admin/user'
const route = useRoute() const route = useRoute()
@@ -203,8 +240,11 @@ const userList = ref([])
// 对话框 // 对话框
const balanceDialogVisible = ref(false) const balanceDialogVisible = ref(false)
const recordDialogVisible = ref(false) const recordDialogVisible = ref(false)
const refundDialogVisible = ref(false)
const balanceFormRef = ref(null) const balanceFormRef = ref(null)
const recordFormRef = ref(null) const recordFormRef = ref(null)
const refundFormRef = ref(null)
const refundLoading = ref(false)
// 余额表单 // 余额表单
const balanceForm = reactive({ const balanceForm = reactive({
@@ -231,7 +271,8 @@ const recordForm = reactive({
apply_balance: false, apply_balance: false,
price: 0, price: 0,
state: 'add', state: 'add',
note: '' note: '',
paymentOrderId: ''
}) })
const recordRules = { const recordRules = {
@@ -243,6 +284,18 @@ const recordRules = {
note: [{ required: true, message: '请输入备注', trigger: 'blur' }] note: [{ required: true, message: '请输入备注', trigger: 'blur' }]
} }
// 退款表单
const refundForm = reactive({
history_id: 0,
order_id: 0,
bandwidth: false
})
const refundRules = {
order_id: [{ required: true, message: '请输入订单ID', trigger: 'blur' }],
bandwidth: [{ required: true, message: '请选择退款方式', trigger: 'change' }]
}
// 当前余额显示 // 当前余额显示
const currentBalanceDisplay = computed(() => { const currentBalanceDisplay = computed(() => {
const balance = balanceList.value.find(b => userBalance[b.type]?.type === balanceForm.balance_type) const balance = balanceList.value.find(b => userBalance[b.type]?.type === balanceForm.balance_type)
@@ -365,7 +418,7 @@ const fetchBalanceRecord = async () => {
const res = await getUserBalanceRecord(recordParams) const res = await getUserBalanceRecord(recordParams)
if (res.data.code === 200) { if (res.data.code === 200) {
recordList.value = res.data.data.data || [] recordList.value = res.data.data.data || []
recordTotal.value = res.data.data.all_count || 0 recordTotal.value = res.data.data.total || 0
} else { } else {
ElMessage.error(res.data.message || '获取余额记录失败') ElMessage.error(res.data.message || '获取余额记录失败')
} }
@@ -401,7 +454,8 @@ const handleAddRecord = (balanceType) => {
apply_balance: false, apply_balance: false,
price: 0, price: 0,
state: 'add', state: 'add',
note: '' note: '',
paymentOrderId: ''
}) })
recordDialogVisible.value = true recordDialogVisible.value = true
} }
@@ -457,6 +511,40 @@ const handleRecordCurrentChange = () => {
fetchBalanceRecord() fetchBalanceRecord()
} }
// 退款
const handleRefund = (row) => {
Object.assign(refundForm, {
history_id: row.Id,
order_id: String(row.OrderId) || 0,
bandwidth: false
})
refundDialogVisible.value = true
}
// 提交退款
const submitRefundForm = () => {
refundFormRef.value?.validate(async (valid) => {
if (valid) {
refundLoading.value = true
try {
const res = await refundBalance(refundForm)
if (res.data.code === 200) {
ElMessage.success('退款成功')
refundDialogVisible.value = false
fetchUserBalance()
} else {
ElMessage.error(res.data.message || '退款失败')
}
} catch (error) {
console.error('退款错误:', error)
ElMessage.error('退款失败')
} finally {
refundLoading.value = false
}
}
})
}
// 初始化 // 初始化
onMounted(() => { onMounted(() => {
getUserListData() getUserListData()
@@ -466,6 +554,19 @@ onMounted(() => {
ElMessage.warning('缺少用户ID参数') ElMessage.warning('缺少用户ID参数')
} }
}) })
// 监听路由参数变化
watch(
() => route.query.user_id,
(newUserId) => {
if (newUserId && newUserId !== queryParams.user_id) {
queryParams.user_id = newUserId
recordParams.user_id = newUserId
fetchUserBalance()
}
},
{ immediate: true }
)
</script> </script>
<style scoped> <style scoped>
@@ -684,6 +785,12 @@ onMounted(() => {
background: #fafafa; background: #fafafa;
} }
.form-tip {
font-size: 12px;
color: #909399;
margin-top: 4px;
}
/* 响应式 */ /* 响应式 */
@media (max-width: 1200px) { @media (max-width: 1200px) {
.balance-cards { .balance-cards {
+18 -598
View File
@@ -7,293 +7,13 @@
}, },
"tags": [], "tags": [],
"paths": { "paths": {
"/api/v1/admin/server/setting/group/list": { "/api/v1/admin/order/list": {
"get": { "get": {
"summary": "获取配置分组列表", "summary": "获取订单列表",
"deprecated": false, "deprecated": false,
"description": "", "description": "",
"tags": [], "tags": [],
"parameters": [ "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", "name": "count",
"in": "query", "in": "query",
@@ -304,18 +24,9 @@
} }
}, },
{ {
"name": "group_id", "name": "page",
"in": "query", "in": "query",
"description": "组id(与组名称二选一)", "description": "获取页码 默认 1",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "group_name",
"in": "query",
"description": "组名称(与组id二选一)",
"required": false, "required": false,
"schema": { "schema": {
"type": "string" "type": "string"
@@ -331,53 +42,27 @@
} }
}, },
{ {
"name": "Authorization", "name": "state",
"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", "in": "query",
"description": "配置id (与name二选一)", "description": "状态筛选",
"required": false, "required": false,
"schema": { "schema": {
"type": "string" "type": "string"
} }
}, },
{ {
"name": "name", "name": "user_id",
"in": "query", "in": "query",
"description": "配置名称 (与id二选一)", "description": "用户id筛选",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "user_key",
"in": "query",
"description": "用户关键词筛选(用户名 手机号 邮箱)",
"required": false, "required": false,
"schema": { "schema": {
"type": "string" "type": "string"
@@ -410,276 +95,11 @@
}, },
"security": [] "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": { "components": {
"schemas": {}, "schemas": {},
"responses": {},
"securitySchemes": {} "securitySchemes": {}
}, },
"servers": [], "servers": [],