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

14
demo/.babelrc Normal file
View File

@@ -0,0 +1,14 @@
{
"presets": [
[
"@babel/preset-env",
{
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}
]
]
}

24
demo/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# 依赖
node_modules/
# 构建输出
dist/
# 日志
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# 编辑器
.vscode/
.idea/
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# 系统文件
.DS_Store
Thumbs.db

351
demo/README-LOCAL.md Normal file
View File

@@ -0,0 +1,351 @@
# Admin Framework Demo - 本地版本
本地开发版本,不依赖 CDN所有依赖都从 node_modules 加载。
## 目录结构
```
demo/
├── public/ # 静态资源
│ └── index.html # HTML 模板
├── src/ # 源代码
│ ├── components/ # 自定义组件
│ │ └── CustomPage.vue
│ ├── main.js # 基础示例入口
│ └── main-advanced.js # 高级示例入口
├── package.json # 依赖配置
├── webpack.config.js # Webpack 配置
└── .babelrc # Babel 配置
```
## 快速开始
### 1. 构建 Admin Framework
首先需要构建框架(在项目根目录执行):
```bash
# 回到项目根目录
cd ..
# 生产构建
npm run build
# 或开发构建(有 sourcemap
npm run build:dev
```
### 2. 安装 Demo 依赖
```bash
# 进入 demo 目录
cd demo
# 安装依赖
npm install
```
### 3. 启动开发服务器
```bash
# 启动基础示例
npm run dev
# 浏览器会自动打开 http://localhost:8080
```
### 4. 构建生产版本
```bash
# 构建
npm run build
# 生成的文件在 demo/dist 目录
```
## 切换示例
### 基础示例(默认)
使用 `src/main.js` 作为入口,展示基本功能。
### 高级示例
要切换到高级示例,修改 `webpack.config.js`
```javascript
module.exports = {
entry: './src/main-advanced.js', // 改为 main-advanced.js
// ... 其他配置
}
```
然后重新运行 `npm run dev`
## 依赖说明
### 生产依赖
- **vue**: ^2.6.14 - Vue 核心库
- **vue-router**: ^3.5.3 - 路由管理
- **vuex**: ^3.6.2 - 状态管理
- **view-design**: ^4.7.0 - UI 组件库
- **axios**: ^0.27.2 - HTTP 客户端
### 开发依赖
- **webpack**: ^5.0.0 - 模块打包工具
- **webpack-dev-server**: ^4.0.0 - 开发服务器
- **babel-loader**: ^8.2.0 - ES6+ 转译
- **vue-loader**: ^15.9.0 - Vue 单文件组件加载器
- **html-webpack-plugin**: ^5.5.0 - HTML 生成插件
## 开发指南
### 1. 添加自定义页面
`src/components` 创建新组件:
```vue
<!-- src/components/MyPage.vue -->
<template>
<div>
<h1>我的页面</h1>
</div>
</template>
<script>
export default {
name: 'MyPage'
}
</script>
```
### 2. 注册到路由
`src/main.js``src/main-advanced.js` 中:
```javascript
import MyPage from './components/MyPage.vue'
const customRoutes = [
{
path: '/my',
component: AdminFramework.Main,
children: [
{
path: 'page',
component: MyPage,
meta: { title: '我的页面' }
}
]
}
]
customRoutes.forEach(route => {
AdminFramework.router.addRoute(route)
})
```
### 3. 添加 Vuex 模块
```javascript
const myModule = {
namespaced: true,
state: { data: null },
mutations: {
SET_DATA(state, data) {
state.data = data
}
},
actions: {
fetchData({ commit }) {
// 获取数据逻辑
}
}
}
AdminFramework.store.registerModule('myModule', myModule)
```
### 4. 使用框架提供的组件
```vue
<template>
<div>
<!-- 表格组件 -->
<Tables
:columns="columns"
:data="tableData"
/>
<!-- 上传组件 -->
<UploadSingle
v-model="fileUrl"
:action="uploadUrl"
/>
<!-- 富文本编辑器 -->
<Editor v-model="content" />
</div>
</template>
<script>
export default {
data() {
return {
columns: [],
tableData: [],
fileUrl: '',
content: '',
uploadUrl: this.$config.uploadUrl
}
}
}
</script>
```
## API 配置
修改 `src/main.js` 中的配置:
```javascript
const config = {
title: '系统标题',
apiUrl: 'http://your-api.com/api/',
uploadUrl: 'http://your-api.com/api/upload'
}
```
## HTTP 请求示例
### 在组件中使用
```javascript
export default {
methods: {
async fetchData() {
try {
// GET 请求
const res = await this.$http.get('/api/users')
console.log(res.data)
// POST 请求
const res2 = await this.$http.post('/api/users', {
name: '张三',
age: 25
})
console.log(res2.data)
} catch (error) {
this.$Message.error('请求失败')
}
}
}
}
```
## 工具函数使用
```javascript
export default {
methods: {
example() {
// 日期格式化
const formatted = this.$tools.formatDate(new Date(), 'yyyy-MM-dd')
// 深拷贝
const copy = this.$tools.deepClone(obj)
// 防抖
const debounced = this.$tools.debounce(() => {
console.log('执行')
}, 500)
// 节流
const throttled = this.$tools.throttle(() => {
console.log('执行')
}, 1000)
}
}
}
```
## UI 工具使用
```javascript
export default {
methods: {
example() {
// 成功提示
this.$uiTool.success('操作成功')
// 错误提示
this.$uiTool.error('操作失败')
// 警告提示
this.$uiTool.warning('警告信息')
// 确认对话框
this.$uiTool.confirm('确定删除吗?').then(() => {
// 确认后的操作
}).catch(() => {
// 取消后的操作
})
// Loading
const loading = this.$uiTool.loading('加载中...')
setTimeout(() => loading.close(), 2000)
}
}
}
```
## 常见问题
### 1. 模块找不到
确保先构建了 admin-framework
```bash
cd .. && npm run build && cd demo
```
### 2. 端口被占用
修改 `webpack.config.js` 中的端口:
```javascript
devServer: {
port: 8081, // 改为其他端口
// ...
}
```
### 3. 热更新不生效
检查 `devServer.hot` 配置是否为 `true`
### 4. 样式不生效
确保在入口文件中引入了 iView 样式:
```javascript
import 'view-design/dist/styles/iview.css'
```
## 生产部署
### 1. 构建
```bash
npm run build
```
### 2. 部署
`demo/dist` 目录下的文件部署到服务器。
### 3. 注意事项
- 确保后端 API 配置正确
- 配置 Nginx 或其他服务器的路由重写规则(支持 Vue Router 的 history 模式)
## 更多示例
- 基础示例: 运行 `npm run dev`(默认)
- 高级示例: 修改 webpack.config.js 的 entry 为 `./src/main-advanced.js`
## 相关链接
- [Admin Framework 完整文档](../_doc/完整使用文档.md)
- [Vue 官方文档](https://cn.vuejs.org/)
- [Vue Router 文档](https://router.vuejs.org/zh/)
- [Vuex 文档](https://vuex.vuejs.org/zh/)
- [iView 文档](https://www.iviewui.com/)

251
demo/README.md Normal file
View File

@@ -0,0 +1,251 @@
# Admin Framework Demo
本目录包含 Admin Framework 的使用示例,提供两种使用方式:
## 📁 文件说明
### 🌐 CDN 版本(快速体验)
- **index.html** - 基础示例CDN
- **advanced.html** - 高级示例CDN
适合快速体验,所有依赖从 CDN 加载,无需安装。
### 💻 本地开发版本(推荐开发使用)
- **src/** - 源代码目录
- **main.js** - 基础示例入口
- **main-advanced.js** - 高级示例入口
- **components/** - 自定义组件
- **package.json** - 依赖配置
- **webpack.config.js** - 构建配置
所有依赖本地安装,支持热更新,适合开发调试。
---
## 🚀 使用方式
### 方式一CDN 版本(快速体验)
#### index.html - 基础示例
最简单的使用示例,展示如何:
- 引入必要的依赖
- 初始化框架
- 创建基本应用
#### advanced.html - 高级示例
完整的使用示例,展示如何:
- 添加自定义页面组件
- 注册自定义 Vuex 模块
- 添加自定义路由
- 配置路由守卫
- 配置 Axios 拦截器
- 使用组件映射
### 方式二:本地开发版本(推荐)
查看详细文档:[README-LOCAL.md](./README-LOCAL.md)
快速开始:
```bash
# 1. 构建框架(在项目根目录)
cd ..
npm run build
# 2. 安装 demo 依赖
cd demo
npm install
# 3. 启动开发服务器
npm run dev
```
## 使用步骤
### 1. 构建框架
首先需要构建 admin-framework
```bash
# 生产构建(压缩,无 sourcemap
npm run build
# 开发构建(不压缩,有 sourcemap
npm run build:dev
```
### 2. 启动示例
有以下几种方式启动示例:
#### 方式一:使用 Live Server推荐
1. 安装 VS Code 的 Live Server 插件
2. 右键 `index.html``advanced.html`
3. 选择 "Open with Live Server"
#### 方式二:使用 HTTP 服务器
```bash
# 安装 http-server
npm install -g http-server
# 在项目根目录运行
http-server
# 访问
# http://localhost:8080/demo/index.html
# http://localhost:8080/demo/advanced.html
```
#### 方式三:直接打开
- 双击 HTML 文件在浏览器中打开
- 注意:某些功能可能因跨域限制无法使用
## 配置说明
### 基本配置
```javascript
const config = {
title: '系统标题',
apiUrl: 'http://your-api.com/api/', // API 基础地址
uploadUrl: 'http://your-api.com/api/upload' // 上传接口地址
}
```
### 初始化框架
```javascript
framework.install(Vue, {
config: config, // 配置对象
ViewUI: iview, // iView 实例
VueRouter: VueRouter, // Vue Router
Vuex: Vuex, // Vuex
createPersistedState: null, // Vuex 持久化插件(可选)
componentMap: {} // 自定义组件映射
})
```
## 内置功能
### 1. 系统页面
- **登录页面**: `/login`
- **首页**: `/home`
- **错误页面**: `/401`, `/404`, `/500`
### 2. 系统管理
- **用户管理**: 系统用户的增删改查
- **角色管理**: 角色权限管理
- **菜单管理**: 动态菜单配置
- **日志管理**: 系统操作日志
### 3. 高级功能
- **动态表单**: 基于配置生成表单
- **动态表格**: 可配置的数据表格
- **文件上传**: 单文件/多文件上传
- **富文本编辑器**: WangEditor
- **代码编辑器**: Ace Editor
## API 使用
### HTTP 请求
```javascript
// GET 请求
framework.http.get('/api/users').then(res => {
console.log(res.data)
})
// POST 请求
framework.http.post('/api/users', {
name: '张三',
age: 25
}).then(res => {
console.log(res.data)
})
// 在组件中使用
this.$http.get('/api/users').then(res => {
console.log(res.data)
})
```
### 工具函数
```javascript
// 使用框架提供的工具函数
const tools = framework.tools
// 日期格式化
tools.formatDate(new Date(), 'yyyy-MM-dd HH:mm:ss')
// 深拷贝
tools.deepClone(obj)
// 防抖
tools.debounce(fn, 500)
// 节流
tools.throttle(fn, 500)
```
### UI 工具
```javascript
// 使用 UI 工具
const uiTool = framework.uiTool
// 成功提示
uiTool.success('操作成功')
// 错误提示
uiTool.error('操作失败')
// 确认对话框
uiTool.confirm('确定删除吗?').then(() => {
// 确认后的操作
})
```
## 常见问题
### 1. 依赖库版本
确保使用以下版本的依赖库:
- Vue: 2.6.x
- Vue Router: 3.x
- Vuex: 3.x
- iView (view-design): 4.x
- Axios: 0.21.x+
### 2. 路径问题
如果无法加载 admin-framework.js检查路径是否正确
```html
<!-- 确保路径指向正确的文件 -->
<script src="../dist/admin-framework.js"></script>
```
### 3. API 地址
记得修改配置中的 API 地址为实际的后端地址:
```javascript
const config = {
apiUrl: 'http://your-real-api.com/api/',
uploadUrl: 'http://your-real-api.com/api/upload'
}
```
### 4. 跨域问题
如果遇到跨域问题,需要配置后端 CORS 或使用代理。
## 开发建议
1. **开发时使用 build:dev**
- 生成 sourcemap方便调试
- 代码不压缩,易读
2. **生产时使用 build**
- 代码压缩,体积小
- 无 sourcemap安全
3. **使用浏览器调试工具**
```javascript
// 所有实例都挂载到 window 上,方便调试
window.app // Vue 实例
window.framework // 框架实例
```
## 更多信息
查看完整文档:`../_doc/完整使用文档.md`

218
demo/advanced.html Normal file
View File

@@ -0,0 +1,218 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Framework - 高级示例</title>
<!-- 引入 iView 样式 -->
<link rel="stylesheet" href="https://unpkg.com/view-design@4.7.0/dist/styles/iview.css">
<style>
body {
margin: 0;
padding: 0;
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;
}
#app {
height: 100vh;
}
</style>
</head>
<body>
<div id="app"></div>
<!-- 引入依赖库 -->
<script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router@3.5.3/dist/vue-router.js"></script>
<script src="https://unpkg.com/vuex@3.6.2/dist/vuex.js"></script>
<script src="https://unpkg.com/view-design@4.7.0/dist/iview.js"></script>
<script src="https://unpkg.com/axios@0.27.2/dist/axios.js"></script>
<!-- 引入 admin-framework -->
<script src="../dist/admin-framework.js"></script>
<script>
// 获取框架实例
const framework = window.AdminFramework
// ========== 1. 自定义页面组件 ==========
const CustomPage = {
name: 'CustomPage',
template: `
<div style="padding: 20px;">
<Card>
<p slot="title">自定义页面</p>
<p>这是一个自定义页面示例</p>
<p>当前时间: {{ currentTime }}</p>
<Button type="primary" @click="handleClick">点击测试</Button>
</Card>
</div>
`,
data() {
return {
currentTime: new Date().toLocaleString()
}
},
methods: {
handleClick() {
this.$Message.success('按钮点击成功!')
}
},
mounted() {
// 定时更新时间
this.timer = setInterval(() => {
this.currentTime = new Date().toLocaleString()
}, 1000)
},
beforeDestroy() {
clearInterval(this.timer)
}
}
// ========== 2. 自定义 Vuex 模块 ==========
const customModule = {
namespaced: true,
state: {
customData: '自定义数据'
},
getters: {
customData: state => state.customData
},
mutations: {
SET_CUSTOM_DATA(state, data) {
state.customData = data
}
},
actions: {
updateCustomData({ commit }, data) {
commit('SET_CUSTOM_DATA', data)
}
}
}
// ========== 3. 配置参数 ==========
const config = {
title: 'Admin Framework 高级示例',
apiUrl: 'http://localhost:3000/api/',
uploadUrl: 'http://localhost:3000/api/upload',
// 其他自定义配置
theme: 'light',
language: 'zh-CN'
}
// ========== 4. 组件映射(用于动态路由加载) ==========
const componentMap = {
'custom/page': CustomPage,
'custom/page.vue': CustomPage
}
// ========== 5. 初始化框架 ==========
framework.install(Vue, {
config: config,
ViewUI: iview,
VueRouter: VueRouter,
Vuex: Vuex,
createPersistedState: null,
componentMap: componentMap
})
// ========== 6. 添加自定义 Vuex 模块 ==========
framework.store.registerModule('custom', customModule)
// ========== 7. 添加自定义路由 ==========
const customRoutes = [
{
path: '/custom',
component: framework.Main, // 使用框架的主布局
children: [
{
path: 'page',
name: 'custom_page',
component: CustomPage,
meta: {
title: '自定义页面',
hideInMenu: false
}
}
]
}
]
// 添加路由
customRoutes.forEach(route => {
framework.router.addRoute(route)
})
// ========== 8. 路由守卫(可选) ==========
framework.router.beforeEach((to, from, next) => {
// 可以在这里添加权限验证等逻辑
console.log('路由跳转:', from.path, '->', to.path)
next()
})
// ========== 9. Axios 拦截器配置(可选) ==========
framework.http.interceptors.request.use(
config => {
// 在请求发送前做些什么
console.log('发送请求:', config.url)
return config
},
error => {
return Promise.reject(error)
}
)
framework.http.interceptors.response.use(
response => {
// 对响应数据做些什么
console.log('收到响应:', response.config.url)
return response
},
error => {
// 对响应错误做些什么
console.error('请求错误:', error)
return Promise.reject(error)
}
)
// ========== 10. 创建 Vue 实例 ==========
const app = new Vue({
router: framework.router,
store: framework.store,
render: h => h('router-view'),
created() {
console.log('=== Admin Framework 高级示例启动 ===')
console.log('配置信息:', this.$config)
console.log('可用路由:', this.$router.options.routes)
}
})
// ========== 11. 挂载应用 ==========
app.$mount('#app')
// ========== 12. 全局暴露(方便调试) ==========
window.app = app
window.rootVue = app
window.framework = framework
// ========== 13. 打印框架信息 ==========
console.log('框架版本:', framework.version)
console.log('框架实例:', framework)
console.log('路由实例:', framework.router)
console.log('Store 实例:', framework.store)
console.log('工具函数:', framework.tools)
console.log('UI 工具:', framework.uiTool)
console.log('HTTP 实例:', framework.http)
// ========== 14. 提示信息 ==========
setTimeout(() => {
iview.Message.success({
content: 'Admin Framework 高级示例启动成功!',
duration: 3
})
}, 500)
</script>
</body>
</html>

77
demo/index.html Normal file
View File

@@ -0,0 +1,77 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Framework Demo</title>
<!-- 引入 iView 样式 -->
<link rel="stylesheet" href="https://unpkg.com/view-design@4.7.0/dist/styles/iview.css">
<style>
body {
margin: 0;
padding: 0;
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;
}
#app {
height: 100vh;
}
</style>
</head>
<body>
<div id="app"></div>
<!-- 引入依赖库 -->
<script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router@3.5.3/dist/vue-router.js"></script>
<script src="https://unpkg.com/vuex@3.6.2/dist/vuex.js"></script>
<script src="https://unpkg.com/view-design@4.7.0/dist/iview.js"></script>
<script src="https://unpkg.com/axios@0.27.2/dist/axios.js"></script>
<!-- 引入 admin-framework -->
<script src="../dist/admin-framework.js"></script>
<script>
// 获取框架实例
const framework = window.AdminFramework
// 配置参数
const config = {
title: 'Admin Framework Demo',
apiUrl: 'http://localhost:3000/api/', // 修改为你的 API 地址
uploadUrl: 'http://localhost:3000/api/upload' // 修改为你的上传地址
}
// 初始化框架
framework.install(Vue, {
config: config,
ViewUI: iview,
VueRouter: VueRouter,
Vuex: Vuex,
createPersistedState: null, // 如需持久化,需引入 vuex-persistedstate
componentMap: {} // 可添加自定义组件映射
})
// 创建 Vue 实例
const app = new Vue({
router: framework.router,
store: framework.store,
render: h => h('router-view')
})
// 挂载应用
app.$mount('#app')
// 将 app 实例挂载到 window方便调试
window.app = app
window.rootVue = app
console.log('Admin Framework Demo 启动成功!')
console.log('框架实例:', framework)
console.log('路由实例:', framework.router)
console.log('Store 实例:', framework.store)
</script>
</body>
</html>

7257
demo/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

33
demo/package.json Normal file
View File

@@ -0,0 +1,33 @@
{
"name": "admin-framework-demo",
"version": "1.0.0",
"description": "Admin Framework 本地示例",
"scripts": {
"install:deps": "npm install",
"dev": "webpack serve --mode development --open",
"build": "webpack --mode production"
},
"dependencies": {
"axios": "^0.27.2",
"view-design": "^4.7.0",
"vue": "^2.6.14",
"vue-router": "^3.5.3",
"vuex": "^3.6.2"
},
"devDependencies": {
"@babel/core": "^7.12.0",
"@babel/preset-env": "^7.12.0",
"babel-loader": "^8.2.0",
"css-loader": "^5.0.0",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0",
"style-loader": "^2.0.0",
"vue-loader": "^15.9.0",
"vue-style-loader": "^4.1.0",
"vue-template-compiler": "^2.6.14",
"webpack": "^5.0.0",
"webpack-cli": "^4.0.0",
"webpack-dev-server": "^4.0.0"
}
}

12
demo/public/index.html Normal file
View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Framework Demo</title>
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@@ -0,0 +1,21 @@
import http from "@/libs/http";
class SysAdServer {
async getAll(param) {
return await http.get("/sys_ad/index", param);
}
async add(row) {
return await http.post("/sys_ad/add", row);
}
async edit(row) {
return await http.post("/sys_ad/edit", row);
}
async del(row) {
return await http.post("/sys_ad/del", row);
}
}
const sysAdServer = new SysAdServer();
export default sysAdServer;

View File

@@ -0,0 +1,35 @@
import http from '@/libs/http';
class ai_messagesClServer {
async all(param) {
let res= await http.get('/ai_messages/all', param);
return res;
}
async page(row) {
let res= await http.post('/ai_messages/page', row);
return res;
}
async exportCsv(row) {
let res = http.fileExport("/ai_messages/export", row);
return res;
}
async add(row) {
let res= await http.post('/ai_messages/add', row);
return res;
}
async edit(row) {
let res= await http.post('/ai_messages/edit', row);
return res;
}
async del(row) {
let res= await http.post('/ai_messages/del', row);
return res;
}
}
const ai_messagesServer = new ai_messagesClServer();
export default ai_messagesServer;

View File

@@ -0,0 +1,53 @@
import http from '@/libs/http'
export default {
// 获取球局评论列表
getGameCommentsList: (params) => {
return http.post('/game_comments/page', params)
},
// 获取所有球局评论
getAllGameComments: (params) => {
return http.get('/game_comments/all', params)
},
// 获取球局评论详情
getGameCommentDetail: (params) => {
return http.get('/game_comments/detail', params)
},
// 新增球局评论
addGameComment: (params) => {
return http.post('/game_comments/add', params)
},
// 更新球局评论
updateGameComment: (params) => {
return http.post('/game_comments/edit', params)
},
// 删除球局评论
deleteGameComment: (params) => {
return http.post('/game_comments/del', params)
},
// 批量删除球局评论
batchDeleteGameComments: (params) => {
return http.post('/game_comments/batch_delete', params)
},
// 导出球局评论数据
exportGameComments: (params) => {
return http.post('/game_comments/export', params)
},
// 审核评论
auditComment: (params) => {
return http.post('/game_comments/audit', params)
},
// 获取评论回复
getCommentReplies: (params) => {
return http.get('/game_comments/replies', params)
}
}

View File

@@ -0,0 +1,48 @@
import http from '@/libs/http'
export default {
// 获取球局参与者列表
getGameParticipantsList: (params) => {
return http.post('/game_participants/page', params)
},
// 获取所有球局参与者
getAllGameParticipants: (params) => {
return http.get('/game_participants/all', params)
},
// 获取球局参与者详情
getGameParticipantDetail: (params) => {
return http.get('/game_participants/detail', params)
},
// 新增球局参与者
addGameParticipant: (params) => {
return http.post('/game_participants/add', params)
},
// 更新球局参与者
updateGameParticipant: (params) => {
return http.post('/game_participants/edit', params)
},
// 删除球局参与者
deleteGameParticipant: (params) => {
return http.post('/game_participants/del', params)
},
// 批量删除球局参与者
batchDeleteGameParticipants: (params) => {
return http.post('/game_participants/batch_delete', params)
},
// 导出球局参与者数据
exportGameParticipants: (params) => {
return http.post('/game_participants/export', params)
},
// 更新参与者状态
updateParticipantStatus: (params) => {
return http.post('/game_participants/update_status', params)
}
}

View File

@@ -0,0 +1,50 @@
import http from '@/libs/http';
class gamesClServer {
async all(param) {
let res= await http.get('/gme_games/all', param);
return res;
}
async page(row) {
let res= await http.post('/gme_games/page', row);
return res;
}
async exportCsv(row) {
let res = http.fileExport("/gme_games/export", row);
return res;
}
async add(row) {
let res= await http.post('/gme_games/add', row);
return res;
}
async edit(row) {
let res= await http.post('/gme_games/edit', row);
return res;
}
async del(row) {
let res= await http.post('/gme_games/del', row);
return res;
}
async cancel(row) {
let res = await http.post('/gme_games/cancel', row);
return res;
}
async updateStatus(row) {
let res = await http.post('/gme_games/update_status', row);
return res;
}
async statistics() {
let res = await http.get('/gme_games/statistics', {});
return res;
}
}
const gamesServer = new gamesClServer();
export default gamesServer;

View File

@@ -0,0 +1,38 @@
import http from '@/libs/http';
class paymentOrdersClServer {
async all(param) {
let res= await http.get('/pay_orders/all', param);
return res;
}
async page(row) {
let res= await http.post('/pay_orders/page', row);
return res;
}
async exportCsv(row) {
let res = http.fileExport("/pay_orders/export", row);
return res;
}
async add(row) {
let res= await http.post('/pay_orders/add', row);
return res;
}
async edit(row) {
let res= await http.post('/pay_orders/edit', row);
return res;
}
async del(row) {
let res= await http.post('/pay_orders/del', row);
return res;
}
}
const paymentOrdersServer = new paymentOrdersClServer();
export default paymentOrdersServer;

View File

@@ -0,0 +1,36 @@
import http from '@/libs/http'
// 获取资源列表(分页)
export function getList(params) {
return http.post('/gal_resources/page', params)
}
// 添加资源
export function add(params) {
return http.post('/gal_resources/add', params)
}
// 编辑资源
export function edit(params) {
return http.post('/gal_resources/edit', params)
}
// 删除资源
export function del(params) {
return http.post('/gal_resources/del', params)
}
// 导出资源
export function exportData(params) {
return http.fileExport('/gal_resources/export', params)
}
// 获取所有资源
export function getAll(params) {
return http.get('/gal_resources/all', params)
}
// 获取资源详情
export function getDetail(params) {
return http.get('/gal_resources/detail', params)
}

View File

@@ -0,0 +1,38 @@
import http from '@/libs/http';
class venuesClServer {
async all(param) {
let res= await http.get('/ven_venues/all', param);
return res;
}
async page(row) {
let res= await http.post('/ven_venues/page', row);
return res;
}
async exportCsv(row) {
let res = http.fileExport("/ven_venues/export", row);
return res;
}
async add(row) {
let res= await http.post('/ven_venues/add', row);
return res;
}
async edit(row) {
let res= await http.post('/ven_venues/edit', row);
return res;
}
async del(row) {
let res= await http.post('/ven_venues/del', row);
return res;
}
}
const venuesServer = new venuesClServer();
export default venuesServer;

View File

@@ -0,0 +1,36 @@
import http from '@/libs/http';
class walletTransactionsClServer {
async all(param) {
let res= await http.get('/wch_wallet_transactions/all', param);
return res;
}
async page(row) {
let res= await http.post('/wch_wallet_transactions/page', row);
return res;
}
async exportCsv(row) {
let res = http.fileExport("/wch_wallet_transactions/export", row);
return res;
}
async detail(id) {
let res= await http.get('/wch_wallet_transactions/detail', { id });
return res;
}
async edit(row) {
let res= await http.post('/wch_wallet_transactions/edit', row);
return res;
}
async statistics() {
let res = await http.get('/wch_wallet_transactions/statistics', {});
return res;
}
}
const walletTransactionsServer = new walletTransactionsClServer();
export default walletTransactionsServer;

View File

@@ -0,0 +1,36 @@
import http from '@/libs/http';
class walletsClServer {
async all(param) {
let res= await http.get('/wch_wallets/all', param);
return res;
}
async page(row) {
let res= await http.post('/wch_wallets/page', row);
return res;
}
async exportCsv(row) {
let res = http.fileExport("/wch_wallets/export", row);
return res;
}
async edit(row) {
let res= await http.post('/wch_wallets/edit', row);
return res;
}
async adjustBalance(row) {
let res= await http.post('/wch_wallets/adjust_balance', row);
return res;
}
async statistics() {
let res = await http.get('/wch_wallets/statistics', {});
return res;
}
}
const walletsServer = new walletsClServer();
export default walletsServer;

View File

@@ -0,0 +1,38 @@
import http from '@/libs/http';
class wch_usersClServer {
async all(param) {
let res= await http.get('/wch_users/all', param);
return res;
}
async page(row) {
let res= await http.post('/wch_users/page', row);
return res;
}
async exportCsv(row) {
let res = http.fileExport("/wch_users/export", row);
return res;
}
async add(row) {
let res= await http.post('/wch_users/add', row);
return res;
}
async edit(row) {
let res= await http.post('/wch_users/edit', row);
return res;
}
async del(row) {
let res= await http.post('/wch_users/del', row);
return res;
}
}
const wch_usersServer = new wch_usersClServer();
export default wch_usersServer;

View File

@@ -0,0 +1,33 @@
import request from '@/libs/http'
export const getList = (params) => {
return request({
url: '/business/games/page',
method: 'get',
params
})
}
export const add = (data) => {
return request({
url: '/business/games',
method: 'post',
data
})
}
export const edit = (data) => {
return request({
url: '/business/games',
method: 'put',
data
})
}
export const del = (params) => {
return request({
url: '/business/games',
method: 'delete',
params
})
}

View File

@@ -0,0 +1,48 @@
import http from '@/libs/http'
export default {
// 获取热门城市二维码配置列表
getHotCityQrConfigsList: (params) => {
return http.post('/wch_hot_city_qr_config/page', params)
},
// 获取所有热门城市二维码配置
getAllHotCityQrConfigs: (params) => {
return http.get('/wch_hot_city_qr_config/all', params)
},
// 获取热门城市二维码配置详情
getHotCityQrConfigDetail: (params) => {
return http.get('/wch_hot_city_qr_config/detail', params)
},
// 新增热门城市二维码配置
addHotCityQrConfig: (params) => {
return http.post('/wch_hot_city_qr_config/add', params)
},
// 更新热门城市二维码配置
updateHotCityQrConfig: (params) => {
return http.post('/wch_hot_city_qr_config/edit', params)
},
// 删除热门城市二维码配置
deleteHotCityQrConfig: (params) => {
return http.post('/wch_hot_city_qr_config/del', params)
},
// 批量删除热门城市二维码配置
batchDeleteHotCityQrConfigs: (params) => {
return http.post('/wch_hot_city_qr_config/batch_delete', params)
},
// 导出热门城市二维码配置
exportHotCityQrConfigs: (params) => {
return http.post('/wch_hot_city_qr_config/export', params)
},
// 生成二维码
generateQRCode: (params) => {
return http.post('/wch_hot_city_qr_config/generate_qr', params)
}
}

View File

@@ -0,0 +1,39 @@
import http from '@/libs/http'
export default {
// 获取列表(分页)
page(params) {
return http.get('/wch_hot_city_qr_config/list', params)
},
// 获取所有(不分页)
all() {
return http.get('/wch_hot_city_qr_config/all')
},
// 获取详情
detail(id) {
return http.get(`/wch_hot_city_qr_config/detail/${id}`)
},
// 新增
add(data) {
return http.post('/wch_hot_city_qr_config/create', data)
},
// 编辑
edit(id, data) {
return http.post(`/wch_hot_city_qr_config/update/${id}`, data)
},
// 删除
del(id) {
return http.post(`/wch_hot_city_qr_config/delete/${id}`)
},
// 批量删除
batchDel(ids) {
return http.post('/wch_hot_city_qr_config/batch_delete', { ids })
}
}

View File

@@ -0,0 +1,38 @@
import http from '@/libs/http';
class infoClServer {
async all(param) {
let res= await http.get('/inf_info/all', param);
return res;
}
async page(row) {
let res= await http.post('/inf_info/page', row);
return res;
}
async exportCsv(row) {
let res = http.fileExport("/inf_info/export", row);
return res;
}
async add(row) {
let res= await http.post('/inf_info/add', row);
return res;
}
async edit(row) {
let res= await http.post('/inf_info/edit', row);
return res;
}
async del(row) {
let res= await http.post('/inf_info/del', row);
return res;
}
}
const infoServer = new infoClServer();
export default infoServer;

View File

@@ -0,0 +1,38 @@
import http from '@/libs/http';
class info_typeClServer {
async all(param) {
let res= await http.get('/inf_info_type/all', param);
return res;
}
async page(row) {
let res= await http.post('/inf_info_type/page', row);
return res;
}
async exportCsv(row) {
let res = http.fileExport("/inf_info_type/export", row);
return res;
}
async add(row) {
let res= await http.post('/inf_info_type/add', row);
return res;
}
async edit(row) {
let res= await http.post('/inf_info_type/edit', row);
return res;
}
async del(row) {
let res= await http.post('/inf_info_type/del', row);
return res;
}
}
const info_typeServer = new info_typeClServer();
export default info_typeServer;

View File

@@ -0,0 +1,26 @@
import http from "@/libs/http";
class HomeServer {
// 获取订单统计
async getOderCount() {
let res = await http.get("/order/count");
return res;
}
async getUserCount() {
let res = await http.get("/user/count");
return res;
}
async getSalesRank() {
let res = await http.get("/index/salesRank");
return res;
}
async userRecommendRank() {
let res = await http.get("/index/userRecommendRank");
return res;
}
}
const homeServer = new HomeServer();
export default homeServer;

View File

@@ -0,0 +1,28 @@
import http from '@/libs/http'
export default {
// 获取消息通知列表
page: (params) => {
return http.post('/msg_notifications/page', params)
},
// 新增消息通知
add: (params) => {
return http.post('/msg_notifications/add', params)
},
// 更新消息通知
edit: (params) => {
return http.post('/msg_notifications/edit', params)
},
// 删除消息通知
del: (params) => {
return http.post('/msg_notifications/del', params)
},
// 导出消息通知数据
exportCsv: (params) => {
return http.get('/msg_notifications/export', params)
}
}

View File

@@ -0,0 +1,35 @@
import http from '@/libs/http';
class ntr_questionsClServer {
async all(param) {
let res= await http.get('/ntr_questions/all', param);
return res;
}
async page(row) {
let res= await http.post('/ntr_questions/page', row);
return res;
}
async exportCsv(row) {
let res = http.fileExport("/ntr_questions/export", row);
return res;
}
async add(row) {
let res= await http.post('/ntr_questions/add', row);
return res;
}
async edit(row) {
let res= await http.post('/ntr_questions/edit', row);
return res;
}
async del(row) {
let res= await http.post('/ntr_questions/del', row);
return res;
}
}
const ntr_questionsServer = new ntr_questionsClServer();
export default ntr_questionsServer;

View File

@@ -0,0 +1,35 @@
import http from '@/libs/http';
class ntr_recordsClServer {
async all(param) {
let res= await http.get('/ntr_records/all', param);
return res;
}
async page(row) {
let res= await http.post('/ntr_records/page', row);
return res;
}
async exportCsv(row) {
let res = http.fileExport("/ntr_records/export", row);
return res;
}
async add(row) {
let res= await http.post('/ntr_records/add', row);
return res;
}
async edit(row) {
let res= await http.post('/ntr_records/edit', row);
return res;
}
async del(row) {
let res= await http.post('/ntr_records/del', row);
return res;
}
}
const ntr_recordsServer = new ntr_recordsClServer();
export default ntr_recordsServer;

View File

@@ -0,0 +1,53 @@
import http from '@/libs/http'
export default {
// 获取冻结资金列表
getFrozenFundsList: (params) => {
return http.post('/frozen_funds/page', params)
},
// 获取所有冻结资金
getAllFrozenFunds: (params) => {
return http.get('/frozen_funds/all', params)
},
// 获取冻结资金详情
getFrozenFundDetail: (params) => {
return http.get('/frozen_funds/detail', params)
},
// 新增冻结资金
addFrozenFund: (params) => {
return http.post('/frozen_funds/add', params)
},
// 更新冻结资金
updateFrozenFund: (params) => {
return http.post('/frozen_funds/edit', params)
},
// 删除冻结资金
deleteFrozenFund: (params) => {
return http.post('/frozen_funds/del', params)
},
// 批量删除冻结资金
batchDeleteFrozenFunds: (params) => {
return http.post('/frozen_funds/batch_delete', params)
},
// 导出冻结资金数据
exportFrozenFunds: (params) => {
return http.post('/frozen_funds/export', params)
},
// 解冻资金
unfreezeFund: (params) => {
return http.post('/frozen_funds/unfreeze', params)
},
// 获取冻结统计
getFrozenStats: (params) => {
return http.get('/frozen_funds/stats', params)
}
}

View File

@@ -0,0 +1,28 @@
import http from '@/libs/http'
export default {
// 获取订单状态列表
getOrderStatusList: (params) => {
return http.post('/order/status/list', params)
},
// 批量更新订单状态
batchUpdateOrderStatus: (params) => {
return http.post('/order/status/batch_update', params)
},
// 更新单个订单状态
updateSingleOrderStatus: (params) => {
return http.post('/order/status/update', params)
},
// 取消订单
cancelOrder: (params) => {
return http.post('/order/cancel', params)
},
// 获取订单状态流转日志
getOrderStatusLogs: (params) => {
return http.post('/order/status/logs', params)
}
}

View File

@@ -0,0 +1,73 @@
import http from '@/libs/http'
export default {
// 获取支付订单列表
getPaymentOrdersList: (params) => {
return http.post('/pay_orders/page', params)
},
// 获取所有支付订单
getAllPaymentOrders: (params) => {
return http.get('/pay_orders/all', params)
},
// 获取支付订单详情
getPaymentOrderDetail: (params) => {
return http.get('/pay_orders/detail', params)
},
// 新增支付订单
addPaymentOrder: (params) => {
return http.post('/pay_orders/add', params)
},
// 更新支付订单
updatePaymentOrder: (params) => {
return http.post('/pay_orders/edit', params)
},
// 删除支付订单
deletePaymentOrder: (params) => {
return http.post('/pay_orders/del', params)
},
// 批量删除支付订单
batchDeletePaymentOrders: (params) => {
return http.post('/pay_orders/batch_delete', params)
},
// 导出支付订单
exportPaymentOrders: (params) => {
return http.post('/pay_orders/export', params)
},
// 获取支付订单统计
getPaymentOrderStats: (params) => {
return http.post('/pay_orders/statistics', params)
},
// 更新订单状态
updateOrderStatus: (params) => {
return http.post('/pay_orders/update_status', params)
},
// 取消订单
cancelPaymentOrder: (id) => {
return http.post(`/pay_orders/cancel/${id}`)
},
// 获取订单支付详情
getOrderPaymentDetail: (params) => {
return http.get('/pay_orders/payment_detail', params)
},
// 重新发起支付
retryPayment: (params) => {
return http.post('/pay_orders/retry_payment', params)
},
// 获取订单退款信息
getOrderRefundInfo: (params) => {
return http.get('/pay_orders/refund_info', params)
}
}

View File

@@ -0,0 +1,23 @@
import http from '@/libs/http'
export default {
// 获取支付统计数据
getPaymentStats: (params) => {
return http.post('/payment/stats', params)
},
// 获取支付趋势数据
getPaymentTrend: (params) => {
return http.post('/payment/trend', params)
},
// 获取订单状态分布
getOrderStatusDistribution: (params) => {
return http.post('/payment/status_distribution', params)
},
// 导出支付统计
exportPaymentStats: (params) => {
return http.post('/payment/export_stats', params)
}
}

View File

@@ -0,0 +1,35 @@
import http from '@/libs/http';
class transfer_detailsClServer {
async all(param) {
let res= await http.get('/transfer_details/all', param);
return res;
}
async page(row) {
let res= await http.post('/transfer_details/page', row);
return res;
}
async exportCsv(row) {
let res = http.fileExport("/transfer_details/export", row);
return res;
}
async add(row) {
let res= await http.post('/transfer_details/add', row);
return res;
}
async edit(row) {
let res= await http.post('/transfer_details/edit', row);
return res;
}
async del(row) {
let res= await http.post('/transfer_details/del', row);
return res;
}
}
const transfer_detailsServer = new transfer_detailsClServer();
export default transfer_detailsServer;

View File

@@ -0,0 +1,53 @@
import http from '@/libs/http'
export default {
// 获取钱包交易记录列表
getWalletTransactionsList: (params) => {
return http.post('/wallet_transactions/page', params)
},
// 获取所有钱包交易记录
getAllWalletTransactions: (params) => {
return http.get('/wallet_transactions/all', params)
},
// 获取钱包交易记录详情
getWalletTransactionDetail: (params) => {
return http.get('/wallet_transactions/detail', params)
},
// 新增钱包交易记录
addWalletTransaction: (params) => {
return http.post('/wallet_transactions/add', params)
},
// 更新钱包交易记录
updateWalletTransaction: (params) => {
return http.post('/wallet_transactions/edit', params)
},
// 删除钱包交易记录
deleteWalletTransaction: (params) => {
return http.post('/wallet_transactions/del', params)
},
// 批量删除钱包交易记录
batchDeleteWalletTransactions: (params) => {
return http.post('/wallet_transactions/batch_delete', params)
},
// 导出钱包交易记录
exportWalletTransactions: (params) => {
return http.post('/wallet_transactions/export', params)
},
// 获取交易统计
getTransactionStats: (params) => {
return http.get('/wallet_transactions/stats', params)
},
// 获取用户交易记录
getUserTransactions: (params) => {
return http.get('/wallet_transactions/user', params)
}
}

View File

@@ -0,0 +1,88 @@
import http from '@/libs/http'
export default {
// 获取用户钱包列表
getWalletsList: (params) => {
return http.post('/wch_wallets/page', params)
},
// 获取所有钱包
getAllWallets: (params) => {
return http.get('/wch_wallets/all', params)
},
// 获取钱包详情
getWalletDetail: (params) => {
return http.get('/wch_wallets/detail', params)
},
// 新增钱包
addWallet: (params) => {
return http.post('/wch_wallets/add', params)
},
// 更新钱包
updateWallet: (params) => {
return http.post('/wch_wallets/edit', params)
},
// 删除钱包
deleteWallet: (params) => {
return http.post('/wallets/delete', params)
},
// 批量删除钱包
batchDeleteWallets: (params) => {
return http.post('/wallets/batch_delete', params)
},
// 导出钱包数据
exportWallets: (params) => {
return http.post('/wallets/export', params)
},
// 获取钱包统计
getWalletStats: (params) => {
return http.post('/wallets/stats', params)
},
// 更新钱包状态
updateWalletStatus: (params) => {
return http.post('/wallets/update_status', params)
},
// 钱包充值
rechargeWallet: (params) => {
return http.post('/wallets/recharge', params)
},
// 钱包提现
withdrawWallet: (params) => {
return http.post('/wallets/withdraw', params)
},
// 冻结钱包资金
freezeWalletFunds: (params) => {
return http.post('/wallets/freeze_funds', params)
},
// 解冻钱包资金
unfreezeWalletFunds: (params) => {
return http.post('/wallets/unfreeze_funds', params)
},
// 获取钱包交易记录
getWalletTransactions: (params) => {
return http.post('/wallets/transactions', params)
},
// 重置钱包密码
resetWalletPassword: (params) => {
return http.post('/wallets/reset_password', params)
},
// 获取钱包余额统计
getWalletBalanceStats: (params) => {
return http.post('/wallets/balance_stats', params)
}
}

View File

@@ -0,0 +1,28 @@
import http from '@/libs/http'
export default {
// 获取提现审核列表
getWithdrawAuditList: (params) => {
return http.post('/wallet/withdraw_audit/list', params)
},
// 审核提现申请
auditWithdraw: (params) => {
return http.post('/wallet/withdraw_audit/audit', params)
},
// 批量审核提现申请
batchAuditWithdraw: (params) => {
return http.post('/wallet/withdraw_audit/batch_audit', params)
},
// 获取提现详情
getWithdrawDetail: (params) => {
return http.get('/wallet/withdraw_audit/detail', params)
},
// 获取提现统计
getWithdrawStats: (params) => {
return http.post('/wallet/withdraw_audit/stats', params)
}
}

View File

@@ -0,0 +1,33 @@
import request from '@/libs/http'
export const getList = (params) => {
return request({
url: '/sys_file/page',
method: 'get',
params
})
}
export const add = (data) => {
return request({
url: '/sys_file',
method: 'post',
data
})
}
export const edit = (data) => {
return request({
url: '/sys_file',
method: 'put',
data
})
}
export const del = (params) => {
return request({
url: '/sys_file',
method: 'delete',
params
})
}

View File

@@ -0,0 +1,57 @@
import http from '@/libs/http'
// 获取球局统计列表
export const getList = (params) => {
return http.request({
url: '/admin/game_statistics/list',
method: 'post',
data: params
})
}
// 获取球局统计详情
export const getDetail = (params) => {
return http.request({
url: '/admin/game_statistics/detail',
method: 'post',
data: params
})
}
// 添加球局统计
export const add = (params) => {
return http.request({
url: '/admin/game_statistics/add',
method: 'post',
data: params
})
}
// 编辑球局统计
export const edit = (params) => {
return http.request({
url: '/admin/game_statistics/edit',
method: 'post',
data: params
})
}
// 删除球局统计
export const del = (params) => {
return http.request({
url: '/admin/game_statistics/del',
method: 'post',
data: params
})
}
// 导出球局统计
export const exportData = (params) => {
return http.request({
url: '/admin/game_statistics/export',
method: 'post',
data: params,
responseType: 'blob'
})
}

View File

@@ -0,0 +1,53 @@
import http from '@/libs/http'
export default {
// 获取资源列表
getResourcesList: (params) => {
return http.post('/resources/page', params)
},
// 获取所有资源
getAllResources: (params) => {
return http.get('/resources/all', params)
},
// 获取资源详情
getResourceDetail: (params) => {
return http.get('/resources/detail', params)
},
// 新增资源
addResource: (params) => {
return http.post('/resources/add', params)
},
// 更新资源
updateResource: (params) => {
return http.post('/resources/edit', params)
},
// 删除资源
deleteResource: (params) => {
return http.post('/resources/del', params)
},
// 批量删除资源
batchDeleteResources: (params) => {
return http.post('/resources/batch_delete', params)
},
// 导出资源数据
exportResources: (params) => {
return http.post('/resources/export', params)
},
// 上传资源
uploadResource: (params) => {
return http.post('/resources/upload', params)
},
// 获取资源统计
getResourceStats: (params) => {
return http.get('/resources/stats', params)
}
}

View File

@@ -0,0 +1,66 @@
import http from '@/libs/http'
// 获取营收统计列表
export const getList = (params) => {
return http.request({
url: '/admin/revenue_statistics/list',
method: 'post',
data: params
})
}
// 获取营收统计详情
export const getDetail = (params) => {
return http.request({
url: '/admin/revenue_statistics/detail',
method: 'post',
data: params
})
}
// 获取营收统计概览
export const getOverview = (params) => {
return http.request({
url: '/admin/revenue_statistics/overview',
method: 'post',
data: params
})
}
// 添加营收统计
export const add = (params) => {
return http.request({
url: '/admin/revenue_statistics/add',
method: 'post',
data: params
})
}
// 编辑营收统计
export const edit = (params) => {
return http.request({
url: '/admin/revenue_statistics/edit',
method: 'post',
data: params
})
}
// 删除营收统计
export const del = (params) => {
return http.request({
url: '/admin/revenue_statistics/del',
method: 'post',
data: params
})
}
// 导出营收统计
export const exportData = (params) => {
return http.request({
url: '/admin/revenue_statistics/export',
method: 'post',
data: params,
responseType: 'blob'
})
}

View File

@@ -0,0 +1,15 @@
import http from "@/libs/http";
class FileServe {
async upload_oos_img(row) {
let res = await http.postFormData("/sys_file/upload_oos_img", row);
return res;
}
async upload_Img(row) {
let res = await http.postFormData("/file/upload_Img", row);
return res;
}
}
const fileServe = new FileServe();
export default fileServe;

View File

@@ -0,0 +1,51 @@
import http from '@/libs/http';
/**
* 热门城市二维码配置相关API
*/
// 获取配置列表
const getHotCityQrConfigList = (params) => {
return http.get('/wch_hot_city_qr_config/list', { params });
};
// 获取所有配置
const getAllHotCityQrConfig = () => {
return http.get('/wch_hot_city_qr_config/all');
};
// 获取配置详情
const getHotCityQrConfigDetail = (id) => {
return http.get(`/wch_hot_city_qr_config/detail/${id}`);
};
// 创建配置
const createHotCityQrConfig = (data) => {
return http.post('/wch_hot_city_qr_config/create', data);
};
// 更新配置
const updateHotCityQrConfig = (id, data) => {
return http.post(`/wch_hot_city_qr_config/update/${id}`, data);
};
// 删除配置
const deleteHotCityQrConfig = (id) => {
return http.post(`/wch_hot_city_qr_config/delete/${id}`);
};
// 批量删除配置
const batchDeleteHotCityQrConfig = (ids) => {
return http.post('/wch_hot_city_qr_config/batch_delete', { ids });
};
export default {
getHotCityQrConfigList,
getAllHotCityQrConfig,
getHotCityQrConfigDetail,
createHotCityQrConfig,
updateHotCityQrConfig,
deleteHotCityQrConfig,
batchDeleteHotCityQrConfig
};

View File

@@ -0,0 +1,38 @@
import http from '@/libs/http'
export default {
// 获取所有平台账户
all(param) {
return http.get('/pla_account/all', param)
},
// 获取平台账户详情
detail(param) {
return http.get('/pla_account/detail', param)
},
// 分页查询平台账户
page(param) {
return http.post('/pla_account/page', param)
},
// 新增平台账户
add(param) {
return http.post('/pla_account/add', param)
},
// 编辑平台账户
edit(param) {
return http.post('/pla_account/edit', param)
},
// 删除平台账户
del(param) {
return http.post('/pla_account/del', param)
},
// 导出平台账户数据
exportCsv(param) {
return http.post('/pla_account/export', param)
}
}

View File

@@ -0,0 +1,30 @@
import http from '@/libs/http'
class RolePermissionServer {
async getRoles(callback) {
let res = await http.get('/SysRolePermission/Query', {})
return res
}
async getRole(row) {
let res = await http.get('/SysRolePermission/QueryByRoleId', row)
return res
}
async add(row) {
let res = await http.post('/SysRolePermission/add', row)
return res
}
async edit(row) {
let res = await http.post('/SysRolePermission/edit', row)
return res
}
async del(row) {
let res = await http.post('/SysRolePermission/del', row)
return res
}
}
const rolePermissionServer = new RolePermissionServer()
export default rolePermissionServer

View File

@@ -0,0 +1,26 @@
import http from "@/libs/http";
class RoleServer {
async list() {
let res = await http.get("/sys_role/index", {});
return res;
}
async add(row) {
let res = await http.post("/sys_role/add", row);
return res;
}
async edit(row) {
let res = await http.post("/sys_role/edit", row);
return res;
}
async del(row) {
let res = await http.post("/sys_role/del", row);
return res;
}
}
const roleServer = new RoleServer();
export default roleServer;

View File

@@ -0,0 +1,15 @@
import http from '@/libs/http'
class ShpProfitServer {
async report(param) {
let res = await http.post('/shpProfit/report', param)
return res
}
async list(param) {
let res = await http.post('/shpProfit/list', param)
return res
}
}
const shpProfitServer = new ShpProfitServer()
export default shpProfitServer

View File

@@ -0,0 +1,25 @@
import http from '@/libs/http'
class SpecificationServer {
async list() {
let res = await http.post('/specification/list', {})
return res
}
async add(row) {
let res = await http.post('/specification/add', row)
return res
}
async edit(row) {
let res = await http.post('/specification/edit', row)
return res
}
async del(row) {
let res = await http.post('/specification/del', row)
return res
}
}
const specificationServer = new SpecificationServer()
export default specificationServer

View File

@@ -0,0 +1,10 @@
import http from "@/libs/http";
class SysAddress {
async index(param) {
let res = await http.get("/sys_address/index", param);
return res;
}
}
const sysAddress = new SysAddress();
export default sysAddress;

View File

@@ -0,0 +1,30 @@
import http from "@/libs/http";
class SysModuleServer {
async all() {
let res = await http.get("/sys_menu/all", {});
return res;
}
async list(row) {
let res = await http.get("/sys_menu/all", row);
return res;
}
async add(row) {
let res = await http.post("/sys_menu/add", row);
return res;
}
async edit(row) {
let res = await http.post("/sys_menu/edit", row);
return res;
}
async del(row) {
let res = await http.post("/sys_menu/del", row);
return res;
}
}
const sysModuleServer = new SysModuleServer();
export default sysModuleServer;

View File

@@ -0,0 +1,30 @@
import http from "@/libs/http";
class SysLogServe {
async all(param) {
let res = await http.get("/sys_log/all", param);
return res;
}
async detail(param) {
let res = await http.get("/sys_log/detail", param);
return res;
}
async delete(param) {
let res = await http.get("/sys_log/delete", param);
return res;
}
async delete_all(param) {
let res = await http.get("/sys_log/delete_all", param);
return res;
}
async operates(param) {
let res = await http.get("/sys_log/operates", param);
return res;
}
}
const sys_log_serve = new SysLogServe();
export default sys_log_serve;

View File

@@ -0,0 +1,38 @@
import http from '@/libs/http';
class systemTypeClServer {
async all(param) {
let res= await http.get('/sys_project_type/all', param);
return res;
}
async page(row) {
let res= await http.post('/sys_project_type/page', row);
return res;
}
async exportCsv(row) {
let res = http.fileExport("/sys_project_type/export", row);
return res;
}
async add(row) {
let res= await http.post('/sys_project_type/add', row);
return res;
}
async edit(row) {
let res= await http.post('/sys_project_type/edit', row);
return res;
}
async del(row) {
let res= await http.post('/sys_project_type/del', row);
return res;
}
}
const systemTypeServer = new systemTypeClServer();
export default systemTypeServer;

View File

@@ -0,0 +1,31 @@
import http from '@/libs/http'
class TableServer {
async getAll(callback) {
return await http.get('/table/index', {})
}
async add(row, callback) {
return await http.post('/table/add', row)
}
async edit(row, callback) {
return await http.post('/table/edit', row, function(res) {
callback && callback(res)
})
}
async del(row, callback) {
return await http.post('/table/del', row)
}
async autoApi(id) {
return await http.get('/template/api', { id: id })
}
async autoDb(id) {
return await http.get('/template/autoDb', { id: id })
}
}
const tableServer = new TableServer()
export default tableServer

View File

@@ -0,0 +1,40 @@
import http from "@/libs/http";
class UserServer {
async login(row) {
let res = await http.post("/sys_user/login", row);
return res;
}
async all() {
let res = await http.get("/sys_user/index", {});
return res;
}
async exportCsv(row) {
let res = http.fileExport("/sys_user/export", row);
return res;
}
async authorityMenus() {
let res = await http.post("/sys_user/authorityMenus", {});
return res;
}
async add(row) {
let res = await http.post("/sys_user/add", row);
return res;
}
async edit(row) {
let res = await http.post("/sys_user/edit", row);
return res;
}
async del(row) {
let res = await http.post("/sys_user/del", row);
return res;
}
}
const userServer = new UserServer();
export default userServer;

View File

@@ -0,0 +1,33 @@
import http from '@/libs/http'
export default {
// 获取职业列表
page: (params) => {
return http.post('/wch_professions/page', params)
},
// 获取所有职业
all: (params) => {
return http.post('/wch_professions/all', params)
},
// 新增职业
add: (params) => {
return http.post('/wch_professions/add', params)
},
// 更新职业
edit: (params) => {
return http.post('/wch_professions/edit', params)
},
// 删除职业
del: (params) => {
return http.post('/wch_professions/del', params)
},
// 导出职业数据
exportCsv: (params) => {
return http.post('/wch_professions/exportCsv', params)
}
}

View File

@@ -0,0 +1,30 @@
import http from "@/libs/http";
class FormFieldServer {
async all(param) {
let res = await http.get("/sys_form_field/all", param);
return res;
}
async page(row) {
let res = await http.post("/sys_form_field/page", row);
return res;
}
async add(row) {
let res = await http.post("/sys_form_field/add", row);
return res;
}
async edit(row) {
let res = await http.post("/sys_form_field/edit", row);
return res;
}
async del(row) {
let res = await http.post("/sys_form_field/del", row);
return res;
}
}
const formFieldServer = new FormFieldServer();
export default formFieldServer;

View File

@@ -0,0 +1,35 @@
import http from "@/libs/http";
class FormServer {
async all(param) {
let res = await http.get("/sys_form/all", param);
return res;
}
async page(row) {
let res = await http.post("/sys_form/page", row);
return res;
}
async generate(row) {
let res = await http.post("/sys_form/generate", row);
return res;
}
async add(row) {
let res = await http.post("/sys_form/add", row);
return res;
}
async edit(row) {
let res = await http.post("/sys_form/edit", row);
return res;
}
async del(row) {
let res = await http.post("/sys_form/del", row);
return res;
}
}
const formServer = new FormServer();
export default formServer;

View File

@@ -0,0 +1,49 @@
import http from "@/libs/http";
class MenuServer {
async list(row) {
let res = await http.get("/sys_menu/index", row);
return res;
}
async generate(row) {
let res = await http.post("/sys_menu/generate", row);
return res;
}
async add(row) {
let res = await http.post("/sys_menu/add", row);
return res;
}
async edit(row) {
let res = await http.post("/sys_menu/edit", row);
return res;
}
async del(row) {
let res = await http.post("/sys_menu/del", row);
return res;
}
async generate(row) {
let res = await http.post("/form/generate", row);
return res;
}
async generateModel(row) {
let res = await http.post("/model/generate", row);
return res;
}
async modelAll(row) {
let res = await http.post("/model/all", row);
return res;
}
async modelInterface(row) {
let res = await http.post("/model/interface", row);
return res;
}
}
const menuServer = new MenuServer();
export default menuServer;

View File

@@ -0,0 +1,32 @@
import http from "@/libs/http";
class ModelFieldServer {
async all(row) {
let res = await http.get("/sys_model_field/all", row);
return res;
}
async allByKey(row) {
let res = await http.get("/sys_model_field/allByKey", row, {
hideLoad: true
});
return res;
}
async add(row) {
let res = await http.post("/sys_model_field/add", row);
return res;
}
async edit(row) {
let res = await http.post("/sys_model_field/edit", row);
return res;
}
async del(row) {
let res = await http.post("/sys_model_field/del", row);
return res;
}
}
const modelFieldServer = new ModelFieldServer();
export default modelFieldServer;

View File

@@ -0,0 +1,40 @@
import http from "@/libs/http";
class ModelServer {
async interface(row) {
let res = await http.post("/sys_model/interface", row);
return res;
}
async all() {
let res = await http.get("/sys_model/all", {});
return res;
}
async detail(row) {
let res = await http.get("/sys_model/detail", row);
return res;
}
async regenerate(row) {
let res = await http.post("/sys_model/regenerate", row);
return res;
}
async add(row) {
let res = await http.post("/sys_model/add", row);
return res;
}
async edit(row) {
let res = await http.post("/sys_model/edit", row);
return res;
}
async del(row) {
let res = await http.post("/sys_model/del", row);
return res;
}
}
const modelServer = new ModelServer();
export default modelServer;

View File

@@ -0,0 +1,29 @@
import http from "@/libs/http";
class ParamSetupServer {
async getAll() {
return await http.get("/sys_parameter/index", {});
}
async getOne(key) {
return await http.get("/sys_parameter/key", { key });
}
async add(row) {
return await http.post("/sys_parameter/add", row);
}
async edit(row) {
return await http.post("/sys_parameter/edit", row);
}
async setSysConfig(row) {
return await http.post("/sys_parameter/setSysConfig", row);
}
async del(row) {
return await http.post("/sys_parameter/del", row);
}
}
const paramSetupServer = new ParamSetupServer();
export default paramSetupServer;

View File

@@ -0,0 +1,29 @@
import http from "@/libs/http";
class SysControlTypeServer {
async all(param) {
let res = await http.get("/sys_control_type/all", param);
return res;
}
async page(row) {
let res = await http.post("/sys_control_type/page", row);
return res;
}
async add(param) {
let res = await http.post("/sys_control_type/add", param);
return res;
}
async edit(param) {
let res = await http.post("/sys_control_type/edit", param);
return res;
}
async del(param) {
let res = await http.post("/sys_control_type/del", param);
return res;
}
}
const sysControlTypeServer = new SysControlTypeServer();
export default sysControlTypeServer;

View File

@@ -0,0 +1,35 @@
import http from '@/libs/http';
class recommend_blocksClServer {
async all(param) {
let res= await http.get('/recommend_blocks/all', param);
return res;
}
async page(row) {
let res= await http.post('/recommend_blocks/page', row);
return res;
}
async exportCsv(row) {
let res = http.fileExport("/recommend_blocks/export", row);
return res;
}
async add(row) {
let res= await http.post('/recommend_blocks/add', row);
return res;
}
async edit(row) {
let res= await http.post('/recommend_blocks/edit', row);
return res;
}
async del(row) {
let res= await http.post('/recommend_blocks/del', row);
return res;
}
}
const recommend_blocksServer = new recommend_blocksClServer();
export default recommend_blocksServer;

View File

@@ -0,0 +1,35 @@
import http from '@/libs/http';
class user_followsClServer {
async all(param) {
let res= await http.get('/user_follows/all', param);
return res;
}
async page(row) {
let res= await http.post('/user_follows/page', row);
return res;
}
async exportCsv(row) {
let res = http.fileExport("/user_follows/export", row);
return res;
}
async add(row) {
let res= await http.post('/user_follows/add', row);
return res;
}
async edit(row) {
let res= await http.post('/user_follows/edit', row);
return res;
}
async del(row) {
let res= await http.post('/user_follows/del', row);
return res;
}
}
const user_followsServer = new user_followsClServer();
export default user_followsServer;

View File

@@ -0,0 +1,35 @@
import http from '@/libs/http';
class user_trackingClServer {
async all(param) {
let res= await http.get('/user_tracking/all', param);
return res;
}
async page(row) {
let res= await http.post('/user_tracking/page', row);
return res;
}
async exportCsv(row) {
let res = http.fileExport("/user_tracking/export", row);
return res;
}
async add(row) {
let res= await http.post('/user_tracking/add', row);
return res;
}
async edit(row) {
let res= await http.post('/user_tracking/edit', row);
return res;
}
async del(row) {
let res= await http.post('/user_tracking/del', row);
return res;
}
}
const user_trackingServer = new user_trackingClServer();
export default user_trackingServer;

View File

@@ -0,0 +1,53 @@
import http from '@/libs/http'
export default {
// 获取城市列表
getCitiesList: (params) => {
return http.post('/wch_cities/page', params)
},
// 获取所有城市
getAllCities: (params) => {
return http.get('/wch_cities/all', params)
},
// 获取城市详情
getCityDetail: (params) => {
return http.get('/wch_cities/detail', params)
},
// 新增城市
addCity: (params) => {
return http.post('/wch_cities/add', params)
},
// 更新城市
updateCity: (params) => {
return http.post('/wch_cities/edit', params)
},
// 删除城市
deleteCity: (params) => {
return http.post('/wch_cities/del', params)
},
// 批量删除城市
batchDeleteCities: (params) => {
return http.post('/wch_cities/batch_delete', params)
},
// 导出城市数据
exportCities: (params) => {
return http.post('/wch_cities/export', params)
},
// 获取热门城市
getHotCities: (params) => {
return http.get('/wch_cities/hot', params)
},
// 设置热门城市
setHotCities: (params) => {
return http.post('/wch_cities/set_hot', params)
}
}

View File

@@ -0,0 +1,35 @@
import http from '@/libs/http';
class wch_citiesClServer {
async all(param) {
let res= await http.get('/wch_cities/all', param);
return res;
}
async page(row) {
let res= await http.post('/wch_cities/page', row);
return res;
}
async exportCsv(row) {
let res = http.fileExport("/wch_cities/export", row);
return res;
}
async add(row) {
let res= await http.post('/wch_cities/add', row);
return res;
}
async edit(row) {
let res= await http.post('/wch_cities/edit', row);
return res;
}
async del(row) {
let res= await http.post('/wch_cities/del', row);
return res;
}
}
const wch_citiesServer = new wch_citiesClServer();
export default wch_citiesServer;

View File

@@ -0,0 +1,171 @@
<template>
<div style="padding: 20px;">
<Card>
<p slot="title">
<Icon type="ios-cube" />
自定义页面示例
</p>
<div style="margin-bottom: 20px;">
<Alert type="success" show-icon>
这是一个自定义页面组件示例展示如何在 Admin Framework 中添加自定义页面
</Alert>
</div>
<Row :gutter="16">
<Col span="12">
<Card title="基本信息" style="margin-bottom: 16px;">
<p><strong>当前时间:</strong> {{ currentTime }}</p>
<p><strong>页面路径:</strong> {{ $route.path }}</p>
<p><strong>框架版本:</strong> {{ framework.version }}</p>
</Card>
</Col>
<Col span="12">
<Card title="Vuex 状态" style="margin-bottom: 16px;">
<p><strong>自定义数据:</strong> {{ customData }}</p>
<p><strong>计数器:</strong> {{ count }}</p>
<div style="margin-top: 10px;">
<Button type="primary" @click="handleIncrement">增加计数</Button>
<Button type="default" @click="handleIncrementAsync" style="margin-left: 8px;">
异步增加
</Button>
</div>
</Card>
</Col>
</Row>
<Card title="API 测试" style="margin-bottom: 16px;">
<Form :label-width="80">
<FormItem label="请求地址">
<Input v-model="apiUrl" placeholder="输入 API 地址" />
</FormItem>
<FormItem>
<Button type="primary" @click="handleApiTest" :loading="apiLoading">
发送请求
</Button>
<Button @click="handleClear" style="margin-left: 8px;">清空结果</Button>
</FormItem>
<FormItem label="响应结果" v-if="apiResult">
<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px;">{{ apiResult }}</pre>
</FormItem>
</Form>
</Card>
<Card title="UI 工具测试">
<Button type="success" @click="showSuccess">成功提示</Button>
<Button type="warning" @click="showWarning" style="margin-left: 8px;">警告提示</Button>
<Button type="error" @click="showError" style="margin-left: 8px;">错误提示</Button>
<Button type="info" @click="showConfirm" style="margin-left: 8px;">确认对话框</Button>
</Card>
</Card>
</div>
</template>
<script>
import { mapGetters, mapActions, mapMutations } from 'vuex'
export default {
name: 'CustomPage',
data() {
return {
currentTime: new Date().toLocaleString(),
timer: null,
apiUrl: '/api/test',
apiLoading: false,
apiResult: null,
framework: window.framework
}
},
computed: {
...mapGetters('custom', ['customData', 'count'])
},
methods: {
...mapMutations('custom', ['INCREMENT']),
...mapActions('custom', ['incrementAsync', 'updateCustomData']),
// 增加计数
handleIncrement() {
this.INCREMENT()
this.$Message.success('计数已增加')
},
// 异步增加
handleIncrementAsync() {
this.$Message.info('1秒后增加计数...')
this.incrementAsync()
},
// API 测试
async handleApiTest() {
this.apiLoading = true
this.apiResult = null
try {
const response = await this.$http.get(this.apiUrl)
this.apiResult = JSON.stringify(response.data, null, 2)
this.$Message.success('请求成功')
} catch (error) {
this.apiResult = JSON.stringify({
error: error.message,
details: error.toString()
}, null, 2)
this.$Message.error('请求失败: ' + error.message)
} finally {
this.apiLoading = false
}
},
// 清空结果
handleClear() {
this.apiResult = null
},
// UI 工具测试
showSuccess() {
this.$uiTool.success('这是一个成功提示')
},
showWarning() {
this.$uiTool.warning('这是一个警告提示')
},
showError() {
this.$uiTool.error('这是一个错误提示')
},
showConfirm() {
this.$uiTool.confirm('确定要执行此操作吗?').then(() => {
this.$Message.success('已确认')
}).catch(() => {
this.$Message.info('已取消')
})
}
},
mounted() {
// 定时更新时间
this.timer = setInterval(() => {
this.currentTime = new Date().toLocaleString()
}, 1000)
console.log('CustomPage 组件已挂载')
console.log('可用的工具函数:', this.$tools)
console.log('可用的 UI 工具:', this.$uiTool)
},
beforeDestroy() {
if (this.timer) {
clearInterval(this.timer)
}
}
}
</script>
<style scoped>
pre {
margin: 0;
font-size: 12px;
max-height: 300px;
overflow: auto;
}
</style>

0
demo/src/libs/funTool.js Normal file
View File

0
demo/src/libs/http.js Normal file
View File

0
demo/src/libs/uiTool.js Normal file
View File

59
demo/src/main.js Normal file
View File

@@ -0,0 +1,59 @@
// 引入依赖
import Vue from 'vue'
import VueRouter from 'vue-router'
import Vuex from 'vuex'
import ViewUI from 'view-design'
import axios from 'axios'
// 引入样式
import 'view-design/dist/styles/iview.css'
// 引入 Admin Framework使用本地构建的文件
import AdminFramework from '../../dist/admin-framework.js'
// 引入组件映射表
import componentMap from './router/component-map.js'
// 使用插件
Vue.use(ViewUI)
// 配置参数
const config = {
title: 'Admin Framework Demo',
apiUrl: 'http://localhost:9098/admin_api/', // 修改为你的 API 地址
uploadUrl: 'http://localhost:9098/admin_api/upload' // 修改为你的上传地址
}
// 初始化框架
AdminFramework.install(Vue, {
config: config,
ViewUI: ViewUI,
VueRouter: VueRouter,
Vuex: Vuex,
createPersistedState: null,
componentMap: componentMap // 传入组件映射表,用于动态路由
})
// 创建 Vue 实例
const app = new Vue({
router: AdminFramework.router,
store: AdminFramework.store,
render: h => h('router-view'),
created() {
console.log('=== Admin Framework Demo 启动成功 ===')
console.log('框架版本:', AdminFramework.version)
console.log('配置信息:', this.$config)
}
})
// 挂载应用
app.$mount('#app')
// 全局暴露(方便调试)
window.app = app
window.rootVue = app
window.framework = AdminFramework
// 提示信息
ViewUI.Message.success('Admin Framework Demo 启动成功!')

View File

@@ -0,0 +1,108 @@
// 组件映射表 - 将后端返回的组件路径映射到实际的 Vue 组件
// 后端返回的 component 字段需要与这里的 key 对应
// ========== AI 模块 ==========
import AiMessages from '../views/ai/ai_messages.vue'
// ========== 球场模块 ==========
import GameComments from '../views/ball/game_comments.vue'
import GameParticipants from '../views/ball/game_participants.vue'
import Games from '../views/ball/games.vue'
import Venues from '../views/ball/venues.vue'
import WchUsers from '../views/ball/wch_users.vue'
// ========== 业务模块 ==========
import HotCityQr from '../views/business/hot_city_qr.vue'
// ========== 消息模块 ==========
import MsgNotifications from '../views/message/msg_notifications.vue'
// ========== NTRP 模块 ==========
import NtrQuestions from '../views/ntrp/ntr_questions.vue'
import NtrRecords from '../views/ntrp/ntr_records.vue'
// ========== 订单模块 ==========
import FrozenFunds from '../views/order/frozen_funds.vue'
import PayOrders from '../views/order/pay_orders.vue'
import TransferDetails from '../views/order/transfer_details.vue'
import WalletTransactions from '../views/order/wallet_transactions.vue'
import WchWallets from '../views/order/wch_wallets.vue'
// ========== 统计模块 ==========
import Resources from '../views/statistics/resources.vue'
// ========== 用户模块 ==========
import RecommendBlocks from '../views/users/recommend_blocks.vue'
import UserFollows from '../views/users/user_follows.vue'
import UserTracking from '../views/users/user_tracking.vue'
// ========== 自定义组件 ==========
import CustomPage from '../components/CustomPage.vue'
/**
* 组件映射对象
* key: 后端返回的组件路径(去掉 .vue 后缀或保留都可以)
* value: 实际的 Vue 组件
*/
const componentMap = {
// ===== AI 模块 =====
'ai/ai_messages': AiMessages,
'ai/ai_messages.vue': AiMessages,
// ===== 球场模块 =====
'ball/game_comments': GameComments,
'ball/game_comments.vue': GameComments,
'ball/game_participants': GameParticipants,
'ball/game_participants.vue': GameParticipants,
'ball/games': Games,
'ball/games.vue': Games,
'ball/venues': Venues,
'ball/venues.vue': Venues,
'ball/wch_users': WchUsers,
'ball/wch_users.vue': WchUsers,
// ===== 业务模块 =====
'business/hot_city_qr': HotCityQr,
'business/hot_city_qr.vue': HotCityQr,
// ===== 消息模块 =====
'message/msg_notifications': MsgNotifications,
'message/msg_notifications.vue': MsgNotifications,
// ===== NTRP 模块 =====
'ntrp/ntr_questions': NtrQuestions,
'ntrp/ntr_questions.vue': NtrQuestions,
'ntrp/ntr_records': NtrRecords,
'ntrp/ntr_records.vue': NtrRecords,
// ===== 订单模块 =====
'order/frozen_funds': FrozenFunds,
'order/frozen_funds.vue': FrozenFunds,
'order/pay_orders': PayOrders,
'order/pay_orders.vue': PayOrders,
'order/transfer_details': TransferDetails,
'order/transfer_details.vue': TransferDetails,
'order/wallet_transactions': WalletTransactions,
'order/wallet_transactions.vue': WalletTransactions,
'order/wch_wallets': WchWallets,
'order/wch_wallets.vue': WchWallets,
// ===== 统计模块 =====
'statistics/resources': Resources,
'statistics/resources.vue': Resources,
// ===== 用户模块 =====
'users/recommend_blocks': RecommendBlocks,
'users/recommend_blocks.vue': RecommendBlocks,
'users/user_follows': UserFollows,
'users/user_follows.vue': UserFollows,
'users/user_tracking': UserTracking,
'users/user_tracking.vue': UserTracking,
// ===== 自定义组件 =====
'custom/page': CustomPage,
'custom/page.vue': CustomPage
}
export default componentMap

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>

54
demo/webpack.config.js Normal file
View File

@@ -0,0 +1,54 @@
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'app.js',
clean: true
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader']
},
{
test: /\.(png|jpe?g|gif|svg|woff2?|eot|ttf|otf)$/,
type: 'asset/resource'
}
]
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: './public/index.html',
title: 'Admin Framework Demo'
})
],
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'@': path.resolve(__dirname, 'src'),
'vue$': 'vue/dist/vue.esm.js'
}
},
devServer: {
hot: true,
open: true,
port: 8080,
historyApiFallback: true
}
}

