!1 生产环境配置及目录变化

Merge pull request !1 from 2026-4-8SongslDev
This commit is contained in:
Admin 2026-04-22 06:34:01 +00:00 committed by Gitee
commit 792e21ab23
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
20 changed files with 1390 additions and 796 deletions

View File

@ -1,7 +1,7 @@
# 开发环境标识Vite 内置变量,无需修改) # 开发环境标识Vite 内置变量,无需修改)
NODE_ENV = development NODE_ENV = development
# 开发环境接口基础地址192.168.11.183:8085/api # 开发环境接口基础地址(对应你配置的 http://192.168.11.183:8085/api
VITE_API_BASE_URL = http://192.168.11.183:8085/api VITE_API_BASE_URL = http://192.168.11.183:8085/api
# 请求超时时间(毫秒) # 请求超时时间(毫秒)

View File

@ -2,7 +2,7 @@
NODE_ENV = production NODE_ENV = production
# 生产环境接口基础地址 # 生产环境接口基础地址
VITE_API_BASE_URL = https://api.yourdomain.com VITE_API_BASE_URL = http://18.167.120.9:8086/api
# 请求超时时间(毫秒) # 请求超时时间(毫秒)
VITE_REQUEST_TIMEOUT = 10000 VITE_REQUEST_TIMEOUT = 10000

2
.gitignore vendored
View File

@ -28,6 +28,8 @@ coverage
.eslintcache .eslintcache
package-lock.json
# Cypress # Cypress
/cypress/videos/ /cypress/videos/
/cypress/screenshots/ /cypress/screenshots/

View File

@ -6,6 +6,7 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "run-p type-check \"build-only {@}\" --", "build": "run-p type-check \"build-only {@}\" --",
"build:prod": "vite build --mode production",
"preview": "vite preview", "preview": "vite preview",
"build-only": "vite build", "build-only": "vite build",
"type-check": "vue-tsc --build", "type-check": "vue-tsc --build",

View File

