This commit is contained in:
张成
2025-10-09 18:00:37 +08:00
parent 4823e1d152
commit 366c18bcea
96 changed files with 16623 additions and 12 deletions

View File

@@ -0,0 +1,169 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<Button type="primary" @click="showAddWarp">新增</Button>
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
<FormItem :label-width="20" class="flex">
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
:placeholder="seachTypePlaceholder">
<Option v-for="item in seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
placeholder="请输入关键字" @on-search="query(1)" />
</FormItem>
<FormItem label="消息类型">
<Select v-model="gridOption.param.seachOption.message_type" style="width: 120px" clearable @on-change="query(1)">
<Option value="question">提问</Option>
<Option value="feedback">反馈</Option>
<Option value="suggestion">建议</Option>
</Select>
</FormItem>
<FormItem label="处理状态">
<Select v-model="gridOption.param.seachOption.status" style="width: 120px" clearable @on-change="query(1)">
<Option value="pending">待处理</Option>
<Option value="processing">处理中</Option>
<Option value="completed">已完成</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="query(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="default" @click="exportCsv">导出</Button>
</FormItem>
</Form>
</div>
<div class="table-body">
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
@changePage="query"></tables>
</div>
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules"> </editModal>
</div>
</template>
<script>
import funTool from '@/libs/funTool'
import uiTool from '@/libs/uiTool'
import ai_messagesServer from '@/api/ai/ai_messages_server.js'
export default {
data() {
let rules = {}
rules["user_id"] = [{ required: true, type: "number", message: '请填写用户ID', trigger: 'change' }];
rules["message_type"] = [{ required: true, message: '请填写消息类型' }];
rules["content"] = [{ required: true, message: '请填写消息内容' }];
return {
seachTypes: [
{ key: 'user_id', value: '用户ID' },
{ key: 'nickname', value: '用户昵称' }
],
gridOption: {
param: {
seachOption: {
key: 'nickname',
value: '',
message_type: null,
status: null
},
pageOption: {
page: 1,
pageSize: 20
}
},
data: [],
rules: rules
},
listColumns: [
{ title: 'ID', key: 'id', minWidth: 80 },
{ title: '用户ID', key: 'user_id', minWidth: 120 },
{ title: '用户昵称', key: 'nickname', minWidth: 120 },
{ title: '消息类型', key: 'message_type', minWidth: 120 },
{ title: '消息内容', key: 'content', minWidth: 300 },
{ title: 'AI回复', key: 'ai_response', minWidth: 300 },
{ title: '创建时间', key: 'create_time', minWidth: 150 },
{
title: '操作',
key: 'action',
width: 200,
type: 'template',
render: (h, params) => {
let btns = [
{
title: '编辑',
type: 'primary',
click: () => {
this.showEditWarp(params.row)
},
},
{
title: '删除',
type: 'error',
click: () => {
this.delConfirm(params.row)
},
},
]
return uiTool.getBtn(h, btns)
},
}
],
editColumns: [
{ title: '用户ID', key: 'user_id', type: 'number', required: true },
{ title: '消息类型', key: 'message_type', type: 'text', required: true },
{ title: '消息内容', key: 'content', type: 'textarea', required: true },
{ title: 'AI回复', key: 'ai_response', type: 'textarea' },
{ title: '处理状态', key: 'status', type: 'select', options: [
{ value: 'pending', label: '待处理' },
{ value: 'processing', label: '处理中' },
{ value: 'completed', label: '已完成' },
{ value: 'failed', label: '处理失败' }
]}
]
}
},
mounted() {
this.query(1);
},
methods: {
query(page) {
this.gridOption.param.pageOption.page = page;
ai_messagesServer.page(this.gridOption.param).then(res => {
this.gridOption.data = res.data.rows;
this.gridOption.param.pageOption.total = res.data.count;
});
},
showAddWarp() {
this.$refs.editModal.showModal();
},
showEditWarp(row) {
this.$refs.editModal.showModal(row);
},
delConfirm(row) {
uiTool.delConfirm(async () => {
await ai_messagesServer.del(row)
rootVue.$Message.success('删除成功!')
this.query(1)
})
},
exportCsv() {
ai_messagesServer.exportCsv(this.gridOption.param).then(res => {
funTool.downloadFile(res, 'AI消息管理.csv');
});
},
resetQuery() {
this.gridOption.param.seachOption = {
key: 'nickname',
value: '',
message_type: null,
status: null
};
this.query(1);
}
},
computed: {
seachTypePlaceholder() {
const selected = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key);
return selected ? selected.value : '请选择搜索类型';
}
}
}
</script>

View File

@@ -0,0 +1,161 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<Button type="primary" @click="showAddWarp">新增</Button>
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
<FormItem :label-width="20" class="flex">
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
:placeholder="seachTypePlaceholder">
<Option v-for="item in seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
placeholder="请输入关键字" @on-search="query(1)" />
</FormItem>
<FormItem label="状态">
<Select v-model="gridOption.param.seachOption.status" style="width: 120px" clearable @on-change="query(1)">
<Option value="active">正常</Option>
<Option value="hidden">隐藏</Option>
<Option value="deleted">已删除</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="query(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="default" @click="exportCsv" class="ml10">导出</Button>
</FormItem>
</Form>
</div>
<div class="table-body">
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
@changePage="query"></tables>
</div>
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules"> </editModal>
</div>
</template>
<script>
import funTool from '@/libs/funTool'
import uiTool from '@/libs/uiTool'
import game_commentsServer from '@/api/ball/game_comments_server.js'
export default {
data() {
let rules = {}
rules["game_id"] = [{ required: true, type: "number", message: '请填写球局ID', trigger: 'change' }];
rules["user_id"] = [{ required: true, type: "number", message: '请填写用户ID', trigger: 'change' }];
rules["content"] = [{ required: true, message: '请填写评论内容' }];
return {
// 搜索类型:只包含适合文本搜索的字段
seachTypes: [
{ key: 'game_id', value: '球局ID' },
{ key: 'user_id', value: '用户ID' },
{ key: 'content', value: '评论内容' }
],
gridOption: {
param: {
seachOption: {
key: 'content',
value: '',
status: null // 状态筛选
},
pageOption: {
page: 1,
pageSize: 20
}
},
data: [],
rules: rules
},
listColumns: [
{ title: 'ID', key: 'id', minWidth: 80 },
{ title: '球局ID', key: 'game_id', minWidth: 120 },
{ title: '用户ID', key: 'user_id', minWidth: 120 },
{ title: '用户昵称', key: 'nickname', minWidth: 120 },
{ title: '评论内容', key: 'content', minWidth: 300 },
{ title: '点赞数', key: 'like_count', minWidth: 120 },
{ title: '创建时间', key: 'create_time', minWidth: 150 },
{
title: '操作',
key: 'action',
width: 200,
type: 'template',
render: (h, params) => {
let btns = [
{
title: '编辑',
type: 'primary',
click: () => {
this.showEditWarp(params.row)
},
},
{
title: '删除',
type: 'error',
click: () => {
this.delConfirm(params.row)
},
},
]
return uiTool.getBtn(h, btns)
},
}
],
editColumns: [
{ title: '球局ID', key: 'game_id', type: 'number', required: true },
{ title: '用户ID', key: 'user_id', type: 'number', required: true },
{ title: '评论内容', key: 'content', type: 'textarea', required: true },
{ title: '点赞数', key: 'like_count', type: 'number' },
{ title: '状态', key: 'status', type: 'select', options: [
{ value: 'active', label: '正常' },
{ value: 'hidden', label: '隐藏' },
{ value: 'deleted', label: '已删除' }
]}
]
}
},
mounted() {
this.query(1);
},
methods: {
query(page) {
this.gridOption.param.pageOption.page = page;
game_commentsServer.page(this.gridOption.param).then(res => {
this.gridOption.data = res.data.rows;
this.gridOption.param.pageOption.total = res.data.count;
});
},
showAddWarp() {
this.$refs.editModal.showModal();
},
showEditWarp(row) {
this.$refs.editModal.showModal(row);
},
delConfirm(row) {
uiTool.delConfirm(async () => {
await game_commentsServer.del(row)
rootVue.$Message.success('删除成功!')
this.query(1)
})
},
resetQuery() {
this.gridOption.param.seachOption = {
key: 'content',
value: '',
status: null
};
this.query(1);
},
exportCsv() {
game_commentsServer.exportCsv(this.gridOption.param).then(res => {
funTool.downloadFile(res, '球局评论.csv');
});
}
},
computed: {
seachTypePlaceholder() {
const selected = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key);
return selected ? selected.value : '请选择搜索类型';
}
}
}
</script>

View File

@@ -0,0 +1,314 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<Button type="primary" @click="showAddWarp">新增</Button>
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
<FormItem :label-width="20" class="flex">
<Select v-model="gridOption.param.seachOption.key" style="width: 120px" :placeholder="seachTypePlaceholder">
<Option v-for="item in seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
placeholder="请输入关键字" @on-search="query(1)" />
</FormItem>
<FormItem label="参与状态">
<Select v-model="gridOption.param.seachOption.status" style="width: 120px" clearable @on-change="query(1)">
<Option value="joined">已加入</Option>
<Option value="cancelled">已取消</Option>
<Option value="substitute">替补</Option>
</Select>
</FormItem>
<FormItem label="付款状态">
<Select v-model="gridOption.param.seachOption.payment_status" style="width: 120px" clearable @on-change="query(1)">
<Option value="pending">待支付</Option>
<Option value="paid">已支付</Option>
<Option value="refunded">已退款</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="query(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="default" @click="exportCsv" class="ml10">导出</Button>
</FormItem>
</Form>
</div>
<div class="table-body">
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
@changePage="query"></tables>
</div>
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules"> </editModal>
</div>
</template>
<script>
import funTool from '@/libs/funTool'
import uiTool from '@/libs/uiTool'
import menuServer from '@/api/system_high/menuServer.js'
import gameParticipantsServer from '@/api/ball/game_participants_server.js'
export default {
data() {
let rules = {}
rules["id"] = [{ required: true, message: '请填写ID' }];
rules["game_id"] = [{ required: true, type: "number", message: '请输入球局ID', trigger: 'change' }];
rules["user_id"] = [{ required: true, message: '请填写参与用户ID' }];
rules["status"] = [{ required: true, message: '请选择参与状态' }];
rules["payment_status"] = [{ required: true, message: '请选择付款状态' }];
rules["price"] = [{ required: false, type: "number", message: '请输入价格(元)', trigger: 'change' }];
rules["payment_order_id"] = [{ required: false, message: '请填写支付订单ID' }];
rules["join_message"] = [{ required: false, message: '请填写加入留言' }];
rules["skill_level"] = [{ required: false, type: "number", message: '请输入技能水平(NTRP)', trigger: 'change' }];
rules["contact_info"] = [{ required: false, message: '请填写联系方式' }];
rules["joined_at"] = [{ required: true, message: '请选择加入时间' }];
rules["cancelled_at"] = [{ required: false, message: '请选择取消时间' }];
rules["cancel_reason"] = [{ required: false, message: '请填写取消原因' }];
return {
// 搜索类型:只包含适合文本搜索的字段
seachTypes: [
{ key: "game_id", value: "球局ID" },
{ key: "user_id", value: "用户ID" },
{ key: "join_message", value: "加入留言" }
],
gridOption: {
param: {
seachOption: {
key: "game_id",
value: "",
status: null, // 参与状态筛选
payment_status: null // 付款状态筛选
},
pageOption: {
total: 0,
page: 1,
pageSize: 20
}
},
rules,
columns: [
{ key: 'id', title: 'ID', minWidth: 80, is_show_edit: 0 },
{ key: "id", title: "ID", disabled: true, is_show_edit: 1, is_show_list: 1, com: "Input" },
{
key: "game_id",
title: "球局",
disabled: true,
is_show_edit: 1,
is_show_list: 1,
data_type: "number",
com: "InputNumber",
render: (h, params) => {
const game = params.row.gme_game;
if (game) {
return h('span', { attrs: { title: game.title } }, `#${game.id} ${game.title.substring(0, 10)}...`);
}
return h('span', `#${params.row.game_id}`);
}
},
{
key: "user_id",
title: "用户",
disabled: true,
is_show_edit: 1,
is_show_list: 1,
com: "Input",
render: (h, params) => {
const user = params.row.wch_user;
if (user) {
return h('span', { attrs: { title: `${user.nickname} (${user.phone})` } }, user.nickname);
}
return h('span', `用户#${params.row.user_id}`);
}
},
{
key: "status",
title: "参与状态",
disabled: false,
is_show_edit: 1,
is_show_list: 1,
com: "Select",
options: [
{ value: "joined", label: "已加入" },
{ value: "cancelled", label: "已取消" },
{ value: "substitute", label: "替补" }
],
render: (h, params) => {
const statusMap = {
'joined': { text: '已加入', color: 'green' },
'cancelled': { text: '已取消', color: 'red' },
'substitute': { text: '替补', color: 'orange' }
};
const status = statusMap[params.row.status] || { text: params.row.status || '未知', color: 'default' };
return h('Tag', { props: { color: status.color } }, status.text);
}
},
{
key: "payment_status", title: "付款状态", disabled: false, is_show_edit: 1, is_show_list: 0, com: "Select", options: [
{ value: "pending", label: "待支付" },
{ value: "paid", label: "已支付" },
{ value: "refunded", label: "已退款" }
]
},
{ key: "price", title: "价格(元)", disabled: false, is_show_edit: 1, is_show_list: 0, data_type: "number", com: "InputNumber" },
{ key: "payment_order_id", title: "支付订单ID", disabled: true, is_show_edit: 1, is_show_list: 0, com: "Input" },
{ key: "join_message", title: "加入留言", disabled: false, is_show_edit: 1, is_show_list: 0, com: "TextArea" },
{
key: "skill_level",
title: "技能水平",
disabled: false,
is_show_edit: 1,
is_show_list: 1,
data_type: "number",
com: "InputNumber",
min: 0,
max: 7,
step: 0.1,
render: (h, params) => {
const level = params.row.skill_level;
if (level === null || level === undefined || level === 0) {
return h('span', { style: { color: '#c5c8ce' } }, '未设置');
}
return h('Tag', { props: { color: 'blue' } }, `NTRP ${level}`);
}
},
{ key: "contact_info", title: "联系方式", disabled: false, is_show_edit: 1, is_show_list: 0, com: "Input" },
{ key: "joined_at", title: "加入时间", disabled: true, is_show_edit: 1, is_show_list: 1, com: "DatePicker" },
{ key: "cancelled_at", title: "取消时间", disabled: false, is_show_edit: 1, is_show_list: 0, com: "DatePicker" },
{ key: "cancel_reason", title: "取消原因", disabled: false, is_show_edit: 1, is_show_list: 0, com: "TextArea" },
{ key: 'create_time', title: '创建时间', minWidth: 150, is_show_edit: 0 },
{ key: 'last_modify_time', title: '更新时间', minWidth: 150, is_show_edit: 0 },
{
title: '操作',
key: 'action',
width: 200,
type: 'template',
is_show_list: 1,
render: (h, params) => {
let btns = [
{
title: '查看',
type: 'info',
click: () => {
this.showViewWarp(params.row)
},
},
{
title: '修改',
type: 'primary',
click: () => {
this.showEditWarp(params.row)
},
},
{
title: '删除',
type: 'error',
click: () => {
this.delConfirm(params.row)
},
},
]
return uiTool.getBtn(h, btns)
},
},],
data: []
},
}
},
computed: {
seachTypePlaceholder() {
const selected = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key);
return selected ? selected.value : '请选择搜索类型';
},
editColumns() {
let editTempColumns = this.gridOption.columns.filter(p => p.is_show_edit === 1)
return editTempColumns
},
listColumns() {
let listTempColumns = this.gridOption.columns.filter(p => p.is_show_list === 1)
return listTempColumns
}
},
mounted() {
this.init()
this.initCol()
},
methods: {
init() {
this.query(1)
},
async initCol() {
let columnRows = []
let columnKeys = columnRows.map(p => p.key)
let newColumns = this.gridOption.columns.filter(p => columnKeys.indexOf(p.key) > -1)
for (let i = 0; i < newColumns.length; i++) {
let curColumn = newColumns[i]
let modelMap = columnRows[i].map_option
let res = await menuServer.modelInterface({ model_key: columnRows[i].modelKey, map_option: modelMap })
curColumn.source = res.data
}
},
async inquiry() {
let res = await gameParticipantsServer.all(this.gridOption.param)
this.gridOption.data = res.data
},
async query(page) {
if (page) {
this.gridOption.param.pageOption.page = page
}
let res = await gameParticipantsServer.page(this.gridOption.param)
this.gridOption.data = res.data.rows
this.gridOption.param.pageOption.total = res.data.count
},
async showAddWarp() {
this.$refs.editModal.addShow({
'status': 'joined',
'payment_status': 'pending',
'price': 0,
'skill_level': 0,
'join_message': '',
'contact_info': '',
'cancel_reason': '',
'joined_at': 'CURRENT_TIMESTAMP'
}, async (newRow) => {
let res = await gameParticipantsServer.add(newRow)
rootVue.$Message.success('新增成功!')
this.init()
})
},
async showViewWarp(row) {
this.$refs.editModal.viewShow(row)
},
async showEditWarp(row) {
this.$refs.editModal.editShow(row, async (newRow) => {
let valid = await this.$refs['editModal'].$refs['From'].validate()
if (valid) {
let res = await gameParticipantsServer.edit(newRow)
rootVue.$Message.success('修改成功!')
this.init()
}
})
},
async delConfirm(row) {
uiTool.delConfirm(async () => {
await gameParticipantsServer.del(row)
rootVue.$Message.success('删除成功!')
this.init()
})
},
resetQuery() {
this.gridOption.param.seachOption = {
key: 'game_id',
value: '',
status: null,
payment_status: null
};
this.query(1);
},
async exportCsv(row) {
await gameParticipantsServer.exportCsv(row)
}
}
}
</script>
<style lang="less">
</style>

View File