View File

@@ -5,7 +5,10 @@
"main": "dist/admin-framework.js",
"scripts": {
"build": "webpack --mode production",
"dev": "webpack --mode development --watch"
"build:dev": "cross-env NODE_ENV=development webpack --mode production",
"dev": "webpack --mode development --watch",
"serve": "npm run build && npx http-server -p 8080 -o /demo/index.html",
"serve:dev": "npm run build:dev && npx http-server -p 8080 -o /demo/index.html"
},
"keywords": [
"admin",

View File

@@ -82,6 +82,12 @@ html {
overflow-y: auto;
}
html,
body {
width: 100%;
height: 100%;
}
/*滚动条整体样式*/
*::-webkit-scrollbar {
width: 10px;
@@ -103,47 +109,52 @@ html {
background: #dcdee2;
}
.ml(@i) when(@i <= 300) {
.ml(@i) when(@i <=300) {
.ml@{i} {
margin-left: @i + 0px;
}
.ml((@i + 5));
}
.ml(5);
.mt(@i) when(@i <= 100) {
.mt(@i) when(@i <=100) {
.mt@{i} {
margin-top: @i + 0px;
}
.mt((@i + 5));
}
.mt(5);
.pa(@i) when(@i <= 100) {
.pa(@i) when(@i <=100) {
.pa@{i} {
padding: @i + 0px;
}
.pa((@i + 5));
}
.pa(5);
.w(@i) when(@i <= 100) {
.w(@i) when(@i <=100) {
.w@{i} {
width: @i + 0%;
}
.w((@i + 5));
}
.w(5);
.h(@i) when(@i <= 100) {
.h(@i) when(@i <=100) {
.h@{i} {
height: @i + 0%;
}
.h((@i + 5));
}
.h(5);
.h(5);

View File

@@ -4,17 +4,23 @@ const webpack = require('webpack')
const { VueLoaderPlugin } = require('vue-loader')
const TerserPlugin = require('terser-webpack-plugin')
// 判断是否为开发环境
const isDev = process.env.NODE_ENV === 'development'
module.exports = {
mode: 'production', // 明确指定生产模式
target: 'web', // 明确指定浏览器环境
entry: './src/index.js',
devtool: isDev ? 'source-map' : false, // 开发环境生成 sourcemap
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'admin-framework.js',
library: 'AdminFramework',
libraryTarget: 'umd',
libraryExport: 'default',
globalObject: 'typeof self !== \'undefined\' ? self : this',
library: {
name: 'AdminFramework', // 挂载到 window.AdminFramework
type: 'umd',
export: 'default'
},
globalObject: 'window', // 明确使用 window 对象
umdNamedDefine: true, // 为 UMD 模块添加名称
clean: true // 构建前清理 dist 目录
},
@@ -166,7 +172,7 @@ module.exports = {
}
},
optimization: {
minimize: true,
minimize: !isDev, // 开发环境不压缩
minimizer: [
new TerserPlugin({
terserOptions: {