@ -2,7 +2,8 @@ import axios from 'axios'
import type { AxiosResponse, AxiosError, InternalAxiosRequestConfig } from 'axios' import type { AxiosResponse, AxiosError, InternalAxiosRequestConfig } from 'axios'
import type { ApiResponse } from '@/api/types' import type { ApiResponse } from '@/api/types'
import { ElLoading, ElNotification } from 'element-plus' import { ElLoading, ElNotification } from 'element-plus'
import { getStorage, setStorage } from '@/utils/storage' import { getStorage, setStorage, removeStorage } from '@/utils/storage'
import router from '../router'
// 定义loading实例类型用于控制loading的显示和关闭 // 定义loading实例类型用于控制loading的显示和关闭
let loadingInstance: ReturnType<typeof ElLoading.service> | null = null let loadingInstance: ReturnType<typeof ElLoading.service> | null = null
@ -14,7 +15,7 @@ const showLoading = () => {
if (requestCount === 0 && !loadingInstance) { if (requestCount === 0 && !loadingInstance) {
loadingInstance = ElLoading.service({ loadingInstance = ElLoading.service({
lock: true, // 锁定屏幕滚动 lock: true, // 锁定屏幕滚动
fullscreen: true // 全屏显示 fullscreen: true, // 全屏显示
}) })
} }
requestCount++ requestCount++
@ -46,7 +47,7 @@ const service = axios.create({
// 3. 请求拦截器:直接使用 axios 内置类型 // 3. 请求拦截器:直接使用 axios 内置类型
const requestInterceptor = (config: InternalAxiosRequestConfig) => { const requestInterceptor = (config: InternalAxiosRequestConfig) => {
console.log('请求配置', config); console.log('请求配置', config)
// 显示loading可通过config配置项控制是否显示比如某些请求不需要loading // 显示loading可通过config配置项控制是否显示比如某些请求不需要loading
if (config.headers?.showLoading !== false) { if (config.headers?.showLoading !== false) {
@ -82,9 +83,9 @@ const responseInterceptor = <T = unknown>(response: AxiosResponse<ApiResponse<T>
if (response.headers['new_token'] !== undefined) { if (response.headers['new_token'] !== undefined) {
setStorage('token', response.headers['new_token'] as string) setStorage('token', response.headers['new_token'] as string)
} }
console.log('响应response', response); console.log('响应response', response)
const { data } = response const { data } = response
console.log('响应数据data', data); console.log('响应数据data', data)
// 判断业务状态码 // 判断业务状态码
if (data.code == 200 || data.code == 0) { if (data.code == 200 || data.code == 0) {
if (config.showMessage !== false) { if (config.showMessage !== false) {
@ -97,19 +98,19 @@ const responseInterceptor = <T = unknown>(response: AxiosResponse<ApiResponse<T>
} }
return data.data return data.data
} else { } else {
if (data.code === 1) { switch (data.code) {
// 不弹错误提示,返回带特殊标识的结果 case 1:
// 其他业务失败弹错误提示并拒绝Promise if (!data.msg) {
ElNotification({ data.msg = '未知的错误!'
message: data.msg || '未知的错误!', }
type: 'error', break
duration: 2000, case -101:
customClass: 'global-notification' data.msg = 'token错误'
}) removeStorage('token')
const result = { router.push({ name: 'Login' })
isUserNotExist: true, // 自定义标识,标记“用户不存在” break
} as T default:
return result break;
} }
// 其他业务失败弹错误提示并拒绝Promise // 其他业务失败弹错误提示并拒绝Promise
@ -117,7 +118,7 @@ const responseInterceptor = <T = unknown>(response: AxiosResponse<ApiResponse<T>
message: data.msg || '未知的错误!', message: data.msg || '未知的错误!',
type: 'error', type: 'error',
duration: 2000, duration: 2000,
customClass: 'global-notification' customClass: 'global-notification',
}) })
return Promise.reject(data) return Promise.reject(data)
} }

View File

@ -0,0 +1,38 @@
import { request } from '@/api';
type SettingFormParams = {
key: string
value: string
description: string
}
type SettingListResponse = SettingFormParams & {
id: number
}
/**
*
* @returns Promise<MenuListResponse>
*/
export async function getSettingApi(): Promise<SettingListResponse[]> {
return request.get<SettingListResponse[]>('/setting/getList')
}
/**
*
* @param params
* @returns Promise<void>
*/
export async function createSettingApi(params: SettingFormParams): Promise<void> {
return request.post<void>('/setting/add', params)
}
/**
*
* @param params id
* @returns Promise<void>
*/
export async function editSettingApi(params: SettingFormParams): Promise<void> {
return request.post<void>('/setting/edit', params)
}

View File

@ -7,15 +7,15 @@ export async function getGroupListApi(): Promise<any> {
// 创建交易组 // 创建交易组
export async function addGroupApi(params: any): Promise<any> { export async function addGroupApi(params: any): Promise<any> {
return request.post<any>('/contractVariety/createGroup', {...params}) return request.post<any>('/contractVariety/createGroup', { ...params })
} }
//编辑交易组 //编辑交易组
export async function editGroupApi(params: any): Promise<any> { export async function editGroupApi(params: any): Promise<any> {
return request.post<any>('/contractVariety/editGroup', {...params}) return request.post<any>('/contractVariety/editGroup', { ...params })
} }
// 删除交易组 // 删除交易组
export async function deleteGroupApi(params: any): Promise<any> { export async function deleteGroupApi(params: any): Promise<any> {
return request.post<any>('/contractVariety/delGroup', {...params}) return request.post<any>('/contractVariety/delGroup', { ...params })
} }

View File

@ -0,0 +1,21 @@
import { request } from '@/api';
// 获取节假日列表
export async function getHolidayList(): Promise<any> {
return request.get<any>('/contractVariety/getHolidayList')
}
// 添加节假日
export async function addHoliday(params: any): Promise<any> {
return request.post<any>('/contractVariety/addHoliday', { ...params })
}
//编辑节假日
export async function editHoliday(params: any): Promise<any> {
return request.post<any>('/contractVariety/editHoliday', { ...params })
}
// 删除节假日
export async function delHoliday(params: any): Promise<any> {
return request.post<any>('/contractVariety/delHoliday', { ...params })
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -1,11 +1,11 @@
<template> <template>
<aside> <aside>
<div class="sidebar-header"> <div class="sidebar-header">
<div class="logo"> <div class="logo">
<img src="@/assets/images/logo.webp" alt="logo" :router="true" /> <img src="@/assets/images/logo.webp" alt="logo" :router="true" />
<span>Admin System</span> <span>Admin System</span>
</div> </div>
</div> </div>
<!-- 菜单区域 --> <!-- 菜单区域 -->
<el-menu class="menu" :unique-opened="true" :default-active="activeMenu" router> <el-menu class="menu" :unique-opened="true" :default-active="activeMenu" router>
@ -18,32 +18,40 @@
<template #title>{{ menu.label }}</template> <template #title>{{ menu.label }}</template>
</el-menu-item> </el-menu-item>
<el-sub-menu v-else :index="menu.path"> <el-sub-menu v-else :index="menu.path">
<template #title> <template #title>
<el-icon> <el-icon>
<component :is="iconMap[menu.icon]" /> <component :is="iconMap[menu.icon]" />
</el-icon> </el-icon>
<span>{{ menu.label }}</span> <span>{{ menu.label }}</span>
</template> </template>
<el-menu-item v-for="subMenu in menu.children" :key="subMenu.id" :index="subMenu.path"> <el-menu-item v-for="subMenu in menu.children" :key="subMenu.id" :index="subMenu.path">
<el-icon v-if="subMenu.icon"> <el-icon v-if="subMenu.icon">
<component :is="iconMap[subMenu.icon]" /> <component :is="iconMap[subMenu.icon]" />
</el-icon> </el-icon>
<template #title>{{ subMenu.label }}</template> <template #title>{{ subMenu.label }}</template>
</el-menu-item> </el-menu-item>
</el-sub-menu> </el-sub-menu>
</template> </template>
</el-menu> </el-menu>
</aside> </aside>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { HomeFilled, List, UserFilled, Platform, BellFilled, GoodsFilled, WarningFilled, Tools } from '@element-plus/icons-vue' import {
HomeFilled,
List,
UserFilled,
Platform,
BellFilled,
GoodsFilled,
WarningFilled,
Tools,
} from '@element-plus/icons-vue'
// //
import { useUserStore } from '@/stores/modules/user' import { useUserStore } from '@/stores/modules/user'
const route = useRoute() const route = useRoute()
const userStore = useUserStore() const userStore = useUserStore()
const menuList = ref<any[]>([]) const menuList = ref<any[]>([])
@ -54,369 +62,370 @@ const activeMenu = computed(() => {
}) })
// //
onMounted(async () => { onMounted(async () => {
menuList.value = userStore.sideMenu || [] menuList.value = userStore.sideMenu || []
}) })
// //
const iconMap: Record<string, any> = { const iconMap: Record<string, any> = {
HomeFilled, HomeFilled,
List, List,
UserFilled, UserFilled,
Platform, Platform,
BellFilled, BellFilled,
GoodsFilled, GoodsFilled,
WarningFilled, WarningFilled,
Tools Tools,
} }
// //
import type { ElMenuItem } from '@/utils/menuFormat'; import type { ElMenuItem } from '@/utils/menuFormat'
// //
const staticMenuList = ref<ElMenuItem[]>( const staticMenuList = ref<ElMenuItem[]>([
[ //
// {
{ id: 1,
id: 1, label: '首页',
label: '首页', path: 'dashboard', // el-menu-item index="dashboard"
path: 'dashboard', // el-menu-item index="dashboard" icon: 'HomeFilled', // <HomeFilled />
icon: 'HomeFilled' // 对应 <HomeFilled /> 图标 },
}, //
// // {
// { // id: 2,
// id: 2, // label: '',
// label: '', // path: '2', // el-sub-menu index="2"
// path: '2', // el-sub-menu index="2" // icon: 'List', // <List />
// icon: 'List', // <List /> // children: [
// children: [ // {
// { // id: 21,
// id: 21, // label: '',
// label: '', // path: '2-1', // el-menu-item index="2-1"
// path: '2-1', // el-menu-item index="2-1" // icon: ''
// icon: '' // //
// // // }
// } // ]
// ] // },
// }, //
// {
{ id: 3,
id: 3, label: '账户管理',
label: '账户管理', path: 'account', // el-sub-menu index="account"
path: 'account', // el-sub-menu index="account" icon: 'UserFilled', // <UserFilled />
icon: 'UserFilled', // <UserFilled /> children: [
children: [ {
{ id: 31,
id: 31, label: '客户管理',
label: '客户管理', path: '/account/client', // index="client"
path: '/account/client', // index="client" icon: '',
icon: '' },
}, {
{ id: 32,
id: 32, label: '代理管理',
label: '代理管理', path: '/account/agentList', // index="3-2"
path: '/account/agentList', // index="3-2" icon: '',
icon: '' },
}, {
{ id: 33,
id: 33, label: '做市商管理',
label: '做市商管理', path: '/account/market-maker', // index="3-3"
path: '/account/market-maker', // index="3-3" icon: '',
icon: '' },
}, {
{ id: 34,
id: 34, label: '档案管理',
label: '档案管理', path: '/account/archives', // index="3-4"
path: '/account/archives', // index="3-4" icon: '',
icon: '' },
}, {
{ id: 35,
id: 35, label: '账号管理',
label: '账号管理', path: '/account/accountDetail', // index="3-5"
path: '/account/accountDetail', // index="3-5" icon: '',
icon: '' },
}, ],
] },
}, //
// {
{ id: 4,
id: 4, label: '交易管理',
label: '交易管理', path: '4', // index="4"
path: '4', // index="4" icon: 'Platform', // <Platform />
icon: 'Platform', // <Platform /> children: [
children: [ {
{ id: 41,
id: 41, label: '品种',
label: '品种', path: '/trade/variety', // index="4-1"
path: '/trade/variety', // index="4-1" icon: '',
icon: '' },
}, {
{ id: 42,
id: 42, label: '交易时间',
label: '交易组', path: '/trade/group', // index="4-2"
path: '/trade/group', // index="4-2" icon: '',
icon: '' },
}, {
{ id: 43,
id: 43, label: '持仓统计',
label: '持仓统计', path: '/trade/positionStat', // index="4-3"
path: '/trade/positionStat', // index="4-3" icon: '',
icon: '' },
}, {
{ id: 44,
id: 44, label: '历史交易',
label: '历史交易', path: '/trade/historyTrade', // index="4-4"
path: '/trade/historyTrade', // index="4-4" icon: '',
icon: '' },
}, {
{ id: 45,
id: 45, label: '交易软件',
label: '交易软件', path: '/trade/tradeSoftware', // index="4-5"
path: '/trade/tradeSoftware', // index="4-5" icon: '',
icon: '' },
}, {
// { id: 46,
// id: 43, label: '节假日时间',
// label: '', path: '/trade/time', // index="4-3"
// path: '/trade/time', // index="4-3" icon: '',
// icon: '' },
// } ],
] },
}, //
// {
{ id: 5,
id: 5, label: '公告管理',
label: '公告管理', path: '/notice', // index="5"
path: '/notice', // index="5" icon: 'BellFilled', // <BellFilled />
icon: 'BellFilled' // 对应 <BellFilled /> 图标 },
}, //
// {
{ id: 6,
id: 6, label: '资金管理',
label: '资金管理', path: '6', // index="6"
path: '6', // index="6" icon: 'GoodsFilled', // <GoodsFilled />
icon: 'GoodsFilled', // <GoodsFilled /> children: [
children: [ // {
// { // id: 61,
// id: 61, // label: '',
// label: '', // path: '/funding/deposit', // index="6-1"
// path: '/funding/deposit', // index="6-1" // icon: ''
// icon: '' // },
// }, // {
// { // id: 62,
// id: 62, // label: '',
// label: '', // path: '/funding/withdrawal', // index="6-2"
// path: '/funding/withdrawal', // index="6-2" // icon: ''
// icon: '' // },
// }, // {
// { // id: 63,
// id: 63, // label: '',
// label: '', // path: '/funding/withdrawalDetail', // index="6-3"
// path: '/funding/withdrawalDetail', // index="6-3" // icon: ''
// icon: '' // },
// }, // {
// { // id: 64,
// id: 64, // label: '',
// label: '', // path: '/funding/depositDetail', // index="6-4"
// path: '/funding/depositDetail', // index="6-4" // icon: ''
// icon: '' // },
// }, {
{ id: 64,
id: 64, label: '转账',
label: '转账', path: '/funding/transfer', // index="6-4"
path: '/funding/transfer', // index="6-4" icon: '',
icon: '' },
}, {
{ id: 65,
id: 65, label: '转账明细',
label: '转账明细', path: '/funding/transferDetail', // index="6-5"
path: '/funding/transferDetail', // index="6-5" icon: '',
icon: '' },
}, {
{ id: 66,
id: 66, label: '资金明细',
label: '资金明细', path: '/funding/fundDetail', // index="6-6"
path: '/funding/fundDetail', // index="6-6" icon: '',
icon: '' },
}, // {
// { // id: 66,
// id: 66, // label: '',
// label: '', // path: '/funding/flowDetail', // index="6-6"
// path: '/funding/flowDetail', // index="6-6" // icon: ''
// icon: '' // },
// }, // {
// { // id: 67,
// id: 67, // label: '',
// label: '', // path: '/funding/receiverAccount', // index="6-7"
// path: '/funding/receiverAccount', // index="6-7" // icon: ''
// icon: '' // },
// }, // {
// { // id: 68,
// id: 68, // label: '',
// label: '', // path: '/funding/depositChannel', // index="6-8"
// path: '/funding/depositChannel', // index="6-8" // icon: ''
// icon: '' // }
// } ],
] },
}, //
// {
{ id: 7,
id: 7, label: '系统管理',
label: '系统管理', path: 'system', // index="7"
path: 'system', // index="7" icon: 'Tools', // <Tools />
icon: 'Tools', // <Tools /> children: [
children: [ {
{ id: 71,
id: 71, label: '菜单管理',
label: '菜单管理', path: '/system/menu', // index="6-1" index 6-1
path: '/system/menu', // index="6-1" index 6-1 icon: '',
icon: '' },
}, {
{ id: 72,
id: 72, label: '角色管理',
label: '角色管理', path: '/system/role', // index="6-2" index 6-2
path: '/system/role', // index="6-2" index 6-2 icon: '',
icon: '' },
} {
] id: 73,
}, label: '系统设置',
// path: '/system/setting', // index="6-2" index 6-2
{ icon: '',
id: 8, },
label: '风控管理', ],
path: '/risk', // index="8" },
icon: 'WarningFilled' // 对应 <WarningFilled /> 图标 //
}, {
id: 8,
label: '风控管理',
path: '/risk', // index="8"
icon: 'WarningFilled', // <WarningFilled />
},
//
// {
// id: 9,
// label: '',
// path: 'funding', // index="funding"
// icon: 'GoodsFilled', // <GoodsFilled />
// children: [
// ///// /
// //
// // ]
// { // },
// id: 9, //
// label: '', // {
// path: 'funding', // index="funding" // id: 10,
// icon: 'GoodsFilled', // <GoodsFilled /> // label: '',
// children: [ // path: 'trade', // index="trade"
// ///// / // icon: 'ChartBarFilled', // <ChartBarFilled />
// // // children: [
// //
// ] // //
// }, // //
// // ]
// { // },
// id: 10, //
// label: '', // {
// path: 'trade', // index="trade" // id: 11,
// icon: 'ChartBarFilled', // <ChartBarFilled /> // label: '',
// children: [ // path: 'account', // index="account"
// // // icon: 'UserFilled', // <UserFilled />
// // // children: [
// // // //
// ] // //
// }, // ]
// // },
// { //
// id: 11, {
// label: '', id: 12,
// path: 'account', // index="account" label: '个性化交易',
// icon: 'UserFilled', // <UserFilled /> path: 'personalized', // index="personalized"
// children: [ icon: 'SettingFilled', // <SettingFilled />
// // children: [
// // // app
// ] //
// }, {
// id: 121,
{ label: '跟单记录',
id: 12, path: '/personalized/followRecord', // index="12-1"
label: '个性化交易', icon: '',
path: 'personalized', // index="personalized" },
icon: 'SettingFilled', // <SettingFilled /> //
children: [ {
// app id: 122,
// label: '带单记录',
{ path: '/personalized/leadRecord', // index="12-2"
id: 121, icon: '',
label: '跟单记录', },
path: '/personalized/followRecord', // index="12-1" ],
icon: '' },
}, //
// {
{ id: 13,
id: 122, label: '代理管理',
label: '带单记录', path: 'agent', // index="agent"
path: '/personalized/leadRecord', // index="12-2" icon: 'UserFilled', // <UserFilled />
icon: '' children: [
}, //
] {
}, id: 132,
// label: '客户列表',
{ path: '/agent/customerList', // index="13-2"
id: 13, icon: '',
label: '代理管理', },
path: 'agent', // index="agent" //
icon: 'UserFilled', // <UserFilled /> {
children: [ id: 133,
// label: '客户交易订单',
{ path: '/agent/customerTradeOrder', // index="13-3"
id: 132, icon: '',
label: '客户列表', },
path: '/agent/customerList', // index="13-2" //
icon: '' {
}, id: 135,
// label: '客户资金流水',
{ path: '/agent/customerCapitalFlow', // index="13-5"
id: 133, icon: '',
label: '客户交易订单', },
path: '/agent/customerTradeOrder', // index="13-3" ],
icon: '' },
}, //
// {
{ id: 14,
id: 135, label: '通知管理',
label: '客户资金流水', path: 'notification', // index="notification"
path: '/agent/customerCapitalFlow', // index="13-5" icon: 'NotificationFilled', // <NotificationFilled />
icon: '' },
}, ])
]
},
//
{
id: 14,
label: '通知管理',
path: 'notification', // index="notification"
icon: 'NotificationFilled', // <NotificationFilled />
},
]
)
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
aside { aside {
.sidebar-header { .sidebar-header {
height: 60px; height: 60px;
border-bottom: solid 1px var(--el-menu-border-color); border-bottom: solid 1px var(--el-menu-border-color);
padding: 0 10px; padding: 0 10px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
.logo { .logo {
display: flex; display: flex;
align-items: center; align-items: center;
img { img {
width: 100px; width: 100px;
} }
}
} }
}
.menu { .menu {
height: 100%; height: 100%;
border: none; border: none;
:deep(.el-menu-item.is-active) {
background-color: var(--el-menu-hover-bg-color)
}
:deep(.el-menu-item.is-active) {
background-color: var(--el-menu-hover-bg-color);
} }
}
} }
</style> </style>