@@ -0,0 +1,553 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<Button type="primary" @click="showAddWarp">新增</Button>
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
<FormItem :label-width="20" class="flex">
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
:placeholder="seachTypePlaceholder">
<Option v-for="item in seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
placeholder="请输入关键字" @on-search="query(1)" />
</FormItem>
<FormItem label="球局类型">
<Select v-model="gridOption.param.seachOption.game_type" style="width: 120px" clearable @on-change="query(1)">
<Option value="个人球局">个人球局</Option>
<Option value="团队球局">团队球局</Option>
<Option value="比赛球局">比赛球局</Option>
</Select>
</FormItem>
<FormItem label="玩法类型">
<Select v-model="gridOption.param.seachOption.play_type" style="width: 120px" clearable @on-change="query(1)">
<Option value="单打">单打</Option>
<Option value="双打">双打</Option>
<Option value="混合双打">混合双打</Option>
</Select>
</FormItem>
<FormItem label="状态">
<Select v-model="gridOption.param.seachOption.match_status" style="width: 120px" clearable @on-change="query(1)">
<Option :value="0">未开始</Option>
<Option :value="1">进行中</Option>
<Option :value="2">已结束</Option>
<Option :value="3">已取消</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="query(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="default" @click="exportCsv" class="ml10">导出</Button>
</FormItem>
</Form>
</div>
<div class="table-body">
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
@changePage="query"></tables>
</div>
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules"> </editModal>
</div>
</template>
<script>
import funTool from '@/libs/funTool'
import uiTool from '@/libs/uiTool'
import menuServer from '@/api/system_high/menuServer.js'
import gamesServer from '@/api/ball/games_server.js'
export default {
data() {
let rules = {}
rules["id"] = [{ required: true, message: '请填写' }];
rules["title"] = [{ required: true, message: '请填写球局标题' }];
rules["description"] = [{ required: true, message: '请填写球局描述' }];
rules["game_type"] = [{ required: true, message: '请填写球局类型' }];
rules["publisher_id"] = [{ required: true, message: '请填写发布者ID' }];
rules["venue_id"] = [{ required: true, message: '请填写场地ID' }];
rules["max_players"] = [{ required: true, type: "number", message: '请填写最大参与人数', trigger: 'change' }];
rules["current_players"] = [{ required: false, type: "number", message: '请填写当前参与人数', trigger: 'change' }];
rules["start_time"] = [{ required: true, message: '请选择开始时间', trigger: 'change' }];
rules["end_time"] = [{ required: true, message: '请选择结束时间', trigger: 'change' }];
rules["price"] = [{ required: true, type: "number", message: '请填写价格', trigger: 'change' }];
rules["price_mode"] = [{ required: true, message: '请选择费用模式', trigger: 'change' }];
rules["court_type"] = [{ required: true, message: '请选择场地类型', trigger: 'change' }];
rules["court_surface"] = [{ required: true, message: '请选择场地材质', trigger: 'change' }];
rules["gender_limit"] = [{ required: true, message: '请选择性别限制', trigger: 'change' }];
rules["skill_level_min"] = [{ required: false, type: "number", message: '请填写最低水平(NTRP)', trigger: 'change' }];
rules["skill_level_max"] = [{ required: false, type: "number", message: '请填写最高水平(NTRP)', trigger: 'change' }];
rules["is_urgent"] = [{ required: true, message: '请选择是否急招', trigger: 'change' }];
rules["is_substitute_supported"] = [{ required: true, message: '请选择是否支持替补', trigger: 'change' }];
rules["privacy_level"] = [{ required: true, message: '请选择隐私级别', trigger: 'change' }];
rules["member_visibility"] = [{ required: true, message: '请选择成员可见性', trigger: 'change' }];
rules["match_status"] = [{ required: true, message: '请选择状态', trigger: 'change' }];
rules["location"] = [{ required: false, message: '请填写位置信息' }];
rules["latitude"] = [{ required: false, type: "number", message: '请填写纬度', trigger: 'change' }];
rules["longitude"] = [{ required: false, type: "number", message: '请填写经度', trigger: 'change' }];
rules["play_type"] = [{ required: true, message: '请选择玩法类型', trigger: 'change' }];
rules["is_wechat_contact"] = [{ required: false, type: "number", message: '请选择是否允许微信联系', trigger: 'change' }];
rules["wechat_contact"] = [{ required: false, message: '请填写微信号' }];
rules["venue_description"] = [{ required: false, message: '请填写场地描述' }];
rules["venue_image_list"] = [{ required: false, message: '请上传场地预定截图' }];
rules["location_name"] = [{ required: false, message: '请填写位置名称' }];
rules["remark"] = [{ required: false, message: '请填写备注' }];
return {
// 搜索类型:只包含适合文本搜索的字段
seachTypes: [
{ key: "title", value: "球局标题" },
{ key: "description", value: "球局描述" },
{ key: "publisher_id", value: "发布者ID" },
{ key: "venue_id", value: "场地ID" },
{ key: "location_name", value: "位置名称" }
],
gridOption: {
param: {
seachOption: {
key: "title",
value: "",
game_type: null, // 球局类型筛选
play_type: null, // 玩法类型筛选
match_status: null // 状态筛选
},
pageOption: {
total: 0,
page: 1,
pageSize: 20
}
},
rules,
columns: [
{ key: 'id', title: 'id', minWidth: 80, is_show_edit: 0 },
{ key: "id", title: "ID", disabled: true, is_show_edit: 1, is_show_list: 1, com: "Input" },
{
key: "title",
title: "标题",
disabled: false,
is_show_edit: 1,
is_show_list: 1,
com: "Input",
render: (h, params) => {
const title = params.row.title || '';
const displayText = title.length > 15 ? title.substring(0, 15) + '...' : title;
return h('span', { attrs: { title: title } }, displayText);
}
},
{
key: "game_type",
title: "类型",
disabled: false,
is_show_edit: 1,
is_show_list: 1,
com: "Select",
options: [
{ value: "个人球局", label: "个人球局" },
{ value: "团队球局", label: "团队球局" },
{ value: "比赛球局", label: "比赛球局" }
],
render: (h, params) => {
const typeMap = {
'个人球局': { text: '个人', color: 'blue' },
'团队球局': { text: '团队', color: 'green' },
'比赛球局': { text: '比赛', color: 'purple' }
};
const type = typeMap[params.row.game_type] || { text: params.row.game_type || '未知', color: 'default' };
return h('Tag', { props: { color: type.color } }, type.text);
}
},
{
key: "play_type",
title: "玩法",
disabled: false,
is_show_edit: 1,
is_show_list: 1,
com: "Select",
options: [
{ value: "单打", label: "单打" },
{ value: "双打", label: "双打" },
{ value: "混合双打", label: "混合双打" }
],
render: (h, params) => {
const playMap = {
'单打': { text: '单打', color: 'orange' },
'双打': { text: '双打', color: 'cyan' },
'混合双打': { text: '混双', color: 'pink' }
};
const play = playMap[params.row.play_type] || { text: params.row.play_type || '未知', color: 'default' };
return h('Tag', { props: { color: play.color } }, play.text);
}
},
{ key: "publisher_id", title: "发布者ID", disabled: true, is_show_edit: 1, is_show_list: 0, com: "Input" },
{ key: "venue_id", title: "场地ID", disabled: false, is_show_edit: 1, is_show_list: 0, com: "Input" },
{
key: "max_players",
title: "最大人数",
disabled: false,
is_show_edit: 1,
is_show_list: 1,
data_type: "number",
com: "InputNumber",
render: (h, params) => {
const max = params.row.max_players || 0;
return h('span', { style: { color: '#2d8cf0' } }, `${max}`);
}
},
{
key: "current_players",
title: "当前人数",
disabled: false,
is_show_edit: 1,
is_show_list: 1,
data_type: "number",
com: "InputNumber",
render: (h, params) => {
const current = params.row.current_players || 0;
const max = params.row.max_players || 0;
const color = current >= max ? '#f56c6c' : '#67c23a';
return h('span', { style: { color: color } }, `${current}`);
}
},
{
key: "start_time",
title: "开始时间",
disabled: false,
is_show_edit: 1,
is_show_list: 1,
com: "DatePicker",
render: (h, params) => {
const time = params.row.start_time;
if (!time) {
return h('span', { style: { color: '#c5c8ce' } }, '未设置');
}
// 只显示月-日 时:分
const date = new Date(time);
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hour = String(date.getHours()).padStart(2, '0');
const minute = String(date.getMinutes()).padStart(2, '0');
const displayTime = `${month}-${day} ${hour}:${minute}`;
return h('span', { attrs: { title: time } }, displayTime);
}
},
{ key: "end_time", title: "结束时间", disabled: false, is_show_edit: 1, is_show_list: 0, com: "DatePicker" },
{
key: "price",
title: "价格",
disabled: false,
is_show_edit: 1,
is_show_list: 1,
com: "InputNumber",
data_type: "number",
render: (h, params) => {
const price = params.row.price;
if (price === null || price === undefined || price === 0) {
return h('Tag', { props: { color: 'green' } }, '免费');
}
return h('span', { style: { color: '#f56c6c', fontWeight: 'bold' } }, `¥${price}`);
}
},
{
key: "price_mode", title: "费用模式", disabled: false, is_show_edit: 1, is_show_list: 0, com: "Select", options: [
{ value: "AA", label: "AA制" },
{ value: "免费", label: "免费" },
{ value: "付费", label: "付费" }
]
},
{
key: "court_type",
title: "场地类型",
disabled: false,
is_show_edit: 1,
is_show_list: 1,
com: "Select",
options: [
{ value: "室内", label: "室内" },
{ value: "室外", label: "室外" }
],
render: (h, params) => {
const typeMap = {
'室内': { text: '室内', color: 'blue' },
'室外': { text: '室外', color: 'green' }
};
const type = typeMap[params.row.court_type] || { text: params.row.court_type || '未知', color: 'default' };
return h('Tag', { props: { color: type.color } }, type.text);
}
},
{
key: "court_surface",
title: "场地材质",
disabled: false,
is_show_edit: 1,
is_show_list: 1,
com: "Select",
options: [
{ value: "硬地", label: "硬地" },
{ value: "红土", label: "红土" },
{ value: "草地", label: "草地" },
{ value: "地毯", label: "地毯" }
],
render: (h, params) => {
const surfaceMap = {
'硬地': { text: '硬地', color: 'cyan' },
'红土': { text: '红土', color: 'orange' },
'草地': { text: '草地', color: 'green' },
'地毯': { text: '地毯', color: 'purple' }
};
const surface = surfaceMap[params.row.court_surface] || { text: params.row.court_surface || '未知', color: 'default' };
return h('Tag', { props: { color: surface.color } }, surface.text);
}
},
{
key: "gender_limit",
title: "性别限制",
disabled: false,
is_show_edit: 1,
is_show_list: 1,
com: "Select",
options: [
{ value: "0", label: "不限" },
{ value: "1", label: "仅限男生" },
{ value: "2", label: "仅限女生" },
{ value: "3", label: "仅限同性" },
{ value: "4", label: "仅限异性" }
],
render: (h, params) => {
const genderMap = {
'0': { text: '不限', color: 'default' },
'1': { text: '仅限男生', color: 'blue' },
'2': { text: '仅限女生', color: 'pink' },
'3': { text: '仅限同性', color: 'purple' },
'4': { text: '仅限异性', color: 'orange' }
};
const gender = genderMap[params.row.gender_limit] || { text: '不限', color: 'default' };
return h('Tag', { props: { color: gender.color } }, gender.text);
}
},
{ key: "skill_level_min", title: "最低水平(NTRP)", disabled: false, is_show_edit: 1, is_show_list: 0, com: "InputNumber", data_type: "number" },
{ key: "skill_level_max", title: "最高水平(NTRP)", disabled: false, is_show_edit: 1, is_show_list: 0, com: "InputNumber", data_type: "number" },
{
key: "is_urgent", title: "是否急招", disabled: false, is_show_edit: 1, is_show_list: 0, com: "Radio", options: [
{ value: "0", label: "否" },
{ value: "1", label: "是" }
]
},
{
key: "is_substitute_supported", title: "支持替补", disabled: false, is_show_edit: 1, is_show_list: 0, com: "Radio", options: [
{ value: "0", label: "否" },
{ value: "1", label: "是" }
]
},
{
key: "is_wechat_contact", title: "微信联系", disabled: false, is_show_edit: 1, is_show_list: 0, com: "Radio", options: [
{ value: 0, label: "否" },
{ value: 1, label: "是" }
]
},
{ key: "wechat_contact", title: "微信号", disabled: false, is_show_edit: 1, is_show_list: 0, com: "Input" },
{ key: "substitute_limit", title: "替补人数", disabled: false, is_show_edit: 1, is_show_list: 0, data_type: "number", com: "InputNumber" },
{
key: "privacy_level", title: "隐私级别", disabled: false, is_show_edit: 1, is_show_list: 0, com: "Select", options: [
{ value: "public", label: "公开" },
{ value: "private", label: "私密" },
{ value: "friends", label: "仅好友" }
]
},
{
key: "member_visibility", title: "成员可见性", disabled: false, is_show_edit: 1, is_show_list: 0, com: "Select", options: [
{ value: "all", label: "所有人" },
{ value: "participants", label: "仅参与者" },
{ value: "friends", label: "仅好友" }
]
},
{
key: "match_status",
title: "状态",
disabled: false,
is_show_edit: 1,
is_show_list: 1,
com: "Select",
options: [
{ value: 0, label: "未开始" },
{ value: 1, label: "进行中" },
{ value: 2, label: "已结束" },
{ value: 3, label: "已取消" }
],
render: (h, params) => {
const statusMap = {
0: { text: '未开始', color: 'blue' },
1: { text: '进行中', color: 'green' },
2: { text: '已结束', color: 'gray' },
3: { text: '已取消', color: 'red' }
};
const status = statusMap[params.row.match_status] || { text: '未知', color: 'default' };
return h('Tag', { props: { color: status.color } }, status.text);
}
},
{ key: "venue_description", title: "场地描述", disabled: false, is_show_edit: 1, is_show_list: 0, com: "Input" },
{ key: "venue_image_list", title: "预定截图", disabled: false, is_show_edit: 1, is_show_list: 0, com: "Upload", action: "/api/upload" },
{ key: "location_name", title: "位置名称", disabled: false, is_show_edit: 1, is_show_list: 0, com: "Input" },
{ key: "location", title: "位置信息", disabled: false, is_show_edit: 1, is_show_list: 0, com: "Input" },
{ key: "latitude", title: "纬度", disabled: false, is_show_edit: 1, is_show_list: 0, com: "InputNumber", data_type: "number" },
{ key: "longitude", title: "经度", disabled: false, is_show_edit: 1, is_show_list: 0, com: "InputNumber", data_type: "number" },
{ key: "deadline_hours", title: "截止时间(小时)", disabled: false, is_show_edit: 1, is_show_list: 0, data_type: "number", com: "InputNumber" },
{ key: "remark", title: "备注", disabled: false, is_show_edit: 1, is_show_list: 0, com: "TextArea" },
{ key: "description", title: "球局描述", disabled: false, is_show_edit: 1, is_show_list: 0, com: "TextArea" },
{ key: "last_modify_time", title: "更新时间", disabled: true, is_show_edit: 1, is_show_list: 0, com: "DatePicker" },
{ key: 'create_time', title: '创建时间', minWidth: 100, is_show_edit: 0 },
{ key: 'last_modify_time', title: '更新时间', minWidth: 100, is_show_edit: 0 },
{
title: '操作',
key: 'action',
width: 200,
type: 'template',
is_show_list: 1,
render: (h, params) => {
let btns = [
{
title: '修改',
type: 'primary',
click: () => {
this.showEditWarp(params.row)
},
},
{
title: '删除',
type: 'primary',
click: () => {
this.delConfirm(params.row)
},
},
]
return uiTool.getBtn(h, btns)
},
},],
data: []
},
}
},
computed: {
seachTypePlaceholder() {
const selected = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key);
return selected ? selected.value : '请选择搜索类型';
},
editColumns() {
let editTempColumns = this.gridOption.columns.filter(p => p.is_show_edit === 1)
return editTempColumns
},
listColumns() {
let listTempColumns = this.gridOption.columns.filter(p => p.is_show_list === 1)
return listTempColumns
}
},
mounted() {
this.init()
this.initCol()
},
methods: {
init() {
this.query(1)
},
async initCol() {
let columnRows = []
let columnKeys = columnRows.map(p => p.key)
let newColumns = this.gridOption.columns.filter(p => columnKeys.indexOf(p.key) > -1)
for (let i = 0; i < newColumns.length; i++) {
let curColumn = newColumns[i]
let modelMap = columnRows[i].map_option
let res = await menuServer.modelInterface({ model_key: columnRows[i].modelKey, map_option: modelMap })
curColumn.source = res.data
}
},
async inquiry() {
let res = await gamesServer.all(this.gridOption.param)
this.gridOption.data = res.data
},
async query(page) {
if (page) {
this.gridOption.param.pageOption.page = page
}
let res = await gamesServer.page(this.gridOption.param)
this.gridOption.data = res.data.rows
this.gridOption.param.pageOption.total = res.data.count
},
async showAddWarp() {
this.$refs.editModal.addShow({
'title': '',
'description': '',
'game_type': '个人球局',
'play_type': '双打',
'publisher_id': '',
'venue_id': '',
'max_players': 4,
'current_players': 0,
'start_time': '',
'end_time': '',
'price': 0,
'price_mode': 'AA',
'court_type': '室外',
'court_surface': '硬地',
'gender_limit': 0,
'skill_level_min': 1.0,
'skill_level_max': 7.0,
'is_urgent': 0,
'is_substitute_supported': 0,
'is_wechat_contact': 0,
'wechat_contact': '',
'substitute_limit': 0,
'privacy_level': 'public',
'member_visibility': 'all',
'match_status': 0,
'location': '',
'location_name': '',
'latitude': 0,
'longitude': 0,
'deadline_hours': 1,
'venue_description': '',
'remark': ''
}, async (newRow) => {
let res = await gamesServer.add(newRow)
rootVue.$Message.success('新增成功!')
this.init()
})
},
async showEditWarp(row) {
this.$refs.editModal.editShow(row, async (newRow) => {
let valid = await this.$refs['editModal'].$refs['From'].validate()
if (valid) {
let res = await gamesServer.edit(newRow)
rootVue.$Message.success('修改成功!')
this.init()
}
})
},
async delConfirm(row) {
uiTool.delConfirm(async () => {
await gamesServer.del(row)
rootVue.$Message.success('删除成功!')
this.init()
})
},
resetQuery() {
this.gridOption.param.seachOption = {
key: 'title',
value: '',
game_type: null,
play_type: null,
match_status: null
};
this.query(1);
},
async exportCsv(row) {
await gamesServer.exportCsv(row)
}
}
}
</script>
<style lang="less"></style>

View File

