Compare commits

...

91 Commits

Author SHA1 Message Date
张成
b122a34331 1 2025-12-19 13:14:42 +08:00
张成
463d7921c1 1 2025-11-26 17:50:14 +08:00
张成
a147fb8a28 Merge branch 'main' of https://git.light120.com/zc/admin_core 2025-11-20 11:27:32 +08:00
张成
46e654aace 1 2025-11-20 11:27:30 +08:00
张成
d1546678e1 1 2025-11-19 22:48:09 +08:00
张成
ff2d6004b6 1 2025-11-19 22:37:07 +08:00
张成
ce7916c097 1 2025-11-19 22:26:36 +08:00
张成
3e40ab372b 1 2025-11-19 22:19:57 +08:00
张成
af16450eb6 1 2025-11-19 15:43:45 +08:00
张成
9a93a6dc99 1 2025-11-19 15:42:42 +08:00
张成
cdd78aa057 1 2025-11-19 15:39:23 +08:00
张成
9b5f89aa10 1 2025-11-19 15:35:25 +08:00
张成
29efa21f0a 1 2025-11-19 15:05:43 +08:00
张成
4a8c9c4a2d 1 2025-11-13 11:44:58 +08:00
张成
ae36d6da81 1 2025-11-13 11:42:24 +08:00
张成
2066796977 1 2025-11-06 16:19:23 +08:00
张成
bad718ecb9 1 2025-11-06 16:15:22 +08:00
张成
869dda515d 1 2025-11-06 16:14:52 +08:00
张成
2c7d438750 1 2025-11-06 15:50:38 +08:00
张成
9a2ca57313 1 2025-11-06 15:48:56 +08:00
张成
795cc37b49 1 2025-11-06 15:46:34 +08:00
张成
5ae32a550d 1 2025-11-06 15:42:49 +08:00
张成
02c2d1a31c 1 2025-11-06 15:03:55 +08:00
张成
84563bafa1 1 2025-11-06 14:59:12 +08:00
张成
6953193cf5 1 2025-11-06 14:02:48 +08:00
张成
9d507c88ab 1 2025-11-06 13:59:58 +08:00
张成
c734e698de 1 2025-11-06 13:56:53 +08:00
张成
b02853d5f7 1 2025-11-06 12:55:46 +08:00
张成
cac6e71adf Merge branch 'main' of https://git.light120.com/zc/admin_core 2025-11-06 11:53:01 +08:00
张成
2bd32e9a34 1 2025-11-06 11:52:41 +08:00
张成
cfe380218e 1 2025-11-05 21:19:30 +08:00
张成
ff78438cfe 1 2025-11-05 21:13:35 +08:00
张成
fbed54474d 1 2025-11-05 21:08:03 +08:00
张成
bd7704f2c9 1 2025-10-29 14:28:27 +08:00
张成
756a657c58 1 2025-10-29 14:24:20 +08:00
张成
73d756628b 1 2025-10-29 14:17:30 +08:00
张成
970edeb759 1 2025-10-28 16:11:23 +08:00
张成
0a723c4dfe 1 2025-10-28 15:51:56 +08:00
张成
42c977b815 1 2025-10-28 15:31:49 +08:00
张成
5bea5f8c02 1 2025-10-28 14:10:05 +08:00
张成
b101f49048 1 2025-10-28 11:32:33 +08:00
张成
e66dd4430c 1 2025-10-28 11:29:28 +08:00
张成
e039ae8c62 1 2025-10-28 11:24:11 +08:00
张成
a8d52f74ad 1 2025-10-20 18:50:01 +08:00
张成
96c8ba8c7e 1 2025-10-20 18:45:22 +08:00
张成
db46666bd4 1 2025-10-20 18:44:48 +08:00
张成
d79a272f50 1 2025-10-20 18:41:28 +08:00
张成
fd3c1a5563 init 2025-10-20 18:18:27 +08:00
张成
0047086013 1 2025-10-19 11:41:00 +08:00
张成
81aa1d49fd 1 2025-10-10 00:37:30 +08:00
张成
b39b206c4c 1 2025-10-10 00:13:11 +08:00
张成
f86ee00e3b 1 2025-10-10 00:08:35 +08:00
张成
88cd8d6e72 1 2025-10-09 23:50:03 +08:00
张成
0b78af46cd 1 2025-10-09 23:26:40 +08:00
张成
b81e768035 1 2025-10-09 23:18:09 +08:00
张成
1d3eb75c64 1 2025-10-09 23:13:42 +08:00
张成
f91206702b 1 2025-10-09 18:47:19 +08:00
张成
23a68e5b0b 1 2025-10-09 18:44:16 +08:00
张成
18f9e42497 1 2025-10-09 18:43:57 +08:00
张成
910992e192 1 2025-10-09 18:43:34 +08:00
张成
fffdb69815 1 2025-10-09 18:41:16 +08:00
张成
bf0d15f4aa 1 2025-10-09 18:39:14 +08:00
张成
26ef31886b 1 2025-10-09 18:36:55 +08:00
张成
e72aea100e 1 2025-10-09 18:34:49 +08:00
张成
6c4ebd6e8b 1 2025-10-09 18:28:18 +08:00
张成
0e372cdb01 1 2025-10-09 18:25:54 +08:00
张成
6be5f7ed4f 1 2025-10-09 18:21:05 +08:00
张成
ba61c9e45d 1 2025-10-09 18:17:41 +08:00
张成
366c18bcea 1 2025-10-09 18:00:37 +08:00
张成
4823e1d152 1 2025-10-09 16:26:02 +08:00
张成
d5fb0075b2 1 2025-10-09 16:09:53 +08:00
张成
d941bec7d8 1 2025-10-09 07:03:01 +08:00
张成
d387a2d0e9 1 2025-10-08 23:12:55 +08:00
张成
7371313d39 1 2025-10-08 22:13:36 +08:00
张成
453414f647 1 2025-10-08 21:42:22 +08:00
张成
07aa8d493a 1 2025-10-08 20:08:31 +08:00
张成
b27c047930 1 2025-10-08 20:01:26 +08:00
张成
70d32e311b 1 2025-10-08 19:59:17 +08:00
张成
8f36705282 1 2025-10-08 19:42:42 +08:00
张成
99f73eff84 1 2025-10-08 19:30:09 +08:00
张成
7e888970d3 1 2025-10-08 19:20:24 +08:00
张成
845658f193 1 2025-10-08 18:53:38 +08:00
张成
43eb9715fa 1 2025-10-08 17:15:31 +08:00
张成
cda6e0f222 1 2025-10-08 17:03:41 +08:00
张成
39396b9663 1 2025-10-08 16:55:55 +08:00
张成
54d4c19cb1 1 2025-10-08 16:48:44 +08:00
张成
eae1156548 1 2025-10-08 16:23:35 +08:00
张成
9d47927c63 1 2025-10-08 16:20:17 +08:00
张成
a136ed7438 1 2025-10-08 16:17:33 +08:00
张成
73132f616a 1 2025-10-08 16:15:30 +08:00
张成
654f340a37 1 2025-10-08 16:12:13 +08:00
168 changed files with 21945 additions and 2299 deletions

View File

@@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(npm run build:*)"
],
"deny": [],
"ask": []
}
}

0
README.md Normal file
View File

781
_doc/使用说明.md Normal file
View File