View File

@ -14,39 +14,39 @@ const routes = [
path: 'login', path: 'login',
name: 'Login', name: 'Login',
component: () => import('@/views/user/Login.vue'), component: () => import('@/views/user/Login.vue'),
meta: { isAuthPage: true, title: '登录' } meta: { isAuthPage: true, title: '登录' },
}, },
{ {
path: 'register', path: 'register',
name: 'Register', name: 'Register',
component: () => import('@/views/user/Register.vue'), component: () => import('@/views/user/Register.vue'),
meta: { isAuthPage: true, title: '注册' } meta: { isAuthPage: true, title: '注册' },
}, },
{ {
path: 'forgot-password', path: 'forgot-password',
name: 'ForgotPassword', name: 'ForgotPassword',
component: () => import('@/views/user/ForgotPassword.vue'), component: () => import('@/views/user/ForgotPassword.vue'),
meta: { isAuthPage: true, title: '忘记密码' } meta: { isAuthPage: true, title: '忘记密码' },
}, },
{ {
path: 'change-password', path: 'change-password',
name: 'ChangePassword', name: 'ChangePassword',
component: () => import('@/views/user/ChangePassword.vue'), component: () => import('@/views/user/ChangePassword.vue'),
meta: { requiresAuth: true, title: '修改密码' } meta: { requiresAuth: true, title: '修改密码' },
}, },
{ {
path: 'bind-email', path: 'bind-email',
name: 'BindEmail', name: 'BindEmail',
component: () => import('@/views/user/bindEmail.vue'), component: () => import('@/views/user/bindEmail.vue'),
meta: { requiresAuth: true, title: '绑定邮箱' } meta: { requiresAuth: true, title: '绑定邮箱' },
}, },
{ {
path: 'bind-phone', path: 'bind-phone',
name: 'BindPhone', name: 'BindPhone',
component: () => import('@/views/user/bindPhone.vue'), component: () => import('@/views/user/bindPhone.vue'),
meta: { requiresAuth: true, title: '绑定手机号' } meta: { requiresAuth: true, title: '绑定手机号' },
} },
] ],
}, },
// 带侧边栏布局的核心业务页面 // 带侧边栏布局的核心业务页面
{ {
@ -62,7 +62,7 @@ const routes = [
component: () => import('@/views/dashboard/index.vue'), component: () => import('@/views/dashboard/index.vue'),
meta: { meta: {
title: '首页', // 左侧菜单显示的标题 title: '首页', // 左侧菜单显示的标题
} },
}, },
// 系统管理 // 系统管理
{ {
@ -78,7 +78,7 @@ const routes = [
component: () => import('@/views/system/menu/index.vue'), component: () => import('@/views/system/menu/index.vue'),
meta: { meta: {
title: '菜单管理', // 左侧菜单显示的标题 title: '菜单管理', // 左侧菜单显示的标题
} },
}, },
{ {
path: 'role', path: 'role',
@ -86,9 +86,17 @@ const routes = [
component: () => import('@/views/system/role/index.vue'), component: () => import('@/views/system/role/index.vue'),
meta: { meta: {
title: '角色管理', // 左侧菜单显示的标题 title: '角色管理', // 左侧菜单显示的标题
} },
} },
] {
path: 'setting',
name: 'Setting',
component: () => import('@/views/system/setting/index.vue'),
meta: {
title: '系统设置', // 左侧菜单显示的标题
},
},
],
}, },
//模板配置 //模板配置
{ {
@ -97,7 +105,7 @@ const routes = [
meta: { meta: {
title: '模板配置', // 左侧菜单显示的标题 title: '模板配置', // 左侧菜单显示的标题
}, },
children: [] children: [],
}, },
//账户管理 //账户管理
{ {
@ -113,7 +121,7 @@ const routes = [
component: () => import('@/views/account/client/index.vue'), component: () => import('@/views/account/client/index.vue'),
meta: { meta: {
title: '客户管理', // 左侧菜单显示的标题 title: '客户管理', // 左侧菜单显示的标题
} },
}, },
{ {
path: 'agentList', path: 'agentList',
@ -121,7 +129,7 @@ const routes = [
component: () => import('@/views/account/agentList/index.vue'), component: () => import('@/views/account/agentList/index.vue'),
meta: { meta: {
title: '代理管理', // 左侧菜单显示的标题 title: '代理管理', // 左侧菜单显示的标题
} },
}, },
{ {
path: 'market-maker', path: 'market-maker',
@ -129,7 +137,7 @@ const routes = [
component: () => import('@/views/account/market-maker/index.vue'), component: () => import('@/views/account/market-maker/index.vue'),
meta: { meta: {
title: '做市商管理', // 左侧菜单显示的标题 title: '做市商管理', // 左侧菜单显示的标题
} },
}, },
{ {
path: 'archives', path: 'archives',
@ -137,7 +145,7 @@ const routes = [
component: () => import('@/views/account/archives/index.vue'), component: () => import('@/views/account/archives/index.vue'),
meta: { meta: {
title: '档案管理', // 左侧菜单显示的标题 title: '档案管理', // 左侧菜单显示的标题
} },
}, },
{ {
path: 'accountDetail', path: 'accountDetail',
@ -145,9 +153,9 @@ const routes = [
component: () => import('@/views/account/accountDetail/index.vue'), component: () => import('@/views/account/accountDetail/index.vue'),
meta: { meta: {
title: '账号管理', // 左侧菜单显示的标题 title: '账号管理', // 左侧菜单显示的标题
} },
}, },
] ],
}, },
//交易管理 //交易管理
{ {
@ -163,31 +171,31 @@ const routes = [
component: () => import('@/views/trade/variety/index.vue'), component: () => import('@/views/trade/variety/index.vue'),
meta: { meta: {
title: '品种', // 左侧菜单显示的标题 title: '品种', // 左侧菜单显示的标题
} },
}, },
{ {
path: 'group', path: 'group',
name: 'Group', name: 'Group',
component: () => import('@/views/trade/group/index.vue'), component: () => import('@/views/trade/group/index.vue'),
meta: { meta: {
title: '交易组', // 左侧菜单显示的标题 title: '交易时间', // 左侧菜单显示的标题
} },
},
{
path: 'time',
name: 'Time',
component: () => import('@/views/trade/time/index.vue'),
meta: {
title: '节假日时间', // 左侧菜单显示的标题
},
}, },
// {
// path: 'time',
// name: 'Time',
// component: () => import('@/views/trade/time/index.vue'),
// meta: {
// title: '交易时间', // 左侧菜单显示的标题
// }
// },
{ {
path: 'positionStat', path: 'positionStat',
name: 'PositionStat', name: 'PositionStat',
component: () => import('@/views/trade/positionStat/index.vue'), component: () => import('@/views/trade/positionStat/index.vue'),
meta: { meta: {
title: '持仓统计', // 左侧菜单显示的标题 title: '持仓统计', // 左侧菜单显示的标题
} },
}, },
{ {
path: 'historyTrade', path: 'historyTrade',
@ -195,7 +203,7 @@ const routes = [
component: () => import('@/views/trade/historyTrade/index.vue'), component: () => import('@/views/trade/historyTrade/index.vue'),
meta: { meta: {
title: '历史交易', // 左侧菜单显示的标题 title: '历史交易', // 左侧菜单显示的标题
} },
}, },
{ {
path: 'tradeSoftware', path: 'tradeSoftware',
@ -203,9 +211,9 @@ const routes = [
component: () => import('@/views/trade/tradeSoftware/index.vue'), component: () => import('@/views/trade/tradeSoftware/index.vue'),
meta: { meta: {
title: '交易软件', // 左侧菜单显示的标题 title: '交易软件', // 左侧菜单显示的标题
} },
}, },
] ],
}, },
//公告管理 //公告管理
{ {
@ -230,7 +238,7 @@ const routes = [
component: () => import('@/views/funding/deposit/index.vue'), component: () => import('@/views/funding/deposit/index.vue'),
meta: { meta: {
title: '存款审核', // 左侧菜单显示的标题 title: '存款审核', // 左侧菜单显示的标题
} },
}, },
{ {
path: 'withdrawal', path: 'withdrawal',
@ -238,7 +246,7 @@ const routes = [
component: () => import('@/views/funding/withdrawl/index.vue'), component: () => import('@/views/funding/withdrawl/index.vue'),
meta: { meta: {
title: '取款审核', // 左侧菜单显示的标题 title: '取款审核', // 左侧菜单显示的标题
} },
}, },
{ {
path: 'withdrawalDetail', path: 'withdrawalDetail',
@ -246,7 +254,7 @@ const routes = [
component: () => import('@/views/funding/withdrawalDetail/index.vue'), component: () => import('@/views/funding/withdrawalDetail/index.vue'),
meta: { meta: {
title: '取款明细', // 左侧菜单显示的标题 title: '取款明细', // 左侧菜单显示的标题
} },
}, },
{ {
path: 'depositDetail', path: 'depositDetail',
@ -254,7 +262,7 @@ const routes = [
component: () => import('@/views/funding/depositDetail/index.vue'), component: () => import('@/views/funding/depositDetail/index.vue'),
meta: { meta: {
title: '存款明细', // 左侧菜单显示的标题 title: '存款明细', // 左侧菜单显示的标题
} },
}, },
{ {
path: 'transfer', path: 'transfer',
@ -262,7 +270,7 @@ const routes = [
component: () => import('@/views/funding/transfer/index.vue'), component: () => import('@/views/funding/transfer/index.vue'),
meta: { meta: {
title: '转账', // 左侧菜单显示的标题 title: '转账', // 左侧菜单显示的标题
} },
}, },
{ {
path: 'transferDetail', path: 'transferDetail',
@ -270,7 +278,7 @@ const routes = [
component: () => import('@/views/funding/transferDetail/index.vue'), component: () => import('@/views/funding/transferDetail/index.vue'),
meta: { meta: {
title: '转账明细', // 左侧菜单显示的标题 title: '转账明细', // 左侧菜单显示的标题
} },
}, },
{ {
path: 'capitalFlow', path: 'capitalFlow',
@ -278,7 +286,7 @@ const routes = [
component: () => import('@/views/funding/capitalFlow/index.vue'), component: () => import('@/views/funding/capitalFlow/index.vue'),
meta: { meta: {
title: '资金流水', // 左侧菜单显示的标题 title: '资金流水', // 左侧菜单显示的标题
} },
}, },
{ {
path: 'receiverAccount', path: 'receiverAccount',
@ -286,7 +294,7 @@ const routes = [
component: () => import('@/views/funding/receiverAccount/index.vue'), component: () => import('@/views/funding/receiverAccount/index.vue'),
meta: { meta: {
title: '收款账户列表', // 左侧菜单显示的标题 title: '收款账户列表', // 左侧菜单显示的标题
} },
}, },
{ {
path: 'depositChannel', path: 'depositChannel',
@ -294,7 +302,7 @@ const routes = [
component: () => import('@/views/funding/depositChannel/index.vue'), component: () => import('@/views/funding/depositChannel/index.vue'),
meta: { meta: {
title: '存款渠道列表', // 左侧菜单显示的标题 title: '存款渠道列表', // 左侧菜单显示的标题
} },
}, },
{ {
path: 'fundDetail', path: 'fundDetail',
@ -302,9 +310,9 @@ const routes = [
component: () => import('@/views/funding/fundDetail/index.vue'), component: () => import('@/views/funding/fundDetail/index.vue'),
meta: { meta: {
title: '资金明细', // 左侧菜单显示的标题 title: '资金明细', // 左侧菜单显示的标题
} },
}, },
] ],
}, },
{ {
path: 'risk', path: 'risk',
@ -329,7 +337,7 @@ const routes = [
component: () => import('@/views/personalized/followRecord/index.vue'), component: () => import('@/views/personalized/followRecord/index.vue'),
meta: { meta: {
title: '跟单记录', // 左侧菜单显示的标题 title: '跟单记录', // 左侧菜单显示的标题
} },
}, },
// 带单记录 // 带单记录
{ {
@ -338,9 +346,9 @@ const routes = [
component: () => import('@/views/personalized/leadRecord/index.vue'), component: () => import('@/views/personalized/leadRecord/index.vue'),
meta: { meta: {
title: '带单记录', // 左侧菜单显示的标题 title: '带单记录', // 左侧菜单显示的标题
} },
}, },
] ],
}, },
//代理管理 //代理管理
{ {
@ -357,7 +365,7 @@ const routes = [
component: () => import('@/views/agent/customerList/index.vue'), component: () => import('@/views/agent/customerList/index.vue'),
meta: { meta: {
title: '客户列表', // 左侧菜单显示的标题 title: '客户列表', // 左侧菜单显示的标题
} },
}, },
//客户详情 //客户详情
{ {
@ -366,7 +374,7 @@ const routes = [
component: () => import('@/views/agent/customerDetail/index.vue'), component: () => import('@/views/agent/customerDetail/index.vue'),
meta: { meta: {
title: '客户详情', // 左侧菜单显示的标题 title: '客户详情', // 左侧菜单显示的标题
} },
}, },
//客户交易订单 //客户交易订单
{ {
@ -375,7 +383,7 @@ const routes = [
component: () => import('@/views/agent/customerTradeOrder/index.vue'), component: () => import('@/views/agent/customerTradeOrder/index.vue'),
meta: { meta: {
title: '客户交易订单', // 左侧菜单显示的标题 title: '客户交易订单', // 左侧菜单显示的标题
} },
}, },
//客户资金流水 //客户资金流水
{ {
@ -384,17 +392,17 @@ const routes = [
component: () => import('@/views/agent/customerCapitalFlow/index.vue'), component: () => import('@/views/agent/customerCapitalFlow/index.vue'),
meta: { meta: {
title: '客户资金流水', // 左侧菜单显示的标题 title: '客户资金流水', // 左侧菜单显示的标题
} },
}, },
] ],
}, },
] ],
}, },
{ {
path: '/:pathMatch(.*)*', path: '/:pathMatch(.*)*',
redirect: '/user/login' // 404 重定向到登录页,避免循环 redirect: '/user/login', // 404 重定向到登录页,避免循环
} },
]; ]
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHistory(),
@ -429,4 +437,4 @@ router.afterEach((to) => {
} }
}); });
export default router; export default router;