@@ -0,0 +1,297 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<Button type="primary" @click="showAddWarp">新增</Button>
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
<FormItem :label-width="20" class="flex">
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
:placeholder="seachTypePlaceholder">
<Option v-for="item in seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
placeholder="请输入关键字" @on-search="query(1)" />
</FormItem>
<FormItem label="场地类型">
<Select v-model="gridOption.param.seachOption.venue_type" style="width: 120px" clearable @on-change="query(1)">
<Option value="indoor">室内</Option>
<Option value="outdoor">室外</Option>
<Option value="semi-outdoor">半室外</Option>
</Select>
</FormItem>
<FormItem label="地面类型">
<Select v-model="gridOption.param.seachOption.surface_type" style="width: 120px" clearable @on-change="query(1)">
<Option value="hard">硬地</Option>
<Option value="clay">红土</Option>
<Option value="grass">草地</Option>
<Option value="carpet">地毯</Option>
</Select>
</FormItem>
<FormItem label="状态">
<Select v-model="gridOption.param.seachOption.status" style="width: 120px" clearable @on-change="query(1)">
<Option value="active">营业中</Option>
<Option value="inactive">暂停营业</Option>
<Option value="closed">已关闭</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="query(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="default" @click="exportCsv" class="ml10">导出</Button>
</FormItem>
</Form>
</div>
<div class="table-body">
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
@changePage="query"></tables>
</div>
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules"> </editModal>
</div>
</template>
<script>
import funTool from '@/libs/funTool'
import uiTool from '@/libs/uiTool'
import menuServer from '@/api/system_high/menuServer.js'
import venuesServer from '@/api/ball/venues_server.js'
export default {
data() {
let rules = {}
rules["id"] = [{ required: true, message: '请填写' }];
rules["name"] = [{ required: true, message: '请填写场地名称' }];
rules["address"] = [{ required: true, message: '请填写详细地址' }];
rules["latitude"] = [{ required: true, message: '请填写纬度' }];
rules["longitude"] = [{ required: true, message: '请填写经度' }];
rules["venue_type"] = [{ required: true, message: '请填写场地类型' }];
rules["court_count"] = [{ required: true, type: "number", message: '请选择场地数量', trigger: 'change' }];
rules["price_per_hour"] = [{ required: true, message: '请填写每小时价格' }];
rules["facilities"] = [{ required: true, message: '请填写设施描述' }];
rules["surface_type"] = [{ required: true, message: '请选择地面类型' }];
rules["timeSlot"] = [{ required: false, message: '请填写营业时间' }];
return {
// 搜索类型:只包含适合文本搜索的字段
seachTypes: [
{ key: "name", value: "场地名称" },
{ key: "address", value: "详细地址" },
{ key: "facilities", value: "设施描述" }
],
gridOption: {
param: {
seachOption: {
key: "name",
value: "",
venue_type: null, // 场地类型筛选
surface_type: null, // 地面类型筛选
status: null // 状态筛选
},
pageOption: {
total: 0,
page: 1,
pageSize: 20
}
},
rules,
columns: [
{ key: 'id', title: 'id', is_show_edit: 0 },
{ key: "name", title: "场地名称", disabled: false, is_show_edit: 1, is_show_list: 1, com: "Input" },
{ key: "address", title: "详细地址", disabled: false, is_show_edit: 1, is_show_list: 1, com: "Input" },
{ key: "latitude", title: "纬度", disabled: false, is_show_edit: 1, is_show_list: 1, com: "InputNumber", data_type: "number" },
{ key: "longitude", title: "经度", disabled: false, is_show_edit: 1, is_show_list: 1, com: "InputNumber", data_type: "number" },
{
key: "venue_type",
title: "场地类型",
disabled: false,
is_show_edit: 1,
is_show_list: 1,
com: "Select",
options: [
{ value: "indoor", label: "室内" },
{ value: "outdoor", label: "室外" },
{ value: "semi-outdoor", label: "半室外" }
],
render: (h, params) => {
const typeMap = {
'indoor': { text: '室内', color: 'blue' },
'outdoor': { text: '室外', color: 'green' },
'semi-outdoor': { text: '半室外', color: 'cyan' }
};
const type = typeMap[params.row.venue_type] || { text: params.row.venue_type || '未知', color: 'default' };
return h('Tag', { props: { color: type.color } }, type.text);
}
},
{
key: "surface_type",
title: "地面类型",
disabled: false,
is_show_edit: 1,
is_show_list: 1,
com: "Select",
options: [
{ value: "hard", label: "硬地" },
{ value: "clay", label: "红土" },
{ value: "grass", label: "草地" },
{ value: "carpet", label: "地毯" },
{ value: "indoor", label: "室内地面" },
{ value: "other", label: "其他" }
],
render: (h, params) => {
const surfaceMap = {
'hard': { text: '硬地', color: 'cyan' },
'clay': { text: '红土', color: 'orange' },
'grass': { text: '草地', color: 'green' },
'carpet': { text: '地毯', color: 'purple' },
'indoor': { text: '室内地面', color: 'blue' },
'other': { text: '其他', color: 'default' }
};
const surface = surfaceMap[params.row.surface_type] || { text: params.row.surface_type || '未知', color: 'default' };
return h('Tag', { props: { color: surface.color } }, surface.text);
}
},
{ key: "court_count", title: "场地数量", disabled: false, is_show_edit: 1, is_show_list: 1, data_type: "number", com: "InputNumber" },
{ key: "price_per_hour", title: "每小时价格", disabled: false, is_show_edit: 1, is_show_list: 1, com: "InputNumber", data_type: "number" },
{ key: "facilities", title: "设施描述", disabled: false, is_show_edit: 1, is_show_list: 0, com: "TextArea" },
{ key: "timeSlot", title: "营业时间", disabled: false, is_show_edit: 1, is_show_list: 1, com: "Input" },
{
key: "status",
title: "状态",
disabled: false,
is_show_edit: 1,
is_show_list: 1,
com: "Select",
options: [
{ value: "active", label: "营业中" },
{ value: "inactive", label: "暂停营业" },
{ value: "closed", label: "已关闭" }
],
render: (h, params) => {
const statusMap = {
'active': { text: '营业中', color: 'green' },
'inactive': { text: '暂停营业', color: 'orange' },
'closed': { text: '已关闭', color: 'red' }
};
const status = statusMap[params.row.status] || { text: params.row.status || '未知', color: 'default' };
return h('Tag', { props: { color: status.color } }, status.text);
}
},
{ key: 'create_time', title: '创建时间', is_show_edit: 0 },
{ key: 'last_modify_time', title: '更新时间', is_show_edit: 0 },
{
title: '操作',
key: 'action',
width: 200,
type: 'template',
render: (h, params) => {
let btns = [
{
title: '修改',
type: 'primary',
click: () => {
this.showEditWarp(params.row)
},
},
{
title: '删除',
type: 'primary',
click: () => {
this.delConfirm(params.row)
},
},
]
return uiTool.getBtn(h, btns)
},
}
],
data: []
},
}
},
computed: {
seachTypePlaceholder() {
const selected = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key);
return selected ? selected.value : '请选择搜索类型';
},
editColumns() {
let editTempColumns = this.gridOption.columns.filter(p => p.is_show_edit === 1)
return editTempColumns
},
listColumns() {
let listTempColumns = this.gridOption.columns.filter(p => p.is_show_list !== 0)
return listTempColumns
}
},
mounted() {
this.init()
this.initCol()
},
methods: {
init() {
this.query(1)
},
async initCol() {
let columnRows = []
let columnKeys = columnRows.map(p => p.key)
let newColumns = this.gridOption.columns.filter(p => columnKeys.indexOf(p.key) > -1)
for (let i = 0; i < newColumns.length; i++) {
let curColumn = newColumns[i]
let modelMap = columnRows[i].map_option
let res = await menuServer.modelInterface({ model_key: columnRows[i].modelKey, map_option: modelMap })
curColumn.source = res.data
}
},
async inquiry() {
let res = await venuesServer.all(this.gridOption.param)
this.gridOption.data = res.data
},
async query(page) {
if (page) {
this.gridOption.param.pageOption.page = page
}
let res = await venuesServer.page(this.gridOption.param)
this.gridOption.data = res.data.rows
this.gridOption.param.pageOption.total = res.data.count
},
async showAddWarp() {
this.$refs.editModal.addShow({ 'venue_type': 'indoor', 'surface_type': 'hard', 'court_count': '1', 'status': 'active', 'create_time': 'CURRENT_TIMESTAMP', 'updated_at': 'CURRENT_TIMESTAMP', }, async (newRow) => {
let res = await venuesServer.add(newRow)
rootVue.$Message.success('新增成功!')
this.init()
})
},
async showEditWarp(row) {
this.$refs.editModal.editShow(row, async (newRow) => {
let valid = await this.$refs['editModal'].$refs['From'].validate()
if (valid) {
let res = await venuesServer.edit(newRow)
rootVue.$Message.success('修改成功!')
this.init()
}
})
},
async delConfirm(row) {
uiTool.delConfirm(async () => {
await venuesServer.del(row)
rootVue.$Message.success('删除成功!')
this.init()
})
},
resetQuery() {
this.gridOption.param.seachOption = {
key: 'name',
value: '',
venue_type: null,
surface_type: null,
status: null
};
this.query(1);
},
async exportCsv(row) {
await venuesServer.exportCsv(row)
}
}
}
</script>
<style lang="less"></style>

View File

@@ -0,0 +1,357 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<Button type="primary" @click="showAddWarp">新增</Button>
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
<FormItem :label-width="20" class="flex">
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
:placeholder="seachTypePlaceholder">
<Option v-for="item in seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
placeholder="请输入关键字" @on-search="query(1)" />
</FormItem>
<FormItem label="性别">
<Select v-model="gridOption.param.seachOption.gender" style="width: 120px" clearable @on-change="query(1)">
<Option :value="0">未知</Option>
<Option :value="1"></Option>
<Option :value="2"></Option>
</Select>
</FormItem>
<FormItem label="NTRP等级">
<Select v-model="gridOption.param.seachOption.ntrp_level" style="width: 120px" clearable @on-change="query(1)">
<Option :value="1">1.0</Option>
<Option :value="2">2.0</Option>
<Option :value="3">3.0</Option>
<Option :value="4">4.0</Option>
<Option :value="5">5.0</Option>
<Option :value="6">6.0</Option>
<Option :value="7">7.0</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="query(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="default" @click="exportCsv" class="ml10">导出</Button>
</FormItem>
</Form>
</div>
<div class="table-body">
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
@changePage="query"></tables>
</div>
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules"> </editModal>
</div>
</template>
<script>
import funTool from '@/libs/funTool'
import uiTool from '@/libs/uiTool'
import menuServer from '@/api/system_high/menuServer.js'
import wch_usersServer from '@/api/ball/wch_users_server.js'
export default {
data() {
let rules = {}
rules["nickname"] = [{ required: true, message: '请填写微信昵称' }];
rules["phone"] = [{ required: false, message: '请填写手机号' }];
rules["ntrp_level"] = [{ required: false, type: "number", message: '请填写网球等级', trigger: 'change' }];
rules["occupation"] = [{ required: false, message: '请填写职业' }];
rules["openid"] = [{ required: false, message: '请填写微信openid' }];
rules["unionid"] = [{ required: false, message: '请填写微信unionid' }];
rules["session_key"] = [{ required: false, message: '请填写会话密钥' }];
rules["avatar_url"] = [{ required: false, message: '请填写微信头像URL' }];
rules["gender"] = [{ required: false, type: "number", message: '请选择性别', trigger: 'change' }];
rules["country"] = [{ required: false, message: '请填写国家' }];
rules["province"] = [{ required: false, message: '请填写省份' }];
rules["city"] = [{ required: false, message: '请填写城市' }];
rules["language"] = [{ required: false, message: '请填写语言' }];
rules["is_subscribed"] = [{ required: false, type: "number", message: '请选择是否关注公众号', trigger: 'change' }];
rules["subscribe_time"] = [{ required: false, message: '请填写关注时间', trigger: 'change' }];
rules["last_login_time"] = [{ required: false, message: '请填写最后登录时间', trigger: 'change' }];
return {
// 搜索类型:只包含适合文本搜索的字段
seachTypes: [
{ key: "id", value: "用户ID" },
{ key: "nickname", value: "昵称" },
{ key: "phone", value: "手机号" }
],
gridOption: {
param: {
seachOption: {
key: "nickname",
value: "",
gender: null, // 性别筛选
ntrp_level: null // NTRP等级筛选
},
pageOption: {
total: 0,
page: 1,
pageSize: 20
}
},
rules,
columns: [
{ key: "id", title: "ID", disabled: true, is_show_edit: 1, is_show_list: 1, com: "Input" },
{ key: "openid", title: "微信openid", disabled: true, is_show_edit: 1, is_show_list: 0, com: "Input" },
{ key: "unionid", title: "微信unionid", disabled: true, is_show_edit: 1, is_show_list: 0, com: "Input" },
{ key: "session_key", title: "会话密钥", disabled: true, is_show_edit: 1, is_show_list: 0, com: "Input" },
{
key: "nickname",
title: "昵称",
disabled: false,
is_show_edit: 1,
is_show_list: 1,
com: "Input",
render: (h, params) => {
const nickname = params.row.nickname || '';
const displayText = nickname.length > 10 ? nickname.substring(0, 10) + '...' : nickname;
return h('span', { attrs: { title: nickname } }, displayText);
}
},
{
key: "avatar_url",
title: "头像",
disabled: false,
is_show_edit: 1,
is_show_list: 1,
com: "UploadSingle",
render: (h, params) => {
if (!params.row.avatar_url) {
return h('span', { style: { color: '#c5c8ce' } }, '无头像');
}
return h('div', {
style: {
'display': 'flex',
'justify-content': 'center',
'align-items': 'center',
'padding': '4px 0'
}
}, [
h('img', {
style: {
width: '40px',
height: '40px',
borderRadius: '50%',
objectFit: 'cover',
cursor: 'pointer'
},
attrs: {
src: params.row.avatar_url,
alt: params.row.nickname || '头像',
title: '点击查看大图'
},
on: {
click: () => {
// 点击查看大图
this.$Modal.info({
title: params.row.nickname + ' 的头像',
render: (h) => {
return h('div', { style: { textAlign: 'center' } }, [
h('img', {
style: {
maxWidth: '100%',
maxHeight: '400px',
borderRadius: '8px'
},
attrs: { src: params.row.avatar_url }
})
]);
}
});
}
}
})
]);
}
},
{
key: "gender",
title: "性别",
disabled: false,
is_show_edit: 1,
is_show_list: 1,
com: "Radio",
options: [
{ value: 0, label: "未知" },
{ value: 1, label: "男" },
{ value: 2, label: "女" }
],
render: (h, params) => {
const genderMap = {
0: { text: '未知', color: 'default' },
1: { text: '男', color: 'blue' },
2: { text: '女', color: 'pink' }
};
const gender = genderMap[params.row.gender] || { text: '未知', color: 'default' };
return h('Tag', { props: { color: gender.color } }, gender.text);
}
},
{ key: "country", title: "国家", disabled: false, is_show_edit: 1, is_show_list: 0, com: "Input" },
{ key: "province", title: "省份", disabled: false, is_show_edit: 1, is_show_list: 0, com: "Input" },
{ key: "city", title: "城市", disabled: false, is_show_edit: 1, is_show_list: 1, com: "Input" },
{ key: "language", title: "语言", disabled: false, is_show_edit: 1, is_show_list: 0, com: "Input" },
{
key: "phone",
title: "手机号",
disabled: false,
is_show_edit: 1,
is_show_list: 1,
com: "Input",
render: (h, params) => {
const phone = params.row.phone || '';
if (!phone) {
return h('span', { style: { color: '#c5c8ce' } }, '未填写');
}
// 手机号脱敏显示
const maskedPhone = phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
return h('span', { attrs: { title: phone } }, maskedPhone);
}
},
{
key: "ntrp_level",
title: "等级",
disabled: false,
is_show_edit: 1,
is_show_list: 1,
com: "InputNumber",
render: (h, params) => {
const level = params.row.ntrp_level;
if (level === null || level === undefined || level === 0) {
return h('span', { style: { color: '#c5c8ce' } }, '未设置');
}
return h('Tag', { props: { color: 'blue' } }, `${level}`);
}
},
{
key: "is_subscribed", title: "是否关注公众号", disabled: false, is_show_edit: 1, is_show_list: 0, com: "Radio", options: [
{ value: 0, label: "否" },
{ value: 1, label: "是" }
]
},
{ key: "subscribe_time", title: "关注时间", disabled: true, is_show_edit: 1, is_show_list: 0, com: "DatePicker" },
{ key: "last_login_time", title: "最后登录时间", disabled: true, is_show_edit: 1, is_show_list: 0, com: "DatePicker" },
{ key: 'create_time', title: '创建时间', minWidth: 100, is_show_edit: 0, com: "DatePicker" },
{ key: 'last_modify_time', title: '更新时间', minWidth: 100, is_show_edit: 0, com: "DatePicker" },
{
title: '操作',
key: 'action',
width: 200,
type: 'template',
is_show_list: 1,
render: (h, params) => {
let btns = [
{
title: '修改',
type: 'primary',
click: () => {
this.showEditWarp(params.row)
},
},
{
title: '删除',
type: 'primary',
click: () => {
this.delConfirm(params.row)
},
},
]
return uiTool.getBtn(h, btns)
},
},],
data: []
},
}
},
computed: {
seachTypePlaceholder() {
const selected = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key);
return selected ? selected.value : '请选择搜索类型';
},
editColumns() {
let editTempColumns = this.gridOption.columns.filter(p => p.is_show_edit === 1)
return editTempColumns
},
listColumns() {
let listTempColumns = this.gridOption.columns.filter(p => p.is_show_list === 1)
return listTempColumns
}
},
mounted() {
this.init()
this.initCol()
},
methods: {
init() {
this.query(1)
},
async initCol() {
let columnRows = []
let columnKeys = columnRows.map(p => p.key)
let newColumns = this.gridOption.columns.filter(p => columnKeys.indexOf(p.key) > -1)
for (let i = 0; i < newColumns.length; i++) {
let curColumn = newColumns[i]
let modelMap = columnRows[i].map_option
let res = await menuServer.modelInterface({ model_key: columnRows[i].modelKey, map_option: modelMap })
curColumn.source = res.data
}
},
async inquiry() {
let res = await wch_usersServer.all(this.gridOption.param)
this.gridOption.data = res.data
},
async query(page) {
if (page) {
this.gridOption.param.pageOption.page = page
}
let res = await wch_usersServer.page(this.gridOption.param)
this.gridOption.data = res.data.rows
this.gridOption.param.pageOption.total = res.data.count
},
async showAddWarp() {
this.$refs.editModal.addShow({ 'is_subscribed': '0', 'last_login_time': 'CURRENT_TIMESTAMP', 'create_time': 'CURRENT_TIMESTAMP', 'updated_at': 'CURRENT_TIMESTAMP', }, async (newRow) => {
let res = await wch_usersServer.add(newRow)
rootVue.$Message.success('新增成功!')
this.init()
})
},
async showEditWarp(row) {
this.$refs.editModal.editShow(row, async (newRow) => {
let valid = await this.$refs['editModal'].$refs['From'].validate()
if (valid) {
let res = await wch_usersServer.edit(newRow)
rootVue.$Message.success('修改成功!')
this.init()
}
})
},
async delConfirm(row) {
uiTool.delConfirm(async () => {
await wch_usersServer.del(row)
rootVue.$Message.success('删除成功!')
this.init()
})
},
resetQuery() {
this.gridOption.param.seachOption = {
key: 'nickname',
value: '',
gender: null,
ntrp_level: null
};
this.query(1);
},
async exportCsv(row) {
await wch_usersServer.exportCsv(row)
}
}
}
</script>
<style lang="less"></style>