@@ -0,0 +1,781 @@
# Admin Framework 使用说明
一个基于 Vue2 的通用后台管理系统框架,包含完整的系统功能、登录、路由管理、布局等核心功能。
## 📦 框架特性
### ✨ 核心功能
-**简化的 API** - 只需调用 `createApp()` 即可完成所有初始化
-**模块化设计** - 组件、路由、状态管理等功能按模块组织
-**完整的系统管理页面** - 用户、角色、菜单、日志等管理
-**登录和权限管理** - 完整的登录流程和权限控制
-**动态路由管理** - 基于权限菜单的动态路由生成
-**Vuex 状态管理** - 用户、应用状态管理
-**全局组件库** - Tables、Editor、Upload、TreeGrid、FieldRenderer、FloatPanel 等
-**工具库** - HTTP、日期、Token、Cookie 等工具
-**内置样式** - base.less、animate.css、iconfont 等
-**响应式布局** - 支持移动端适配
### 🎯 内置页面组件
- **主页组件** (`HomePage`) - 欢迎页面,显示系统标题
- **系统管理页面** (`SysUser`, `SysRole`, `SysLog`, `SysParamSetup`)
- **高级管理页面** (`SysMenu`, `SysControl`, `SysTitle`)
- **登录页面** (`LoginPage`)
- **错误页面** (`Page401`, `Page404`, `Page500`)
### 🛠️ 内置工具
- **HTTP 工具** (`http`) - 封装了 axios支持拦截器、文件上传下载
- **UI 工具** (`uiTool`) - 删除确认、树形转换、响应式设置、文件下载
- **通用工具** (`tools`) - 日期格式化、UUID 生成、Cookie 操作、深拷贝等
- **文件下载** - 支持 CSV 等格式的文件下载,自动处理换行符
## 🚀 快速开始
### 方式一:使用 Demo 项目(推荐)
我们提供了一个完整的 demo 项目,可以直接运行查看效果:
```bash
# 1. 进入 demo 项目
cd demo
# 2. 安装依赖
npm install
# 3. 启动开发服务器
npm run dev
```
浏览器会自动打开 `http://localhost:8080`,查看:
- `/login` - 登录页面
- `/home` - 主页
- `/system/user` - 用户管理
- `/ball/games` - 业务示例页面
### 方式二:构建框架
```bash
# 1. 安装依赖
npm install
# 2. 构建框架
npm run build
# 3. 产物在 dist/admin-framework.js
```
## 🎯 极简使用方式
### 只需 3 步即可完成集成!
#### 1. 引入框架
```javascript
import AdminFramework from './admin-framework.js'
```
#### 2. 创建应用
```javascript
const app = AdminFramework.createApp({
title: '我的管理系统',
apiUrl: 'http://localhost:9098/admin_api/',
componentMap: {
'business/product': ProductComponent,
'business/order': OrderComponent
}
})
```
#### 3. 挂载应用
```javascript
app.$mount('#app')
```
**就这么简单!** 框架会自动完成所有初始化工作。
## 📖 完整使用指南
### 1. 项目结构准备
```
your-project/
├── src/
│ ├── config/
│ │ └── index.js # 配置文件
│ ├── libs/
│ │ └── admin-framework.js # 框架文件
│ ├── views/
│ │ └── business/ # 业务页面
│ ├── api/
│ │ └── business/ # 业务 API
│ ├── App.vue
│ └── main.js
├── package.json
└── webpack.config.js
```
### 2. 安装依赖
```bash
npm install vue vue-router vuex view-design axios dayjs js-cookie vuex-persistedstate
```
### 3. 创建配置文件
`src/config/index.js` 中:
```javascript
module.exports = {
title: '你的系统名称',
homeName: '首页',
apiUrl: 'http://localhost:9090/admin_api/',
uploadUrl: 'http://localhost:9090/admin_api/upload',
cookieExpires: 7,
uploadMaxLimitSize: 10,
oss: {
region: 'oss-cn-shanghai',
accessKeyId: 'your-key',
accessKeySecret: 'your-secret',
bucket: 'your-bucket',
url: 'http://your-bucket.oss-cn-shanghai.aliyuncs.com',
basePath: 'your-path/'
}
}
```
### 4. 创建 main.js新版本 - 推荐)
```javascript
import AdminFramework from './libs/admin-framework.js'
// 导入业务组件(根据权限菜单接口的 component 字段)
import GamesComponent from './views/ball/games.vue'
import PayOrdersComponent from './views/order/pay_orders.vue'
// 🎉 只需一行代码!框架自动完成所有初始化
const app = AdminFramework.createApp({
title: '我的管理系统',
apiUrl: 'http://localhost:9098/admin_api/',
componentMap: {
'ball/games': GamesComponent,
'order/pay_orders': PayOrdersComponent
// 添加更多业务组件...
},
onReady() {
console.log('应用已启动!')
// 应用启动完成后的回调
}
})
// 挂载应用
app.$mount('#app')
```
### 5. 创建 App.vue
```vue
<template>
<div id="app">
<router-view/>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
```
## 🔧 API 使用指南
### 框架实例方法
#### createApp(config) - 推荐使用
创建应用实例(新版本 API
```javascript
const app = AdminFramework.createApp({
title: '我的管理系统', // 应用标题(必需)
apiUrl: 'http://localhost:9098/admin_api/', // API 基础地址(必需)
uploadUrl: 'http://localhost:9098/admin_api/upload', // 上传地址(可选,默认为 apiUrl + 'upload'
componentMap: { // 业务组件映射(可选)
'business/product': ProductComponent,
'business/order': OrderComponent
},
onReady() { // 应用启动完成回调(可选)
console.log('应用已启动!')
}
})
```
### 工具库使用
#### HTTP 工具
```javascript
// 在组件中使用
export default {
async mounted() {
// GET 请求
const res = await this.$http.get('/api/users', { page: 1 })
// POST 请求
const result = await this.$http.post('/api/users', { name: 'test' })
// 文件导出
await this.$http.fileExport('/api/export', { type: 'excel' })
}
}
// 在非 Vue 组件中使用
import AdminFramework from './libs/admin-framework.js'
const res = await AdminFramework.http.get('/api/users')
```
#### UI 工具
```javascript
// 在组件中使用
export default {
methods: {
handleDelete() {
// 删除确认
this.$uiTool.delConfirm(() => {
// 执行删除逻辑
})
// 设置响应式字体
this.$uiTool.setRem()
// 树形转换
const treeData = this.$uiTool.transformTree(flatData)
}
}
}
```
#### 功能工具
```javascript
// 在组件中使用
export default {
methods: {
downloadFile() {
// 文件下载
this.$uiTool.downloadFile(response, 'filename.csv')
}
}
}
```
#### 通用工具
```javascript
// 在组件中使用
export default {
methods: {
formatDate() {
// 日期格式化
return this.$tools.formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss')
},
generateId() {
// UUID 生成
return this.$tools.generateUUID()
},
setCookie() {
// Cookie 操作
this.$tools.setCookie('name', 'value')
const value = this.$tools.getCookie('name')
}
}
}
```
### Store 模块使用
#### user 模块
```javascript
// 登录
await this.$store.dispatch('user/handleLogin', {
userFrom: { username, password },
Main: AdminFramework.Main,
ParentView: AdminFramework.ParentView,
Page404: AdminFramework.Page404
})
// 登出
this.$store.dispatch('user/handleLogOut')
// 设置权限菜单
this.$store.dispatch('user/setAuthorityMenus', {
Main: AdminFramework.Main,
ParentView: AdminFramework.ParentView,
Page404: AdminFramework.Page404
})
// 获取用户信息
const userName = this.$store.getters['user/userName']
const token = this.$store.state.user.token
```
#### app 模块
```javascript
// 设置面包屑
this.$store.commit('app/setBreadCrumb', route)
// 获取系统标题
this.$store.dispatch('app/getSysTitle', {
defaultTitle: '系统名称',
defaultLogo: '/logo.png'
})
// 获取系统配置
const sysFormModel = this.$store.getters['app/sysFormModel']
```
## 🗂️ 组件映射配置
### 业务组件映射
当后端权限菜单接口返回组件路径时,需要配置映射表:
```javascript
// 1. 导入业务组件
import GamesComponent from './views/ball/games.vue'
import PayOrdersComponent from './views/order/pay_orders.vue'
// 2. 配置映射
const componentMap = {
'ball/games': GamesComponent,
'ball/games.vue': GamesComponent, // 支持带 .vue 后缀
'order/pay_orders': PayOrdersComponent,
'order/pay_orders.vue': PayOrdersComponent
}
// 3. 在 Vue.use 时传入
Vue.use(AdminFramework, {
config,
ViewUI,
VueRouter,
Vuex,
createPersistedState,
componentMap // 传入组件映射表
})
```
### 框架已自动映射的系统组件
以下组件**无需配置**,框架已自动映射:
-`home/index` - 主页
-`system/sys_user` - 用户管理
-`system/sys_role` - 角色管理
-`system/sys_log` - 日志管理
-`system/sys_param_setup` - 参数设置
-`system/sys_menu` - 菜单管理
-`system/sys_control` - 控制器管理
-`system/sys_title` - 系统标题设置
## 🌐 全局访问
### window.framework
框架实例会自动暴露到全局,可以在任何地方访问:
```javascript
// 在非 Vue 组件中使用
const http = window.framework.http
const uiTool = window.framework.uiTool
const config = window.framework.config
// HTTP 请求
const res = await window.framework.http.get('/api/users')
// UI 工具
window.framework.uiTool.delConfirm(() => {
// 删除逻辑
})
```
### Vue 原型方法
在 Vue 组件中可以直接使用:
```javascript
export default {
methods: {
async loadData() {
// 直接使用 this.$xxx
const res = await this.$http.get('/api/users')
this.$uiTool.delConfirm(() => {})
this.$tools.formatDate(new Date())
this.$uiTool.downloadFile(response, 'file.csv')
}
}
}
```
## 📁 文件下载功能
### 使用 downloadFile 方法
框架提供了便捷的文件下载功能,支持 CSV 等格式:
```javascript
// 在 Vue 组件中使用
export default {
methods: {
// 导出数据
exportData() {
// 调用 API 获取数据
this.$http.fileExport('/api/export', params).then(res => {
// 使用 downloadFile 下载
this.$uiTool.downloadFile(res, '数据导出.csv')
this.$Message.success('导出成功!')
}).catch(error => {
this.$Message.error('导出失败:' + error.message)
})
}
}
}
```
### 支持的数据格式
- **CSV 格式**:自动处理换行符,保持表格格式
- **Blob 对象**:支持二进制文件下载
- **文本数据**:支持纯文本文件下载
### 自动处理特性
-**换行符保持**CSV 文件的换行符会被正确保持
-**文件名处理**:自动清理文件名中的特殊字符
-**浏览器兼容**:支持所有现代浏览器
-**内存管理**:自动清理临时 URL 对象
## 🎨 全局组件使用
### FloatPanel - 浮动面板组件
`FloatPanel` 是一个浮动在父窗体上的面板组件,类似于抽屉效果,常用于详情展示、表单编辑等场景。
**基本使用:**
```vue
<template>
<div>
<Button @click="showPanel">打开浮动面板</Button>
<FloatPanel
ref="floatPanel"
title="详情面板"
position="right"
:show-back="true"
back-text="返回"
@back="handleBack"
>
<div>这里是面板内容</div>
</FloatPanel>
</div>
</template>
<script>
export default {
methods: {
showPanel() {
// 通过 ref 调用 show 方法显示面板
this.$refs.floatPanel.show()
},
hidePanel() {
// 通过 ref 调用 hide 方法隐藏面板
this.$refs.floatPanel.hide()
},
handleBack() {
console.log('返回按钮被点击')
this.hidePanel()
}
}
}
</script>
```
**属性说明:**
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `title` | String | `''` | 面板标题 |
| `width` | String/Number | `'100%'` | 面板宽度(字符串或数字),默认占满父容器 |
| `height` | String/Number | `'100%'` | 面板高度(字符串或数字),默认占满父容器 |
| `position` | String | `'right'` | 面板位置:`left``right``top``bottom``center` |
| `showBack` | Boolean | `true` | 是否显示返回按钮 |
| `showClose` | Boolean | `false` | 是否显示关闭按钮 |
| `backText` | String | `'返回'` | 返回按钮文字 |
| `closeOnClickBackdrop` | Boolean | `false` | 点击遮罩是否关闭 |
| `mask` | Boolean | `false` | 是否显示遮罩(默认不显示) |
| `zIndex` | Number | `1000` | 层级 |
**方法:**
| 方法 | 说明 | 参数 |
|------|------|------|
| `show(callback)` | 显示面板 | `callback`: 可选的回调函数 |
| `hide()` | 隐藏面板 | - |
**事件:**
| 事件 | 说明 | 参数 |
|------|------|------|
| `back` | 点击返回按钮时触发 | - |
**插槽:**
| 插槽 | 说明 |
|------|------|
| `default` | 面板主体内容 |
| `header-right` | 头部右侧内容(可用于添加自定义按钮) |
**位置说明:**
- `left`: 从左侧滑入
- `right`: 从右侧滑入(默认)
- `top`: 从顶部滑入
- `bottom`: 从底部滑入
- `center`: 居中显示,带缩放动画
**完整示例:**
```vue
<template>
<div>
<Button @click="openDetailPanel">查看详情</Button>
<FloatPanel
ref="detailPanel"
title="用户详情"
position="right"
:show-back="true"
:show-close="true"
back-text="返回"
@back="handleBack"
>
<template #header-right>
<Button type="primary" @click="handleSave">保存</Button>
</template>
<div class="detail-content">
<Form :model="formData" :label-width="100">
<FormItem label="用户名">
<Input v-model="formData.username" />
</FormItem>
<FormItem label="邮箱">
<Input v-model="formData.email" />
</FormItem>
</Form>
</div>
</FloatPanel>
</div>
</template>
<script>
export default {
data() {
return {
formData: {
username: '',
email: ''
}
}
},
methods: {
openDetailPanel() {
this.$refs.detailPanel.show()
},
handleBack() {
this.$refs.detailPanel.hide()
},
handleSave() {
// 保存逻辑
console.log('保存数据', this.formData)
this.$Message.success('保存成功')
this.$refs.detailPanel.hide()
}
}
}
</script>
```
**特性说明:**
- ✅ 基于父元素定位,不会遮挡菜单
- ✅ 宽度和高度默认 100%,占满父容器
- ✅ 无遮罩背景,完全浮在父页面上
- ✅ 路由切换或组件销毁时自动关闭
- ✅ 支持多种位置和动画效果
- ✅ 支持自定义头部右侧内容
## 📝 业务开发示例
### 创建业务页面
```vue
<!-- src/views/business/product.vue -->
<template>
<div>
<h1>产品管理</h1>
<Button @click="loadData">加载数据</Button>
<Tables :columns="columns" :data="list" />
</div>
</template>
<script>
export default {
data() {
return {
list: [],
columns: [
{ title: 'ID', key: 'id' },
{ title: '名称', key: 'name' },
{ title: '价格', key: 'price' }
]
}
},
async mounted() {
await this.loadData()
},
methods: {
async loadData() {
// 使用框架提供的 http 工具
const res = await this.$http.get('/product/list', { page: 1 })
this.list = res.data
},
async handleDelete(id) {
// 使用框架提供的 UI 工具
this.$uiTool.delConfirm(async () => {
await this.$http.post('/product/delete', { id })
this.$Message.success('删除成功')
await this.loadData()
})
}
}
}
</script>
```
### 创建业务 API
```javascript
// src/api/business/productServer.js
// 注意:不需要 import http直接使用 http
class ProductServer {
async getList(params) {
return await http.get('/product/list', params)
}
async save(data) {
return await http.post('/product/save', data)
}
async delete(id) {
return await http.post('/product/delete', { id })
}
async exportCsv(params) {
return await http.fileExport('/product/export', params)
}
}
export default new ProductServer()
```
## ❓ 常见问题
### Q1: 打包后文件太大怎么办?
A: 框架已经将 Vue、VueRouter、Vuex、ViewUI、Axios 设置为外部依赖,不会打包进去。确保在项目中单独安装这些依赖。
### Q2: 如何只使用部分功能?
A: 可以按需导入:
```javascript
import { http, uiTool, tools } from './libs/admin-framework.js'
```
### Q3: 权限菜单中的业务页面显示 404 怎么办?
A: 需要配置组件映射表:
```javascript
Vue.use(AdminFramework, {
// ... 其他配置
componentMap: {
'ball/games': GamesComponent,
'order/pay_orders': PayOrdersComponent
}
})
```
### Q4: 如何自定义配置?
A: 修改 `config/index.js` 文件:
```javascript
module.exports = {
title: '你的系统名称',
apiUrl: 'http://your-api-url/',
// ... 其他配置
}
```
### Q5: 如何使用登录功能?
A: 在组件中:
```javascript
export default {
methods: {
async login() {
await this.$store.dispatch('user/handleLogin', {
userFrom: { username: 'admin', password: '123456' },
Main: AdminFramework.Main,
ParentView: AdminFramework.ParentView,
Page404: AdminFramework.Page404
})
this.$router.push({ name: 'home' })
}
}
}
```
### Q6: 需要单独引入样式文件吗?
A: **不需要!** 框架已内置所有样式:
-`base.less` - 基础样式
-`animate.css` - 动画样式
-`ivewExpand.less` - ViewUI 扩展样式
-`iconfont.css` - 字体图标样式
只需引入框架即可:
```javascript
import AdminFramework from './libs/admin-framework.js'
Vue.use(AdminFramework, { ... })
```
## 📦 技术栈
- Vue 2.6+
- Vue Router 3.x
- Vuex 3.x
- View Design (iView) 4.x
- Axios
- Less
- Webpack 5
## 📄 许可证
MIT License
## 👨‍💻 作者
light
---
**祝开发愉快!** 🎉
如有问题,请查看 Demo 项目示例或联系开发团队。

1582
_doc/完整使用文档.md Normal file

File diff suppressed because it is too large Load Diff

325
_doc/快速开始.md Normal file
View File

@@ -0,0 +1,325 @@
# AdminFramework 快速开始
## 🚀 3 分钟上手
### 第一步:引入框架
```javascript
import AdminFramework from './admin-framework.js'
```
### 第二步:创建应用
```javascript
const app = AdminFramework.createApp({
title: '我的管理系统',
apiUrl: 'http://localhost:9098/admin_api/',
componentMap: {
'business/product': ProductComponent,
'business/order': OrderComponent
}
})
```
### 第三步挂载应用1
```javascript
app.$mount('#app')
```
## ✨ 就这么简单!
**完整代码只需 12 行:**
## 📁 文件下载功能
框架内置了便捷的文件下载功能:
```javascript
// 在组件中使用
export default {
methods: {
exportData() {
// 调用 API 获取数据
this.$http.fileExport('/api/export', params).then(res => {
// 下载文件(自动处理换行符)
this.$uiTool.downloadFile(res, '数据导出.csv')
})
}
}
}
```
**特性:**
- ✅ 自动处理 CSV 换行符
- ✅ 支持多种文件格式
- ✅ 浏览器兼容性好
```javascript
// main.js
import AdminFramework from './admin-framework.js'
import ProductComponent from './views/business/product.vue'
import OrderComponent from './views/business/order.vue'
const app = AdminFramework.createApp({
title: '我的管理系统',
apiUrl: 'http://localhost:9098/admin_api/',
componentMap: {
'business/product': ProductComponent,
'business/order': OrderComponent
}
})
app.$mount('#app')
```
## 📋 组件映射表配置
`componentMap` 用于定义业务页面的动态路由。
**重要提示:** 只需传递不带 `.vue` 后缀的路径,框架会自动处理带后缀和不带后缀的两种情况!
```javascript
// 直接在 createApp 中配置
const app = AdminFramework.createApp({
title: '我的管理系统',
apiUrl: 'http://localhost:9098/admin_api/',
componentMap: {
// ✅ 正确:只传递不带 .vue 的路径
'business/product': ProductComponent,
'business/order': OrderComponent,
'system/user': UserComponent,
// ❌ 错误:不要同时传递带和不带 .vue 的路径(多余)
// 'business/product.vue': ProductComponent, // 框架会自动添加
}
})
```
**说明:** 框架内部会自动为每个组件创建两个映射:
- `'business/product'` → ProductComponent
- `'business/product.vue'` → ProductComponent自动添加
所以后台菜单配置 `business/product``business/product.vue` 都可以正常工作!
## ⚙️ 配置说明
### 必填参数
| 参数 | 说明 | 示例 |
|------|------|------|
| `title` | 应用标题 | `'我的管理系统'` |
| `apiUrl` | API 基础地址 | `'http://localhost:9098/admin_api/'` |
| `componentMap` | 组件映射表 | 见上方示例 |
### 可选参数
| 参数 | 说明 | 默认值 |
|------|------|--------|
| `uploadUrl` | 上传文件地址 | `apiUrl + 'upload'` |
| `onReady` | 应用启动回调 | - |
## 🎯 完整示例
```javascript
import AdminFramework from './admin-framework.js'
import componentMap from './router/component-map.js'
const app = AdminFramework.createApp({
// 必填配置
title: '我的管理系统',
apiUrl: 'http://localhost:9098/admin_api/',
componentMap: componentMap,
// 可选配置
uploadUrl: 'http://cdn.example.com/upload', // 可选,默认为 apiUrl + 'upload'
// 应用启动完成回调
onReady() {
console.log('应用已启动!')
console.log('当前登录用户:', this.$store.state.user)
}
})
app.$mount('#app')
```
## 📦 框架内置功能
使用 `createApp()` 后,框架会自动提供:
### 1. 内置页面
- ✅ 登录页面 (`/login`)
- ✅ 主页 (`/home`)
- ✅ 404 页面 (`/404`)
- ✅ 401 页面 (`/401`)
- ✅ 500 页面 (`/500`)
### 2. 系统管理页面
- ✅ 用户管理 (`/system/user`)
- ✅ 角色管理 (`/system/role`)
- ✅ 日志管理 (`/system/log`)
- ✅ 参数设置 (`/system/param`)
- ✅ 菜单管理 (`/system/menu`)
- ✅ 权限控制 (`/system/control`)
- ✅ 标题设置 (`/system/title`)
### 3. 全局组件
-`<Tables>` - 增强型表格
-`<UploadSingle>` - 单图上传
-`<UploadMultiple>` - 多图上传
-`<TreeGrid>` - 树形表格
-`<AsyncModal>` - 异步弹窗
-`<Editor>` - 富文本编辑器
-`<CommonIcon>` - 图标选择器
-`<FloatPanel>` - 浮动面板
### 4. 工具方法
在任何组件中可以使用:
```javascript
// HTTP 请求
this.$http.get('/api/users')
this.$http.post('/api/users', data)
// UI 工具
this.$uiTool.showLoading()
this.$uiTool.hideLoading()
this.$uiTool.showSuccess('操作成功')
// 通用工具
this.$tools.formatDate(new Date())
this.$tools.deepClone(obj)
// 文件下载
this.$uiTool.downloadFile(response, 'filename.xlsx')
// 配置信息
this.$config.title
this.$config.apiUrl
this.$config.uploadUrl
// 状态管理
this.$store.state.user.token
this.$store.state.app.sysTitle
```
## 🎨 自定义页面
### 1. 创建页面组件
```vue
<!-- views/business/product-list.vue -->
<template>
<div>
<Tables :columns="columns" :data="list" />
</div>
</template>
<script>
export default {
data() {
return {
list: [],
columns: [
{ title: '产品名称', key: 'name' },
{ title: '价格', key: 'price' }
]
}
},
created() {
this.loadData()
},
methods: {
async loadData() {
const res = await this.$http.get('/products')
this.list = res.data
}
}
}
</script>
```
### 2. 添加到组件映射表
```javascript
// router/component-map.js
import ProductList from '../views/business/product-list.vue'
export default {
'business/product': ProductList // 路径对应后台菜单的 component 字段
}
```
### 3. 在后台配置菜单
在"菜单管理"中添加菜单项:
- 菜单名称: `产品列表`
- 路由路径: `/business/product`
- 组件路径: `business/product`(对应 componentMap 的 key
## 📝 常见问题
### Q1: uploadUrl 如何配置?
**A:** 默认情况下无需配置,框架会自动设置为 `apiUrl + 'upload'`
如果需要自定义(如使用 CDN可以手动传入
```javascript
AdminFramework.createApp({
apiUrl: 'http://localhost:9098/admin_api/',
uploadUrl: 'http://cdn.example.com/upload' // 自定义上传地址
})
```
### Q2: 如何访问框架实例?
**A:** 框架实例会自动暴露到全局:
```javascript
// 浏览器控制台
window.framework // 框架实例
window.app // Vue 实例
window.rootVue // Vue 实例(别名)
```
### Q3: 如何添加自定义 Vuex 模块?
**A:** 使用旧的 `install()` 方式:
```javascript
import AdminFramework from './admin-framework.js'
AdminFramework.install(Vue, {
config: { ... },
ViewUI,
VueRouter,
Vuex,
customModules: {
myModule: myModuleConfig
}
})
```
### Q4: 框架文件太大怎么办?
**A:** 新版框架3.6 MB内置了所有依赖使用更方便。如果需要减小文件大小可以使用旧的 `install()` 方式,手动引入依赖。
## 🔗 相关文档
- [简化使用说明.md](./简化使用说明.md) - 详细的使用说明和对比
- [README.md](./README.md) - 项目介绍和特性
- [demo/src/main.js](./demo/src/main.js) - 完整示例代码
## 💡 最佳实践
1. **推荐使用 `createApp()`** - 代码更简洁,减少 77% 的代码量
2. **组件映射表单独管理** - 方便维护和扩展
3. **利用内置组件** - 如 `<Tables>``<UploadSingle>` 等,提高开发效率
4. **使用 `onReady` 回调** - 在应用启动后执行初始化逻辑
## 🎉 开始开发吧!
现在你已经了解了 AdminFramework 的快速使用方法,开始构建你的管理系统吧!

View File

@@ -3,13 +3,19 @@ module.exports = {
[
'@babel/preset-env',
{
modules: false,
modules: 'auto', // 修改为 auto让 webpack 自动处理模块格式
targets: {
browsers: ['>1%', 'last 2 versions', 'not ie <= 8']
}
}
],
'@vue/babel-preset-jsx'
[
'@vue/babel-preset-jsx',
{
// Vue 2 JSX 配置
injectH: true // 自动注入 h 函数
}
]
]
}

View File

@@ -1,76 +0,0 @@
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
echo ================================
echo Admin Framework 构建工具
echo ================================
echo.
REM 检查 Node.js 是否安装
where node >nul 2>nul
if %errorlevel% neq 0 (
echo ❌ 错误: 未检测到 Node.js请先安装 Node.js
pause
exit /b 1
)
for /f "tokens=*" %%i in ('node -v') do set NODE_VERSION=%%i
for /f "tokens=*" %%i in ('npm -v') do set NPM_VERSION=%%i
echo ✅ Node.js 版本: %NODE_VERSION%
echo ✅ NPM 版本: %NPM_VERSION%
echo.
REM 检查是否已安装依赖
if not exist "node_modules" (
echo 📦 正在安装依赖...
call npm install
if !errorlevel! neq 0 (
echo ❌ 依赖安装失败
pause
exit /b 1
)
echo ✅ 依赖安装成功
echo.
) else (
echo ✅ 依赖已安装
echo.
)
REM 执行打包
echo 🔨 正在打包框架...
call npm run build
if %errorlevel% equ 0 (
echo.
echo ================================
echo ✅ 打包成功!
echo ================================
echo.
echo 📦 输出文件: dist\admin-framework.js
REM 显示文件大小
if exist "dist\admin-framework.js" (
for %%A in (dist\admin-framework.js) do (
set SIZE=%%~zA
set /a SIZE_KB=!SIZE! / 1024
echo 📊 文件大小: !SIZE_KB! KB
)
)
echo.
echo 📚 下一步:
echo 1. 将 dist\admin-framework.js 复制到你的项目
echo 2. 查看 QUICK_START.md 了解如何使用
echo 3. 查看 USAGE_EXAMPLE.md 了解详细示例
echo.
) else (
echo.
echo ❌ 打包失败,请检查错误信息
pause
exit /b 1
)
pause

View File

@@ -1,65 +0,0 @@
#!/bin/bash
# Admin Framework 构建脚本
echo "================================"
echo " Admin Framework 构建工具"
echo "================================"
echo ""
# 检查 Node.js 是否安装
if ! command -v node &> /dev/null
then
echo "❌ 错误: 未检测到 Node.js请先安装 Node.js"
exit 1
fi
echo "✅ Node.js 版本: $(node -v)"
echo "✅ NPM 版本: $(npm -v)"
echo ""
# 检查是否已安装依赖
if [ ! -d "node_modules" ]; then
echo "📦 正在安装依赖..."
npm install
if [ $? -ne 0 ]; then
echo "❌ 依赖安装失败"
exit 1
fi
echo "✅ 依赖安装成功"
echo ""
else
echo "✅ 依赖已安装"
echo ""
fi
# 执行打包
echo "🔨 正在打包框架..."
npm run build
if [ $? -eq 0 ]; then
echo ""
echo "================================"
echo " ✅ 打包成功!"
echo "================================"
echo ""
echo "📦 输出文件: dist/admin-framework.js"
# 显示文件大小
if [ -f "dist/admin-framework.js" ]; then
SIZE=$(du -h dist/admin-framework.js | cut -f1)
echo "📊 文件大小: $SIZE"
fi
echo ""
echo "📚 下一步:"
echo " 1. 将 dist/admin-framework.js 复制到你的项目"
echo " 2. 查看 QUICK_START.md 了解如何使用"
echo " 3. 查看 USAGE_EXAMPLE.md 了解详细示例"
echo ""
else
echo ""
echo "❌ 打包失败,请检查错误信息"
exit 1
fi

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

261
demo/README.md Normal file
View File

@@ -0,0 +1,261 @@
# 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
# 在项目根目录执行
cd ..
npm run build
```
### 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
// 引入 Admin Framework
import AdminFramework from '../../dist/admin-framework.js'
// 引入组件映射表
import componentMap from './router/component-map.js'
// 创建应用
const app = AdminFramework.createApp({
title: '我的管理系统',
apiUrl: 'http://localhost:9098/admin_api/',
componentMap: componentMap,
onReady() {
console.log('应用已准备就绪!')
}
})
// 挂载应用
app.$mount('#app')
```
## 内置功能
### 1. 系统页面
- **登录页面**: `/login`
- **首页**: `/home`
- **错误页面**: `/401`, `/404`, `/500`
### 2. 系统管理
- **用户管理**: 系统用户的增删改查
- **角色管理**: 角色权限管理
- **菜单管理**: 动态菜单配置
- **日志管理**: 系统操作日志
### 3. 高级功能
- **动态表单**: 基于配置生成表单
- **动态表格**: 可配置的数据表格
- **文件上传**: 单文件/多文件上传
- **富文本编辑器**: WangEditor
- **代码编辑器**: Ace Editor
## API 使用
### HTTP 请求
```javascript
// GET 请求
window.framework.http.get('/api/users').then(res => {
console.log(res.data)
})
// POST 请求
window.framework.http.post('/api/users', {
name: '张三',
age: 25
}).then(res => {
console.log(res.data)
})
// 在 API 文件中使用
class UserServer {
async getList(params) {
return await window.framework.http.get('/api/users', params)
}
}
```
### 工具函数
```javascript
// 使用框架提供的工具函数
const tools = window.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 = window.framework.uiTool
// 成功提示
window.framework.uiTool.success('操作成功')
// 错误提示
window.framework.uiTool.error('操作失败')
// 确认对话框
window.framework.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`

7460
demo/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

35
demo/package.json Normal file
View File

@@ -0,0 +1,35 @@
{
"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",
"less": "^4.1.0",
"less-loader": "^10.0.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 </title>
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

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

View File

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

View File

@@ -0,0 +1,62 @@
const gameCommentsServer = {
// 获取球局评论列表
getGameCommentsList: (params) => {
return window.framework.http.post('/game_comments/page', params)
},
// 获取所有球局评论
getAllGameComments: (params) => {
return window.framework.http.get('/game_comments/all', params)
},
// 获取球局评论详情
getGameCommentDetail: (params) => {
return window.framework.http.get('/game_comments/detail', params)
},
// 新增球局评论
addGameComment: (params) => {
return window.framework.http.post('/game_comments/add', params)
},
// 更新球局评论
updateGameComment: (params) => {
return window.framework.http.post('/game_comments/edit', params)
},
// 删除球局评论
deleteGameComment: (params) => {
return window.framework.http.post('/game_comments/del', params)
},
// 批量删除球局评论
batchDeleteGameComments: (params) => {
return window.framework.http.post('/game_comments/batch_delete', params)
},
// 导出球局评论数据
exportGameComments: (params) => {
return window.framework.http.post('/game_comments/export', params)
},
// 审核评论
auditComment: (params) => {
return window.framework.http.post('/game_comments/audit', params)
},
// 获取评论回复
getCommentReplies: (params) => {
return window.framework.http.get('/game_comments/replies', params)
}
}
// 添加简写方法名别名,保持向后兼容
gameCommentsServer.page = gameCommentsServer.getGameCommentsList
gameCommentsServer.all = gameCommentsServer.getAllGameComments
gameCommentsServer.add = gameCommentsServer.addGameComment
gameCommentsServer.edit = gameCommentsServer.updateGameComment
gameCommentsServer.del = gameCommentsServer.deleteGameComment
gameCommentsServer.exportCsv = gameCommentsServer.exportGameComments
export default gameCommentsServer

View File

@@ -0,0 +1,58 @@
const gameParticipantsServer = {
// 获取球局参与者列表
getGameParticipantsList: (params) => {
return window.framework.http.post('/game_participants/page', params)
},
// 获取所有球局参与者
getAllGameParticipants: (params) => {
return window.framework.http.get('/game_participants/all', params)
},
// 获取球局参与者详情
getGameParticipantDetail: (params) => {
return window.framework.http.get('/game_participants/detail', params)
},
// 新增球局参与者
addGameParticipant: (params) => {
return window.framework.http.post('/game_participants/add', params)
},
// 更新球局参与者
updateGameParticipant: (params) => {
return window.framework.http.post('/game_participants/edit', params)
},
// 删除球局参与者
deleteGameParticipant: (params) => {
return window.framework.http.post('/game_participants/del', params)
},
// 批量删除球局参与者
batchDeleteGameParticipants: (params) => {
return window.framework.http.post('/game_participants/batch_delete', params)
},
// 导出球局参与者数据
exportGameParticipants: (params) => {
return window.framework.http.post('/game_participants/export', params)
},
// 更新参与者状态
updateParticipantStatus: (params) => {
return window.framework.http.post('/game_participants/update_status', params)
}
}
// 添加简写方法名别名,保持向后兼容
gameParticipantsServer.page = gameParticipantsServer.getGameParticipantsList
gameParticipantsServer.all = gameParticipantsServer.getAllGameParticipants
gameParticipantsServer.add = gameParticipantsServer.addGameParticipant
gameParticipantsServer.edit = gameParticipantsServer.updateGameParticipant
gameParticipantsServer.del = gameParticipantsServer.deleteGameParticipant
gameParticipantsServer.exportCsv = gameParticipantsServer.exportGameParticipants
export default gameParticipantsServer

View File

@@ -0,0 +1,86 @@
/**
* 球赛管理 API
*
* 使用说明:
* 1. 不需要 import http
* 2. 直接使用 window.framework.http 调用接口
* 3. 所有方法返回 Promise
*/
class GamesServer {
/**
* 获取球赛列表(分页)
* @param {Object} params - 查询参数 { page, size, keyword }
* @returns {Promise}
*/
async getPage(params) {
return await window.framework.http.post('/games/page', params)
}
/**
* 获取所有球赛
* @returns {Promise}
*/
async getAll() {
return await window.framework.http.get('/games/all')
}
/**
* 获取球赛详情
* @param {Number|String} id - 球赛ID
* @returns {Promise}
*/
async getDetail(id) {
return await window.framework.http.get(`/games/detail/${id}`)
}
/**
* 创建球赛
* @param {Object} data - 球赛数据
* @returns {Promise}
*/
async create(data) {
return await window.framework.http.post('/games/create', data)
}
/**
* 更新球赛
* @param {Number|String} id - 球赛ID
* @param {Object} data - 球赛数据
* @returns {Promise}
*/
async update(id, data) {
return await window.framework.http.post(`/games/update/${id}`, data)
}
/**
* 删除球赛
* @param {Number|String} id - 球赛ID
* @returns {Promise}
*/
async delete(id) {
return await window.framework.http.post(`/games/delete/${id}`, { id })
}
/**
* 批量删除球赛
* @param {Array} ids - 球赛ID数组
* @returns {Promise}
*/
async batchDelete(ids) {
return await window.framework.http.post('/games/batch_delete', { ids })
}
/**
* 导出数据
* @param {Object} params - 查询参数
* @returns {Promise}
*/
async exportData(params) {
return await window.framework.http.fileExport('/games/export', params)
}
}
// 导出单例
export default new GamesServer()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,24 @@
/**
* 业务游戏管理 API
*
* 使用说明:
* 1. 不需要 import http
* 2. 直接使用 window.framework.http 调用接口
* 3. 所有方法返回 Promise
*/
export const getList = (params) => {
return window.framework.http.get('/business/games/page', params)
}
export const add = (data) => {
return window.framework.http.post('/business/games/add', data)
}
export const edit = (data) => {
return window.framework.http.post('/business/games/edit', data)
}
export const del = (params) => {
return window.framework.http.post('/business/games/del', params)
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,38 @@
class info_typeClServer {
async all(param) {
let res= await window.framework.http.get('/inf_info_type/all', param);
return res;
}
async page(row) {
let res= await window.framework.http.post('/inf_info_type/page', row);
return res;
}
async exportCsv(row) {
let res = window.framework.http.fileExport("/inf_info_type/export", row);
return res;
}
async add(row) {
let res= await window.framework.http.post('/inf_info_type/add', row);
return res;
}
async edit(row) {
let res= await window.framework.http.post('/inf_info_type/edit', row);
return res;
}
async del(row) {
let res= await window.framework.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 @@
class HomeServer {
// 获取订单统计
async getOderCount() {
let res = await window.framework.http.get("/order/count");
return res;
}
async getUserCount() {
let res = await window.framework.http.get("/user/count");
return res;
}
async getSalesRank() {
let res = await window.framework.http.get("/index/salesRank");
return res;
}
async userRecommendRank() {
let res = await window.framework.http.get("/index/userRecommendRank");
return res;
}
}
const homeServer = new HomeServer();
export default homeServer;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,24 @@
/**
* 系统文件管理 API
*
* 使用说明:
* 1. 不需要 import http
* 2. 直接使用 window.framework.http 调用接口
* 3. 所有方法返回 Promise
*/
export const getList = (params) => {
return window.framework.http.get('/sys_file/page', params)
}
export const add = (data) => {
return window.framework.http.post('/sys_file/add', data)
}
export const edit = (data) => {
return window.framework.http.post('/sys_file/edit', data)
}
export const del = (params) => {
return window.framework.http.post('/sys_file/del', params)
}

View File

@@ -0,0 +1,41 @@
/**
* 球局统计 API
*
* 使用说明:
* 1. 不需要 import http
* 2. 直接使用 window.framework.http 调用接口
* 3. 所有方法返回 Promise
*/
// 获取球局统计列表
export const getList = (params) => {
return window.framework.http.post('/admin/game_statistics/list', params)
}
// 获取球局统计详情
export const getDetail = (params) => {
return window.framework.http.post('/admin/game_statistics/detail', params)
}
// 添加球局统计
export const add = (params) => {
return window.framework.http.post('/admin/game_statistics/add', params)
}
// 编辑球局统计
export const edit = (params) => {
return window.framework.http.post('/admin/game_statistics/edit', params)
}
// 删除球局统计
export const del = (params) => {
return window.framework.http.post('/admin/game_statistics/del', params)
}
// 导出球局统计
export const exportData = (params) => {
return window.framework.http.fileExport('/admin/game_statistics/export', params)
}

View File

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

View File

@@ -0,0 +1,46 @@
/**
* 营收统计 API
*
* 使用说明:
* 1. 不需要 import http
* 2. 直接使用 window.framework.http 调用接口
* 3. 所有方法返回 Promise
*/
// 获取营收统计列表
export const getList = (params) => {
return window.framework.http.post('/admin/revenue_statistics/list', params)
}
// 获取营收统计详情
export const getDetail = (params) => {
return window.framework.http.post('/admin/revenue_statistics/detail', params)
}
// 获取营收统计概览
export const getOverview = (params) => {
return window.framework.http.post('/admin/revenue_statistics/overview', params)
}
// 添加营收统计
export const add = (params) => {
return window.framework.http.post('/admin/revenue_statistics/add', params)
}
// 编辑营收统计
export const edit = (params) => {
return window.framework.http.post('/admin/revenue_statistics/edit', params)
}
// 删除营收统计
export const del = (params) => {
return window.framework.http.post('/admin/revenue_statistics/del', params)
}
// 导出营收统计
export const exportData = (params) => {
return window.framework.http.fileExport('/admin/revenue_statistics/export', params)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,30 @@
class SysLogServe {
async all(param) {
let res = await window.framework.http.get("/sys_log/all", param);
return res;
}
async detail(param) {
let res = await window.framework.http.get("/sys_log/detail", param);
return res;
}
async delete(param) {
let res = await window.framework.http.get("/sys_log/delete", param);
return res;
}
async delete_all(param) {
let res = await window.framework.http.get("/sys_log/delete_all", param);
return res;
}
async operates(param) {
let res = await window.framework.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 @@
class systemTypeClServer {
async all(param) {
let res= await window.framework.http.get('/sys_project_type/all', param);
return res;
}
async page(row) {
let res= await window.framework.http.post('/sys_project_type/page', row);
return res;
}
async exportCsv(row) {
let res = window.framework.http.fileExport("/sys_project_type/export", row);
return res;
}
async add(row) {
let res= await window.framework.http.post('/sys_project_type/add', row);
return res;
}
async edit(row) {
let res= await window.framework.http.post('/sys_project_type/edit', row);
return res;
}
async del(row) {
let res= await window.framework.http.post('/sys_project_type/del', row);
return res;
}
}
const systemTypeServer = new systemTypeClServer();
export default systemTypeServer;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

@@ -0,0 +1,22 @@
// 开发环境直接引用源码以支持热更新
import AdminFramework from '../../dist/admin-framework.js'
// 引入组件映射表
import componentMap from './router/component-map.js'
// 【超级简化】只需一个函数调用!
const app = AdminFramework.createApp({
title: 'tennis管理系统',
apiUrl: 'http://localhost:9098/admin_api/', // API 地址uploadUrl 会自动设置为 apiUrl + 'upload'
componentMap: componentMap, // 传入组件映射表,用于动态路由
onReady() {
// 可选:应用启动完成后的回调
console.log('应用已准备就绪!')
}
})
// 挂载应用
app.$mount('#app')

View File

@@ -0,0 +1,96 @@
// 组件映射表 - 将后端返回的组件路径映射到实际的 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 WchCities from '../views/users/wch_cities.vue'
import WchProfessions from '../views/users/wch_professions.vue'
/**
* 组件映射对象
*
* ✅ 重要提示:只需传递不带 .vue 后缀的路径!
* 框架会自动为每个组件创建带 .vue 和不带 .vue 的两个映射
*
* 例如:'ai/ai_messages': AiMessages
* 框架自动处理为:
* - 'ai/ai_messages' → AiMessages
* - 'ai/ai_messages.vue' → AiMessages (自动添加)
*
* key: 后端返回的组件路径(不带 .vue 后缀)
* value: 实际的 Vue 组件
*/
const componentMap = {
// ===== AI 模块 =====
'ai/ai_messages': AiMessages,
// ===== 球场模块 =====
'ball/game_comments': GameComments,
'ball/game_participants': GameParticipants,
'ball/games': Games,
'ball/venues': Venues,
'ball/wch_users': WchUsers,
// ===== 业务模块 =====
'business/hot_city_qr': HotCityQr,
// ===== 消息模块 =====
'message/msg_notifications': MsgNotifications,
// ===== NTRP 模块 =====
'ntrp/ntr_questions': NtrQuestions,
'ntrp/ntr_records': NtrRecords,
// ===== 订单模块 =====
'order/frozen_funds': FrozenFunds,
'order/pay_orders': PayOrders,
'order/transfer_details': TransferDetails,
'order/wallet_transactions': WalletTransactions,
'order/wch_wallets': WchWallets,
// ===== 统计模块 =====
'statistics/resources': Resources,
// ===== 用户模块 =====
'users/recommend_blocks': RecommendBlocks,
'users/user_follows': UserFollows,
'users/user_tracking': UserTracking,
'users/wch_cities': WchCities,
'users/wch_professions': WchProfessions,
}
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 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 window.framework.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) {
window.framework.uiTool.delConfirm(async () => {
await ai_messagesServer.del(row)
this.$Message.success('删除成功!')
this.query(1)
})
},
exportCsv() {
ai_messagesServer.exportCsv(this.gridOption.param).then(res => {
window.framework.tools.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 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 window.framework.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) {
window.framework.uiTool.delConfirm(async () => {
await game_commentsServer.del(row)
this.$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 => {
window.framework.tools.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 menuServer from '@/api/system/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 window.framework.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)
this.$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)
this.$Message.success('修改成功!')
this.init()
}
})
},
async delConfirm(row) {
window.framework.uiTool.delConfirm(async () => {
await gameParticipantsServer.del(row)
this.$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 menuServer from '@/api/system/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 window.framework.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)
this.$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)
this.$Message.success('修改成功!')
this.init()
}
})
},
async delConfirm(row) {
window.framework.uiTool.delConfirm(async () => {
await gamesServer.del(row)
this.$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 menuServer from '@/api/system/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 window.framework.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)
this.$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)
this.$Message.success('修改成功!')
this.init()
}
})
},
async delConfirm(row) {
window.framework.uiTool.delConfirm(async () => {
await venuesServer.del(row)
this.$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,358 @@
<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 menuServer from '@/api/system/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 window.framework.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)
this.$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)
this.$Message.success('修改成功!')
this.init()
}
})
},
async delConfirm(row) {
window.framework.uiTool.delConfirm(async () => {
await wch_usersServer.del(row)
this.$Message.success('删除成功!')
this.init()
})
},
resetQuery() {
this.gridOption.param.seachOption = {
key: 'nickname',
value: '',
gender: null,
ntrp_level: null
};
this.query(1);
},
async exportCsv(row) {
let res = 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 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 window.framework.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 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 window.framework.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) {
window.framework.uiTool.delConfirm(async () => {
await msg_notificationsServer.del(row)
this.$Message.success('删除成功!')
this.query(1)
})
},
exportCsv() {
msg_notificationsServer.exportCsv(this.gridOption.param).then(res => {
window.framework.tools.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 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 window.framework.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) {
window.framework.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 => {
window.framework.tools.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,259 @@
<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 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 window.framework.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) {
window.framework.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)
}
},
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 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 window.framework.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 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 window.framework.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) {
window.framework.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 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 window.framework.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) {
window.framework.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) {
window.framework.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 menuServer from '@/api/system/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 window.framework.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 => {
window.framework.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 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 window.framework.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'
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 window.framework.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 => {
window.framework.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,164 @@
<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 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: '用户昵称' }
],
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 window.framework.uiTool.getBtn(h, btns)
},
}
],
editColumns: [
{
title: '用户ID',
key: 'user_id',
data_type: 'number',
com: 'InputNumber',
required: true,
is_show_edit: 1
},
{
title: '被屏蔽用户ID',
key: 'blocked_user_id',
data_type: 'number',
com: 'InputNumber',
required: true,
is_show_edit: 1
},
{
title: '屏蔽时间',
key: 'block_time',
com: 'DatePicker',
is_show_edit: 1
}
]
}
},
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) {
window.framework.uiTool.delConfirm(async () => {
await recommend_blocksServer.del(row)
this.$Message.success('删除成功!')
this.query(1)
})
},
exportCsv() {
recommend_blocksServer.exportCsv(this.gridOption.param).then(res => {
window.framework.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,172 @@
<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 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: '用户昵称' }
],
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 window.framework.uiTool.getBtn(h, btns)
},
}
],
editColumns: [
{
title: '关注者ID',
key: 'follower_id',
data_type: 'number',
com: 'InputNumber',
is_show_edit: 1
},
{
title: '被关注者ID',
key: 'following_id',
data_type: 'number',
com: 'InputNumber',
is_show_edit: 1
},
{
title: '关注时间',
key: 'follow_time',
com: 'DatePicker',
is_show_edit: 1
}
]
}
},
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.addShow({
'follower_id': null,
'following_id': null,
'follow_time': null
}, async (newRow) => {
let res = await user_followsServer.add(newRow)
this.$Message.success('新增成功!')
this.query(1)
})
},
showEditWarp(row) {
this.$refs.editModal.editShow(row, async (newRow) => {
let res = await user_followsServer.edit(newRow)
this.$Message.success('修改成功!')
this.query(1)
})
},
delConfirm(row) {
window.framework.uiTool.delConfirm(async () => {
await user_followsServer.del(row)
this.$Message.success('删除成功!')
this.query(1)
})
},
exportCsv() {
user_followsServer.exportCsv(this.gridOption.param).then(res => {
window.framework.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 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 window.framework.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) {
window.framework.uiTool.delConfirm(async () => {
await user_trackingServer.del(row)
this.$Message.success('删除成功!')
this.query(1)
})
},
exportCsv() {
user_trackingServer.exportCsv(this.gridOption.param).then(res => {
window.framework.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>

View File

@@ -21,11 +21,55 @@
@changePage="query"></tables>
</div>
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules"> </editModal>
<FloatPanel ref="floatPanel" title="城市详情" position="right">
<div v-if="currentRow" class="detail-content">
<div class="detail-item">
<label>ID:</label>
<span>{{ currentRow.id }}</span>
</div>
<div class="detail-item">
<label>城市名称(中文):</label>
<span>{{ currentRow.cn_city }}</span>
</div>
<div class="detail-item">
<label>城市代码:</label>
<span>{{ currentRow.city_code }}</span>
</div>
<div class="detail-item">
<label>省份(中文):</label>
<span>{{ currentRow.cn_state || '-' }}</span>
</div>
<div class="detail-item">
<label>国家(中文):</label>
<span>{{ currentRow.cn_country || '-' }}</span>
</div>
<div class="detail-item">
<label>城市名称(英文):</label>
<span>{{ currentRow.city || '-' }}</span>
</div>
<div class="detail-item">
<label>省份(英文):</label>
<span>{{ currentRow.state || '-' }}</span>
</div>
<div class="detail-item">
<label>国家(英文):</label>
<span>{{ currentRow.country || '-' }}</span>
</div>
<div class="detail-item">
<label>省份代码:</label>
<span>{{ currentRow.state_code || '-' }}</span>
</div>
<div class="detail-item">
<label>国家代码:</label>
<span>{{ currentRow.country_code || '-' }}</span>
</div>
</div>
</FloatPanel>
</div>
</template>
<script>
import tools from '@/utils/tools'
import uiTool from '@/utils/uiTool'
import wch_citiesServer from '@/api/venues/wch_cities_server.js'
export default {
data() {
@@ -37,6 +81,7 @@ export default {
rules["cn_country"] = [{ required: false, message: '请填写国家' }];
return {
currentRow: null,
seachTypes: [
{ key: 'cn_city', value: '城市名称' },
{ key: 'city_code', value: '城市代码' },
@@ -70,10 +115,17 @@ export default {
{
title: '操作',
key: 'action',
width: 200,
width: 250,
type: 'template',
render: (h, params) => {
let btns = [
{
title: '详情',
type: 'info',
click: () => {
this.showDetail(params.row)
},
},
{
title: '编辑',
type: 'primary',
@@ -89,7 +141,7 @@ export default {
},
},
]
return uiTool.getBtn(h, btns)
return window.framework.uiTool.getBtn(h, btns)
}
}
],
@@ -139,7 +191,43 @@ export default {
wch_citiesServer.exportCsv(this.gridOption.param).then(res => {
tools.downloadFile(res, '城市管理.csv');
});
},
showDetail(row) {
this.currentRow = row
this.$refs.floatPanel.show()
},
hideDetail() {
this.$refs.floatPanel.hide()
}
}
}
</script>
<style lang="less" scoped>
.detail-content {
padding: 10px 0;
.detail-item {
display: flex;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
label {
min-width: 120px;
font-weight: 500;
color: #515a6e;
margin-right: 10px;
}
span {
flex: 1;
color: #17233d;
}
}
}
</style>

View File

@@ -24,9 +24,9 @@
</div>
</template>
<script>
import tools from '@/utils/tools'
import uiTool from '@/utils/uiTool'
import menuServer from '@/api/system_high/menuServer.js'
import menuServer from '@/api/system/menuServer.js'
import wch_professionsServer from '@/api/system/wch_professions_server.js'
export default {
data() {
@@ -147,7 +147,7 @@ export default {
},
},
]
return uiTool.getBtn(h, btns)
return window.framework.uiTool.getBtn(h, btns)
},
},
],
@@ -223,7 +223,7 @@ export default {
})
},
async delConfirm(row) {
uiTool.delConfirm(async () => {
window.framework.uiTool.delConfirm(async () => {
await wch_professionsServer.del(row)
this.$Message.success('删除成功!')
this.init()

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

@@ -0,0 +1,59 @@
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,
publicPath: '/'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader']
},
{
test: /\.less$/,
use: ['vue-style-loader', 'css-loader', 'less-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
}
}

488
package-lock.json generated
View File

@@ -11,19 +11,29 @@
"dependencies": {
"@vue/babel-preset-jsx": "^1.4.0",
"brace": "^0.11.1",
"codemirror": "^6.0.2",
"cropperjs": "^2.0.1",
"dayjs": "^1.10.0",
"js-cookie": "^2.2.1",
"simplemde": "^1.11.2",
"uuid": "^13.0.0",
"vue2-ace-editor": "^0.0.15",
"vuex-persistedstate": "^4.0.0"
"vuex-persistedstate": "^4.0.0",
"wangeditor": "^4.7.15"
},
"devDependencies": {
"@babel/core": "^7.12.0",
"@babel/plugin-syntax-jsx": "^7.27.1",
"@babel/plugin-transform-react-jsx": "^7.27.1",
"@babel/preset-env": "^7.12.0",
"autoprefixer": "^10.4.21",
"babel-loader": "^8.2.0",
"cross-env": "^10.1.0",
"css-loader": "^5.0.0",
"file-loader": "^6.2.0",
"less": "^4.0.0",
"less-loader": "^7.0.0",
"postcss": "^8.5.6",
"style-loader": "^2.0.0",
"url-loader": "^4.1.0",
"vue-loader": "^15.9.0",
@@ -1210,6 +1220,26 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-jsx": {
"version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz",
"integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.1",
"@babel/helper-module-imports": "^7.27.1",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/plugin-syntax-jsx": "^7.27.1",
"@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-regenerator": {
"version": "7.28.4",
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz",
@@ -1507,6 +1537,27 @@
"@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0"
}
},
"node_modules/@babel/runtime": {
"version": "7.28.4",
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.4.tgz",
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/runtime-corejs3": {
"version": "7.28.4",
"resolved": "https://registry.npmmirror.com/@babel/runtime-corejs3/-/runtime-corejs3-7.28.4.tgz",
"integrity": "sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==",
"license": "MIT",
"dependencies": {
"core-js-pure": "^3.43.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
"version": "7.27.2",
"resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.27.2.tgz",
@@ -1552,6 +1603,207 @@
"node": ">=6.9.0"
}
},
"node_modules/@codemirror/autocomplete": {
"version": "6.19.0",
"resolved": "https://registry.npmmirror.com/@codemirror/autocomplete/-/autocomplete-6.19.0.tgz",
"integrity": "sha512-61Hfv3cF07XvUxNeC3E7jhG8XNi1Yom1G0lRC936oLnlF+jrbrv8rc/J98XlYzcsAoTVupfsf5fLej1aI8kyIg==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.17.0",
"@lezer/common": "^1.0.0"
}
},
"node_modules/@codemirror/commands": {
"version": "6.9.0",
"resolved": "https://registry.npmmirror.com/@codemirror/commands/-/commands-6.9.0.tgz",
"integrity": "sha512-454TVgjhO6cMufsyyGN70rGIfJxJEjcqjBG2x2Y03Y/+Fm99d3O/Kv1QDYWuG6hvxsgmjXmBuATikIIYvERX+w==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.4.0",
"@codemirror/view": "^6.27.0",
"@lezer/common": "^1.1.0"
}
},
"node_modules/@codemirror/language": {
"version": "6.11.3",
"resolved": "https://registry.npmmirror.com/@codemirror/language/-/language-6.11.3.tgz",
"integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.23.0",
"@lezer/common": "^1.1.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0",
"style-mod": "^4.0.0"
}
},
"node_modules/@codemirror/lint": {
"version": "6.9.0",
"resolved": "https://registry.npmmirror.com/@codemirror/lint/-/lint-6.9.0.tgz",
"integrity": "sha512-wZxW+9XDytH3SKvS8cQzMyQCaaazH8XL1EMHleHe00wVzsv7NBQKVW2yzEHrRhmM7ZOhVdItPbvlRBvMp9ej7A==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.35.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/search": {
"version": "6.5.11",
"resolved": "https://registry.npmmirror.com/@codemirror/search/-/search-6.5.11.tgz",
"integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/state": {
"version": "6.5.2",
"resolved": "https://registry.npmmirror.com/@codemirror/state/-/state-6.5.2.tgz",
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
"license": "MIT",
"dependencies": {
"@marijn/find-cluster-break": "^1.0.0"
}
},
"node_modules/@codemirror/view": {
"version": "6.38.5",
"resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.38.5.tgz",
"integrity": "sha512-SFVsNAgsAoou+BjRewMqN+m9jaztB9wCWN9RSRgePqUbq8UVlvJfku5zB2KVhLPgH/h0RLk38tvd4tGeAhygnw==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.5.0",
"crelt": "^1.0.6",
"style-mod": "^4.1.0",
"w3c-keyname": "^2.2.4"
}
},
"node_modules/@cropper/element": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/@cropper/element/-/element-2.0.1.tgz",
"integrity": "sha512-Jn1hR7XWzWQM/QfXRGMGzdkJ2gG/UcLdQPZQ7OKs0JiFfRzKpzu4u/nYrXHeH3MM2iOslLqh2kqYju6mjZLMJQ==",
"license": "MIT",
"dependencies": {
"@cropper/utils": "^2.0.1"
}
},
"node_modules/@cropper/element-canvas": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/@cropper/element-canvas/-/element-canvas-2.0.1.tgz",
"integrity": "sha512-OKxq/O0HL9W2JegOsc2zh1NRpERZcLM5+M8aQ/eXdmMcfi1lzosPftag3Irp6pTsVpwV6B6ypIxKESzJ4ci9Fw==",
"license": "MIT",
"dependencies": {
"@cropper/element": "^2.0.1",
"@cropper/utils": "^2.0.1"
}
},
"node_modules/@cropper/element-crosshair": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/@cropper/element-crosshair/-/element-crosshair-2.0.1.tgz",
"integrity": "sha512-bS5msU9cTU/jf1/kDw+QJmEM9/rw8IgOdpolR85iMVUCR8sRcLa0wgom42MBHcpBYB6hvL5YfiOeXZ7lHIYMpw==",
"license": "MIT",
"dependencies": {
"@cropper/element": "^2.0.1",
"@cropper/utils": "^2.0.1"
}
},
"node_modules/@cropper/element-grid": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/@cropper/element-grid/-/element-grid-2.0.1.tgz",
"integrity": "sha512-ayqCvYQJ+GVT31HhFpttzHabW1T/LsIwLJY5PLTMG0cEZLw/E8ihg8mxctjZbo852D7oEePbz6/2SeuCb1018Q==",
"license": "MIT",
"dependencies": {
"@cropper/element": "^2.0.1",
"@cropper/utils": "^2.0.1"
}
},
"node_modules/@cropper/element-handle": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/@cropper/element-handle/-/element-handle-2.0.1.tgz",
"integrity": "sha512-fdifyyPIaR9S2eQ7qPHuM8fX8uToAfBsi8vQlR9EM+oJkDNil0uO4rWyArLWEtlr0/q7U0OvsufcuJ7ffqfmpg==",
"license": "MIT",
"dependencies": {
"@cropper/element": "^2.0.1",
"@cropper/utils": "^2.0.1"
}
},
"node_modules/@cropper/element-image": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/@cropper/element-image/-/element-image-2.0.1.tgz",
"integrity": "sha512-gPj5Sl2T8Cno198Cz3F3TDfcYoALW3yJ3fV6PHXmhMnX8sBkL7J441do7Vwkg0mEd2CogCtTLAf+p7ljdV0kgA==",
"license": "MIT",
"dependencies": {
"@cropper/element": "^2.0.1",
"@cropper/element-canvas": "^2.0.1",
"@cropper/utils": "^2.0.1"
}
},
"node_modules/@cropper/element-selection": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/@cropper/element-selection/-/element-selection-2.0.1.tgz",
"integrity": "sha512-atv+Aeq2N2eWawelIRPGh1kYFdNrpb0QkUPPheGxz1ImfxpLdcHO9gb9T5noQijizUW2G0pNvts4ZaITQ0I71Q==",
"license": "MIT",
"dependencies": {
"@cropper/element": "^2.0.1",
"@cropper/element-canvas": "^2.0.1",
"@cropper/element-image": "^2.0.1",
"@cropper/utils": "^2.0.1"
}
},
"node_modules/@cropper/element-shade": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/@cropper/element-shade/-/element-shade-2.0.1.tgz",
"integrity": "sha512-YIYgJ690NdFQ6wJLRFh/EySNVxGFKArncQ4FrsJ3yHU+ShgtOKz4FpjFLpqJRJB9swoVbD3WKTimGyzXrwjZrQ==",
"license": "MIT",
"dependencies": {
"@cropper/element": "^2.0.1",
"@cropper/element-canvas": "^2.0.1",
"@cropper/element-selection": "^2.0.1",
"@cropper/utils": "^2.0.1"
}
},
"node_modules/@cropper/element-viewer": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/@cropper/element-viewer/-/element-viewer-2.0.1.tgz",
"integrity": "sha512-HDj25l08pWi/AO6El/OqfQHBpBC4Lh5NEnQN1SOldsmxEwt27Ubv6ndDsF8LkTK7XPwjjZRpyQPyfig4w8L2JQ==",
"license": "MIT",
"dependencies": {
"@cropper/element": "^2.0.1",
"@cropper/element-canvas": "^2.0.1",
"@cropper/element-image": "^2.0.1",
"@cropper/element-selection": "^2.0.1",
"@cropper/utils": "^2.0.1"
}
},
"node_modules/@cropper/elements": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/@cropper/elements/-/elements-2.0.1.tgz",
"integrity": "sha512-paFbBLXTKXNngn1yDi2ZIf+FO1pIEQXyBntmqOjuxqtG73KuEKv633wsJPFpj958bgcfSakgBbF80j+3nHbPug==",
"license": "MIT",
"dependencies": {
"@cropper/element": "^2.0.1",
"@cropper/element-canvas": "^2.0.1",
"@cropper/element-crosshair": "^2.0.1",
"@cropper/element-grid": "^2.0.1",
"@cropper/element-handle": "^2.0.1",
"@cropper/element-image": "^2.0.1",
"@cropper/element-selection": "^2.0.1",
"@cropper/element-shade": "^2.0.1",
"@cropper/element-viewer": "^2.0.1"
}
},
"node_modules/@cropper/utils": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/@cropper/utils/-/utils-2.0.1.tgz",
"integrity": "sha512-A9RnAFmgNF5aZk5q2VZnFnHtXWu1kPyEN0LVsX8wJ2LBRu2nyETKwz+ZXVsVWliktToCaYojHKrS+6/HODyEZA==",
"license": "MIT"
},
"node_modules/@discoveryjs/json-ext": {
"version": "0.5.7",
"resolved": "https://registry.npmmirror.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
@@ -1562,6 +1814,13 @@
"node": ">=10.0.0"
}
},
"node_modules/@epic-web/invariant": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/@epic-web/invariant/-/invariant-1.0.0.tgz",
"integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -1618,6 +1877,36 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@lezer/common": {
"version": "1.2.3",
"resolved": "https://registry.npmmirror.com/@lezer/common/-/common-1.2.3.tgz",
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==",
"license": "MIT"
},
"node_modules/@lezer/highlight": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/@lezer/highlight/-/highlight-1.2.1.tgz",
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@lezer/lr": {
"version": "1.4.2",
"resolved": "https://registry.npmmirror.com/@lezer/lr/-/lr-1.4.2.tgz",
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@marijn/find-cluster-break": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
"license": "MIT"
},
"node_modules/@types/eslint": {
"version": "9.6.1",
"resolved": "https://registry.npmmirror.com/@types/eslint/-/eslint-9.6.1.tgz",
@@ -2198,6 +2487,44 @@
"license": "MIT",
"peer": true
},
"node_modules/autoprefixer": {
"version": "10.4.21",
"resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.21.tgz",
"integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"browserslist": "^4.24.4",
"caniuse-lite": "^1.0.30001702",
"fraction.js": "^4.3.7",
"normalize-range": "^0.1.2",
"picocolors": "^1.1.1",
"postcss-value-parser": "^4.2.0"
},
"bin": {
"autoprefixer": "bin/autoprefixer"
},
"engines": {
"node": "^10 || ^12 || >=14"
},
"peerDependencies": {
"postcss": "^8.1.0"
}
},
"node_modules/axios": {
"version": "0.21.4",
"resolved": "https://registry.npmmirror.com/axios/-/axios-0.21.4.tgz",
@@ -2403,6 +2730,30 @@
"node": ">=6"
}
},
"node_modules/codemirror": {
"version": "6.0.2",
"resolved": "https://registry.npmmirror.com/codemirror/-/codemirror-6.0.2.tgz",
"integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/commands": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/lint": "^6.0.0",
"@codemirror/search": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0"
}
},
"node_modules/codemirror-spell-checker": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/codemirror-spell-checker/-/codemirror-spell-checker-1.1.2.tgz",
"integrity": "sha512-2Tl6n0v+GJRsC9K3MLCdLaMOmvWL0uukajNJseorZJsslaxZyZMgENocPU8R0DyoTAiKsyqiemSOZo7kjGV0LQ==",
"license": "MIT",
"dependencies": {
"typo-js": "*"
}
},
"node_modules/colorette": {
"version": "2.0.20",
"resolved": "https://registry.npmmirror.com/colorette/-/colorette-2.0.20.tgz",
@@ -2471,6 +2822,51 @@
"url": "https://opencollective.com/core-js"
}
},
"node_modules/core-js-pure": {
"version": "3.45.1",
"resolved": "https://registry.npmmirror.com/core-js-pure/-/core-js-pure-3.45.1.tgz",
"integrity": "sha512-OHnWFKgTUshEU8MK+lOs1H8kC8GkTi9Z1tvNkxrCcw9wl3MJIO7q2ld77wjWn4/xuGrVu2X+nME1iIIPBSdyEQ==",
"hasInstallScript": true,
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmmirror.com/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
"license": "MIT"
},
"node_modules/cropperjs": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/cropperjs/-/cropperjs-2.0.1.tgz",
"integrity": "sha512-hiJwk2SCPZqxMA7aR3byzLpYUqOrQo+ihMk8k/WRm/xe/LX8wNzAIzMwEB/NEGJYA6sbewxW9TUlrRUYi/2Ipg==",
"license": "MIT",
"dependencies": {
"@cropper/elements": "^2.0.1",
"@cropper/utils": "^2.0.1"
}
},
"node_modules/cross-env": {
"version": "10.1.0",
"resolved": "https://registry.npmmirror.com/cross-env/-/cross-env-10.1.0.tgz",
"integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@epic-web/invariant": "^1.0.0",
"cross-spawn": "^7.0.6"
},
"bin": {
"cross-env": "dist/bin/cross-env.js",
"cross-env-shell": "dist/bin/cross-env-shell.js"
},
"engines": {
"node": ">=20"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -2901,6 +3297,20 @@
}
}
},
"node_modules/fraction.js": {
"version": "4.3.7",
"resolved": "https://registry.npmmirror.com/fraction.js/-/fraction.js-4.3.7.tgz",
"integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
"dev": true,
"license": "MIT",
"engines": {
"node": "*"
},
"funding": {
"type": "patreon",
"url": "https://github.com/sponsors/rawify"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
@@ -3377,6 +3787,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/marked": {
"version": "16.4.0",
"resolved": "https://registry.npmmirror.com/marked/-/marked-16.4.0.tgz",
"integrity": "sha512-CTPAcRBq57cn3R8n3hwc2REddc28hjR7RzDXQ+lXLmMJYqn20BaI2cGw6QjgZGIgVfp2Wdfw4aMzgNteQ6qJgQ==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/merge-source-map": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/merge-source-map/-/merge-source-map-1.1.0.tgz",
@@ -3496,6 +3918,16 @@
"integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==",
"license": "MIT"
},
"node_modules/normalize-range": {
"version": "0.1.2",
"resolved": "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz",
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz",
@@ -4020,6 +4452,17 @@
"deprecated": "older versions vulnerable to prototype pollution",
"license": "MIT"
},
"node_modules/simplemde": {
"version": "1.11.2",
"resolved": "https://registry.npmmirror.com/simplemde/-/simplemde-1.11.2.tgz",
"integrity": "sha512-AUXuHJ/tEEDEcN/MTitHIw3AuBcheizJt7SVwtyn00B0UK5RKZ3GB+JndmRcJ5wfYGCIL0O2yJm/uz0sJOFSLg==",
"license": "MIT",
"dependencies": {
"codemirror": "*",
"codemirror-spell-checker": "*",
"marked": "*"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz",
@@ -4089,6 +4532,12 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/style-mod": {
"version": "4.1.2",
"resolved": "https://registry.npmmirror.com/style-mod/-/style-mod-4.1.2.tgz",
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==",
"license": "MIT"
},
"node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz",
@@ -4259,9 +4708,14 @@
"version": "2.8.1",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD"
},
"node_modules/typo-js": {
"version": "1.3.1",
"resolved": "https://registry.npmmirror.com/typo-js/-/typo-js-1.3.1.tgz",
"integrity": "sha512-elJkpCL6Z77Ghw0Lv0lGnhBAjSTOQ5FhiVOCfOuxhaoTT2xtLVbqikYItK5HHchzPbHEUFAcjOH669T2ZzeCbg==",
"license": "BSD-3-Clause"
},
"node_modules/undici-types": {
"version": "7.14.0",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.14.0.tgz",
@@ -4407,6 +4861,19 @@
"dev": true,
"license": "MIT"
},
"node_modules/uuid": {
"version": "13.0.0",
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-13.0.0.tgz",
"integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist-node/bin/uuid"
}
},
"node_modules/v-click-outside-x": {
"version": "3.7.1",
"resolved": "https://registry.npmmirror.com/v-click-outside-x/-/v-click-outside-x-3.7.1.tgz",
@@ -4625,6 +5092,23 @@
"node": ">=0.10.0"
}
},
"node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmmirror.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
"license": "MIT"
},
"node_modules/wangeditor": {
"version": "4.7.15",
"resolved": "https://registry.npmmirror.com/wangeditor/-/wangeditor-4.7.15.tgz",
"integrity": "sha512-aPTdREd8BxXVyJ5MI+LU83FQ7u1EPd341iXIorRNYSOvoimNoZ4nPg+yn3FGbB93/owEa6buLw8wdhYnMCJQLg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.11.2",
"@babel/runtime-corejs3": "^7.11.2",
"tslib": "^2.1.0"
}
},
"node_modules/watchpack": {
"version": "2.4.4",
"resolved": "https://registry.npmmirror.com/watchpack/-/watchpack-2.4.4.tgz",

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",
@@ -25,19 +28,29 @@
"dependencies": {
"@vue/babel-preset-jsx": "^1.4.0",
"brace": "^0.11.1",
"codemirror": "^6.0.2",
"cropperjs": "^2.0.1",
"dayjs": "^1.10.0",
"js-cookie": "^2.2.1",
"simplemde": "^1.11.2",
"uuid": "^13.0.0",
"vue2-ace-editor": "^0.0.15",
"vuex-persistedstate": "^4.0.0"
"vuex-persistedstate": "^4.0.0",
"wangeditor": "^4.7.15"
},
"devDependencies": {
"@babel/core": "^7.12.0",
"@babel/plugin-syntax-jsx": "^7.27.1",
"@babel/plugin-transform-react-jsx": "^7.27.1",
"@babel/preset-env": "^7.12.0",
"autoprefixer": "^10.4.21",
"babel-loader": "^8.2.0",
"cross-env": "^10.1.0",
"css-loader": "^5.0.0",
"file-loader": "^6.2.0",
"less": "^4.0.0",
"less-loader": "^7.0.0",
"postcss": "^8.5.6",
"style-loader": "^2.0.0",
"url-loader": "^4.1.0",
"vue-loader": "^15.9.0",

16
postcss.config.js Normal file
View File

@@ -0,0 +1,16 @@
// PostCSS 配置文件
// 用于处理 CSS 样式转换和优化
module.exports = {
plugins: {
// 自动添加浏览器前缀
autoprefixer: {
overrideBrowserslist: [
'> 1%',
'last 2 versions',
'not dead',
'not ie <= 11'
]
}
}
}

View File

@@ -1,12 +1,12 @@
import http from "@/utils/http";
class FileServe {
async upload_oos_img(row) {
let res = await http.postFormData("/sys_file/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);
let res = await http.postFormData("/file/upload_Img", row);
return res;
}
}

View File

@@ -1,27 +1,27 @@
import http from "@/utils/http";
class FormFieldServer {
async all(param) {
let res = await http.get("/sys_form_field/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);
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);
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);
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);
let res = await http.post("/sys_form_field/del", row);
return res;
}
}

Some files were not shown because too many files have changed in this diff Show More