View File

@ -0,0 +1,156 @@
<template>
<div class="system">
<!-- 菜单列表头 -->
<MenuHeader :title="menuTitle" :buttons="menuHeaderButtons" @click="handleAddMenu" />
<!-- 菜单列表表格 -->
<MenuTable :table-data="menuTree" :columns="menuTableColumns" row-key="id">
<template #action="{ row }">
<el-button link type="primary" size="small" @click="handleEditMenu(row)"> 编辑 </el-button>
</template>
</MenuTable>
</div>
<!-- 新增/编辑弹窗 -->
<MenuDialog
:visible="dialogVisible"
:title="dialogTitle"
:type="dialogType"
@confirm="handleSubmit"
@cancel="handleCancel"
@close="handleClose"
>
<MenuForm
:form-data="menuForm"
:form-rules="menuFormRules"
:form-items="menuFormItemOptions"
ref="menuFormRef"
/>
</MenuDialog>
</template>
<script lang="ts" setup>
import type { FormInstance } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
//
import MenuHeader from '@/components/common/Header.vue'
import MenuTable from '@/components/common/Table.vue'
import MenuDialog from '@/components/common/Dialog.vue'
import MenuForm from '@/components/common/Form.vue'
import { menuTableColumns, menuFormItemOptions } from './options'
import { rules } from './rules'
import { getSettingApi, createSettingApi, editSettingApi } from '@/api/modules/system/setting'
type SettingItem = {
id: number
key: string
value: string
description: string
}
// header
const menuTitle = ref('系统设置')
const menuHeaderButtons = [
{ label: '新增设置', type: 'primary' as const, icon: Plus, key: 'addMenu' },
]
//
const menuTree = ref<SettingItem[]>([])
//
const dialogVisible = ref(false)
const dialogTitle = ref('')
const dialogType = ref('add')
//
const menuForm = ref({
key: '',
value: '',
description: '',
})
const menuFormRef = ref<FormInstance>()
//
const menuFormRules = rules()
/**
@description: 新增菜单
**/
const handleAddMenu = () => {
dialogVisible.value = true
dialogTitle.value = '新增菜单'
menuFormItemOptions[0]!.disabled = false
}
/**
@description: 菜单数据处理
**/
//
const fetchSettingList = async () => {
try {
const data = await getSettingApi()
menuTree.value = data
} catch (err) {
console.error('获取菜单列表失败', err)
}
}
//
onMounted(async () => {
await fetchSettingList()
})
/**
@description: 处理菜单编辑/删除/状态切换
**/
const handleEditMenu = (row: SettingItem) => {
dialogVisible.value = true
menuFormItemOptions[0]!.disabled = true
dialogTitle.value = '编辑菜单'
dialogType.value = 'edit'
menuForm.value = {
...row
}
}
/**
@description: 处理弹窗提交/取消/关闭事件
**/
const handleSubmit = async () => {
console.log('提交菜单表单')
try {
await menuFormRef.value?.validate()
if (dialogType.value === 'add') {
const params = {
key: menuForm.value.key,
value: menuForm.value.value,
description: menuForm.value.description,
}
await createSettingApi(params)
await fetchSettingList()
} else {
await editSettingApi(menuForm.value)
console.log('编辑菜单成功', menuForm.value)
await fetchSettingList()
}
dialogVisible.value = false
} catch (err) {
console.log('表单验证失败', err)
return
}
}
const handleCancel = () => {
dialogVisible.value = false
dialogType.value = 'add'
menuForm.value = {
key: '',
value: '',
description: '',
}
}
const handleClose = () => {
dialogVisible.value = false
dialogType.value = 'add'
menuForm.value = {
key: '',
value: '',
description: '',
}
}
</script>