View File

@@ -0,0 +1,394 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<Button type="primary" @click="showAddWarp">新增城市二维码</Button>
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
<FormItem label="城市名称" :label-width="80">
<Input v-model="gridOption.param.seachOption.city_name" style="width: 200px" placeholder="请输入城市名称"
@on-enter="query(1)" clearable />
</FormItem>
<FormItem label="状态" :label-width="60">
<Select v-model="gridOption.param.seachOption.is_active" style="width: 120px" clearable>
<Option :value="1">启用</Option>
<Option :value="0">禁用</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="query(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="warning" @click="batchDelete" class="ml10" :disabled="selectedIds.length === 0">
批量删除 ({{ selectedIds.length }})
</Button>
</FormItem>
</Form>
</div>
<div class="table-body">
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
@changePage="query" @on-selection-change="handleSelectionChange"></tables>
</div>
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules"> </editModal>
</div>
</template>
<script>
import funTool from '@/libs/funTool'
import uiTool from '@/libs/uiTool'
import hotCityQrServer from '@/api/business/hot_city_qr_server.js'
export default {
data() {
let rules = {}
rules["city_name"] = [{ required: true, message: '请填写城市名称', trigger: 'blur' }];
rules["qr_code_url"] = [{ required: true, message: '请上传二维码图片', trigger: 'change' }];
rules["sort_order"] = [{ required: false, type: "number", message: '请填写排序顺序', trigger: 'change' }];
return {
selectedIds: [],
gridOption: {
param: {
seachOption: {
city_name: '',
is_active: ''
},
pageOption: {
page: 1,
pageSize: 20
}
},
data: [],
rules: rules
},
listColumns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: 'ID',
key: 'id',
width: 80,
sortable: true
},
{
title: '城市名称',
key: 'city_name',
minWidth: 120,
sortable: true
},
{
title: '二维码图片',
key: 'qr_code_url',
minWidth: 150,
type: 'template',
render: (h, params) => {
if (params.row.qr_code_url) {
return h('div', {
style: {
display: 'flex',
alignItems: 'center',
gap: '10px'
}
}, [
h('img', {
attrs: {
src: params.row.qr_code_url,
alt: '二维码'
},
style: {
width: '60px',
height: '60px',
objectFit: 'contain',
cursor: 'pointer',
border: '1px solid #ddd',
borderRadius: '4px'
},
on: {
click: () => {
this.previewImage(params.row.qr_code_url)
}
}
}),
h('a', {
attrs: {
href: params.row.qr_code_url,
target: '_blank'
},
style: {
fontSize: '12px',
color: '#2d8cf0'
}
}, '查看原图')
])
}
return h('span', '-')
}
},
{
title: '描述说明',
key: 'description',
minWidth: 200,
tooltip: true
},
{
title: '排序',
key: 'sort_order',
width: 100,
sortable: true,
type: 'template',
render: (h, params) => {
return h('Tag', {
props: {
color: 'blue'
}
}, params.row.sort_order || 0)
}
},
{
title: '状态',
key: 'is_active',
width: 100,
type: 'template',
render: (h, params) => {
return h('Tag', {
props: {
color: params.row.is_active === 1 ? 'success' : 'default'
}
}, params.row.is_active === 1 ? '启用' : '禁用')
}
},
{
title: '创建时间',
key: 'create_time',
width: 160,
sortable: true
},
{
title: '更新时间',
key: 'update_time',
width: 160,
sortable: true
},
{
title: '操作',
key: 'action',
width: 200,
fixed: 'right',
type: 'template',
render: (h, params) => {
let btns = [
{
title: '编辑',
type: 'primary',
size: 'small',
click: () => {
this.showEditWarp(params.row)
},
},
{
title: '删除',
type: 'error',
size: 'small',
click: () => {
this.delConfirm(params.row)
},
},
]
return uiTool.getBtn(h, btns)
},
}
],
editColumns: [
{
title: '城市名称',
key: 'city_name',
type: 'text',
required: true,
placeholder: '请输入城市名称,如:北京、上海'
},
{
title: '二维码图片',
key: 'qr_code_url',
com: 'UploadSingle',
required: true,
uploadType: 'image',
tip: '建议上传正方形图片,支持 JPG、PNG 格式'
},
{
title: '描述说明',
key: 'description',
com: 'TextArea',
placeholder: '请输入描述说明(选填)',
rows: 3
},
{
title: '排序顺序',
key: 'sort_order',
data_type: 'number',
placeholder: '数字越小越靠前,默认为 0',
min: 0,
max: 9999
},
{
title: '是否启用',
key: 'is_active',
com: 'i-switch',
data_type: 'boolean',
activeValue: 1,
inactiveValue: 0,
activeText: '启用',
inactiveText: '禁用'
}
]
}
},
mounted() {
this.query(1)
},
methods: {
// 查询列表
query(page) {
if (page) {
this.gridOption.param.pageOption.page = page
}
const params = {
page: this.gridOption.param.pageOption.page,
pageSize: this.gridOption.param.pageOption.pageSize,
...this.gridOption.param.seachOption
}
hotCityQrServer.page(params).then(res => {
if (res.code === 0) {
this.gridOption.data = res.data.list || []
this.gridOption.param.pageOption.total = res.data.total || 0
}
})
},
// 重置查询
resetQuery() {
this.gridOption.param.seachOption = {
city_name: '',
is_active: ''
}
this.query(1)
},
// 显示新增弹窗
showAddWarp() {
this.$refs.editModal.addShow({
city_name: '',
qr_code_url: '',
description: '',
sort_order: 0,
is_active: 1
}, (data) => {
hotCityQrServer.add(data).then(res => {
if (res.code === 0) {
this.$Message.success('添加成功')
this.query(1)
} else {
this.$Message.error(res.message || '添加失败')
}
})
})
},
// 显示编辑弹窗
showEditWarp(row) {
this.$refs.editModal.editShow(row, (data) => {
hotCityQrServer.edit(row.id, data).then(res => {
if (res.code === 0) {
this.$Message.success('编辑成功')
this.query()
} else {
this.$Message.error(res.message || '编辑失败')
}
})
})
},
// 删除确认
delConfirm(row) {
this.$Modal.confirm({
title: '确认删除',
content: `确定要删除城市"${row.city_name}"的二维码配置吗?`,
onOk: () => {
hotCityQrServer.del(row.id).then(res => {
if (res.code === 0) {
this.$Message.success('删除成功')
this.query()
} else {
this.$Message.error(res.message || '删除失败')
}
})
}
})
},
// 选择变化
handleSelectionChange(selection) {
this.selectedIds = selection.map(item => item.id)
},
// 批量删除
batchDelete() {
if (this.selectedIds.length === 0) {
this.$Message.warning('请先选择要删除的记录')
return
}
this.$Modal.confirm({
title: '确认批量删除',
content: `确定要删除选中的 ${this.selectedIds.length} 条记录吗?`,
onOk: () => {
hotCityQrServer.batchDel(this.selectedIds).then(res => {
if (res.code === 0) {
this.$Message.success('批量删除成功')
this.selectedIds = []
this.query()
} else {
this.$Message.error(res.message || '批量删除失败')
}
})
}
})
},
// 预览图片
previewImage(url) {
this.$Modal.info({
title: '二维码预览',
render: (h) => {
return h('div', {
style: {
textAlign: 'center',
padding: '20px'
}
}, [
h('img', {
attrs: {
src: url,
alt: '二维码'
},
style: {
maxWidth: '100%',
maxHeight: '500px'
}
})
])
},
width: 600
})
}
}
}
</script>
<style scoped>
.ml10 {
margin-left: 10px;
}
</style>

View File

@@ -0,0 +1,187 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<Button type="primary" @click="showAddWarp">新增</Button>
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
<FormItem :label-width="20" class="flex">
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
:placeholder="seachTypePlaceholder">
<Option v-for="item in seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
placeholder="请输入关键字" @on-search="query(1)" />
</FormItem>
<FormItem label="消息类型">
<Select v-model="gridOption.param.seachOption.type" style="width: 120px" clearable @on-change="query(1)">
<Option value="system">系统消息</Option>
<Option value="game">球局消息</Option>
<Option value="payment">支付消息</Option>
<Option value="activity">活动消息</Option>
</Select>
</FormItem>
<FormItem label="推送状态">
<Select v-model="gridOption.param.seachOption.status" style="width: 120px" clearable @on-change="query(1)">
<Option value="pending">待发送</Option>
<Option value="sent">已发送</Option>
<Option value="failed">发送失败</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="query(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="default" @click="exportCsv">导出</Button>
</FormItem>
</Form>
</div>
<div class="table-body">
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
@changePage="query"></tables>
</div>
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules"> </editModal>
</div>
</template>
<script>
import funTool from '@/libs/funTool'
import uiTool from '@/libs/uiTool'
import msg_notificationsServer from '@/api/message/msg_notifications_server.js'
export default {
data() {
let rules = {}
rules["user_id"] = [{ required: true, type: "number", message: '请填写用户ID', trigger: 'change' }];
rules["title"] = [{ required: true, message: '请填写消息标题' }];
rules["content"] = [{ required: true, message: '请填写消息内容' }];
rules["type"] = [{ required: true, message: '请填写消息类型' }];
return {
seachTypes: [
{ key: 'user_id', value: '用户ID' },
{ key: 'title', value: '消息标题' }
],
gridOption: {
param: {
seachOption: {
key: 'title',
value: '',
type: null,
status: null
},
pageOption: {
page: 1,
pageSize: 20
}
},
data: [],
rules: rules
},
listColumns: [
{ title: 'ID', key: 'id', minWidth: 80 },
{ title: '用户ID', key: 'user_id', minWidth: 120 },
{ title: '用户昵称', key: 'nickname', minWidth: 120 },
{ title: '消息标题', key: 'title', minWidth: 200 },
{ title: '消息内容', key: 'content', minWidth: 300 },
{ title: '消息类型', key: 'type', minWidth: 120 },
{ title: '推送状态', key: 'status', minWidth: 120 },
{ title: '创建时间', key: 'create_time', minWidth: 150 },
{
title: '操作',
key: 'action',
width: 200,
type: 'template',
render: (h, params) => {
let btns = [
{
title: '编辑',
type: 'primary',
click: () => {
this.showEditWarp(params.row)
},
},
{
title: '发送',
type: 'success',
click: () => {
this.sendMessage(params.row)
},
},
{
title: '删除',
type: 'error',
click: () => {
this.delConfirm(params.row)
},
},
]
return uiTool.getBtn(h, btns)
},
}
],
editColumns: [
{ title: '用户ID', key: 'user_id', type: 'number', required: true },
{ title: '消息标题', key: 'title', type: 'text', required: true },
{ title: '消息内容', key: 'content', type: 'textarea', required: true },
{ title: '消息类型', key: 'type', type: 'select', required: true, options: [
{ value: 'game_reminder', label: '球局提醒' },
{ value: 'payment_notification', label: '支付通知' },
{ value: 'system_announcement', label: '系统公告' },
{ value: 'activity_notification', label: '活动通知' }
]},
{ title: '推送时间', key: 'send_time', type: 'datetime' },
{ title: '推送状态', key: 'status', type: 'select', options: [
{ value: 'pending', label: '待发送' },
{ value: 'sent', label: '已发送' },
{ value: 'failed', label: '发送失败' }
]}
]
}
},
mounted() {
this.query(1);
},
methods: {
query(page) {
this.gridOption.param.pageOption.page = page;
msg_notificationsServer.page(this.gridOption.param).then(res => {
this.gridOption.data = res.data.rows;
this.gridOption.param.pageOption.total = res.data.count;
});
},
showAddWarp() {
this.$refs.editModal.showModal();
},
showEditWarp(row) {
this.$refs.editModal.showModal(row);
},
sendMessage(row) {
this.$Message.info('发送功能暂未实现');
},
delConfirm(row) {
uiTool.delConfirm(async () => {
await msg_notificationsServer.del(row)
this.$Message.success('删除成功!')
this.query(1)
})
},
exportCsv() {
msg_notificationsServer.exportCsv(this.gridOption.param).then(res => {
funTool.downloadFile(res, '消息通知.csv');
});
},
resetQuery() {
this.gridOption.param.seachOption = {
key: 'title',
value: '',
type: null,
status: null
};
this.query(1);
}
},
computed: {
seachTypePlaceholder() {
const selected = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key);
return selected ? selected.value : '请选择搜索类型';
}
}
}
</script>

View File

@@ -0,0 +1,243 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<Button type="primary" @click="showAddWarp">新增</Button>
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
<FormItem :label-width="20" class="flex">
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
:placeholder="seachTypePlaceholder">
<Option v-for="item in seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
placeholder="请输入关键字" @on-search="query(1)" />
</FormItem>
<FormItem label="启用状态">
<Select v-model="gridOption.param.seachOption.is_active" style="width: 120px" clearable @on-change="query(1)">
<Option :value="1">启用</Option>
<Option :value="0">禁用</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="query(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="default" @click="exportCsv" class="ml10">导出</Button>
</FormItem>
</Form>
</div>
<div class="table-body">
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
@changePage="query"></tables>
</div>
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules" @on-save="handleSave"></editModal>
</div>
</template>
<script>
import funTool from '@/libs/funTool'
import uiTool from '@/libs/uiTool'
import ntr_questionsServer from '@/api/ntrp/ntr_questions_server.js'
export default {
data() {
let rules = {}
rules["question_title"] = [{ required: true, message: '请填写题目标题' }];
rules["question_content"] = [{ required: true, message: '请填写题目内容' }];
rules["question_data"] = [{ required: true, message: '请填写题目数据' }];
rules["sort_order"] = [{ required: true, type: "number", message: '请填写排序', trigger: 'change' }];
return {
// 搜索类型:只包含适合文本搜索的字段
seachTypes: [
{ key: 'question_title', value: '题目标题' },
{ key: 'question_content', value: '题目内容' }
],
seachTypePlaceholder: '请选择搜索类型',
gridOption: {
param: {
seachOption: {
key: 'question_title',
value: '',
is_active: null // 启用状态筛选
},
pageOption: {
page: 1,
pageSize: 20
}
},
data: [],
rules: rules
},
// 列表列配置:按照规范排序(系统→核心→详细→配置→时间→操作)
listColumns: [
// 系统字段
{ title: 'ID', key: 'id', minWidth: 80 },
// 核心业务字段
{ title: '题目标题', key: 'question_title', minWidth: 200 },
{ title: '题目内容', key: 'question_content', minWidth: 300 },
// 配置字段
{ title: '排序', key: 'sort_order', minWidth: 80, align: 'center' },
{
title: '启用状态',
key: 'is_active',
minWidth: 100,
align: 'center',
render: (h, params) => {
const isActive = params.row.is_active;
return h('Tag', {
props: {
color: isActive ? 'success' : 'default'
}
}, isActive ? '启用' : '禁用');
}
},
// 时间字段
{ title: '创建时间', key: 'create_time', minWidth: 150 },
// 操作列
{
title: '操作',
key: 'action',
width: 200,
fixed: 'right',
type: 'template',
render: (h, params) => {
let btns = [
{
title: '编辑',
type: 'primary',
click: () => {
this.showEditWarp(params.row)
},
},
{
title: '删除',
type: 'error',
click: () => {
this.delConfirm(params.row)
},
},
]
return uiTool.getBtn(h, btns)
},
}
],
// 编辑字段配置:按照规范排序和配置
editColumns: [
// 核心业务字段
{
title: '题目标题',
key: 'question_title',
com: 'Input',
disabled: false,
required: true,
maxlength: 200,
placeholder: '请输入题目标题'
},
{
title: '题目内容',
key: 'question_content',
com: 'TextArea',
rows: 4,
disabled: false,
required: true,
maxlength: 1000,
placeholder: '请输入题目内容'
},
{
title: '题目数据(JSON)',
key: 'question_data',
com: 'TextArea',
rows: 6,
disabled: false,
required: true,
placeholder: '请输入题目选项数据JSON格式'
},
// 配置字段
{
title: '排序',
key: 'sort_order',
com: 'InputNumber',
data_type: 'number',
min: 0,
max: 9999,
disabled: false,
required: true,
placeholder: '数字越小越靠前'
},
{
title: '是否启用',
key: 'is_active',
com: 'Radio',
disabled: false,
required: true,
options: [
{ value: 1, label: '启用' },
{ value: 0, label: '禁用' }
]
}
]
}
},
mounted() {
this.query(1);
},
methods: {
query(page) {
this.gridOption.param.pageOption.page = page;
ntr_questionsServer.page(this.gridOption.param).then(res => {
this.gridOption.data = res.data.rows;
this.gridOption.param.pageOption.total = res.data.count;
});
},
showAddWarp() {
this.$refs.editModal.showModal();
},
showEditWarp(row) {
this.$refs.editModal.showModal(row);
},
async handleSave({ data, isEdit }) {
try {
if (isEdit) {
await ntr_questionsServer.edit(data);
this.$Message.success('编辑成功!');
} else {
await ntr_questionsServer.add(data);
this.$Message.success('新增成功!');
}
this.query(this.gridOption.param.pageOption.page);
} catch (error) {
this.$Message.error(error.message || '操作失败!');
}
},
delConfirm(row) {
uiTool.delConfirm(async () => {
await ntr_questionsServer.del(row)
this.$Message.success('删除成功!')
this.query(1)
})
},
resetQuery() {
this.gridOption.param.seachOption = {
key: 'question_title',
value: '',
is_active: null
};
this.query(1);
},
exportCsv() {
ntr_questionsServer.exportCsv(this.gridOption.param).then(res => {
funTool.downloadFile(res, '题库管理.csv');
});
}
},
computed: {
seachTypePlaceholder() {
const selected = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key);
return selected ? selected.value : '请选择搜索类型';
}
}
}
</script>

View File

