diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 36802cf..2a6f195 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,7 +2,8 @@ "permissions": { "allow": [ "Bash(dir:*)", - "Bash(npm run build:*)" + "Bash(npm run build:*)", + "Bash(findstr:*)" ], "deny": [], "ask": [] diff --git a/doc/image/异常错误页面/微信图片_20251129185716_267_2.png b/doc/image/异常错误页面/微信图片_20251129185716_267_2.png new file mode 100644 index 0000000..a326329 Binary files /dev/null and b/doc/image/异常错误页面/微信图片_20251129185716_267_2.png differ diff --git a/doc/image/异常错误页面/微信图片_20251130112538_268_2.png b/doc/image/异常错误页面/微信图片_20251130112538_268_2.png new file mode 100644 index 0000000..0c59729 Binary files /dev/null and b/doc/image/异常错误页面/微信图片_20251130112538_268_2.png differ diff --git a/doc/image/异常错误页面/微信图片_20251130112801_269_2.png b/doc/image/异常错误页面/微信图片_20251130112801_269_2.png new file mode 100644 index 0000000..aa4eea5 Binary files /dev/null and b/doc/image/异常错误页面/微信图片_20251130112801_269_2.png differ diff --git a/doc/image/异常错误页面/微信图片_20251130113116_270_2.png b/doc/image/异常错误页面/微信图片_20251130113116_270_2.png new file mode 100644 index 0000000..bba641b Binary files /dev/null and b/doc/image/异常错误页面/微信图片_20251130113116_270_2.png differ diff --git a/src/api/martial/banner.js b/src/api/martial/banner.js new file mode 100644 index 0000000..3bd381c --- /dev/null +++ b/src/api/martial/banner.js @@ -0,0 +1,92 @@ +import request from '@/axios'; + +// ==================== 轮播图管理接口 ==================== + +/** + * 轮播图分页查询 + * @param {Number} current - 当前页,默认1 + * @param {Number} size - 每页条数,默认10 + * @param {Object} params - 查询参数 + */ +export const getBannerList = (current, size, params) => { + return request({ + url: '/api/blade-martial/banner/list', + method: 'get', + params: { + current, + size, + ...params + } + }) +} + +/** + * 获取轮播图详情 + * @param {Number} id - 轮播图主键ID + */ +export const getBannerDetail = (id) => { + return request({ + url: '/api/blade-martial/banner/detail', + method: 'get', + params: { id } + }) +} + +/** + * 新增轮播图 + * @param {Object} data - 轮播图数据 + */ +export const addBanner = (data) => { + return request({ + url: '/api/blade-martial/banner/save', + method: 'post', + data + }) +} + +/** + * 修改轮播图 + * @param {Object} data - 轮播图数据 + */ +export const updateBanner = (data) => { + return request({ + url: '/api/blade-martial/banner/update', + method: 'post', + data + }) +} + +/** + * 删除轮播图 + * @param {String} ids - 轮播图ID,多个用逗号分隔 + */ +export const removeBanner = (ids) => { + return request({ + url: '/api/blade-martial/banner/remove', + method: 'post', + params: { ids } + }) +} + +/** + * 获取启用的轮播图列表(小程序端使用) + */ +export const getActiveBannerList = () => { + return request({ + url: '/api/blade-martial/banner/active-list', + method: 'get' + }) +} + +/** + * 修改轮播图状态 + * @param {Number} id - 轮播图ID + * @param {Number} status - 状态(0-禁用 1-启用) + */ +export const updateBannerStatus = (id, status) => { + return request({ + url: '/api/blade-martial/banner/update-status', + method: 'post', + params: { id, status } + }) +} diff --git a/src/api/martial/competition.js b/src/api/martial/competition.js index 50d6265..4953999 100644 --- a/src/api/martial/competition.js +++ b/src/api/martial/competition.js @@ -255,3 +255,89 @@ export const removeVenue = (ids) => { params: { ids } }) } + +// ==================== 赛事管理接口 ==================== + +/** + * 新增赛事 + * @param {Object} data - 赛事数据 + * @param {String} data.competitionName - 赛事名称 + * @param {String} data.organizer - 主办单位 + * @param {String} data.location - 地区 + * @param {String} data.venue - 详细地点 + * @param {String} data.registrationStartTime - 报名开始时间 + * @param {String} data.registrationEndTime - 报名结束时间 + * @param {String} data.competitionStartTime - 比赛开始时间 + * @param {String} data.competitionEndTime - 比赛结束时间 + * @param {String} data.introduction - 赛事简介 + * @param {Array} data.posterImages - 宣传图片 + * @param {String} data.contactPerson - 联系人 + * @param {String} data.contactPhone - 联系电话 + * @param {String} data.contactEmail - 联系邮箱 + * @param {String} data.rules - 竞赛规则 + * @param {String} data.requirements - 参赛要求 + * @param {String} data.awards - 奖项设置 + * @param {Array} data.regulationFiles - 规程文件 + * @param {Array} data.schedule - 活动日程 + */ +export const addCompetition = (data) => { + return request({ + url: '/api/blade-martial/competition/save', + method: 'post', + data + }) +} + +/** + * 赛事列表查询 + * @param {Number} current - 当前页 + * @param {Number} size - 每页条数 + * @param {Object} params - 查询参数 + */ +export const getCompetitionList = (current, size, params) => { + return request({ + url: '/api/blade-martial/competition/list', + method: 'get', + params: { + current, + size, + ...params + } + }) +} + +/** + * 获取赛事详情 + * @param {Number} id - 赛事ID + */ +export const getCompetitionDetail = (id) => { + return request({ + url: '/api/blade-martial/competition/detail', + method: 'get', + params: { id } + }) +} + +/** + * 修改赛事 + * @param {Object} data - 赛事数据 + */ +export const updateCompetition = (data) => { + return request({ + url: '/api/blade-martial/competition/update', + method: 'post', + data + }) +} + +/** + * 删除赛事 + * @param {String} ids - 赛事ID,多个用逗号分隔 + */ +export const removeCompetition = (ids) => { + return request({ + url: '/api/blade-martial/competition/remove', + method: 'post', + params: { ids } + }) +} diff --git a/src/api/martial/participant.js b/src/api/martial/participant.js new file mode 100644 index 0000000..016717b --- /dev/null +++ b/src/api/martial/participant.js @@ -0,0 +1,142 @@ +import request from '@/axios'; + +// ==================== 参赛选手管理接口 ==================== + +/** + * 参赛选手分页查询 + * @param {Number} competitionId - 赛事ID + * @param {Number} current - 当前页,默认1 + * @param {Number} size - 每页条数,默认10 + * @param {Object} params - 查询参数 + */ +export const getParticipantList = (competitionId, current, size, params = {}) => { + return request({ + url: '/api/blade-martial/participant/list', + method: 'get', + params: { + competitionId, + current, + size, + ...params + } + }) +} + +/** + * 获取参赛选手详情 + * @param {Number} id - 选手主键ID + */ +export const getParticipantDetail = (id) => { + return request({ + url: '/api/blade-martial/participant/detail', + method: 'get', + params: { id } + }) +} + +/** + * 新增参赛选手 + * @param {Object} data - 选手数据 + * @param {Number} data.competitionId - 赛事ID + * @param {String} data.playerName - 选手姓名 + * @param {Number} data.gender - 性别(1-男,2-女) + * @param {Number} data.age - 年龄 + * @param {String} data.contactPhone - 联系电话 + * @param {String} data.organization - 所属单位 + * @param {String} data.idCard - 身份证号 + * @param {String} data.projectName - 参赛项目 + * @param {String} data.category - 组别 + * @param {Number} data.orderNum - 出场顺序 + * @param {String} data.introduction - 选手简介 + * @param {Array} data.attachments - 附件列表 + * @param {String} data.remark - 备注 + */ +export const addParticipant = (data) => { + return request({ + url: '/api/blade-martial/participant/save', + method: 'post', + data + }) +} + +/** + * 修改参赛选手 + * @param {Object} data - 选手数据 + */ +export const updateParticipant = (data) => { + return request({ + url: '/api/blade-martial/participant/update', + method: 'post', + data + }) +} + +/** + * 删除参赛选手 + * @param {String} ids - 选手ID,多个用逗号分隔 + */ +export const removeParticipant = (ids) => { + return request({ + url: '/api/blade-martial/participant/remove', + method: 'post', + params: { ids } + }) +} + +/** + * 更新出场顺序 + * @param {Number} id - 选手ID + * @param {Number} orderNum - 出场顺序 + */ +export const updateOrder = (id, orderNum) => { + return request({ + url: '/api/blade-martial/participant/update-order', + method: 'post', + params: { id, orderNum } + }) +} + +/** + * 批量导入参赛选手 + * @param {Number} competitionId - 赛事ID + * @param {File} file - Excel文件 + */ +export const importParticipants = (competitionId, file) => { + const formData = new FormData() + formData.append('competitionId', competitionId) + formData.append('file', file) + + return request({ + url: '/api/blade-martial/participant/import', + method: 'post', + data: formData, + headers: { + 'Content-Type': 'multipart/form-data' + } + }) +} + +/** + * 导出参赛选手名单 + * @param {Number} competitionId - 赛事ID + */ +export const exportParticipants = (competitionId) => { + return request({ + url: '/api/blade-martial/participant/export', + method: 'get', + params: { competitionId }, + responseType: 'blob' + }) +} + +/** + * 批量更新出场顺序 + * @param {Array} data - 选手顺序数据 [{id: 1, orderNum: 1}, {id: 2, orderNum: 2}] + */ +export const batchUpdateOrder = (data) => { + return request({ + url: '/api/blade-martial/participant/batch-update-order', + method: 'post', + data + }) +} diff --git a/src/api/martial/referee.js b/src/api/martial/referee.js new file mode 100644 index 0000000..9431382 --- /dev/null +++ b/src/api/martial/referee.js @@ -0,0 +1,91 @@ +import request from '@/axios'; + +// ==================== 评委管理接口 ==================== + +/** + * 评委分页查询 + * @param {Number} current - 当前页,默认1 + * @param {Number} size - 每页条数,默认10 + * @param {Object} params - 查询参数 + * @param {String} params.keyword - 关键词搜索(姓名/手机号) + * @param {Number} params.refereeType - 裁判类型(1-主裁判,2-普通裁判) + */ +export const getRefereeList = (current, size, params) => { + return request({ + url: '/api/blade-martial/referee/list', + method: 'get', + params: { + current, + size, + ...params + } + }) +} + +/** + * 获取评委详情 + * @param {Number} id - 评委主键ID + */ +export const getRefereeDetail = (id) => { + return request({ + url: '/api/blade-martial/referee/detail', + method: 'get', + params: { id } + }) +} + +/** + * 新增评委 + * @param {Object} data - 评委数据 + * @param {String} data.name - 姓名 + * @param {Number} data.gender - 性别(1-男,2-女) + * @param {String} data.phone - 手机号 + * @param {String} data.idCard - 身份证号 + * @param {Number} data.refereeType - 裁判类型(1-主裁判,2-普通裁判) + * @param {String} data.level - 等级/职称 + * @param {String} data.specialty - 擅长项目 + * @param {String} data.remark - 备注 + */ +export const addReferee = (data) => { + return request({ + url: '/api/blade-martial/referee/save', + method: 'post', + data + }) +} + +/** + * 修改评委 + * @param {Object} data - 评委数据 + */ +export const updateReferee = (data) => { + return request({ + url: '/api/blade-martial/referee/update', + method: 'post', + data + }) +} + +/** + * 删除评委 + * @param {String} ids - 评委ID,多个用逗号分隔 + */ +export const removeReferee = (ids) => { + return request({ + url: '/api/blade-martial/referee/remove', + method: 'post', + params: { ids } + }) +} + +/** + * 获取可用评委列表(用于下拉选择) + * @param {Number} refereeType - 裁判类型(可选) + */ +export const getAvailableReferees = (refereeType) => { + return request({ + url: '/api/blade-martial/referee/available', + method: 'get', + params: { refereeType } + }) +} diff --git a/src/api/martial/score.js b/src/api/martial/score.js new file mode 100644 index 0000000..5d3993e --- /dev/null +++ b/src/api/martial/score.js @@ -0,0 +1,84 @@ +import request from '@/axios'; + +// ==================== 评分管理接口 ==================== + +/** + * 评分分页查询 + * @param {Number} current - 当前页,默认1 + * @param {Number} size - 每页条数,默认10 + * @param {Object} params - 查询参数 + */ +export const getScoreList = (current, size, params) => { + return request({ + url: '/api/blade-martial/score/list', + method: 'get', + params: { + current, + size, + ...params + } + }) +} + +/** + * 获取评分详情 + * @param {Number} id - 评分主键ID + */ +export const getScoreDetail = (id) => { + return request({ + url: '/api/blade-martial/score/detail', + method: 'get', + params: { id } + }) +} + +/** + * 获取选手的所有裁判评分 + * @param {Number} playerId - 选手ID + * @param {Number} projectId - 比赛项目ID + */ +export const getPlayerScores = (playerId, projectId) => { + return request({ + url: '/api/blade-martial/score/player-scores', + method: 'get', + params: { playerId, projectId } + }) +} + +/** + * 导出评分数据 + * @param {Object} params - 查询参数 + */ +export const exportScores = (params) => { + return request({ + url: '/api/blade-martial/score/export', + method: 'get', + params, + responseType: 'blob' + }) +} + +/** + * 获取场地列表 + * @param {Number} competitionId - 赛事ID + */ +export const getVenueList = (competitionId) => { + return request({ + url: '/api/blade-martial/venue/list', + method: 'get', + params: { competitionId } + }) +} + +/** + * 获取比赛项目列表 + * @param {Number} competitionId - 赛事ID + * @param {Number} venueId - 场地ID(可选) + */ +export const getProjectList = (competitionId, venueId) => { + return request({ + url: '/api/blade-martial/project/list', + method: 'get', + params: { competitionId, venueId } + }) +} diff --git a/src/page/login/index.vue b/src/page/login/index.vue index b817b6e..b8e2422 100644 --- a/src/page/login/index.vue +++ b/src/page/login/index.vue @@ -1,44 +1,58 @@ + - diff --git a/src/page/login/userlogin.vue b/src/page/login/userlogin.vue index d3b6ccf..e610ea2 100644 --- a/src/page/login/userlogin.vue +++ b/src/page/login/userlogin.vue @@ -412,4 +412,102 @@ export default { }; - + diff --git a/src/router/views/index.js b/src/router/views/index.js index 07c2953..13eb567 100644 --- a/src/router/views/index.js +++ b/src/router/views/index.js @@ -47,13 +47,39 @@ export default [ component: Layout, redirect: '/martial/order/list', children: [ + { + path: 'competition/list', + name: '赛事管理', + meta: { + keepAlive: false, + }, + component: () => import(/* webpackChunkName: "martial" */ '@/views/martial/competition/list.vue'), + }, + { + path: 'competition/create', + name: '赛事详情', + meta: { + keepAlive: false, + menu: false, + }, + component: () => import(/* webpackChunkName: "martial" */ '@/views/martial/competition/create.vue'), + }, { path: 'registration/detail', name: '报名详情', meta: { keepAlive: false, + menu: false, }, - component: () => import(/* webpackChunkName: "martial" */ '@/views/martial/registration/detail.vue'), + component: () => import(/* webpackChunkName: "martial" */ '@/views/martial/registration/index.vue'), + }, + { + path: 'order/list', + name: '订单管理', + meta: { + keepAlive: false, + }, + component: () => import(/* webpackChunkName: "martial" */ '@/views/martial/order/index.vue'), }, { path: 'schedule/list', @@ -61,7 +87,7 @@ export default [ meta: { keepAlive: false, }, - component: () => import(/* webpackChunkName: "martial" */ '@/views/martial/schedule/list.vue'), + component: () => import(/* webpackChunkName: "martial" */ '@/views/martial/schedule/index.vue'), }, { path: 'dispatch/list', @@ -69,7 +95,48 @@ export default [ meta: { keepAlive: false, }, - component: () => import(/* webpackChunkName: "martial" */ '@/views/martial/dispatch/list.vue'), + component: () => import(/* webpackChunkName: "martial" */ '@/views/martial/dispatch/index.vue'), + }, + { + path: 'banner/index', + name: '轮播图管理', + meta: { + keepAlive: true, + }, + component: () => import(/* webpackChunkName: "martial" */ '@/views/martial/banner/index.vue'), + }, + { + path: 'referee/list', + name: '评委管理', + meta: { + keepAlive: true, + }, + component: () => import(/* webpackChunkName: "martial" */ '@/views/martial/referee/index.vue'), + }, + { + path: 'score/index', + name: '评分管理', + meta: { + keepAlive: true, + }, + component: () => import(/* webpackChunkName: "martial" */ '@/views/martial/score/index.vue'), + }, + { + path: 'participant/list', + name: '参赛选手管理', + meta: { + keepAlive: false, + }, + component: () => import(/* webpackChunkName: "martial" */ '@/views/martial/participant/list.vue'), + }, + { + path: 'participant/manage', + name: '选手详情', + meta: { + keepAlive: false, + menu: false, + }, + component: () => import(/* webpackChunkName: "martial" */ '@/views/martial/participant/manage.vue'), }, ], }, diff --git a/src/styles/element-ui.scss b/src/styles/element-ui.scss index 337cc36..640c656 100644 --- a/src/styles/element-ui.scss +++ b/src/styles/element-ui.scss @@ -1,3 +1,14 @@ +// Element Plus 主题色覆盖 - 改为红色主题 +:root { + --el-color-primary: #dc2626; + --el-color-primary-light-3: #ef4444; + --el-color-primary-light-5: #f87171; + --el-color-primary-light-7: #fca5a5; + --el-color-primary-light-8: #fecaca; + --el-color-primary-light-9: #fee2e2; + --el-color-primary-dark-2: #b91c1c; +} + .el-card.is-always-shadow { box-shadow: none; border: none !important; diff --git a/src/styles/sidebar.scss b/src/styles/sidebar.scss index 89e0c0b..0b35b1d 100644 --- a/src/styles/sidebar.scss +++ b/src/styles/sidebar.scss @@ -6,10 +6,10 @@ position: relative; height: 100%; position: relative; - background-color: #031527; + background: linear-gradient(180deg, #1a1a1a 0%, #2d2d2d 100%); transition: width .2s; box-sizing: border-box; - box-shadow: 2px 0 6px rgba(0, 21, 41, .35); + box-shadow: 2px 0 6px rgba(0, 0, 0, .15); .el-scrollbar__wrap { overflow-x: hidden; @@ -60,11 +60,11 @@ left: 0; bottom: 0; width: 4px; - background: #409eff; + background: linear-gradient(180deg, #dc2626 0%, #991b1b 100%); position: absolute; } - background-color: rgba(0, 0, 0, .8); + background-color: rgba(220, 38, 38, .12); i, span { color: #fff; diff --git a/src/styles/tags.scss b/src/styles/tags.scss index 1082bf9..9f39f5b 100644 --- a/src/styles/tags.scss +++ b/src/styles/tags.scss @@ -32,8 +32,8 @@ color: #ccc; &.is-active { - color: #409EFF; - border-bottom: 3px solid #409EFF; + color: #dc2626; + border-bottom: 3px solid #dc2626; } } @@ -85,7 +85,7 @@ } &:hover { - background-color: #409EFF; + background-color: #dc2626; color: #fff; } } diff --git a/src/styles/top.scss b/src/styles/top.scss index 5481f33..2dc9eb3 100644 --- a/src/styles/top.scss +++ b/src/styles/top.scss @@ -116,11 +116,11 @@ .avue-logo { height: $top_height; line-height: $top_height; - background-color: #031527; + background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%); font-size: 20px; overflow: hidden; box-sizing: border-box; - box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.15); + box-shadow: 0 2px 4px 0 rgba(220, 38, 38, 0.2); color: #fff; &_title { diff --git a/src/views/martial/banner/index.vue b/src/views/martial/banner/index.vue new file mode 100644 index 0000000..ef6105e --- /dev/null +++ b/src/views/martial/banner/index.vue @@ -0,0 +1,434 @@ + + + + + diff --git a/src/views/martial/competition/create.vue b/src/views/martial/competition/create.vue new file mode 100644 index 0000000..8d5dce6 --- /dev/null +++ b/src/views/martial/competition/create.vue @@ -0,0 +1,513 @@ + + + + + diff --git a/src/views/martial/competition/index.vue b/src/views/martial/competition/index.vue new file mode 100644 index 0000000..dcfc214 --- /dev/null +++ b/src/views/martial/competition/index.vue @@ -0,0 +1,497 @@ + + + + + diff --git a/src/views/martial/competition/list.vue b/src/views/martial/competition/list.vue new file mode 100644 index 0000000..5526df3 --- /dev/null +++ b/src/views/martial/competition/list.vue @@ -0,0 +1,309 @@ + + + + + diff --git a/src/views/martial/dispatch/list.vue b/src/views/martial/dispatch/index.vue similarity index 100% rename from src/views/martial/dispatch/list.vue rename to src/views/martial/dispatch/index.vue diff --git a/src/views/martial/order/list.vue b/src/views/martial/order/index.vue similarity index 99% rename from src/views/martial/order/list.vue rename to src/views/martial/order/index.vue index dfaaa5a..32b4e06 100644 --- a/src/views/martial/order/list.vue +++ b/src/views/martial/order/index.vue @@ -265,6 +265,9 @@ export default { background: #fff; .page-header { + display: flex; + justify-content: space-between; + align-items: center; margin-bottom: 15px; .page-title { diff --git a/src/views/martial/participant/index.vue b/src/views/martial/participant/index.vue new file mode 100644 index 0000000..29a2c9c --- /dev/null +++ b/src/views/martial/participant/index.vue @@ -0,0 +1,536 @@ + + + + + diff --git a/src/views/martial/participant/list.vue b/src/views/martial/participant/list.vue new file mode 100644 index 0000000..6c3e3c4 --- /dev/null +++ b/src/views/martial/participant/list.vue @@ -0,0 +1,372 @@ + + + + + diff --git a/src/views/martial/participant/manage.vue b/src/views/martial/participant/manage.vue new file mode 100644 index 0000000..818772b --- /dev/null +++ b/src/views/martial/participant/manage.vue @@ -0,0 +1,412 @@ + + + + + diff --git a/src/views/martial/referee/index.vue b/src/views/martial/referee/index.vue new file mode 100644 index 0000000..9f8ac5f --- /dev/null +++ b/src/views/martial/referee/index.vue @@ -0,0 +1,482 @@ + + + + + diff --git a/src/views/martial/registration/detail.vue b/src/views/martial/registration/index.vue similarity index 99% rename from src/views/martial/registration/detail.vue rename to src/views/martial/registration/index.vue index 3621a64..29f655e 100644 --- a/src/views/martial/registration/detail.vue +++ b/src/views/martial/registration/index.vue @@ -181,7 +181,6 @@ export default { { schoolUnit: '清河小学', category: '集体', - hint: '剩余功能在提现上,显示栏标签', individual: 1, dual: 1, team1101: 1, diff --git a/src/views/martial/schedule/list.vue b/src/views/martial/schedule/index.vue similarity index 100% rename from src/views/martial/schedule/list.vue rename to src/views/martial/schedule/index.vue diff --git a/src/views/martial/score/index.vue b/src/views/martial/score/index.vue new file mode 100644 index 0000000..0804103 --- /dev/null +++ b/src/views/martial/score/index.vue @@ -0,0 +1,507 @@ + + + + + diff --git a/src/views/wel/index.vue b/src/views/wel/index.vue index dae11d4..ce3186c 100644 --- a/src/views/wel/index.vue +++ b/src/views/wel/index.vue @@ -1,26 +1,621 @@ \ No newline at end of file +.welcome-banner { + position: relative; + height: 280px; + background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%); + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; +} + +.banner-bg { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + + &::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: + radial-gradient(circle at 30% 50%, rgba(255, 255, 255, 0.1) 0%, transparent 50%), + radial-gradient(circle at 70% 50%, rgba(0, 0, 0, 0.1) 0%, transparent 50%); + animation: bgRotate 30s linear infinite; + } +} + +@keyframes bgRotate { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.bg-pattern { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-image: + repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.03) 0px, rgba(255, 255, 255, 0.03) 1px, transparent 1px, transparent 60px), + repeating-linear-gradient(0deg, rgba(255, 255, 255, 0.03) 0px, rgba(255, 255, 255, 0.03) 1px, transparent 1px, transparent 60px); +} + +.banner-content { + position: relative; + z-index: 2; + text-align: center; + color: #fff; +} + +.logo-wrapper { + margin-bottom: 20px; +} + +.logo-icon { + width: 80px; + height: 80px; + margin: 0 auto; + background: rgba(255, 255, 255, 0.15); + border: 3px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 40px; + font-weight: 900; + color: #fff; + backdrop-filter: blur(10px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2); + animation: logoFloat 3s ease-in-out infinite; +} + +@keyframes logoFloat { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-8px); } +} + +.banner-title { + font-size: 42px; + font-weight: 700; + margin: 0 0 12px 0; + letter-spacing: 6px; + text-shadow: 0 2px 15px rgba(0, 0, 0, 0.3); +} + +.banner-subtitle { + font-size: 16px; + margin: 0 0 20px 0; + opacity: 0.95; + letter-spacing: 3px; +} + +.banner-time { + display: inline-block; + font-size: 14px; + background: rgba(255, 255, 255, 0.15); + padding: 10px 24px; + border-radius: 24px; + backdrop-filter: blur(5px); + + i { + margin-right: 8px; + } +} + +.main-content { + max-width: 1400px; + margin: 0 auto; + padding: 40px 20px; +} + +.section { + margin-bottom: 50px; +} + +.section-header { + display: flex; + align-items: center; + justify-content: center; + gap: 24px; + margin-bottom: 32px; +} + +.header-line { + flex: 1; + height: 2px; + background: linear-gradient(90deg, transparent, #dc2626, transparent); + max-width: 150px; +} + +.section-title { + margin: 0; + font-size: 26px; + font-weight: 700; + color: #1a1a1a; + white-space: nowrap; + position: relative; + padding: 0 16px; + + &::before, + &::after { + content: ''; + position: absolute; + top: 50%; + transform: translateY(-50%); + width: 6px; + height: 6px; + background: #dc2626; + border-radius: 50%; + } + + &::before { + left: 0; + } + + &::after { + right: 0; + } +} + +.quick-access { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 24px; +} + +.access-card { + position: relative; + background: #fff; + border-radius: 12px; + padding: 28px 24px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); + display: flex; + align-items: center; + gap: 20px; + cursor: pointer; + transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); + overflow: hidden; + + &::before { + content: ''; + position: absolute; + left: 0; + top: 0; + width: 4px; + height: 100%; + background: linear-gradient(180deg, #dc2626 0%, #991b1b 100%); + transform: scaleY(0); + transition: transform 0.3s ease; + } + + .card-bg { + position: absolute; + right: -20px; + top: -20px; + width: 100px; + height: 100px; + background: radial-gradient(circle, rgba(220, 38, 38, 0.05) 0%, transparent 70%); + border-radius: 50%; + transition: all 0.4s ease; + } + + &:hover { + transform: translateY(-8px); + box-shadow: 0 12px 32px rgba(220, 38, 38, 0.2); + + &::before { + transform: scaleY(1); + } + + .card-bg { + transform: scale(1.5); + opacity: 0.8; + } + + .card-icon { + background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%); + color: #fff; + transform: scale(1.1) rotate(5deg); + } + + .card-arrow { + transform: translateX(6px); + opacity: 1; + } + } + + .card-icon { + position: relative; + z-index: 1; + width: 56px; + height: 56px; + border-radius: 12px; + background: #fef2f2; + display: flex; + align-items: center; + justify-content: center; + font-size: 26px; + color: #dc2626; + transition: all 0.3s ease; + flex-shrink: 0; + } + + .card-info { + position: relative; + z-index: 1; + flex: 1; + + h3 { + margin: 0 0 6px 0; + font-size: 17px; + font-weight: 600; + color: #1a1a1a; + } + + p { + margin: 0; + font-size: 13px; + color: #666; + } + } + + .card-arrow { + position: relative; + z-index: 1; + font-size: 20px; + color: #dc2626; + opacity: 0.5; + transition: all 0.3s ease; + } +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: 24px; +} + +.stat-card { + background: #fff; + border-radius: 12px; + padding: 28px 24px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); + display: flex; + align-items: center; + gap: 20px; + transition: all 0.3s ease; + + &:hover { + transform: translateY(-6px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); + + .stat-icon { + transform: scale(1.1) rotate(-5deg); + } + } + + .stat-icon { + width: 64px; + height: 64px; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + font-size: 30px; + color: #fff; + transition: all 0.3s ease; + flex-shrink: 0; + + &.red { + background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%); + } + + &.orange { + background: linear-gradient(135deg, #f97316 0%, #ea580c 100%); + } + + &.blue { + background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); + } + + &.green { + background: linear-gradient(135deg, #10b981 0%, #059669 100%); + } + } + + .stat-info { + flex: 1; + position: relative; + + .stat-label { + font-size: 13px; + color: #666; + margin-bottom: 8px; + } + + .stat-value { + font-size: 32px; + font-weight: 700; + color: #1a1a1a; + line-height: 1; + } + + .stat-trend { + position: absolute; + top: 0; + right: 0; + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: #999; + + &.up { + color: #10b981; + } + + i { + font-size: 14px; + } + } + } +} + +@media (max-width: 768px) { + .banner-title { + font-size: 32px; + } + + .quick-access { + grid-template-columns: 1fr; + } + + .stats-grid { + grid-template-columns: repeat(2, 1fr); + } +} + diff --git a/vite.config.js b/vite.config.js index 87e82db..03ebad9 100644 --- a/vite.config.js +++ b/vite.config.js @@ -17,7 +17,7 @@ export default ({ mode, command }) => { port: 2888, proxy: { '/api': { - target: 'http://localhost:82', + target: 'http://localhost:8123', // target: 'http://120.197.149.12:8480', // target: 'https://2df1-223-74-180-188.ngrok-free.app ', //target: 'https://saber3.bladex.cn/api',