View File

@ -0,0 +1,19 @@
//定义table配置
export const menuTableColumns = [
{ prop: 'key', label: 'key', align: 'center' as const },
{ prop: 'value', label: 'value', align: 'center' as const },
{ prop: 'description', label: '备注', align: 'center' as const },
{ label: '操作', slotName: 'action', align: 'center' as const },
]
//定义menuForm配置
export const menuFormItemOptions = [
{ prop: 'key', label: 'key', type: 'input' as const, placeholder: '请输入key', disabled: false },
{ prop: 'value', label: 'value', type: 'input' as const, placeholder: '请选择value' },
{
prop: 'description',
label: 'description',
type: 'input' as const,
placeholder: '请选择description',
},
]

View File

@ -0,0 +1,9 @@
import type { FormRules } from 'element-plus'
export const rules = (): FormRules => {
return {
key: [{ required: true, message: '请选择key', trigger: 'blur' }],
value: [{ required: true, message: '请选择value', trigger: 'blur' }],
description: [{ required: true, message: '请选择备注', trigger: 'blur' }],
}
}

View File

@ -1,207 +1,328 @@
<template> <template>
<div class="trade-group"> <div class="trade-group">
<el-form-item> <el-form-item label="交易组时间设置">
<el-button type="primary" @click="handleAdd">添加</el-button> <el-button type="primary" @click="addTableItem">添加</el-button>
</el-form-item> </el-form-item>
<!-- 表格 --> <!-- 表格 -->
<TradeGroupTable :table-data="tradeGroupTree" :columns="tradeGroupTableColumnsOptions" row-key="id"> <div class="table-box">
<template #tradeTime="{ row }"> <table>
:开仓{{ row.winterStartTime }} - 平仓{{ row.winterEndTime }}<br /> <template v-for="(item, index) in tradeGroupTree" :key="item.id">
<hr> <tr>
:开仓{{ row.summerStartTime }} - 平仓{{ row.summerEndTime }} <td style="width: 50px">顺序</td>
</template> <td style="width: 120px" rowspan="2">
<template #status="{ row }"> <el-input style="width: 100px" v-if="item.editStatus" v-model="item.name" />
<el-switch :model-value="row.status === 1" :active-value="true" :inactive-value="false" /> <span v-else>{{ item.name }}</span>
</template> </td>
<template #action="{ row }"> <td style="width: 70px">夏令时</td>
<el-button type="primary" size="small" @click="handleEdit(row)">编辑</el-button> <td style="width: 60px">
<el-button type="danger" size="small" @click="handleDelete(row)">删除</el-button> <el-input v-if="item.editStatus" v-model="item.x_m" />
</template> <span v-else>{{ item.x_m }}</span>
</TradeGroupTable> </td>
<td style="width: 50px"></td>
<td style="width: 60px">
<el-input v-if="item.editStatus" v-model="item.x_w" />
<span v-else>{{ item.x_w }}</span>
</td>
<td style="width: 50px"></td>
<td style="width: 60px">
<el-input v-if="item.editStatus" v-model="item.x_d" />
<span v-else>{{ item.x_d }}</span>
</td>
<td style="width: 50px"></td>
<td style="width: 90px">交易时间</td>
<td class="time-picker-box">
<template v-if="item.editStatus">
<template v-if="item?.x_time_list">
<el-time-picker
v-for="(time, timeIndex) in item?.x_time_list"
:key="timeIndex"
v-model="item.x_time_list[timeIndex]"
style="width: 180px; flex: 0 0 auto"
format="HH:mm:ss"
value-format="HH:mm:ss"
:picker-options="{
selectableRange: '09:00:00 - 18:00:00',
}"
is-range
/>
</template>
<img
src="@/assets/images/trade/group/add.png"
@click="addTime(index, 'x_time_list')"
/>
</template>
<template v-else>
<template v-if="item?.x_time_list">
<span v-for="(time, timeIndex) in item?.x_time_list" :key="timeIndex">
{{ time[0] }} - {{ time[1] }}
</span>
</template>
</template>
</td>
<td rowspan="2" style="width: 140px">
<el-button type="primary" v-if="!item.editStatus" @click="item.editStatus = true"
>编辑</el-button
>
<el-button type="danger" v-if="!item.editStatus" @click="deleteGroup(item.id)"
>删除</el-button
>
<el-button type="primary" v-if="item.editStatus" @click="submitItem(index)"
>确认</el-button
>
<el-button type="danger" v-if="item.editStatus" @click="item.editStatus = false"
>取消</el-button
>
</td>
</tr>
<tr>
<td>{{ index + 1 }}</td>
<td>冬令时</td>
<td>
<el-input v-if="item.editStatus" v-model="item.d_m" />
<span v-else>{{ item.d_m }}</span>
</td>
<td></td>
<td>
<el-input v-if="item.editStatus" v-model="item.d_w" />
<span v-else>{{ item.d_w }}</span>
</td>
<td></td>
<td>
<el-input v-if="item.editStatus" v-model="item.d_d" />
<span v-else>{{ item.d_d }}</span>
</td>
<td></td>
<td>交易时间</td>
<td class="time-picker-box">
<template v-if="item.editStatus">
<template v-if="item?.d_time_list">
<el-time-picker
v-for="(time, timeIndex) in item?.d_time_list"
:key="timeIndex"
v-model="item.d_time_list[timeIndex]"
style="width: 180px; flex: 0 0 auto"
format="HH:mm:ss"
value-format="HH:mm:ss"
:picker-options="{
selectableRange: '09:00:00 - 18:00:00',
}"
is-range
/>
</template>
<img
src="@/assets/images/trade/group/add.png"
@click="addTime(index, 'd_time_list')"
/>
</template>
<template v-else>
<template v-if="item?.d_time_list">
<span v-for="(time, timeIndex) in item?.d_time_list" :key="timeIndex">
{{ time[0] }} - {{ time[1] }}
</span>
</template>
</template>
</td>
</tr>
</template>
</table>
</div> </div>
<!-- dialog --> </div>
<TradeGroupDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" @confirm="handleSubmit"
@cancel="handleCancel" @close="handleClose">
<TradeGroupForm :form-data="tradeGroupForm" :form-rules="tradeGroupFormRules"
:form-items="dialogFormItemOptions" :options-map="tradeGroupFormOptionsMap" ref="tradeGroupFormRef">
</TradeGroupForm>
</TradeGroupDialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
//
import TradeGroupTable from '@/components/common/Table.vue'
import TradeGroupDialog from '@/components/common/Dialog.vue'
import TradeGroupForm from '@/components/common/Form.vue'
//
import { search, tableColumns, addFormItem, editFormItem } from './options'
import { rules } from './rules'
import dayjs from 'dayjs';
// //
import { getGroupListApi, addGroupApi, editGroupApi, deleteGroupApi } from '@/api/modules/trade/group' import {
getGroupListApi,
addGroupApi,
editGroupApi,
deleteGroupApi,
} from '@/api/modules/trade/group'
import { ElMessageBox } from 'element-plus'
/** /**
* @description: 定义表格数据 * @description: 定义表格数据
*/ */
const tradeGroupTree = ref([]) const tradeGroupTree = ref<any[]>([])
const tradeGroupTableColumnsOptions = ref(tableColumns)
/** const addTableItem = () => {
* @description: 定义弹窗数据 tradeGroupTree.value.unshift({
*/ name: '',
const dialogVisible = ref(false) x_m: '',
const dialogTitle = ref('') x_w: '',
const dialogType = ref('') x_d: '',
x_time_list: [[]],
d_m: '',
d_w: '',
d_d: '',
d_time_list: [[]],
editStatus: true,
})
}
/** const addTime = (idx: number, key: string) => {
* @description: 定义弹窗表格数据 if (tradeGroupTree.value[idx][key].length >= 4) return ElMessage.warning('最多添加4个时间段')
*/ tradeGroupTree.value[idx][key].push([])
const tradeGroupForm = ref({ }
//
tradeGroupName: '', const submitItem = (idx: number) => {
// ID const submitObj = {
exchangeId: '', name: tradeGroupTree.value[idx].name,
// x_m: tradeGroupTree.value[idx].x_m,
status: 1, x_w: tradeGroupTree.value[idx].x_w,
// x_d: tradeGroupTree.value[idx].x_d,
winterTime: [], d_m: tradeGroupTree.value[idx].d_m,
// d_w: tradeGroupTree.value[idx].d_w,
summerTime: [], d_d: tradeGroupTree.value[idx].d_d,
// x_start_time1: tradeGroupTree.value[idx].x_time_list[0]?.[0] || null,
sort: 0, x_end_time1: tradeGroupTree.value[idx].x_time_list[0]?.[1] || null,
// ID x_start_time2: tradeGroupTree.value[idx].x_time_list[1]?.[0] || null,
id: '', x_end_time2: tradeGroupTree.value[idx].x_time_list[1]?.[1] || null,
}) x_start_time3: tradeGroupTree.value[idx].x_time_list[2]?.[0] || null,
const tradeGroupFormRules = ref(rules()) x_end_time3: tradeGroupTree.value[idx].x_time_list[2]?.[1] || null,
const tradeGroupFormRef = ref() x_start_time4: tradeGroupTree.value[idx].x_time_list[3]?.[0] || null,
const dialogFormItemOptions = computed(() => dialogType.value === 'add' ? addFormItem : editFormItem) x_end_time4: tradeGroupTree.value[idx].x_time_list[3]?.[1] || null,
const tradeGroupFormOptionsMap = ref({ d_start_time1: tradeGroupTree.value[idx].d_time_list[0]?.[0] || null,
status: [{ label: '开启', value: 1 }, { label: '关闭', value: 2 }], d_end_time1: tradeGroupTree.value[idx].d_time_list[0]?.[1] || null,
}) d_start_time2: tradeGroupTree.value[idx].d_time_list[1]?.[0] || null,
d_end_time2: tradeGroupTree.value[idx].d_time_list[1]?.[1] || null,
d_start_time3: tradeGroupTree.value[idx].d_time_list[2]?.[0] || null,
d_end_time3: tradeGroupTree.value[idx].d_time_list[2]?.[1] || null,
d_start_time4: tradeGroupTree.value[idx].d_time_list[3]?.[0] || null,
d_end_time4: tradeGroupTree.value[idx].d_time_list[3]?.[1] || null,
}
if (tradeGroupTree.value[idx].id) {
editGroupApi({
...submitObj,
id: tradeGroupTree.value[idx].id,
sort: tradeGroupTree.value[idx].sort,
}).then((res) => {
getGroupList()
})
} else {
addGroupApi({ ...submitObj }).then((res) => {
getGroupList()
})
}
}
const deleteGroup = (id: number) => {
ElMessageBox.confirm('确认删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
}).then(() => {
deleteGroupApi({ id }).then(() => {
getGroupList()
})
})
}
/**
* @description: 定义公共数据处理方法
*/
/** /**
* @description: 定义公共请求数据方法 * @description: 定义公共请求数据方法
*/ */
const getGroupList = async () => { const getGroupList = async () => {
try {
const data = await getGroupListApi() const data = await getGroupListApi()
tradeGroupTree.value = data.map((item: any) => ({ data.forEach((item: any) => {
id: item.id, item.editStatus = false
tradeGroupName: item.name, item.x_time_list = []
status: item.status, if (item.x_start_time1 && item.x_end_time1) {
// item.x_time_list.push([item.x_start_time1, item.x_end_time1])
winterStartTime: dayjs(item.d_start_time * 1000).format('YYYY-MM-DD HH:mm:ss'), }
winterEndTime: dayjs(item.d_end_time * 1000).format('YYYY-MM-DD HH:mm:ss'), if (item.x_start_time2 && item.x_end_time2) {
// item.x_time_list.push([item.x_start_time2, item.x_end_time2])
summerStartTime: dayjs(item.x_start_time * 1000).format('YYYY-MM-DD HH:mm:ss'), }
summerEndTime: dayjs(item.x_end_time * 1000).format('YYYY-MM-DD HH:mm:ss'), if (item.x_start_time3 && item.x_end_time3) {
// item.x_time_list.push([item.x_start_time3, item.x_end_time3])
sort: item.sort, }
})) if (item.x_start_time4 && item.x_end_time4) {
item.x_time_list.push([item.x_start_time4, item.x_end_time4])
}
item.d_time_list = []
if (item.d_start_time1 && item.d_end_time1) {
item.d_time_list.push([item.d_start_time1, item.d_end_time1])
}
if (item.d_start_time2 && item.d_end_time2) {
item.d_time_list.push([item.d_start_time2, item.d_end_time2])
}
if (item.d_start_time3 && item.d_end_time3) {
item.d_time_list.push([item.d_start_time3, item.d_end_time3])
}
if (item.d_start_time4 && item.d_end_time4) {
item.d_time_list.push([item.d_start_time4, item.d_end_time4])
}
})
console.log('data', data)
tradeGroupTree.value = data
} catch (e: any) {
console.log(e)
ElMessage.error(e.msg)
}
} }
/** /**
* @description: 页面加载完成需要请求的数据 * @description: 页面加载完成需要请求的数据
*/ */
onMounted(() => { onMounted(() => {
getGroupList() getGroupList()
}) })
const handleAdd = () => {
dialogVisible.value = true
dialogTitle.value = '添加交易组'
dialogType.value = 'add'
tradeGroupForm.value = {
tradeGroupName: '',
winterTime: [],
summerTime: [],
status: 1,
sort: 0,
id: '',
} as any
}
/**
* @description: 定义表格方法
*/
const handleEdit = (row: any) => {
tradeGroupForm.value = {
tradeGroupName: row.tradeGroupName,
winterTime: [dayjs(row.winterStartTime).format('YYYY-MM-DD HH:mm:ss'), dayjs(row.winterEndTime).format('YYYY-MM-DD HH:mm:ss')],
summerTime: [dayjs(row.summerStartTime).format('YYYY-MM-DD HH:mm:ss'), dayjs(row.summerEndTime).format('YYYY-MM-DD HH:mm:ss')],
status: row.status,
sort: row.sort,
id: row.id,
} as any
dialogVisible.value = true
dialogTitle.value = '编辑交易组'
dialogType.value = 'edit'
}
const handleDelete = async (row: any) => {
console.log(row)
await deleteGroupApi({ id: row.id })
getGroupList()
}
/**
* @description: 定义分页方法
*/
/**
* @description: 定义弹窗方法
*/
const handleSubmit = async () => {
await tradeGroupFormRef.value.validate()
if (dialogType.value === 'add') {
const params = {
name: tradeGroupForm.value.tradeGroupName,
x_start_time: tradeGroupForm.value.summerTime[0],
x_end_time: tradeGroupForm.value.summerTime[1],
d_start_time: tradeGroupForm.value.winterTime[0],
d_end_time: tradeGroupForm.value.winterTime[1],
}
await addGroupApi(params)
getGroupList()
dialogVisible.value = false
} else if (dialogType.value === 'edit') {
//
const params = {
id: tradeGroupForm.value.id,
name: tradeGroupForm.value.tradeGroupName,
x_start_time: tradeGroupForm.value.summerTime[0],
x_end_time: tradeGroupForm.value.summerTime[1],
d_start_time: tradeGroupForm.value.winterTime[0],
d_end_time: tradeGroupForm.value.winterTime[1],
status: tradeGroupForm.value.status,
sort: tradeGroupForm.value.sort,
}
await editGroupApi(params)
getGroupList()
dialogVisible.value = false
}
}
const handleCancel = () => {
dialogVisible.value = false
}
const handleClose = () => {
dialogVisible.value = false
}
/**
* @description: 定义弹窗表格方法
*/
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.search-container { /* 给 table 及所有 td 加边框 */
:deep(.el-form) { table {
.el-form-item { border-collapse: collapse;
flex: none; width: 100%;
table-layout: auto; /* 宽度自适应内容 */
.el-select { .time-picker-box {
width: 160px; display: flex;
} align-items: center;
} flex-wrap: wrap;
gap: 10px;
img {
width: 20px;
height: 20px;
cursor: pointer;
} }
}
td {
height: 59px;
border: 1px solid #dcdfe6;
padding: 4px 8px; /* 内边距缩小 */
text-align: center;
font-size: 12px; /* 文字变小 */
white-space: nowrap; /* 文字不换行 */
vertical-align: middle; /* 垂直居中 */
/* 时间选择器容器:两个选择器横排不换行 */
:deep(.el-time-picker) {
display: inline-block;
.el-range-editor {
width: 100px; /* 缩小时间选择器宽度 */
font-size: 12px;
}
.el-range-input {
font-size: 12px;
}
}
/* input 输入框也缩小 */
:deep(.el-input) {
width: 40px;
.el-input__inner {
font-size: 12px;
height: 28px;
}
}
/* 按钮缩小 */
:deep(.el-button) {
padding: 5px 10px;
font-size: 12px;
}
}
} }
</style> </style>