@@ -0,0 +1,261 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<Button type="primary" @click="showAddWarp">新增</Button>
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
<FormItem :label-width="20" class="flex">
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
:placeholder="seachTypePlaceholder">
<Option v-for="item in seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
placeholder="请输入关键字" @on-search="query(1)" />
</FormItem>
<FormItem label="NTRP等级">
<Select v-model="gridOption.param.seachOption.ntrp_level" style="width: 120px" clearable @on-change="query(1)">
<Option :value="1">1.0</Option>
<Option :value="2">2.0</Option>
<Option :value="3">3.0</Option>
<Option :value="4">4.0</Option>
<Option :value="5">5.0</Option>
<Option :value="6">6.0</Option>
<Option :value="7">7.0</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="query(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="default" @click="exportCsv" class="ml10">导出</Button>
</FormItem>
</Form>
</div>
<div class="table-body">
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
@changePage="query"></tables>
</div>
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules" @on-save="handleSave"> </editModal>
</div>
</template>
<script>
import funTool from '@/libs/funTool'
import uiTool from '@/libs/uiTool'
import ntr_recordsServer from '@/api/ntrp/ntr_records_server.js'
export default {
data() {
let rules = {}
rules["user_id"] = [{ required: true, type: "number", message: '请填写用户ID', trigger: 'change' }];
rules["test_score"] = [{ required: true, type: "number", message: '请填写测试分数', trigger: 'change' }];
rules["ntrp_level"] = [{ required: true, type: "number", message: '请填写NTRP等级', trigger: 'change' }];
return {
// 搜索类型:只包含适合文本搜索的字段
seachTypes: [
{ key: 'user_id', value: '用户ID' },
{ key: 'nickname', value: '用户昵称' }
],
seachTypePlaceholder: '请选择搜索类型',
gridOption: {
param: {
seachOption: {
key: 'nickname',
value: '',
ntrp_level: null // NTRP等级筛选
},
pageOption: {
page: 1,
pageSize: 20
}
},
data: [],
rules: rules
},
// 列表列配置:按照规范排序(系统→核心→关联→详细→时间→操作)
listColumns: [
// 系统字段
{ title: 'ID', key: 'id', minWidth: 80 },
// 关联字段
{ title: '用户ID', key: 'user_id', minWidth: 100 },
{ title: '用户昵称', key: 'nickname', minWidth: 120 },
// 核心业务字段
{ title: '测试分数', key: 'test_score', minWidth: 100 },
{ title: 'NTRP等级', key: 'ntrp_level', minWidth: 100,
render: (h, params) => {
const level = params.row.ntrp_level;
const colors = {
1: 'default', 2: 'default', 3: 'blue',
4: 'green', 5: 'orange', 6: 'red', 7: 'purple'
};
return h('Tag', {
props: { color: colors[level] || 'default' }
}, level ? `${level}.0` : '-');
}
},
// 详细信息
{ title: '测试结果', key: 'test_result', minWidth: 200 },
// 时间字段
{ title: '测试时间', key: 'test_time', minWidth: 150 },
{ title: '创建时间', key: 'create_time', minWidth: 150 },
// 操作列
{
title: '操作',
key: 'action',
width: 200,
fixed: 'right',
type: 'template',
render: (h, params) => {
let btns = [
{
title: '编辑',
type: 'primary',
click: () => {
this.showEditWarp(params.row)
},
},
{
title: '删除',
type: 'error',
click: () => {
this.delConfirm(params.row)
},
},
]
return uiTool.getBtn(h, btns)
},
}
],
// 编辑字段配置:按照规范排序和配置
editColumns: [
// 关联字段(只读)
{
title: '用户ID',
key: 'user_id',
com: 'InputNumber',
data_type: 'number',
disabled: true, // 用户ID不可修改
required: true
},
// 核心业务字段
{
title: '测试分数',
key: 'test_score',
com: 'InputNumber',
data_type: 'number',
min: 0,
max: 100,
disabled: false,
required: true
},
{
title: 'NTRP等级',
key: 'ntrp_level',
com: 'Select',
disabled: false,
required: true,
source: [
{ key: 1, value: '1.0' },
{ key: 2, value: '2.0' },
{ key: 3, value: '3.0' },
{ key: 4, value: '4.0' },
{ key: 5, value: '5.0' },
{ key: 6, value: '6.0' },
{ key: 7, value: '7.0' }
]
},
// 时间字段
{
title: '测试时间',
key: 'test_time',
com: 'DatePicker',
data_type: 'date',
type: 'datetime',
disabled: false
},
// 详细信息
{
title: '测试结果',
key: 'test_result',
com: 'TextArea',
rows: 4,
disabled: false
},
// 辅助字段
{
title: '备注',
key: 'remark',
com: 'TextArea',
rows: 3,
disabled: false
}
]
}
},
mounted() {
this.query(1);
},
methods: {
query(page) {
this.gridOption.param.pageOption.page = page;
ntr_recordsServer.page(this.gridOption.param).then(res => {
this.gridOption.data = res.data.rows;
this.gridOption.param.pageOption.total = res.data.count;
});
},
showAddWarp() {
this.$refs.editModal.showModal();
},
showEditWarp(row) {
this.$refs.editModal.showModal(row);
},
async handleSave({ data, isEdit }) {
try {
if (isEdit) {
await ntr_recordsServer.edit(data);
this.$Message.success('编辑成功!');
} else {
await ntr_recordsServer.add(data);
this.$Message.success('新增成功!');
}
this.query(this.gridOption.param.pageOption.page);
} catch (error) {
this.$Message.error(error.message || '操作失败!');
}
},
delConfirm(row) {
uiTool.delConfirm(async () => {
await ntr_recordsServer.del(row)
this.$Message.success('删除成功!')
this.query(1)
})
},
resetQuery() {
this.gridOption.param.seachOption = {
key: 'nickname',
value: '',
ntrp_level: null
};
this.query(1);
},
exportCsv() {
ntr_recordsServer.exportCsv(this.gridOption.param).then(res => {
funTool.downloadFile(res, '测试记录.csv');
});
}
},
computed: {
seachTypePlaceholder() {
const selected = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key);
return selected ? selected.value : '请选择搜索类型';
}
}
}
</script>

View File

@@ -0,0 +1,402 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<Button type="primary" @click="batchUnfreeze" :disabled="selectedRows.length === 0">
批量解冻
</Button>
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
<FormItem :label-width="20" class="flex">
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
:placeholder="seachTypePlaceholder">
<Option v-for="item in seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
placeholder="请输入关键字" @on-search="query(1)" />
</FormItem>
<FormItem label="冻结类型">
<Select v-model="gridOption.param.seachOption.freezeType" style="width: 120px" clearable>
<Option value="game_deposit">球局定金</Option>
<Option value="withdraw">提现冻结</Option>
<Option value="risk_control">风控冻结</Option>
<Option value="system">系统冻结</Option>
</Select>
</FormItem>
<FormItem label="冻结状态">
<Select v-model="gridOption.param.seachOption.status" style="width: 120px" clearable>
<Option value="frozen">冻结中</Option>
<Option value="unfrozen">已解冻</Option>
<Option value="expired">已过期</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="query(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
</FormItem>
</Form>
</div>
<div class="table-body">
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
@changePage="query" @on-selection-change="onSelectionChange"></tables>
</div>
<!-- 批量解冻模态框 -->
<Modal v-model="batchUnfreezeModal" title="批量解冻资金" @on-ok="confirmBatchUnfreeze">
<Form :model="batchUnfreezeForm" :label-width="100">
<FormItem label="解冻原因">
<Input v-model="batchUnfreezeForm.reason" type="textarea"
placeholder="请输入解冻原因" :rows="3"></Input>
</FormItem>
<FormItem label="选中记录">
<div class="selected-records">
<Tag v-for="record in selectedRows" :key="record.id" closable>
{{ record.id }} - ¥{{ (record.amount / 100).toFixed(2) }}
</Tag>
</div>
</FormItem>
<FormItem label="解冻金额">
<div class="total-amount">
总计¥{{ (totalSelectedAmount / 100).toFixed(2) }}
</div>
</FormItem>
</Form>
</Modal>
<!-- 解冻详情模态框 -->
<Modal v-model="unfreezeDetailModal" title="解冻详情" width="600">
<Form :model="unfreezeDetail" :label-width="100">
<FormItem label="用户ID">
<span>{{ unfreezeDetail.user_id }}</span>
</FormItem>
<FormItem label="冻结金额">
<span>¥{{ (unfreezeDetail.amount / 100).toFixed(2) }}</span>
</FormItem>
<FormItem label="冻结原因">
<span>{{ unfreezeDetail.freeze_reason }}</span>
</FormItem>
<FormItem label="冻结时间">
<span>{{ unfreezeDetail.freeze_time }}</span>
</FormItem>
<FormItem label="解冻原因">
<Input v-model="unfreezeDetail.unfreeze_reason" type="textarea"
placeholder="请输入解冻原因" :rows="3"></Input>
</FormItem>
</Form>
<div slot="footer">
<Button @click="unfreezeDetailModal = false">取消</Button>
<Button type="primary" @click="confirmUnfreeze">确认解冻</Button>
</div>
</Modal>
</div>
</template>
<script>
import funTool from '@/libs/funTool'
import uiTool from '@/libs/uiTool'
import frozenFundsServer from '@/api/order/frozen_funds_server.js'
export default {
data() {
return {
selectedRows: [],
batchUnfreezeModal: false,
batchUnfreezeForm: {
reason: ''
},
unfreezeDetailModal: false,
unfreezeDetail: {},
gridOption: {
param: {
seachOption: {
key: 'user_id',
value: '',
freezeType: '',
status: ''
},
pageOption: {
page: 1,
pageSize: 10,
total: 0
}
},
data: [],
rules: {}
},
seachTypes: [
{ key: 'user_id', value: '用户ID' },
{ key: 'transaction_id', value: '交易ID' },
{ key: 'related_id', value: '关联ID' },
{ key: 'freeze_reason', value: '冻结原因' }
],
listColumns: [
{
type: 'selection',
align: 'center'
},
{
title: 'ID',
key: 'id'
},
{
title: '用户ID',
key: 'user_id'
},
{
title: '冻结金额',
key: 'amount',
render: (h, params) => {
return h('span', { style: { color: '#f56c6c' } }, `¥${(params.row.amount / 100).toFixed(2)}`)
}
},
{
title: '冻结类型',
key: 'freeze_type',
render: (h, params) => {
const typeMap = {
'game_deposit': { text: '球局定金', color: 'blue' },
'withdraw': { text: '提现冻结', color: 'orange' },
'risk_control': { text: '风控冻结', color: 'red' },
'system': { text: '系统冻结', color: 'gray' }
}
const type = typeMap[params.row.freeze_type] || { text: params.row.freeze_type, color: 'default' }
return h('Tag', { props: { color: type.color } }, type.text)
}
},
{
title: '冻结状态',
key: 'status',
render: (h, params) => {
const statusMap = {
'frozen': { text: '冻结中', color: 'red' },
'unfrozen': { text: '已解冻', color: 'green' },
'expired': { text: '已过期', color: 'gray' }
}
const status = statusMap[params.row.status] || { text: params.row.status, color: 'default' }
return h('Tag', { props: { color: status.color } }, status.text)
}
},
{
title: '冻结原因',
key: 'freeze_reason',
render: (h, params) => {
return h('span', {
attrs: { title: params.row.freeze_reason },
style: {
display: 'block',
maxWidth: '180px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}
}, params.row.freeze_reason)
}
},
{
title: '冻结时间',
key: 'freeze_time'
},
{
title: '解冻时间',
key: 'unfreeze_time',
render: (h, params) => {
return h('span', params.row.unfreeze_time || '-')
}
},
{
title: '操作',
key: 'action',
render: (h, params) => {
let btns = [
{
title: '解冻',
type: 'primary',
disabled: params.row.status !== 'frozen',
click: () => {
this.showUnfreezeDetail(params.row)
}
},
{
title: '详情',
type: 'info',
click: () => {
this.viewDetail(params.row)
}
}
]
return uiTool.getBtn(h, btns)
}
}
]
}
},
computed: {
seachTypePlaceholder() {
const selected = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key)
return selected ? selected.value : '请选择搜索类型'
},
totalSelectedAmount() {
return this.selectedRows.reduce((total, row) => total + row.amount, 0)
}
},
mounted() {
this.query(1)
},
methods: {
async query(page) {
this.gridOption.param.pageOption.page = page
// 构建查询参数
const params = {
page: page,
pageSize: this.gridOption.param.pageOption.pageSize,
seachOption: this.gridOption.param.seachOption
}
try {
// 调用API获取冻结资金列表
const res = await frozenFundsServer.getFrozenFundsList(params)
if (res.code === 0) {
this.gridOption.data = res.data.rows || []
this.gridOption.param.pageOption.total = res.data.total || 0
} else {
this.$Message.error(res.message || '获取冻结资金列表失败')
}
} catch (error) {
console.error('获取冻结资金列表失败:', error)
this.$Message.error('获取冻结资金列表失败')
}
},
resetQuery() {
this.gridOption.param.seachOption = {
key: 'user_id',
value: '',
freezeType: '',
status: ''
}
this.query(1)
},
onSelectionChange(selection) {
this.selectedRows = selection
},
batchUnfreeze() {
if (this.selectedRows.length === 0) {
this.$Message.warning('请先选择要解冻的记录')
return
}
this.batchUnfreezeModal = true
},
async confirmBatchUnfreeze() {
if (!this.batchUnfreezeForm.reason) {
this.$Message.error('请输入解冻原因')
return
}
try {
const params = {
ids: this.selectedRows.map(row => row.id),
reason: this.batchUnfreezeForm.reason
}
const res = await frozenFundsServer.batchUnfreezeFunds(params)
if (res.code === 0) {
this.$Message.success(res.message || `成功解冻 ${this.selectedRows.length} 条记录`)
this.batchUnfreezeModal = false
this.batchUnfreezeForm = { reason: '' }
this.selectedRows = []
this.query(1)
} else {
this.$Message.error(res.message || '批量解冻失败')
}
} catch (error) {
console.error('批量解冻失败:', error)
this.$Message.error('批量解冻失败')
}
},
showUnfreezeDetail(row) {
this.unfreezeDetail = { ...row }
this.unfreezeDetailModal = true
},
async confirmUnfreeze() {
if (!this.unfreezeDetail.unfreeze_reason) {
this.$Message.error('请输入解冻原因')
return
}
try {
const params = {
id: this.unfreezeDetail.id,
unfreeze_reason: this.unfreezeDetail.unfreeze_reason
}
const res = await frozenFundsServer.unfreezeFund(params)
if (res.code === 0) {
this.$Message.success('资金解冻成功')
this.unfreezeDetailModal = false
this.unfreezeDetail = {}
this.query(1)
} else {
this.$Message.error(res.message || '解冻失败')
}
} catch (error) {
console.error('解冻失败:', error)
this.$Message.error('解冻失败')
}
},
async viewDetail(row) {
try {
const res = await frozenFundsServer.getFrozenFundDetail({ id: row.id })
if (res.code === 0) {
const detail = res.data
this.$Modal.info({
title: '冻结详情',
content: `
<div>
<p><strong>冻结ID:</strong> ${detail.id}</p>
<p><strong>用户ID:</strong> ${detail.user_id}</p>
<p><strong>用户昵称:</strong> ${detail.user_nickname || '未知'}</p>
<p><strong>冻结金额:</strong> ¥${(detail.amount / 100).toFixed(2)}</p>
<p><strong>冻结类型:</strong> ${detail.freeze_type}</p>
<p><strong>冻结状态:</strong> ${detail.status}</p>
<p><strong>冻结原因:</strong> ${detail.freeze_reason}</p>
<p><strong>冻结时间:</strong> ${detail.freeze_time}</p>
${detail.unfreeze_time ? `<p><strong>解冻时间:</strong> ${detail.unfreeze_time}</p>` : ''}
</div>
`
})
} else {
this.$Message.error(res.message || '获取详情失败')
}
} catch (error) {
console.error('获取详情失败:', error)
this.$Message.error('获取详情失败')
}
}
}
}
</script>
<style scoped>
.selected-records {
max-height: 100px;
overflow-y: auto;
border: 1px solid #dcdee2;
padding: 8px;
border-radius: 4px;
}
.total-amount {
font-size: 16px;
font-weight: bold;
color: #f56c6c;
}
.ml10 {
margin-left: 10px;
}
</style>

View File