View File

@ -1,40 +0,0 @@
/**
* @description:
*/
export const search = [
{ label: '交易组名称', prop: 'tradeGroupName', type: 'input' as const, placeholder: '请输入交易组名称' },
{ label: '状态', prop: 'status', type: 'select' as const, options: [{ label: '全部', value: '0' }, { label: '开启', value: '1' }, { label: '关闭', value: '2' }] },
]
/**
* @description:
*/
export const tableColumns = [
{ label: '交易组名称', prop: 'tradeGroupName' },
//交易所编号
{ label: '交易所编号', prop: 'exchangeId' },
// 交易时间
{ label: '交易时间', slotName: 'tradeTime' },
// 交易状态
{ label: '交易状态', slotName: 'status' },
// 操作
{ label: '操作', slotName: 'action' },
]
/**
* @description: dialog form配置
*/
export const addFormItem = [
{ label: '交易组名称', prop: 'tradeGroupName', type: 'input' as const, placeholder: '请输入交易组名称' },
// { label: '交易所ID', prop: 'exchangeId', type: 'select' as const, options: [] },
// { label: '状态', prop: 'status', type: 'select' as const, options: [{ label: '开启', value: '1' }, { label: '关闭', value: '2' }] },
{ label: '冬季交易时间', prop: 'winterTime', type: 'date' as const, dateType: 'datetimerange' as const, placeholder: '请选择冬季交易时间', valueFormat: 'YYYY-MM-DD HH:mm:ss', labelWidth: '110px' },
{ label: '夏季交易时间', prop: 'summerTime', type: 'date' as const, dateType: 'datetimerange' as const, placeholder: '请选择夏季交易时间', valueFormat: 'YYYY-MM-DD HH:mm:ss' , labelWidth: '110px' },
]
export const editFormItem = [
{ label: '交易组名称', prop: 'tradeGroupName', type: 'input' as const, placeholder: '请输入交易组名称' },
{ label: '冬季交易时间', prop: 'winterTime', type: 'date' as const, dateType: 'datetimerange' as const, placeholder: '请选择冬季交易时间', valueFormat: 'YYYY-MM-DD HH:mm:ss' , labelWidth: '110px'},
{ label: '夏季交易时间', prop: 'summerTime', type: 'date' as const, dateType: 'datetimerange' as const, placeholder: '请选择夏季交易时间', valueFormat: 'YYYY-MM-DD HH:mm:ss' , labelWidth: '110px'},
{ label: '状态', prop: 'status', type: 'radio' as const},
{ label: '排序', prop: 'sort', type: 'number' as const, placeholder: '请输入排序' },
]

View File

@ -1,14 +0,0 @@
import type { FormRules } from 'element-plus'
export const rules = (): FormRules => {
return {
tradeGroupName: [
{ required: true, message: '请输入交易组名称', trigger: ['blur'] }
],
winterTime: [
{ required: true, message: '请选择冬季交易时间', trigger: ['change'] }
],
summerTime: [
{ required: true, message: '请选择夏季交易时间', trigger: ['change'] }
],
}
}

View File

@ -1,5 +1,221 @@
<template> <template>
<div class="trade-time"> <div class="trade-group">
交易时间 <el-form-item label="交易节假日时间设置">
<el-button style="margin-left: 18px" type="primary" @click="handleAdd">添加</el-button>
<el-button style="margin-left: 18px" type="danger" @click="handleDel">删除</el-button>
</el-form-item>
<!-- 表格 -->
<div class="table-box">
<table>
<thead>
<tr>
<th>名称</th>
<th>休市时间</th>
<th>休市市场</th>
<th>备注</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in dataList" :key="`${item.id}-${index}`">
<td>
<el-input v-model="item.name" style="width: 80px" />
</td>
<td>
<el-date-picker
v-model="item.datePicker"
type="datetimerange"
start-placeholder="休市开始时间"
end-placeholder="休市结束时间"
format="YYYY-MM-DD HH:mm:ss"
date-format="YYYY-MM-DD HH:mm:ss"
time-format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</td>
<td>
<el-select
multiple
placeholder="请选择休市市场"
style="width: 240px"
v-model="item.groupId"
>
<el-option
v-for="item in marketList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</td>
<td><el-input style="width: 250px" v-model="item.description" /></td>
<td>
<el-button type="primary" v-if="!item.editStatus" @click="item.editStatus = true">编辑</el-button>
<el-button type="danger" v-if="!item.editStatus" @click="handleDel(item.id)">删除</el-button>
<el-button type="primary" v-if="item.editStatus" @click="submitItem(index)">确认</el-button>
<el-button type="danger" v-if="item.editStatus" @click="item.editStatus = false">取消</el-button>
</td>
</tr>
</tbody>
</table>
</div> </div>
</div>
</template> </template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { getGroupListApi } from '@/api/modules/trade/group'
import { getHolidayList, addHoliday, editHoliday, delHoliday } from '@/api/modules/trade/time'
import { ElMessageBox } from 'element-plus'
type DataItem = {
id?: number | string
name: string
start_time?: string
end_time?: string
datePicker: string[]
groupId: number[]
description: string
editStatus: boolean
}
const dataList = ref<DataItem[]>([])
const marketList = ref<{ label: string; value: number | string }[]>([])
const getMarketList = async () => {
getGroupListApi().then((res) => {
console.log(res)
marketList.value = res.map((item: { name: string; id: number }[]) => ({
label: item.name,
value: item.id + '',
}))
})
}
const initDate = () => {
getHolidayList().then((res) => {
dataList.value = res.map((item: any) => ({
id: item.id + '',
name: item.name,
start_time: item.start_time,
end_time: item.end_time,
datePicker: [item.start_time, item.end_time],
groupId: item.group_id,
description: item.description,
editStatus: false,
}))
})
}
const handleAdd = () => {
dataList.value.unshift({
name: '',
start_time: '',
end_time: '',
datePicker: [],
groupId: [],
description: '',
editStatus: true,
})
}
const submitItem = (idx: number) => {
const submitObj = {
name: dataList.value[idx]!.name,
start_time: dataList.value[idx]!.datePicker[0],
end_time: dataList.value[idx]!.datePicker[1],
group_id: dataList.value[idx]!.groupId.join(','),
description: dataList.value[idx]!.description,
}
if (dataList.value[idx]!.id) {
editHoliday({id: dataList.value[idx]!.id, ...submitObj}).then(() => {
initDate()
})
} else {
addHoliday(submitObj).then(() => {
initDate()
})
}
}
const handleDel = (id: number | string) => {
ElMessageBox.confirm('确认删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
}).then(() => {
delHoliday({id}).then(() => {
initDate()
})
})
}
/**
* @description: 页面加载完成需要请求的数据
*/
onMounted(() => {
getMarketList()
initDate()
})
</script>
<style scoped lang="scss">
/* 给 table 及所有 td 加边框 */
table {
border-collapse: collapse;
width: 100%;
table-layout: auto;
/* 宽度自适应内容 */
th {
font-weight: bold;
}
td,
th {
height: 48px;
border: 1px solid #dcdfe6;
padding: 4px 8px;
/* 内边距缩小 */
text-align: center;
font-size: 12px;
/* 文字变小 */
white-space: nowrap;
/* 文字不换行 */
vertical-align: middle;
/* 垂直居中 */
/* 时间选择器容器:两个选择器横排不换行 */
:deep(.el-time-picker) {
display: inline-block;
.el-range-editor {
width: 100px;
/* 缩小时间选择器宽度 */
font-size: 12px;
}
.el-range-input {
font-size: 12px;
}
}
/* input 输入框也缩小 */
:deep(.el-input) {
width: 60px;
.el-input__inner {
font-size: 12px;
height: 28px;
}
}
/* 按钮缩小 */
:deep(.el-button) {
padding: 5px 10px;
font-size: 12px;
}
}
}
</style>

View File