@@ -0,0 +1,592 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<Button type="primary" @click="showAddModal">新增订单</Button>
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
<FormItem :label-width="20" class="flex">
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
:placeholder="seachTypePlaceholder">
<Option v-for="item in seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
placeholder="请输入关键字" @on-search="query(1)" />
</FormItem>
<FormItem label="订单状态">
<Select v-model="gridOption.param.seachOption.status" style="width: 120px" clearable>
<Option :value="0">待支付</Option>
<Option :value="1">已支付</Option>
<Option :value="2">已结束</Option>
</Select>
</FormItem>
<FormItem label="订单类型">
<Select v-model="gridOption.param.seachOption.order_type" style="width: 120px" clearable>
<Option value="game_deposit">球局定金</Option>
<Option value="venue_booking">场地预订</Option>
<Option value="recharge">充值</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="query(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
</FormItem>
<FormItem>
<Button type="default" @click="exportOrders">导出订单</Button>
</FormItem>
</Form>
</div>
<div class="table-body">
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
@changePage="query"></tables>
</div>
<!-- 新增/编辑订单模态框 -->
<Modal v-model="orderModal" :title="orderModalTitle" width="800" @on-ok="saveOrder">
<Form :model="orderForm" :rules="orderRules" ref="orderForm" :label-width="100">
<Row :gutter="16">
<Col span="12">
<FormItem label="订单号" prop="order_no">
<Input v-model="orderForm.order_no" placeholder="请输入订单号"></Input>
</FormItem>
</Col>
<Col span="12">
<FormItem label="用户ID" prop="user_id">
<Input v-model="orderForm.user_id" placeholder="请输入用户ID"></Input>
</FormItem>
</Col>
</Row>
<Row :gutter="16">
<Col span="12">
<FormItem label="业务类型" prop="order_type">
<Select v-model="orderForm.order_type" placeholder="请选择业务类型">
<Option value="game_deposit">球局定金</Option>
<Option value="venue_booking">场地预订</Option>
<Option value="recharge">充值</Option>
</Select>
</FormItem>
</Col>
<Col span="12">
<FormItem label="业务ID" prop="business_id">
<Input v-model="orderForm.business_id" placeholder="请输入业务ID"></Input>
</FormItem>
</Col>
</Row>
<Row :gutter="16">
<Col span="12">
<FormItem label="订单金额" prop="amount">
<Input v-model="orderForm.amount" placeholder="请输入金额(分)" type="number"></Input>
</FormItem>
</Col>
<Col span="12">
<FormItem label="订单状态" prop="order_status">
<Select v-model="orderForm.order_status" placeholder="请选择订单状态">
<Option :value="0">待支付</Option>
<Option :value="1">已支付</Option>
<Option :value="2">已结束</Option>
</Select>
</FormItem>
</Col>
</Row>
<Row :gutter="16">
<Col span="12">
<FormItem label="支付方式" prop="payment_method">
<Select v-model="orderForm.payment_method" placeholder="请选择支付方式">
<Option value="wechat">微信支付</Option>
<Option value="alipay">支付宝</Option>
<Option value="balance">余额支付</Option>
</Select>
</FormItem>
</Col>
<Col span="12">
<FormItem label="退款状态" prop="refund_status">
<Select v-model="orderForm.refund_status" placeholder="请选择退款状态">
<Option :value="0">无退款</Option>
<Option :value="1">退款中</Option>
<Option :value="2">已退款</Option>
</Select>
</FormItem>
</Col>
</Row>
<FormItem label="订单描述" prop="description">
<Input v-model="orderForm.description" type="textarea"
placeholder="请输入订单描述" :rows="3"></Input>
</FormItem>
<FormItem label="备注" prop="remark">
<Input v-model="orderForm.remark" type="textarea"
placeholder="请输入备注" :rows="2"></Input>
</FormItem>
</Form>
</Modal>
<!-- 订单详情模态框 -->
<Modal v-model="detailModal" title="订单详情" width="800">
<div v-if="orderDetail">
<Row :gutter="16">
<Col span="12">
<div class="detail-item">
<label>订单ID:</label>
<span>{{ orderDetail.id }}</span>
</div>
</Col>
<Col span="12">
<div class="detail-item">
<label>订单号:</label>
<span>{{ orderDetail.order_no }}</span>
</div>
</Col>
</Row>
<Row :gutter="16">
<Col span="12">
<div class="detail-item">
<label>用户ID:</label>
<span>{{ orderDetail.user_id }}</span>
</div>
</Col>
<Col span="12">
<div class="detail-item">
<label>业务类型:</label>
<Tag :color="getBusinessTypeColor(orderDetail.order_type)">
{{ getBusinessTypeText(orderDetail.order_type) }}
</Tag>
</div>
</Col>
</Row>
<Row :gutter="16">
<Col span="12">
<div class="detail-item">
<label>订单金额:</label>
<span class="amount-text">¥{{ (orderDetail.amount / 100).toFixed(2) }}</span>
</div>
</Col>
<Col span="12">
<div class="detail-item">
<label>订单状态:</label>
<Tag :color="getStatusColor(orderDetail.order_status)">
{{ getStatusText(orderDetail.order_status) }}
</Tag>
</div>
</Col>
</Row>
<Row :gutter="16">
<Col span="12">
<div class="detail-item">
<label>支付方式:</label>
<span>{{ getPaymentMethodText(orderDetail.payment_method) }}</span>
</div>
</Col>
<Col span="12">
<div class="detail-item">
<label>退款状态:</label>
<Tag :color="getRefundStatusColor(orderDetail.refund_status)">
{{ getRefundStatusText(orderDetail.refund_status) }}
</Tag>
</div>
</Col>
</Row>
<Row :gutter="16">
<Col span="12">
<div class="detail-item">
<label>创建时间:</label>
<span>{{ orderDetail.create_time }}</span>
</div>
</Col>
<Col span="12">
<div class="detail-item">
<label>支付时间:</label>
<span>{{ orderDetail.pay_time || '-' }}</span>
</div>
</Col>
</Row>
<Row :gutter="16" v-if="orderDetail.description">
<Col span="24">
<div class="detail-item">
<label>订单描述:</label>
<span>{{ orderDetail.description }}</span>
</div>
</Col>
</Row>
<Row :gutter="16" v-if="orderDetail.remark">
<Col span="24">
<div class="detail-item">
<label>备注:</label>
<span>{{ orderDetail.remark }}</span>
</div>
</Col>
</Row>
</div>
</Modal>
</div>
</template>
<script>
import funTool from '@/libs/funTool'
import uiTool from '@/libs/uiTool'
import paymentOrdersServer from '@/api/order/payment_orders_server.js'
export default {
data() {
return {
orderModal: false,
orderModalTitle: '',
orderForm: {
order_no: '',
user_id: '',
order_type: '',
business_id: '',
amount: '',
order_status: 0,
payment_method: '',
refund_status: 0,
description: '',
remark: ''
},
orderRules: {
order_no: [{ required: true, message: '请输入订单号', trigger: 'blur' }],
user_id: [{ required: true, message: '请输入用户ID', trigger: 'blur' }],
order_type: [{ required: true, message: '请选择业务类型', trigger: 'change' }],
amount: [{ required: true, message: '请输入订单金额', trigger: 'blur' }],
order_status: [{ required: true, message: '请选择订单状态', trigger: 'change' }]
},
detailModal: false,
orderDetail: null,
gridOption: {
param: {
seachOption: {
key: 'order_no',
value: '',
status: '',
order_type: ''
},
pageOption: {
page: 1,
pageSize: 10,
total: 0
}
},
data: [],
rules: {}
},
seachTypes: [
{ key: 'order_no', value: '订单号' },
{ key: 'user_id', value: '用户ID' },
{ key: 'business_id', value: '业务ID' },
{ key: 'description', value: '订单描述' }
],
listColumns: [
{
title: '订单ID',
key: 'id',
minWidth: 80
},
{
title: '订单号',
key: 'order_no',
minWidth: 180
},
{
title: '用户ID',
key: 'user_id',
minWidth: 100
},
{
title: '业务类型',
key: 'order_type',
minWidth: 120,
render: (h, params) => {
return h('Tag', { props: { color: this.getBusinessTypeColor(params.row.order_type) } },
this.getBusinessTypeText(params.row.order_type))
}
},
{
title: '业务ID',
key: 'business_id',
minWidth: 100
},
{
title: '订单金额',
key: 'amount',
minWidth: 120
},
{
title: '订单状态',
key: 'order_status',
minWidth: 100,
render: (h, params) => {
return h('Tag', { props: { color: this.getStatusColor(params.row.order_status) } },
this.getStatusText(params.row.order_status))
}
},
{
title: '支付方式',
key: 'payment_method',
minWidth: 100,
render: (h, params) => {
return h('span', this.getPaymentMethodText(params.row.payment_method))
}
},
{
title: '退款状态',
key: 'refund_status',
minWidth: 100,
render: (h, params) => {
return h('Tag', { props: { color: this.getRefundStatusColor(params.row.refund_status) } },
this.getRefundStatusText(params.row.refund_status))
}
},
{
title: '创建时间',
key: 'create_time',
minWidth: 160
},
{
title: '操作',
key: 'action',
width: 250,
type: 'template',
render: (h, params) => {
let btns = [
{
title: '编辑',
type: 'primary',
click: () => {
this.editOrder(params.row)
},
},
{
title: '详情',
type: 'info',
click: () => {
this.viewDetail(params.row)
},
},
{
title: '取消',
type: 'warning',
click: () => {
this.cancelOrder(params.row)
},
},
]
return uiTool.getBtn(h, btns)
},
}
]
}
},
computed: {
seachTypePlaceholder() {
const selected = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key)
return selected ? selected.value : '请选择搜索类型'
}
},
mounted() {
this.query(1)
},
methods: {
query(page) {
if (page) {
this.gridOption.param.pageOption.page = page
}
paymentOrdersServer.getPaymentOrdersList(this.gridOption.param).then(res => {
if (res.code === 0) {
this.gridOption.data = res.data.rows || res.data
this.gridOption.param.pageOption.total = res.data.count || res.data.total || 0
} else {
this.$Message.error(res.message || '查询失败')
}
}).catch(error => {
this.$Message.error('查询失败: ' + error.message)
})
},
resetQuery() {
this.gridOption.param.seachOption = {
key: 'order_no',
value: '',
status: '',
order_type: ''
}
this.query(1)
},
showAddModal() {
this.orderForm = {
order_no: '',
user_id: '',
order_type: '',
business_id: '',
amount: '',
order_status: 0,
payment_method: '',
refund_status: 0,
description: '',
remark: ''
}
this.orderModalTitle = '新增订单'
this.orderModal = true
},
editOrder(row) {
this.orderForm = { ...row }
this.orderModalTitle = '编辑订单'
this.orderModal = true
},
saveOrder() {
this.$refs.orderForm.validate((valid) => {
if (valid) {
if (this.orderForm.id) {
// 编辑订单
paymentOrdersServer.updatePaymentOrder(this.orderForm).then(res => {
if (res.code === 0) {
this.$Message.success('订单更新成功')
this.orderModal = false
this.query(1)
} else {
this.$Message.error(res.message || '更新失败')
}
}).catch(error => {
this.$Message.error('更新失败: ' + error.message)
})
} else {
// 新增订单
paymentOrdersServer.addPaymentOrder(this.orderForm).then(res => {
if (res.code === 0) {
this.$Message.success('订单创建成功')
this.orderModal = false
this.query(1)
} else {
this.$Message.error(res.message || '创建失败')
}
}).catch(error => {
this.$Message.error('创建失败: ' + error.message)
})
}
}
})
},
cancelOrder(row) {
this.$Modal.confirm({
title: '取消订单',
content: `确定要取消订单 ${row.order_no} 吗?`,
onOk: async () => {
paymentOrdersServer.cancelPaymentOrder(row.id).then(res => {
if (res.code === 0) {
this.$Message.success('订单已取消')
this.query(1)
} else {
this.$Message.error(res.message || '取消失败')
}
}).catch(error => {
this.$Message.error('取消失败: ' + error.message)
})
}
})
},
viewDetail(row) {
this.orderDetail = row
this.detailModal = true
},
exportOrders() {
paymentOrdersServer.exportPaymentOrders(this.gridOption.param).then(res => {
if (res.code === 0) {
funTool.downloadFile(res, '订单列表.csv')
} else {
this.$Message.error(res.message || '导出失败')
}
}).catch(error => {
this.$Message.error('导出失败: ' + error.message)
})
},
getBusinessTypeColor(type) {
const colorMap = {
'game_deposit': 'blue',
'venue_booking': 'green',
'recharge': 'orange'
}
return colorMap[type] || 'default'
},
getBusinessTypeText(type) {
const textMap = {
'game_deposit': '球局定金',
'venue_booking': '场地预订',
'recharge': '充值'
}
return textMap[type] || type
},
getStatusColor(status) {
const colorMap = {
0: 'orange', // 待支付
1: 'green', // 已支付
2: 'blue' // 已结束
}
return colorMap[status] || 'default'
},
getStatusText(status) {
const textMap = {
0: '待支付',
1: '已支付',
2: '已结束'
}
return textMap[status] || status
},
getPaymentMethodText(method) {
const textMap = {
'wechat': '微信支付',
'alipay': '支付宝',
'balance': '余额支付'
}
return textMap[method] || method
},
getRefundStatusColor(status) {
const colorMap = {
0: 'default', // 无退款
1: 'orange', // 退款中
2: 'green' // 已退款
}
return colorMap[status] || 'default'
},
getRefundStatusText(status) {
const textMap = {
0: '无退款',
1: '退款中',
2: '已退款'
}
return textMap[status] || status
}
}
}
</script>
<style scoped>
.detail-item {
margin-bottom: 12px;
}
.detail-item label {
display: inline-block;
width: 80px;
font-weight: bold;
color: #666;
}
.amount-text {
color: #f56c6c;
font-weight: bold;
font-size: 16px;
}
.ml10 {
margin-left: 10px;
}
</style>

View File

@@ -0,0 +1,234 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<Button type="primary" @click="showAddWarp">新增</Button>
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
<FormItem :label-width="20" class="flex">
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
:placeholder="seachTypePlaceholder">
<Option v-for="item in seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
placeholder="请输入关键字" @on-search="query(1)" />
</FormItem>
<FormItem label="明细状态">
<Select v-model="gridOption.param.seachOption.detail_status" style="width: 120px" clearable @on-change="query(1)">
<Option value="WAIT_USER_CONFIRM">处理中</Option>
<Option value="SUCCESS">成功</Option>
<Option value="FAIL">失败</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="query(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="default" @click="exportCsv">导出</Button>
</FormItem>
</Form>
</div>
<div class="table-body">
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
@changePage="query"></tables>
</div>
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules"> </editModal>
</div>
</template>
<script>
import funTool from '@/libs/funTool'
import uiTool from '@/libs/uiTool'
import transfer_detailsServer from '@/api/order/transfer_details_server.js'
export default {
data() {
let rules = {}
rules["out_bill_no"] = [{ required: true, message: '请填写商户明细号', trigger: 'blur' }];
rules["openid"] = [{ required: true, message: '请填写收款用户openid', trigger: 'blur' }];
rules["transfer_amount"] = [{ required: true, type: "number", message: '请填写转账金额', trigger: 'change' }];
rules["transfer_remark"] = [{ required: true, message: '请填写转账备注', trigger: 'blur' }];
return {
seachTypes: [
{ key: 'out_bill_no', value: '商户明细号' },
{ key: 'transfer_bill_no', value: '微信交易号' },
{ key: 'openid', value: '收款用户openid' }
],
gridOption: {
param: {
seachOption: {
key: 'out_bill_no',
value: '',
detail_status: null
},
pageOption: {
page: 1,
pageSize: 20
}
},
data: [],
rules: rules
},
listColumns: [
{ title: 'ID', key: 'id' },
{ title: '商户明细号', key: 'out_bill_no' },
{ title: '微信交易号', key: 'transfer_bill_no' },
{ title: '收款用户openid', key: 'openid' },
{ title: '收款用户昵称', key: 'user_nickname' },
{
title: '转账金额',
key: 'transfer_amount',
type: 'template',
render: (h, params) => {
return h('span', { style: { color: '#67c23a', fontWeight: 'bold' } },
`¥${(params.row.transfer_amount / 100).toFixed(2)}`)
}
},
{
title: '转账备注',
key: 'transfer_remark',
type: 'template',
render: (h, params) => {
return h('span', {
attrs: { title: params.row.transfer_remark },
style: {
display: 'block',
maxWidth: '150px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}
}, params.row.transfer_remark)
}
},
{
title: '明细状态',
key: 'detail_status',
type: 'template',
render: (h, params) => {
const statusMap = {
'WAIT_USER_CONFIRM': { text: '处理中', color: 'orange' },
'SUCCESS': { text: '成功', color: 'green' },
'FAIL': { text: '失败', color: 'red' }
}
const status = statusMap[params.row.detail_status] || { text: params.row.detail_status, color: 'default' }
return h('Tag', { props: { color: status.color } }, status.text)
}
},
{ title: '失败原因', key: 'fail_reason' },
{ title: '创建时间', key: 'create_time' },
{ title: '更新时间', key: 'update_time' },
{ title: '完成时间', key: 'finish_time' },
{
title: '操作',
key: 'action',
width: 200,
type: 'template',
render: (h, params) => {
let btns = [
{
title: '编辑',
type: 'primary',
click: () => {
this.showEditWarp(params.row)
},
},
{
title: '删除',
type: 'error',
click: () => {
this.delConfirm(params.row)
},
},
]
return uiTool.getBtn(h, btns)
},
}
],
editColumns: [
{ title: '商户明细号', key: 'out_bill_no', type: 'text', required: true },
{ title: '微信交易号', key: 'transfer_bill_no', type: 'text' },
{ title: '收款用户openid', key: 'openid', type: 'text', required: true },
{ title: '收款用户姓名', key: 'user_name', type: 'text' },
{ title: '转账金额(分)', key: 'transfer_amount', type: 'number', required: true },
{ title: '转账备注', key: 'transfer_remark', type: 'textarea', required: true },
{ title: '明细状态', key: 'detail_status', type: 'select', options: [
{ value: 'WAIT_USER_CONFIRM', label: '处理中' },
{ value: 'SUCCESS', label: '成功' },
{ value: 'FAIL', label: '失败' }
]},
{ title: '失败原因', key: 'fail_reason', type: 'textarea' }
]
}
},
mounted() {
this.query(1);
},
methods: {
async query(page) {
this.gridOption.param.pageOption.page = page;
try {
const res = await transfer_detailsServer.page(this.gridOption.param);
if (res.code === 0) {
this.gridOption.data = res.data.rows || [];
this.gridOption.param.pageOption.total = res.data.count || 0;
} else {
this.$Message.error(res.message || '获取转账详情失败');
}
} catch (error) {
console.error('获取转账详情失败:', error);
this.$Message.error('获取转账详情失败');
}
},
showAddWarp() {
this.$refs.editModal.showModal();
},
showEditWarp(row) {
this.$refs.editModal.showModal(row);
},
delConfirm(row) {
uiTool.delConfirm(async () => {
try {
const res = await transfer_detailsServer.del(row);
if (res.code === 0) {
this.$Message.success('删除成功!');
this.query(1);
} else {
this.$Message.error(res.message || '删除失败');
}
} catch (error) {
console.error('删除失败:', error);
this.$Message.error('删除失败');
}
})
},
async exportCsv() {
try {
const res = await transfer_detailsServer.exportCsv(this.gridOption.param);
if (res.code === 0) {
funTool.downloadFile(res.data, '转账详情.csv');
} else {
this.$Message.error(res.message || '导出失败');
}
} catch (error) {
console.error('导出失败:', error);
this.$Message.error('导出失败');
}
},
resetQuery() {
this.gridOption.param.seachOption = {
key: 'out_bill_no',
value: '',
detail_status: null
};
this.query(1);
}
},
computed: {
seachTypePlaceholder() {
const selected = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key);
return selected ? selected.value : '请选择搜索类型';
},
editColumns() {
return this.gridOption.columns.filter(p => p.is_show_edit === 1);
}
}
}
</script>

View File