@ -10,79 +10,113 @@ export const search = [
* @description: * @description:
*/ */
export const tableColumns = [ export const tableColumns = [
// id // id
{ prop: 'id', label: 'ID', align: 'center' as const }, { prop: 'id', label: 'ID', align: 'center' as const },
// 名称 // 名称
{ prop: 'varietyName', label: '名称', align: 'center' as const }, { prop: 'varietyName', label: '名称', align: 'center' as const },
// 编号 // 编号
{ prop: 'varietyCode', label: '编号', align: 'center' as const }, { prop: 'varietyCode', label: '编号', align: 'center' as const },
//上下线状态 //上下线状态
{ slotName: 'onlineStatus', label: '上下线状态', align: 'center' as const }, { slotName: 'onlineStatus', label: '上下线状态', align: 'center' as const },
// 交易状态 // 交易状态
{ slotName: 'tradeStatus', label: '交易状态', align: 'center' as const }, { slotName: 'tradeStatus', label: '交易状态', align: 'center' as const },
// 最小点差 // 最小点差
{ prop: 'minPoint', label: '最小点差', align: 'center' as const }, { prop: 'minPoint', label: '最小点差', align: 'center' as const },
// 最大点差 // 最大点差
{ prop: 'maxPoint', label: '最大点差', align: 'center' as const }, { prop: 'maxPoint', label: '最大点差', align: 'center' as const },
// 合约大小 // 合约大小
{ prop: 'contractSize', label: '合约大小', align: 'center' as const }, { prop: 'contractSize', label: '合约大小', align: 'center' as const },
// 最小波动 // 最小波动
{ prop: 'minFlux', label: '最小波动', align: 'center' as const }, { prop: 'minFlux', label: '最小波动', align: 'center' as const },
// 单位 // 单位
{ prop: 'unit', label: '单位', align: 'center' as const }, { prop: 'unit', label: '单位', align: 'center' as const },
// 所属交易组 // 交易时间
{ prop: 'tradeGroup', label: '所属交易组', align: 'center' as const }, { prop: 'tradeGroup', label: '交易时间', align: 'center' as const },
// 多头库存费 // 多头库存费
{ prop: 'longStockFee', label: '多头库存费', align: 'center' as const }, { prop: 'longStockFee', label: '多头库存费', align: 'center' as const },
// 空头库存费 // 空头库存费
{ prop: 'shortStockFee', label: '空头库存费', align: 'center' as const }, { prop: 'shortStockFee', label: '空头库存费', align: 'center' as const },
// 类型 // 类型
{ prop: 'type', label: '类型', align: 'center' as const }, { prop: 'type', label: '类型', align: 'center' as const },
// 止盈水平 // 止盈水平
{ prop: 'profitLevel', label: '止盈水平', align: 'center' as const }, { prop: 'profitLevel', label: '止盈水平', align: 'center' as const },
] ]
/** /**
* @description: dialog表单配置 * @description: dialog表单配置
*/ */
export const editDialogFormItem = [ export const editDialogFormItem = [
// 名称 // 名称
{ prop: 'varietyName', label: '名称', type: 'input' as const, placeholder: '请输入名称' }, { prop: 'varietyName', label: '名称', type: 'input' as const, placeholder: '请输入名称' },
// 编号 // 编号
{ prop: 'varietyCode', label: '编号', type: 'input' as const, placeholder: '请输入编号' }, { prop: 'varietyCode', label: '编号', type: 'input' as const, placeholder: '请输入编号' },
// 所属交易组 // 交易时间
{ prop: 'tradeGroup', label: '所属交易组', type: 'select' as const, placeholder: '请输入所属交易组' }, {
// 最大点差 prop: 'tradeGroup',
{ prop: 'maxPoint', label: '最大点差', type: 'input' as const, placeholder: '请输入最大点差' }, label: '交易时间',
// 最小点差 type: 'select' as const,
{ prop: 'minPoint', label: '最小点差', type: 'input' as const, placeholder: '请输入最小点差' }, placeholder: '请选择交易时间',
// 合约大小 },
{ prop: 'contractSize', label: '合约大小', type: 'input' as const, placeholder: '请输入合约大小' }, // 最大点差
// 多头库存费 { prop: 'maxPoint', label: '最大点差', type: 'input' as const, placeholder: '请输入最大点差' },
{ prop: 'longStockFee', label: '多头库存费', type: 'input' as const, placeholder: '请输入多头库存费' }, // 最小点差
// 空头库存费 { prop: 'minPoint', label: '最小点差', type: 'input' as const, placeholder: '请输入最小点差' },
{ prop: 'shortStockFee', label: '空头库存费', type: 'input' as const, placeholder: '请输入空头库存费' }, // 合约大小
// 预付款对冲 {
{ prop: 'prepaymentHedge', label: '预付款对冲', type: 'input' as const, placeholder: '请输入预付款对冲' }, prop: 'contractSize',
// 预付款百分比 label: '合约大小',
{ prop: 'prepaymentPercent', label: '预付款百分比', type: 'input' as const, placeholder: '请输入预付款百分比' }, type: 'input' as const,
// 小数位 placeholder: '请输入合约大小',
{ prop: 'decimalPlaces', label: '小数位', type: 'input' as const, placeholder: '请输入小数位' }, },
// 点差小数位 // 多头库存费
{ prop: 'pointDecimalPlaces', label: '点差小数位', type: 'input' as const, placeholder: '请输入点差小数位' }, {
// 止盈水平 prop: 'longStockFee',
{ prop: 'profitLevel', label: '止盈水平', type: 'input' as const, placeholder: '请输入止盈水平' }, label: '多头库存费',
// 单位 type: 'input' as const,
{ prop: 'unit', label: '单位', type: 'input' as const, placeholder: '请输入单位' }, placeholder: '请输入多头库存费',
// 最小波动 },
{ prop: 'minFlux', label: '最小波动', type: 'input' as const, placeholder: '请输入最小波动' }, // 空头库存费
// 类型 {
{ prop: 'type', label: '类型', type: 'select' as const, placeholder: '请选择类型' }, prop: 'shortStockFee',
// 上传图片地址 label: '空头库存费',
{ prop: 'imageUrl', slotName: 'upload', label: '上传图片' ,type: 'upload' as const}, type: 'input' as const,
// 上下线状态 placeholder: '请输入空头库存费',
{ prop: 'onlineStatus', label: '上下线状态', type: 'switch' as const}, },
// 交易状态 // 预付款对冲
{ prop: 'tradeStatus', label: '交易状态', type: 'switch' as const}, {
prop: 'prepaymentHedge',
] label: '预付款对冲',
type: 'input' as const,
placeholder: '请输入预付款对冲',
},
// 预付款百分比
{
prop: 'prepaymentPercent',
label: '预付款百分比',
type: 'input' as const,
placeholder: '请输入预付款百分比',
},
// 小数位
{ prop: 'decimalPlaces', label: '小数位', type: 'input' as const, placeholder: '请输入小数位' },
// 点差小数位
{
prop: 'pointDecimalPlaces',
label: '点差小数位',
type: 'input' as const,
placeholder: '请输入点差小数位',
},
// 止盈水平
{ prop: 'profitLevel', label: '止盈水平', type: 'input' as const, placeholder: '请输入止盈水平' },
// 单位
{ prop: 'unit', label: '单位', type: 'input' as const, placeholder: '请输入单位' },
// 最小波动
{ prop: 'minFlux', label: '最小波动', type: 'input' as const, placeholder: '请输入最小波动' },
// 类型
{ prop: 'type', label: '类型', type: 'select' as const, placeholder: '请选择类型' },
// 上传图片地址
{ prop: 'imageUrl', slotName: 'upload', label: '上传图片', type: 'upload' as const },
// 上下线状态
{ prop: 'onlineStatus', label: '上下线状态', type: 'switch' as const },
// 交易状态
{ prop: 'tradeStatus', label: '交易状态', type: 'switch' as const },
]

View File

@ -1,5 +1,5 @@
import { fileURLToPath, URL } from 'node:url' import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite' import { loadEnv, defineConfig, ConfigEnv, ViteConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools' import vueDevTools from 'vite-plugin-vue-devtools'
@ -11,40 +11,23 @@ import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig(({ mode }) => { export default defineConfig(({ mode }: ConfigEnv): ViteConfig => {
const isProduction = mode === 'production'
const env = loadEnv(mode, process.cwd())
return { return {
plugins: [ plugins: [
vue(), vue(),
vueDevTools(), // 生产环境移除 devTools 插件(减少产物体积)
//自动导入Vue/路由/Pinia等API无需手动import ...(!isProduction ? [vueDevTools()] : []),
AutoImport({ AutoImport({
// 适配Element Plus的自动导入 resolvers: [ElementPlusResolver()],
resolvers: [ imports: ['vue', 'vue-router', 'pinia', '@vueuse/core'],
ElementPlusResolver(),
],
// 指定要自动导入的库
imports: [
'vue', // 自动导入ref、reactive、watch等Vue API
'vue-router', // 自动导入useRoute、useRouter等路由API
'pinia', // 自动导入defineStore等Pinia API
'@vueuse/core', // 自动导入@vueuse的hooks
],
// 生成类型声明文件TypeScript必备解决类型提示问题
dts: 'src/auto-imports.d.ts', dts: 'src/auto-imports.d.ts',
// 自动生成eslint配置解决eslint报"未定义变量"的问题) eslintrc: { enabled: true },
eslintrc: {
enabled: true, // 开启后会生成 .eslintrc-auto-import.json
},
}), }),
// 2. 自动导入组件包括Element Plus和本地组件
Components({ Components({
// 自动解析Element Plus组件 resolvers: [ElementPlusResolver()],
resolvers: [
ElementPlusResolver(),
],
// 生成组件类型声明文件
dts: 'src/components.d.ts', dts: 'src/components.d.ts',
// 指定要自动导入的本地组件目录src/components下的组件无需手动注册
dirs: ['src/components'], dirs: ['src/components'],
}), }),
], ],
@ -54,30 +37,60 @@ export default defineConfig(({ mode }) => {
}, },
}, },
// 构建配置 // ====== 增强版生产构建配置 ======
build: { build: {
// 输出目录
outDir: 'dist', outDir: 'dist',
// 静态资源目录
assetsDir: 'assets', assetsDir: 'assets',
// 生产环境移除console和debugger
// 生产环境 sourcemap 配置(可选:隐藏源码但保留行映射)
sourcemap: isProduction ? 'hidden' : true,
// chunk 分割策略(优化加载性能)
rollupOptions: {
output: {
manualChunks: {
// 将第三方库单独分包
'element-plus': ['element-plus', '@element-plus/icons-vue'],
vendor: ['vue', 'vue-router', 'pinia'],
utils: ['axios', 'dayjs', 'crypto-js', '@vueuse/core'],
},
// 文件名带 hash缓存优化
chunkFileNames: 'assets/js/[name]-[hash].js',
entryFileNames: 'assets/js/[name]-[hash].js',
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
},
},
// 压缩配置
minify: 'esbuild', minify: 'esbuild',
esbuild: { esbuild: {
drop: mode === 'production' ? ['console', 'debugger'] : [], drop: isProduction ? ['console', 'debugger'] : [],
// 剔除 JS 中的所有注释(包括注释掉的代码) minifyIdentifiers: true,
minifyIdentifiers: true, // 压缩变量名 minifySyntax: true,
minifySyntax: true, // 压缩语法(含剔除普通注释) minifyWhitespace: true,
minifyWhitespace: true, // 剔除空格
}, },
// 可选:进一步压缩打包产物
// terserOptions: mode === 'production' ? { // 大文件警告阈值默认500KB可调整
// format: { chunkSizeWarningLimit: 1000,
// comments: false, // 彻底剔除所有注释
// },
// } : {},
}, },
// 生产环境不显示错误遮罩
define: {
__VUE_PROD_DEVTOOLS__: 'false',
},
server: { server: {
host: true, host: true,
port: 9999,
https: false,
open: true,
proxy: {
'/api': {
target: env.VITE_API_BASE_URL,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
}, },
} }
}) })