@@ -0,0 +1,286 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<Button type="primary" @click="showAddWarp">新增</Button>
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
<FormItem :label-width="20" class="flex">
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
:placeholder="seachTypePlaceholder">
<Option v-for="item in seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
placeholder="请输入关键字" @on-search="query(1)" />
</FormItem>
<FormItem label="交易类型">
<Select v-model="gridOption.param.seachOption.transaction_type" style="width: 120px" clearable @on-change="query(1)">
<Option value="recharge">充值</Option>
<Option value="withdraw">提现</Option>
<Option value="game_deposit">球局定金</Option>
<Option value="game_refund">球局退款</Option>
<Option value="transfer">转账</Option>
</Select>
</FormItem>
<FormItem label="交易状态">
<Select v-model="gridOption.param.seachOption.status" style="width: 120px" clearable @on-change="query(1)">
<Option value="pending">待处理</Option>
<Option value="success">成功</Option>
<Option value="failed">失败</Option>
<Option value="cancelled">已取消</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="query(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="default" @click="exportCsv">导出</Button>
</FormItem>
</Form>
</div>
<div class="table-body">
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
@changePage="query"></tables>
</div>
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules"></editModal>
</div>
</template>
<script>
import funTool from '@/libs/funTool'
import uiTool from '@/libs/uiTool'
import menuServer from '@/api/system_high/menuServer.js'
import walletTransactionsServer from '@/api/ball/wallet_transactions_server.js'
export default {
data() {
let rules = {}
rules["status"] = [{ required: true, message: '请选择状态' }];
rules["remark"] = [{ message: '请填写备注' }];
return {
seachTypes: [
{ key: "user_id", value: "用户ID" },
{ key: "nickname", value: "用户昵称" },
{ key: "transaction_no", value: "交易流水号" }
],
gridOption: {
param: {
seachOption: {
key: "user_id",
value: "",
transaction_type: null,
status: null
},
pageOption: {
page: 1,
pageSize: 20,
total: 0
}
},
rules,
columns: [
{ key: 'id', title: 'ID', minWidth: 80, is_show_edit: 0 },
{
key: "user_id",
title: "用户",
disabled: true,
is_show_edit: 0,
is_show_list: 1,
com: "Input",
render: (h, params) => {
const userInfo = params.row.user_info;
if (userInfo && userInfo.nickname) {
return h('div', [
h('img', {
attrs: {
src: userInfo.avatar_url || '/default-avatar.png',
alt: userInfo.nickname,
title: userInfo.nickname
},
style: {
minWidth: 80,
height: '24px',
borderRadius: '50%',
marginRight: '8px',
verticalAlign: 'middle'
}
}),
h('span', { style: { verticalAlign: 'middle' } }, userInfo.nickname)
]);
}
return h('span', `用户ID: ${params.row.user_id}`);
}
},
{
key: "transaction_type",
title: "交易类型",
disabled: true,
is_show_edit: 0,
is_show_list: 1,
com: "Select",
render: (h, params) => {
const freezeAction = params.row.freeze_action;
let typeText = '';
let color = 'default';
if (params.row.transaction_type === 'income') {
if (freezeAction === 'freeze') {
typeText = '收入(冻结)';
color = 'orange';
} else if (freezeAction === 'unfreeze') {
typeText = '收入(解冻)';
color = 'green';
} else {
typeText = '收入';
color = 'green';
}
} else if (params.row.transaction_type === 'expense') {
if (freezeAction === 'unfreeze') {
typeText = '支出(解冻)';
color = 'purple';
} else {
typeText = '支出';
color = 'red';
}
} else {
typeText = params.row.transaction_type || '未知';
}
return h('Tag', { props: { color: color } }, typeText);
}
},
{
key: "amount",
title: "交易金额",
disabled: true,
is_show_edit: 0,
is_show_list: 1,
com: "InputNumber",
render: (h, params) => {
const amount = parseFloat(params.row.amount || 0);
return h('span', {
style: {
color: amount > 0 ? '#19be6b' : '#ed4014',
fontWeight: 'bold'
}
}, `${amount > 0 ? '+' : ''}¥${amount.toFixed(2)}`);
}
},
{ key: "description", title: "交易描述", disabled: true, is_show_edit: 0, is_show_list: 1, com: "Input" },
{
key: "related_id",
title: "关联ID",
disabled: true,
is_show_edit: 0,
is_show_list: 1,
com: "Input",
render: (h, params) => {
const relatedId = params.row.related_id;
if (relatedId) {
return h('span', relatedId);
}
return h('span', { style: { color: '#c5c8ce' } }, '-');
}
},
{ key: "create_time", title: "交易时间", disabled: true, is_show_edit: 0, is_show_list: 1, com: "DatePicker" }
],
data: [],
seachTypes: this.seachTypes,
apiServer: walletTransactionsServer,
showAdd: false,
showEdit: true,
showDel: false,
showExport: true
},
listColumns: [
{ title: 'ID', key: 'id', minWidth: 80 },
{ title: '用户', key: 'user_id', minWidth: 120 },
{ title: '交易类型', key: 'transaction_type', minWidth: 120 },
{ title: '交易金额', key: 'amount', minWidth: 120 },
{ title: '交易描述', key: 'description', minWidth: 200 },
{ title: '关联ID', key: 'related_id', minWidth: 120 },
{ title: '交易时间', key: 'create_time', minWidth: 150 },
{
title: '操作',
key: 'action',
width: 150,
type: 'template',
render: (h, params) => {
let btns = [
{
title: '编辑',
type: 'primary',
click: () => {
this.showEditWarp(params.row)
},
}
]
return uiTool.getBtn(h, btns)
},
}
],
editColumns: [
{ title: '状态', key: 'status', type: 'select', required: true,
source: [
{ key: 'pending', value: '待处理' },
{ key: 'completed', value: '已完成' },
{ key: 'failed', value: '失败' }
]
},
{ title: '备注', key: 'remark', type: 'textarea' }
]
}
},
mounted() {
this.query(1);
},
methods: {
query(page) {
this.gridOption.param.pageOption.page = page;
walletTransactionsServer.page(this.gridOption.param).then(res => {
this.gridOption.data = res.data.rows;
this.gridOption.param.pageOption.total = res.data.count;
});
},
async showAddWarp() {
// 交易记录不支持手动新增
this.$Message.warning('交易记录由系统自动生成,无需手动添加');
},
showEditWarp(row) {
this.$refs.editModal.showModal(row);
},
async showDelWarp(row) {
// 交易记录不支持删除
this.$Message.warning('交易记录不支持删除操作');
},
exportCsv() {
walletTransactionsServer.exportCsv(this.gridOption.param).then(res => {
funTool.downloadFile(res, '交易记录.csv');
});
},
resetQuery() {
this.gridOption.param.seachOption = {
key: 'user_id',
value: '',
transaction_type: null,
status: null
};
this.query(1);
}
},
computed: {
seachTypePlaceholder() {
const selected = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key);
return selected ? selected.value : '请选择搜索类型';
},
editColumns() {
let editTempColumns = this.gridOption.columns.filter(p => p.is_show_edit === 1)
return editTempColumns
},
listColumns() {
let listTempColumns = this.gridOption.columns.filter(p => p.is_show_list === 1)
return listTempColumns
}
}
}
</script>

View File

@@ -0,0 +1,513 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<Button type="primary" @click="showAddModal">新增钱包</Button>
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
<FormItem :label-width="20" class="flex">
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
:placeholder="seachTypePlaceholder">
<Option v-for="item in seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
placeholder="请输入关键字" @on-search="query(1)" />
</FormItem>
<FormItem label="余额范围">
<InputNumber v-model="gridOption.param.seachOption.minBalance" placeholder="最小余额"
style="width: 100px"></InputNumber>
<span class="ml5 mr5">-</span>
<InputNumber v-model="gridOption.param.seachOption.maxBalance" placeholder="最大余额"
style="width: 100px"></InputNumber>
</FormItem>
<FormItem>
<Button type="primary" @click="query(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
</FormItem>
<FormItem>
<Button type="default" @click="exportWallets">导出钱包</Button>
</FormItem>
</Form>
</div>
<div class="table-body">
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
@changePage="query"></tables>
</div>
<!-- 新增/编辑钱包模态框 -->
<Modal v-model="walletModal" :title="walletModalTitle" width="600" @on-ok="saveWallet">
<Form :model="walletForm" :rules="walletRules" ref="walletForm" :label-width="100">
<Row :gutter="16">
<Col span="12">
<FormItem label="用户ID" prop="user_id">
<Input v-model="walletForm.user_id" placeholder="请输入用户ID"></Input>
</FormItem>
</Col>
</Row>
<Row :gutter="16">
<Col span="12">
<FormItem label="可用余额" prop="balance">
<Input v-model="walletForm.balance" placeholder="请输入可用余额(分)" type="number"></Input>
</FormItem>
</Col>
<Col span="12">
<FormItem label="冻结余额" prop="frozen_balance">
<Input v-model="walletForm.frozen_balance" placeholder="请输入冻结余额(分)" type="number"></Input>
</FormItem>
</Col>
</Row>
<Row :gutter="16">
<Col span="12">
<FormItem label="总收入" prop="total_income">
<Input v-model="walletForm.total_income" placeholder="请输入总收入(分)" type="number"></Input>
</FormItem>
</Col>
</Row>
<FormItem label="钱包密码" prop="password">
<Input v-model="walletForm.password" type="password" placeholder="请输入钱包密码"></Input>
</FormItem>
<FormItem label="备注" prop="remark">
<Input v-model="walletForm.remark" type="textarea" placeholder="请输入备注" :rows="3"></Input>
</FormItem>
</Form>
</Modal>
<!-- 钱包详情模态框 -->
<Modal v-model="detailModal" title="钱包详情" width="800">
<div v-if="walletDetail">
<Row :gutter="16">
<Col span="12">
<div class="detail-item">
<label>钱包ID:</label>
<span>{{ walletDetail.id }}</span>
</div>
</Col>
<Col span="12">
<div class="detail-item">
<label>用户ID:</label>
<span>{{ walletDetail.user_id }}</span>
</div>
</Col>
</Row>
<Row :gutter="16">
<Col span="12">
<div class="detail-item">
<label>总余额:</label>
<span class="amount-text">¥{{ (walletDetail.total_balance / 100).toFixed(2) }}</span>
</div>
</Col>
</Row>
<Row :gutter="16">
<Col span="12">
<div class="detail-item">
<label>可用余额:</label>
<span class="amount-text available">¥{{ (walletDetail.balance / 100).toFixed(2) }}</span>
</div>
</Col>
<Col span="12">
<div class="detail-item">
<label>冻结余额:</label>
<span class="amount-text frozen">¥{{ (walletDetail.frozen_balance / 100).toFixed(2) }}</span>
</div>
</Col>
</Row>
<Row :gutter="16">
<Col span="12">
<div class="detail-item">
<label>总收入:</label>
<span class="amount-text income">¥{{ (walletDetail.total_income / 100).toFixed(2) }}</span>
</div>
</Col>
</Row>
<Row :gutter="16">
<Col span="12">
<div class="detail-item">
<label>创建时间:</label>
<span>{{ walletDetail.create_time }}</span>
</div>
</Col>
<Col span="12">
<div class="detail-item">
<label>更新时间:</label>
<span>{{ walletDetail.last_modify_time }}</span>
</div>
</Col>
</Row>
<Row :gutter="16" v-if="walletDetail.remark">
<Col span="24">
<div class="detail-item">
<label>备注:</label>
<span>{{ walletDetail.remark }}</span>
</div>
</Col>
</Row>
</div>
</Modal>
<!-- 钱包操作模态框 -->
<Modal v-model="operationModal" title="钱包操作" width="500" @on-ok="confirmOperation">
<Form :model="operationForm" :rules="operationRules" ref="operationForm" :label-width="100">
<FormItem label="操作类型" prop="type">
<RadioGroup v-model="operationForm.type">
<Radio label="recharge">充值</Radio>
<Radio label="withdraw">提现</Radio>
<Radio label="freeze">冻结</Radio>
<Radio label="unfreeze">解冻</Radio>
</RadioGroup>
</FormItem>
<FormItem label="操作金额" prop="amount">
<Input v-model="operationForm.amount" placeholder="请输入金额(分)" type="number"></Input>
</FormItem>
<FormItem label="操作原因" prop="reason">
<Input v-model="operationForm.reason" type="textarea" placeholder="请输入操作原因" :rows="3"></Input>
</FormItem>
</Form>
</Modal>
</div>
</template>
<script>
import funTool from '@/libs/funTool'
import uiTool from '@/libs/uiTool'
import wchWalletsServer from '@/api/order/wch_wallets_server.js'
export default {
data() {
return {
walletModal: false,
walletModalTitle: '',
walletForm: {
user_id: '',
balance: '',
frozen_balance: '',
total_income: '',
password: '',
remark: ''
},
walletRules: {
user_id: [{ required: true, message: '请输入用户ID', trigger: 'blur' }],
balance: [{ required: true, message: '请输入可用余额', trigger: 'blur' }],
frozen_balance: [{ required: true, message: '请输入冻结余额', trigger: 'blur' }]
},
detailModal: false,
walletDetail: null,
operationModal: false,
operationForm: {
type: '',
amount: '',
reason: ''
},
operationRules: {
type: [{ required: true, message: '请选择操作类型', trigger: 'change' }],
amount: [{ required: true, message: '请输入操作金额', trigger: 'blur' }],
reason: [{ required: true, message: '请输入操作原因', trigger: 'blur' }]
},
currentWallet: null,
gridOption: {
param: {
seachOption: {
key: 'user_id',
value: '',
minBalance: null,
maxBalance: null
},
pageOption: {
page: 1,
pageSize: 10,
total: 0
}
},
data: [],
rules: {}
},
seachTypes: [
{ key: 'user_id', value: '用户ID' },
{ key: 'id', value: '钱包ID' },
{ key: 'remark', value: '备注' }
],
listColumns: [
{
title: '钱包ID',
key: 'id'
},
{
title: '用户ID',
key: 'user_id'
},
{
title: '可用余额',
key: 'balance',
render: (h, params) => {
return h('span', { style: { color: '#67c23a', fontWeight: 'bold' } },
`¥${(params.row.balance / 100).toFixed(2)}`)
}
},
{
title: '冻结余额',
key: 'frozen_balance',
render: (h, params) => {
return h('span', { style: { color: '#e6a23c', fontWeight: 'bold' } },
`¥${(params.row.frozen_balance / 100).toFixed(2)}`)
}
},
{
title: '总余额',
key: 'total_balance',
render: (h, params) => {
const total = params.row.balance + params.row.frozen_balance
return h('span', { style: { color: '#409eff', fontWeight: 'bold' } },
`¥${(total / 100).toFixed(2)}`)
}
},
{
title: '总收入',
key: 'total_income',
render: (h, params) => {
return h('span', { style: { color: '#67c23a' } },
`¥${(params.row.total_income / 100).toFixed(2)}`)
}
},
{
title: '创建时间',
key: 'create_time'
},
{
title: '操作',
key: 'action',
render: (h, params) => {
let btns = [
{
title: '编辑',
type: 'primary',
click: () => {
this.editWallet(params.row)
}
},
{
title: '详情',
type: 'info',
click: () => {
this.viewDetail(params.row)
}
},
{
title: '操作',
type: 'warning',
click: () => {
this.walletOperation(params.row)
}
},
{
title: '交易记录',
type: 'success',
click: () => {
this.viewTransactions(params.row)
}
}
]
return uiTool.getBtn(h, btns)
}
}
]
}
},
computed: {
seachTypePlaceholder() {
const selected = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key)
return selected ? selected.value : '请选择搜索类型'
}
},
mounted() {
this.query(1)
},
methods: {
async query(page) {
this.gridOption.param.pageOption.page = page
// 构建查询参数
const params = {
page: page,
pageSize: this.gridOption.param.pageOption.pageSize,
seachOption: this.gridOption.param.seachOption
}
try {
// 调用API获取钱包列表
const res = await wchWalletsServer.getWalletsList(params)
if (res.code === 0) {
this.gridOption.data = res.data.rows || []
this.gridOption.param.pageOption.total = res.data.count || 0
} else {
this.$Message.error(res.message || '获取钱包列表失败')
}
} catch (error) {
console.error('获取钱包列表失败:', error)
this.$Message.error('获取钱包列表失败')
}
},
resetQuery() {
this.gridOption.param.seachOption = {
key: 'user_id',
value: '',
minBalance: null,
maxBalance: null
}
this.query(1)
},
showAddModal() {
this.walletForm = {
user_id: '',
balance: '',
frozen_balance: '',
total_income: '',
password: '',
remark: ''
}
this.walletModalTitle = '新增钱包'
this.walletModal = true
},
editWallet(row) {
this.walletForm = { ...row }
this.walletModalTitle = '编辑钱包'
this.walletModal = true
},
saveWallet() {
this.$refs.walletForm.validate(async (valid) => {
if (valid) {
const params = { ...this.walletForm }
try {
// 调用API保存钱包
const apiCall = params.id ? wchWalletsServer.updateWallet(params) : wchWalletsServer.addWallet(params)
const res = await apiCall
if (res.code === 0) {
this.$Message.success('钱包保存成功')
this.walletModal = false
this.query(1)
} else {
this.$Message.error(res.message || '保存失败')
}
} catch (error) {
console.error('保存钱包失败:', error)
this.$Message.error('保存失败')
}
}
})
},
async viewDetail(row) {
try {
// 调用API获取钱包详情
const res = await wchWalletsServer.getWalletDetail({ id: row.id })
if (res.code === 0) {
this.walletDetail = res.data.wallet
this.detailModal = true
} else {
this.$Message.error(res.message || '获取钱包详情失败')
}
} catch (error) {
console.error('获取钱包详情失败:', error)
this.$Message.error('获取钱包详情失败')
}
},
walletOperation(row) {
this.currentWallet = row
this.operationForm = {
type: '',
amount: '',
reason: ''
}
this.operationModal = true
},
confirmOperation() {
this.$refs.operationForm.validate((valid) => {
if (valid) {
this.$Message.success('钱包操作完成')
this.operationModal = false
this.operationForm = { type: '', amount: '', reason: '' }
this.currentWallet = null
this.query(1)
}
})
},
viewTransactions(row) {
this.$Message.info('跳转到交易记录页面')
// 这里可以跳转到交易记录页面传递钱包ID参数
},
async exportWallets() {
// 构建导出参数
const params = {
seachOption: this.gridOption.param.seachOption
}
try {
// 调用API导出钱包数据
const res = await wchWalletsServer.exportWallets(params)
if (res.code === 0) {
this.$Message.success('导出成功')
// 这里可以添加下载文件的逻辑
} else {
this.$Message.error(res.message || '导出失败')
}
} catch (error) {
console.error('导出钱包数据失败:', error)
this.$Message.error('导出失败')
}
},
}
}
</script>
<style scoped>
.detail-item {
margin-bottom: 12px;
}
.detail-item label {
display: inline-block;
width: 80px;
font-weight: bold;
color: #666;
}
.amount-text {
font-weight: bold;
font-size: 16px;
}
.amount-text.available {
color: #67c23a;
}
.amount-text.frozen {
color: #e6a23c;
}
.amount-text.income {
color: #67c23a;
}
.amount-text.expense {
color: #f56c6c;
}
.ml5 {
margin-left: 5px;
}
.mr5 {
margin-right: 5px;
}
.ml10 {
margin-left: 10px;
}
</style>

View File

@@ -0,0 +1,345 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<Button type="primary" @click="showAddModal">新增资源</Button>
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
<FormItem :label-width="20" class="flex">
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
:placeholder="seachTypePlaceholder">
<Option v-for="(item, index) in seachTypes" :key="index" :value="item.key">{{ item.title }}
</Option>
</Select>
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
placeholder="请输入关键字" @on-search="query(1)" />
</FormItem>
<FormItem label="资源类型">
<Select v-model="gridOption.param.seachOption.resource_type" style="width: 120px" clearable @on-change="query(1)">
<Option value="image">图片</Option>
<Option value="video">视频</Option>
<Option value="audio">音频</Option>
<Option value="document">文档</Option>
</Select>
</FormItem>
<FormItem label="状态">
<Select v-model="gridOption.param.seachOption.status" style="width: 120px" clearable @on-change="query(1)">
<Option value="active">正常</Option>
<Option value="inactive">禁用</Option>
<Option value="deleted">已删除</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="query(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="default" @click="exportCsv">导出</Button>
</FormItem>
</Form>
</div>
<div class="table-body">
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
@changePage="query"></tables>
</div>
<Modal v-model="editWarp" :title="editTitle" @on-ok="save" @on-cancel="cancel" width="600">
<Form ref="formValidate" :model="formValidate" :rules="ruleValidate" :label-width="100">
<FormItem v-for="(item, index) in editColumns" :key="index" :label="item.title" :prop="item.key">
<Input v-if="item.type === 'text'" v-model="formValidate[item.key]"
:placeholder="'请输入' + item.title" />
<Input v-else-if="item.type === 'textarea'" v-model="formValidate[item.key]" type="textarea"
:placeholder="'请输入' + item.title" :rows="4" />
<Input v-else-if="item.type === 'number'" v-model="formValidate[item.key]" type="number"
:placeholder="'请输入' + item.title" />
</FormItem>
</Form>
</Modal>
</div>
</template>
<script>
import { getList, add, edit, del, exportData } from '@/api/ball/resources_server'
import uiTool from '@/libs/uiTool'
import funTool from '@/libs/funTool'
export default {
name: 'resources',
data() {
// 定义表单验证规则
const rules = {
resource_name: [{ required: true, message: '请输入资源名称', trigger: 'blur' }],
resource_type: [{ required: true, message: '请输入资源类型', trigger: 'blur' }],
resource_url: [{ required: true, message: '请输入资源链接', trigger: 'blur' }]
}
return {
editWarp: false,
editTitle: '新增',
formValidate: {},
ruleValidate: rules,
seachTypes: [
{ key: 'user_id', title: '用户ID' }
],
gridOption: {
data: [],
param: {
seachOption: {
key: 'user_id',
value: '',
resource_type: null,
status: null
},
pageOption: {
page: 1,
pageSize: 20,
total: 0
}
}
},
listColumns: [
{ title: 'ID', key: 'id' },
{
title: '用户',
key: 'user_id',
render: (h, params) => {
const userInfo = params.row.user_info;
if (userInfo && userInfo.nickname) {
return h('div', [
h('img', {
attrs: {
src: userInfo.avatar_url || '/default-avatar.png',
alt: userInfo.nickname,
title: userInfo.nickname
},
style: {
width: '24px',
height: '24px',
borderRadius: '50%',
marginRight: '8px',
verticalAlign: 'middle'
}
}),
h('span', { style: { verticalAlign: 'middle' } }, userInfo.nickname)
]);
}
return h('span', `用户ID: ${params.row.user_id}`);
}
},
{
title: '资源类型',
key: 'resource_type',
render: (h, params) => {
const typeMap = {
'image': { text: '图片', color: 'blue' },
'video': { text: '视频', color: 'purple' },
'audio': { text: '音频', color: 'orange' },
'document': { text: '文档', color: 'green' },
'other': { text: '其他', color: 'default' }
};
const type = typeMap[params.row.resource_type] || { text: params.row.resource_type || '未知', color: 'default' };
return h('Tag', { props: { color: type.color } }, type.text);
}
},
{
title: '文件预览',
key: 'file_url',
render: (h, params) => {
const fileUrl = params.row.file_url;
const resourceType = params.row.resource_type;
if (!fileUrl) {
return h('span', { style: { color: '#c5c8ce' } }, '无文件');
}
// 图片预览
if (resourceType === 'image') {
return h('img', {
attrs: {
src: fileUrl,
alt: params.row.original_name || '图片',
title: '点击查看大图'
},
style: {
width: '60px',
height: '60px',
objectFit: 'cover',
borderRadius: '4px',
cursor: 'pointer'
},
on: {
click: () => {
window.open(fileUrl, '_blank');
}
}
});
}
// 其他文件类型显示链接
return h('a', {
attrs: {
href: fileUrl,
target: '_blank',
title: '点击下载/查看文件'
},
style: {
color: '#2d8cf0',
textDecoration: 'none'
}
}, '查看文件');
}
},
{ title: '文件大小(字节)', key: 'file_size' },
{ title: 'MIME类型', key: 'mime_type' },
{
title: '是否公开',
key: 'is_public',
render: (h, params) => {
return h('Tag', {
props: { color: params.row.is_public == 1 ? 'green' : 'default' }
}, params.row.is_public == 1 ? '是' : '否');
}
},
{
title: '状态',
key: 'status',
render: (h, params) => {
const statusMap = {
'active': { text: '正常', color: 'green' },
'deleted': { text: '已删除', color: 'red' },
'pending': { text: '待审核', color: 'orange' }
};
const status = statusMap[params.row.status] || { text: params.row.status || '未知', color: 'default' };
return h('Tag', { props: { color: status.color } }, status.text);
}
},
{ title: '创建时间', key: 'create_time' },
{
title: '操作',
key: 'action',
fixed: 'right',
render: (h, params) => {
let btns = [
{
title: '编辑',
type: 'primary',
click: () => {
this.showEditWarp(params.row)
}
},
{
title: '删除',
type: 'error',
click: () => {
this.del(params.row)
}
}
];
return uiTool.getBtn(h, btns);
}
}
],
editColumns: [
{ title: '资源名称', key: 'resource_name', type: 'text', required: true },
{ title: '资源类型', key: 'resource_type', type: 'text', required: true },
{ title: '资源链接', key: 'resource_url', type: 'text', required: true },
{ title: '资源描述', key: 'resource_description', type: 'textarea', required: false }
]
}
},
mounted() {
this.query(1)
},
methods: {
query(page) {
this.gridOption.param.pageOption.page = page
getList(this.gridOption.param).then(res => {
if (res && res.data) {
this.gridOption.data = res.data.rows || res.data
this.gridOption.param.pageOption.total = res.data.count || 0
}
})
},
showAddModal() {
this.editTitle = '新增'
this.formValidate = {}
this.editWarp = true
},
showEditWarp(row) {
this.editTitle = '编辑'
this.formValidate = { ...row }
this.editWarp = true
},
save() {
this.$refs.formValidate.validate((valid) => {
if (valid) {
if (this.formValidate.id) {
edit(this.formValidate).then(res => {
this.$Message.success('编辑成功')
this.editWarp = false
this.query(1)
})
} else {
add(this.formValidate).then(res => {
this.$Message.success('新增成功')
this.editWarp = false
this.query(1)
})
}
}
})
},
cancel() {
this.editWarp = false
},
del(row) {
this.$Modal.confirm({
title: '确认删除',
content: '确定要删除这条记录吗?',
onOk: () => {
del({ id: row.id }).then(res => {
this.$Message.success('删除成功')
this.query(1)
})
}
})
},
exportCsv() {
exportData(this.gridOption.param).then(res => {
funTool.downloadFile(res, '资源管理.csv')
})
},
resetQuery() {
this.gridOption.param.seachOption = {
key: 'user_id',
value: '',
resource_type: null,
status: null
};
this.query(1);
}
},
computed: {
seachTypePlaceholder() {
const selected = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key);
return selected ? selected.title : '请选择搜索类型';
}
}
}
</script>
<style scoped>
.content-view {
padding: 20px;
}
.table-head-tool {
padding: 10px 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.table-body {
margin-top: 10px;
}
.ml10 {
margin-left: 10px;
}
</style>

View File

@@ -0,0 +1,146 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<Button type="primary" @click="showAddWarp">新增</Button>
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
<FormItem :label-width="20" class="flex">
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
:placeholder="seachTypePlaceholder">
<Option v-for="item in seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
placeholder="请输入关键字" @on-search="query(1)" />
</FormItem>
<FormItem>
<Button type="primary" @click="query(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="default" @click="exportCsv">导出</Button>
</FormItem>
</Form>
</div>
<div class="table-body">
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
@changePage="query"></tables>
</div>
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules"> </editModal>
</div>
</template>
<script>
import funTool from '@/libs/funTool'
import uiTool from '@/libs/uiTool'
import recommend_blocksServer from '@/api/users/recommend_blocks_server.js'
export default {
data() {
let rules = {}
rules["user_id"] = [{ required: true, type: "number", message: '请填写用户ID', trigger: 'change' }];
rules["blocked_user_id"] = [{ required: true, type: "number", message: '请填写被屏蔽用户ID', trigger: 'change' }];
rules["block_time"] = [{ required: false, message: '请填写屏蔽时间', trigger: 'change' }];
return {
seachTypes: [
{ key: 'user_id', value: '用户ID' },
{ key: 'blocked_user_id', value: '被屏蔽用户ID' },
{ key: 'nickname', value: '用户昵称' }
],
seachTypePlaceholder: '请选择搜索类型',
gridOption: {
param: {
seachOption: {
key: 'nickname',
value: ''
},
pageOption: {
page: 1,
pageSize: 20
}
},
data: [],
rules: rules
},
listColumns: [
{ title: 'ID', key: 'id', minWidth: 80 },
{ title: '用户ID', key: 'user_id', minWidth: 100 },
{ title: '被屏蔽用户ID', key: 'blocked_user_id', minWidth: 120 },
{ title: '用户昵称', key: 'nickname', minWidth: 150 },
{ title: '被屏蔽用户昵称', key: 'blocked_nickname', minWidth: 150 },
{ title: '屏蔽时间', key: 'block_time', minWidth: 160 },
{ title: '创建时间', key: 'create_time', minWidth: 160 },
{
title: '操作',
key: 'action',
width: 200,
type: 'template',
render: (h, params) => {
let btns = [
{
title: '编辑',
type: 'primary',
click: () => {
this.showEditWarp(params.row)
},
},
{
title: '删除',
type: 'error',
click: () => {
this.delConfirm(params.row)
},
},
]
return uiTool.getBtn(h, btns)
},
}
],
editColumns: [
{ title: '用户ID', key: 'user_id', type: 'number', required: true },
{ title: '被屏蔽用户ID', key: 'blocked_user_id', type: 'number', required: true },
{ title: '屏蔽时间', key: 'block_time', type: 'datetime' }
]
}
},
mounted() {
this.query(1);
},
methods: {
query(page) {
this.gridOption.param.pageOption.page = page;
recommend_blocksServer.page(this.gridOption.param).then(res => {
this.gridOption.data = res.data.rows;
this.gridOption.param.pageOption.total = res.data.count;
});
},
showAddWarp() {
this.$refs.editModal.showModal();
},
showEditWarp(row) {
this.$refs.editModal.showModal(row);
},
delConfirm(row) {
uiTool.delConfirm(async () => {
await recommend_blocksServer.del(row)
rootVue.$Message.success('删除成功!')
this.query(1)
})
},
exportCsv() {
recommend_blocksServer.exportCsv(this.gridOption.param).then(res => {
funTool.downloadFile(res, '推荐屏蔽.csv');
});
},
resetQuery() {
this.gridOption.param.seachOption = {
key: 'nickname',
value: ''
};
this.query(1);
}
},
computed: {
seachTypePlaceholder() {
const selected = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key);
return selected ? selected.value : '请选择搜索类型';
}
}
}
</script>

View File

@@ -0,0 +1,147 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<Button type="primary" @click="showAddWarp">新增</Button>
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
<FormItem :label-width="20" class="flex">
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
:placeholder="seachTypePlaceholder">
<Option v-for="item in seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
placeholder="请输入关键字" @on-search="query(1)" />
</FormItem>
<FormItem>
<Button type="primary" @click="query(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="default" @click="exportCsv">导出</Button>
</FormItem>
</Form>
</div>
<div class="table-body">
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
@changePage="query"></tables>
</div>
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules"> </editModal>
</div>
</template>
<script>
import funTool from '@/libs/funTool'
import uiTool from '@/libs/uiTool'
import menuServer from '@/api/system_high/menuServer.js'
import user_followsServer from '@/api/users/user_follows_server.js'
export default {
data() {
let rules = {}
rules["follower_id"] = [{ required: true, type: "number", message: '请填写关注者ID', trigger: 'change' }];
rules["following_id"] = [{ required: true, type: "number", message: '请填写被关注者ID', trigger: 'change' }];
rules["follow_time"] = [{ required: false, message: '请填写关注时间', trigger: 'change' }];
return {
seachTypes: [
{ key: 'follower_id', value: '关注者ID' },
{ key: 'following_id', value: '被关注者ID' },
{ key: 'nickname', value: '用户昵称' }
],
seachTypePlaceholder: '请选择搜索类型',
gridOption: {
param: {
seachOption: {
key: 'nickname',
value: ''
},
pageOption: {
page: 1,
pageSize: 20
}
},
data: [],
rules: rules
},
listColumns: [
{ title: 'ID', key: 'id', minWidth: 80 },
{ title: '关注者ID', key: 'follower_id', minWidth: 100 },
{ title: '被关注者ID', key: 'following_id', minWidth: 120 },
{ title: '关注者昵称', key: 'nickname', minWidth: 150 },
{ title: '被关注者昵称', key: 'followed_nickname', minWidth: 150 },
{ title: '关注时间', key: 'follow_time', minWidth: 160 },
{ title: '创建时间', key: 'create_time', minWidth: 160 },
{
title: '操作',
key: 'action',
width: 200,
type: 'template',
render: (h, params) => {
let btns = [
{
title: '编辑',
type: 'primary',
click: () => {
this.showEditWarp(params.row)
},
},
{
title: '删除',
type: 'error',
click: () => {
this.delConfirm(params.row)
},
},
]
return uiTool.getBtn(h, btns)
},
}
],
editColumns: [
{ title: '关注者ID', key: 'follower_id', type: 'number', required: true },
{ title: '被关注者ID', key: 'following_id', type: 'number', required: true },
{ title: '关注时间', key: 'follow_time', type: 'datetime' }
]
}
},
mounted() {
this.query(1);
},
methods: {
query(page) {
this.gridOption.param.pageOption.page = page;
user_followsServer.page(this.gridOption.param).then(res => {
this.gridOption.data = res.data.rows;
this.gridOption.param.pageOption.total = res.data.count;
});
},
showAddWarp() {
this.$refs.editModal.showModal();
},
showEditWarp(row) {
this.$refs.editModal.showModal(row);
},
delConfirm(row) {
uiTool.delConfirm(async () => {
await user_followsServer.del(row)
rootVue.$Message.success('删除成功!')
this.query(1)
})
},
exportCsv() {
user_followsServer.exportCsv(this.gridOption.param).then(res => {
funTool.downloadFile(res, '用户关注关系.csv');
});
},
resetQuery() {
this.gridOption.param.seachOption = {
key: 'nickname',
value: ''
};
this.query(1);
}
},
computed: {
seachTypePlaceholder() {
const selected = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key);
return selected ? selected.value : '请选择搜索类型';
}
}
}
</script>

View File

@@ -0,0 +1,173 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<Button type="primary" @click="showAddWarp">新增</Button>
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
<FormItem :label-width="20" class="flex">
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
:placeholder="seachTypePlaceholder">
<Option v-for="item in seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
placeholder="请输入关键字" @on-search="query(1)" />
</FormItem>
<FormItem label="事件类型">
<Select v-model="gridOption.param.seachOption.event_type" style="width: 120px" clearable @on-change="query(1)">
<Option value="page_view">页面浏览</Option>
<Option value="click">点击</Option>
<Option value="search">搜索</Option>
<Option value="share">分享</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="query(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="default" @click="exportCsv">导出</Button>
</FormItem>
</Form>
</div>
<div class="table-body">
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
@changePage="query"></tables>
</div>
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules"> </editModal>
</div>
</template>
<script>
import funTool from '@/libs/funTool'
import uiTool from '@/libs/uiTool'
import user_trackingServer from '@/api/users/user_tracking_server.js'
export default {
data() {
let rules = {}
rules["user_id"] = [{ required: true, type: "number", message: '请填写用户ID', trigger: 'change' }];
rules["event_type"] = [{ required: true, message: '请填写事件类型' }];
rules["event_name"] = [{ required: true, message: '请填写事件名称' }];
rules["page_path"] = [{ required: false, message: '请填写页面路径' }];
rules["duration"] = [{ required: false, type: "number", message: '请填写停留时长', trigger: 'change' }];
return {
seachTypes: [
{ key: 'user_id', value: '用户ID' },
{ key: 'event_name', value: '事件名称' },
{ key: 'nickname', value: '用户昵称' }
],
gridOption: {
param: {
seachOption: {
key: 'nickname',
value: '',
event_type: null
},
pageOption: {
page: 1,
pageSize: 20
}
},
data: [],
rules: rules
},
listColumns: [
{ title: 'ID', key: 'id', minWidth: 80 },
{ title: '用户ID', key: 'user_id', minWidth: 100 },
{ title: '用户昵称', key: 'nickname', minWidth: 120 },
{ title: '事件类型', key: 'event_type', minWidth: 120 },
{ title: '事件名称', key: 'event_name', minWidth: 150 },
{ title: '页面路径', key: 'page_path', minWidth: 200 },
{ title: '停留时长(秒)', key: 'duration', minWidth: 100 },
{ title: 'IP地址', key: 'ip_address', minWidth: 130 },
{ title: '创建时间', key: 'create_time', minWidth: 160 },
{
title: '操作',
key: 'action',
width: 200,
type: 'template',
render: (h, params) => {
let btns = [
{
title: '编辑',
type: 'primary',
click: () => {
this.showEditWarp(params.row)
},
},
{
title: '删除',
type: 'error',
click: () => {
this.delConfirm(params.row)
},
},
]
return uiTool.getBtn(h, btns)
},
}
],
editColumns: [
{ title: '用户ID', key: 'user_id', type: 'number', required: true },
{ title: '事件类型', key: 'event_type', type: 'select', required: true, options: [
{ value: 'user_soure', label: '用户来源' },
{ value: 'page_view', label: '页面访问' },
{ value: 'button_click', label: '按钮点击' },
{ value: 'api_call', label: '接口调用' },
{ value: 'user_action', label: '用户行为' },
{ value: 'form_submit', label: '表单提交' },
{ value: 'search', label: '搜索' },
{ value: 'share', label: '分享' },
{ value: 'error', label: '错误' }
]},
{ title: '事件名称', key: 'event_name', type: 'text', required: true },
{ title: '页面路径', key: 'page_path', type: 'text' },
{ title: '停留时长(秒)', key: 'duration', type: 'number' },
{ title: 'IP地址', key: 'ip_address', type: 'text' },
{ title: '用户代理', key: 'user_agent', type: 'textarea' }
]
}
},
mounted() {
this.query(1);
},
methods: {
query(page) {
this.gridOption.param.pageOption.page = page;
user_trackingServer.page(this.gridOption.param).then(res => {
this.gridOption.data = res.data.rows;
this.gridOption.param.pageOption.total = res.data.count;
});
},
showAddWarp() {
this.$refs.editModal.showModal();
},
showEditWarp(row) {
this.$refs.editModal.showModal(row);
},
delConfirm(row) {
uiTool.delConfirm(async () => {
await user_trackingServer.del(row)
rootVue.$Message.success('删除成功!')
this.query(1)
})
},
exportCsv() {
user_trackingServer.exportCsv(this.gridOption.param).then(res => {
funTool.downloadFile(res, '用户行为追踪.csv');
});
},
resetQuery() {
this.gridOption.param.seachOption = {
key: 'nickname',
value: '',
event_type: null
};
this.query(1);
}
},
computed: {
seachTypePlaceholder() {
const selected = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key);
return selected ? selected.value : '请选择搜索类型';
}
}
}
</script>