diff --git a/.env.development b/.env.development index 81a54878..f1912101 100644 --- a/.env.development +++ b/.env.development @@ -2,33 +2,32 @@ ENV = 'development' # 'development' -#VITE_APP_BASE_API_Url = 'https://aits.jlbank.com.cn:7001' +# VITE_APP_BASE_API_Url = 'https://aits.jlbank.com.cn:7001' VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001' -# VITE_APP_BASE_API_Url = ' http://25.18.122.66:9786' -#sit +# VITE_APP_BASE_API_Url = 'http://192.168.247.200' +# VITE_APP_BASE_API_Url = 'http://aitscdn.jlbank.com.cn:7001' +# VITE_APP_BASE_API_Url = 'http://192.168.108.129' +# dev +#VITE_APP_BASE_API_Url = 'http://25.18.122.65:7001' +# sit # VITE_APP_BASE_API_Url = 'http://25.18.122.91:9786' # UAT # VITE_APP_BASE_API_Url = 'http://25.18.122.78:9786' # DEV - # app入口 -# VITE_APP_BASE_API_Url = 'http://25.18.122.65:7001' + # VITE_APP_BASE_API_Url = 'http://25.16.122.91:9786' # VITE_APP_BASE_API_Url = 'http://25.18.122.66:9786' # h5专用地址x2 -VITE_APP_BASE_H5_API_Url = 'http://25.18.122.65:7001' +# VITE_APP_BASE_H5_API_Url = 'http://25.18.122.65:7001' # VITE_APP_BASE_H5_API_Url = 'http://25.18.122.91:9786' # VITE_APP_BASE_H5_API_Url = 'http://25.18.122.78:9786' - -# VITE_APP_BASE_H5_API_Url_TRAAPP = 'http://25.64.16.130:9601' -# VITE_APP_BASE_H5_API_Url_TRASTUDY = 'http://25.64.16.130:9602' - -# VITE_APP_BASE_H5_API_Url_TRAASK = 'http://25.64.16.144:9605' -VITE_APP_BASE_H5_API_Url_TRAEXAM = 'http://25.64.16.139:9604' - - +#VITE_APP_BASE_H5_API_Url_TRAAPP = 'http://192.168.247.200' +# VITE_APP_BASE_H5_API_Url_TRASTUDY = 'http://25.64.32.154:9602' +#VITE_APP_BASE_H5_API_Url_TRAEXAM = 'http://192.168.247.200' +# VITE_APP_BASE_H5_API_Url_TRAASK = 'http://25.64.32.154:9605' diff --git a/.env.development.rd b/.env.development.rd new file mode 100644 index 00000000..ca382f2c --- /dev/null +++ b/.env.development.rd @@ -0,0 +1,8 @@ +# 任东专用开发环境 +ENV = 'development.rd' +# 'development.rd' + +#VITE_APP_BASE_API_Url = 'http://25.18.122.65:7001' +VITE_APP_BASE_API_Url = 'http://25.18.122.91:9786' + +VITE_APP_BASE_H5_API_Url_TRAEXAM = 'http://25.64.32.154:9604' diff --git a/.env.development.ty b/.env.development.ty new file mode 100644 index 00000000..15d511f0 --- /dev/null +++ b/.env.development.ty @@ -0,0 +1,9 @@ +# 田岩开发环境 +ENV = 'development.ty' +# 'development.ty' + +VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001' + +VITE_APP_BASE_H5_API_Url_TRAEXAM = 'http://192.168.108.129' + + diff --git a/.env.development.yh b/.env.development.yh new file mode 100644 index 00000000..55b67b98 --- /dev/null +++ b/.env.development.yh @@ -0,0 +1,10 @@ +# 杨航开发环境 +ENV = 'development.yh' +# 'development.yh' + +VITE_APP_BASE_API_Url = 'http://25.18.122.65:7001' + +VITE_APP_BASE_H5_API_Url_TRAEXAM = 'http://25.64.32.154:9604' + + + diff --git a/logo/Thumbs.db b/logo/Thumbs.db new file mode 100644 index 00000000..4aac3f54 Binary files /dev/null and b/logo/Thumbs.db differ diff --git a/package.json b/package.json index cc24343e..71972694 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,9 @@ "description": "", "scripts": { "dev:h5": "uni", + "dev:rd": "uni -p h5 --mode development.rd", + "dev:yh": "uni -p h5 --mode development.yh", + "dev:ty": "uni -p h5 --mode development.ty", "dev:h5:sit": "uni -p h5 --mode sit", "dev:h5:uat": "uni -p h5 --mode uat", "dev:h5:production": "uni -p h5 --mode production", diff --git a/src/App.vue b/src/App.vue index b5f9ab78..141b9280 100644 --- a/src/App.vue +++ b/src/App.vue @@ -9,23 +9,7 @@ }); }, onShow: function () { - // function getBrowserInfo() { - // const userAgent = navigator.userAgent; - // let browserName = '未知浏览器'; - // let version = '未知版本'; - // // 检测 Chrome - // if (userAgent.indexOf('Chrome') > -1 && userAgent.indexOf('Edg') === -1) { - // browserName = 'Chrome'; - // version = userAgent.match(/Chrome\/(\d+\.\d+)/)[1]; - // } - - // return { - // browser: browserName, - // version: version, - // userAgent: userAgent - // }; - // } }, onHide: function () { } diff --git a/src/api/competition.js b/src/api/competition.js new file mode 100644 index 00000000..131a92ab --- /dev/null +++ b/src/api/competition.js @@ -0,0 +1,85 @@ +import request from '@/api/request' + +const module_url = '/traexam' + +// 查询竞赛列表 +export const queryTraCompetitionInfoPaging = (data) => { + return request({ + url: module_url + '/traCompetitionInfo/queryTraCompetitionInfoPaging', + method: 'post', + data + }); +}; + +// 查询试卷 +export const queryTestPapers = (data) => { + return request({ + url: module_url + '/traCompetitionInfo/queryTestPapers', + method: 'post', + data + }); +}; + +// 查询PK人员+试题 +export const queryTraCompetitionInfoByPkUser = (data) => { + return request({ + url: module_url + '/traCompetitionInfo/queryTraCompetitionInfoByPkUser', + method: 'post', + data + }); +}; + +// 循环获取简答题结果 +export const queryCompetitionPracticeAnswerResult = (data) => { + return request({ + url: module_url + '/traCompetitionInfo/queryCompetitionPracticeAnswerResult', + method: 'post', + data + }); +}; + +// 答题结束 +export const competitionAnswerEnd = (data) => { + return request({ + url: module_url + '/traCompetitionInfo/competitionAnswerEnd', + method: 'post', + data, + suppressErrors: true // 屏蔽报错信息 + }); +}; + +// 竞赛答题 +export const competitionAnswer = (data) => { + return request({ + url: module_url + '/traCompetitionInfo/competitionAnswer', + method: 'post', + data + }); +}; + +// 查询答题结果 +export const queryCompetitionAnswerResult = (data) => { + return request({ + url: module_url + '/traCompetitionInfo/queryCompetitionAnswerResult', + method: 'post', + data + }); +}; + +// 竞赛排行榜 +export const queryTraCompetitionRankingList = (data) => { + return request({ + url: module_url + '/traCompetitionInfo/queryTraCompetitionRankingList', + method: 'post', + data + }); +}; + +// 全行的机构数据 +export const queryOrgTree = (data) => { + return request({ + url: module_url + '/traCompetitionInfo/queryOrgTree', + method: 'post', + data + }); +}; diff --git a/src/api/login.js b/src/api/login.js index fe1660ef..42ccd781 100644 --- a/src/api/login.js +++ b/src/api/login.js @@ -4,6 +4,10 @@ import { queryCurrentValidMascotInfo } from "@/api/mascot.js" +import { + queryCurrentStatus +} from '@/api/pointsAndRank.js'; + import { setToken, setUserInfo, @@ -24,15 +28,26 @@ export const useAccountLoginApp = (data) => request({ }) => { if (rtnCode === '0000') { setToken(body) - const res = await Promise.all([queryCurrentUser(), queryCurrentValidMascotInfo()]) + const res = await Promise.all([queryCurrentUser(), queryCurrentValidMascotInfo() + // queryCurrentStatus() + ]) const userInfo = getUserInfo() const mascotInfo = res[1] setUserInfo({ ...userInfo, 'mascotInfo': mascotInfo }) + // 积分状态 + // const pointsStatus = res[2].body + + common.setValue('points_data', { + badgeNum: pointsStatus['badgeNum'] ?? 0, // 徽章数 + currentPoints: pointsStatus['currentPoints'] ?? 0, // 当前积分 + currentRankAddr: pointsStatus['ossAddr'] ?? '', // 当前段位图片 + currentRankName: pointsStatus['currentRankName']?? '未知', // 当前段位名称 + }) return { - mascotState:!mascotInfo.mascotId + mascotState: !mascotInfo.mascotId } } return Promise.reject({ @@ -40,22 +55,6 @@ export const useAccountLoginApp = (data) => request({ body }) }).catch(res => { - /** - error: "Service Unavailable" - path: "/traapp/access/userLogin" - requestId: "74d60800-104" - status: 503 - timestamp: "2025-06-24T00:53:35.293+00:00" - */ - /** - body: null - channelNo: "FF6944B69A99745C009F4D0B3C3B22B8E81B22F587D68455E354BABBABCCB6246C54ABD28F3C55AAD34224A16CE7E24E" - head: null - message: "系统异常" - rtnCode: "9999" - rtnTime: "2025-06-24 08:54:52" - */ - return Promise.reject(res) }) @@ -72,7 +71,7 @@ export const queryCurrentUser = (data) => request({ method: 'post', data }).then(res => { - + // deptId: "" // deptName: null // loginName: "ADMIN" // todo id @@ -92,107 +91,109 @@ export const queryCurrentUser = (data) => request({ // #ifdef APP-PLUS // 更新用户信息中的头像路径 const updateUserAvatar = (imagePath) => { - const _userInfo = getUserInfo() ?? {} - setUserInfo({ - ..._userInfo, - ...res.body, - 'imagePath': imagePath - }) + const _userInfo = getUserInfo() ?? {} + setUserInfo({ + ..._userInfo, + ...res.body, + 'imagePath': imagePath + }) } - + // 确保目录存在 const ensureDirectoryExists = (dirPath) => { - return new Promise((resolve) => { - plus.io.requestFileSystem(plus.io.PRIVATE_DOC, (fs) => { - fs.root.getDirectory(dirPath, { create: true }, resolve, resolve) - }) - }) + return new Promise((resolve) => { + plus.io.requestFileSystem(plus.io.PRIVATE_DOC, (fs) => { + fs.root.getDirectory(dirPath, { + create: true + }, resolve, resolve) + }) + }) } - + // 生成唯一文件名 const generateUniqueFilename = (pathName) => { - if (!pathName || typeof pathName !== 'string') { - throw new Error('无效的路径'); - } - - // 直接使用原始文件名(带扩展名) - const filename = pathName.split('/').pop(); - - if (!filename) { - throw new Error('无法从路径中提取文件名'); - } - - // 确保文件扩展名为.png (可选,根据实际需求) - const ext = filename.split('.').pop().toLowerCase(); - if (ext === 'png' || ext === 'jpg' || ext === 'jpeg' || ext === 'webp') { - return filename; - } - - // 如果没有有效扩展名,添加.png - return `${filename}.png`; + if (!pathName || typeof pathName !== 'string') { + throw new Error('无效的路径'); + } + + // 直接使用原始文件名(带扩展名) + const filename = pathName.split('/').pop(); + + if (!filename) { + throw new Error('无法从路径中提取文件名'); + } + + // 确保文件扩展名为.png (可选,根据实际需求) + const ext = filename.split('.').pop().toLowerCase(); + if (ext === 'png' || ext === 'jpg' || ext === 'jpeg' || ext === 'webp') { + return filename; + } + + // 如果没有有效扩展名,添加.png + return `${filename}.png`; }; - + // 下载并保存头像 const downloadAndSaveAvatar = async (fileUrl, localFilePath) => { - try { - await ensureDirectoryExists('avatar') - - return new Promise((resolve, reject) => { - console.log('开始下载头像:', fileUrl) - const dtask = plus.downloader.createDownload(fileUrl, { - filename: localFilePath - }, (d, status) => { - if (status === 200) { - console.log("保存路径:", d.filename) - resolve(d.filename) - } else { - console.error("文件下载失败:", status) - reject(new Error(`下载失败,状态码: ${status}`)) - } - }) - - dtask.start() - }) - } catch (error) { - console.error('创建目录失败:', error) - throw error - } + try { + await ensureDirectoryExists('avatar') + + return new Promise((resolve, reject) => { + console.log('开始下载头像:', fileUrl) + const dtask = plus.downloader.createDownload(fileUrl, { + filename: localFilePath + }, (d, status) => { + if (status === 200) { + console.log("保存路径:", d.filename) + resolve(d.filename) + } else { + console.error("文件下载失败:", status) + reject(new Error(`下载失败,状态码: ${status}`)) + } + }) + + dtask.start() + }) + } catch (error) { + console.error('创建目录失败:', error) + throw error + } } - + // 检查文件是否存在 const checkFileExists = (filePath) => { - return new Promise((resolve) => { - uni.getFileInfo({ - filePath, - success: () => resolve(true), - fail: () => resolve(false) - }) - }) + return new Promise((resolve) => { + uni.getFileInfo({ + filePath, + success: () => resolve(true), + fail: () => resolve(false) + }) + }) } - + // 主逻辑 (async () => { - try { - const pathName = res.body.imageAddr - const fileUrl = base_url + pathName - - // 生成保存路径 - const filename = generateUniqueFilename(pathName) - const localFilePath = '_doc/avatar/' + filename - - // 检查文件是否存在 - const exists = await checkFileExists(localFilePath) - - if (exists) { - // console.log('头像已存在,无需下载') - updateUserAvatar(localFilePath) - } else { - const savedPath = await downloadAndSaveAvatar(fileUrl, localFilePath) - updateUserAvatar(savedPath) - } - } catch (error) { - console.error('头像预加载过程发生错误:', error) - } + try { + const pathName = res.body.imageAddr + const fileUrl = base_url + pathName + + // 生成保存路径 + const filename = generateUniqueFilename(pathName) + const localFilePath = '_doc/avatar/' + filename + + // 检查文件是否存在 + const exists = await checkFileExists(localFilePath) + + if (exists) { + // console.log('头像已存在,无需下载') + updateUserAvatar(localFilePath) + } else { + const savedPath = await downloadAndSaveAvatar(fileUrl, localFilePath) + updateUserAvatar(savedPath) + } + } catch (error) { + console.error('头像预加载过程发生错误:', error) + } })() // #endif @@ -230,4 +231,5 @@ export const loginOut = (data) => request({ setUserInfo(null) common.setValue('me_statistics_data', null) // 我的页统计数据缓存 common.setValue('index_statistics_data', null) // 首页统计数据缓存 + common.setValue('points_data', null) // 首页统计数据缓存 }) \ No newline at end of file diff --git a/src/api/pointsAndRank.js b/src/api/pointsAndRank.js new file mode 100644 index 00000000..bab3c9dc --- /dev/null +++ b/src/api/pointsAndRank.js @@ -0,0 +1,123 @@ +import request from '@/api/request' +const base_url = '/traapp' + +/** + * 查询签到 + * userId 学员ID String + * checkInToday 今日是否签到 String + * days 连续签到天数 int + */ +export const queryCheckIn = (data) => { + return request({ + url: base_url + '/traPointsInfo/queryCheckIn', + method: 'post', + data + }); +}; + +/** + * 点击签到 + */ +export const addPointsByCheckIn = (data) => { + return request({ + url: base_url + '/traPointsInfo/addPointsByCheckIn', + method: 'post', + data + }); +}; + + +/** + * 每次完成任务获得弹窗奖励 + * 入参: + taskId 必输项:true 类型:String + type 必输项:true 类型:String + 出参: + userId 学员ID String + badgeName 徽章名称 String + badgeImg 徽章图片 String + num 次数 int + name 描述 String + pointsBadgeName 积分徽章名称 String + pointsBadgeImg 徽章图片 String + currentPoints 当前积分 int + changePoints 本次获得积分 int + */ +export const addPointsByTask = (data) => { + return request({ + url: base_url + '/traPointsInfo/addPointsByTask', + method: 'post', + data + }); +}; + +/** + * 查询积分明细 + * 出参: + detailId 明细ID String + userId 学员ID String + mattr 积分来源事项 String + mattrDesc 积分来源描述 String + changeValue 积分变化值 String + currentPoints 当前积分 int + countPoints 累计积分 int + ctTime 创建时间 String + */ +export const queryPointsDetail = (data) => { + return request({ + url: base_url + '/traPointsInfo/queryPointsDetail', + method: 'post', + data + }); +}; + + +/** + * 查询段位明细 + * 出参: + rankId 段位ID String + rankName 段位名称 String + currentRankName 当前段位名称 String + ossAddr 段位图片 String + pointsStart 段位开始区间 int + pointsEnd 段位结束区间 int + countPoints 累计积分 int + */ +export const queryRankDetail = (data) => { + return request({ + url: base_url + '/traPointsInfo/queryRankDetail', + method: 'post', + data + }); +}; + + +/** + * 查询当前状态 + * 出参: + userId 学员ID String + orgId 机构ID String + countPoints 累计积分 int + currentPoints 当前积分 int + currentRankName 当前段位 String + badgeNum 徽章数 int + */ +export const queryCurrentStatus = (data) => { + return request({ + url: base_url + '/traPointsInfo/queryCurrentStatus', + method: 'post', + data + }); +}; + + +/** + * 徽章墙 + */ +export const queryRankWall = (data) => { + return request({ + url: base_url + '/traPointsInfo/queryRankWall', + method: 'post', + data + }); +}; diff --git a/src/api/request.js b/src/api/request.js index 50e865f0..e9a30160 100644 --- a/src/api/request.js +++ b/src/api/request.js @@ -4,9 +4,27 @@ import { import common from '@/common/common.js' const ENV = import.meta.env +// function getBrowserInfo() { +// const userAgent = navigator.userAgent; +// let browserName = '未知浏览器'; +// let version = '未知版本'; + +// // 检测 Chrome +// if (userAgent.indexOf('Chrome') > -1 && userAgent.indexOf('Edg') === -1) { +// browserName = 'Chrome'; +// version = userAgent.match(/Chrome\/(\d+\.\d+)/)[1]; +// } + +// return { +// browser: browserName, +// version: version, +// userAgent: userAgent +// }; +// } + export const get_base_url = (url = '') => { - if (ENV.MODE === 'development') { // 开发环境 + if (['development', 'development.rd', 'development.yh', 'development.ty'].includes(ENV.MODE)) { // 开发环境 // #ifdef H5 if (url) { let _str = (url.split('/')[0] || url.split('/')[1])?.toUpperCase() @@ -20,7 +38,7 @@ export const get_base_url = (url = '') => { return ENV.VITE_APP_BASE_API_Url // #endif } else { // 其他环境,包括生产环境,sit环境,uat环境 - + return ENV.VITE_APP_BASE_API_Url // return ENV.VITE_APP_BASE_API_Url diff --git a/src/api/user.js b/src/api/user.js index 87aa7224..b4a180b1 100644 --- a/src/api/user.js +++ b/src/api/user.js @@ -36,4 +36,22 @@ export const updateDfSysUserExtandInfoGender = (data) => { data }); }; - \ No newline at end of file + +// 设置匿名 是Y 否N +export const updateTraUserAnony = (data) => { + return request({ + url: '/traapp/traUserAnony/updateTraUserAnony', + method: 'post', + data + }); +}; + +// 查询是否匿名 是Y 否N +export const queryTraUserAnonyByUserId = (data) => { + return request({ + url: '/traapp/traUserAnony/queryTraUserAnonyByUserId', + method: 'post', + data + }); +}; + diff --git a/src/common/common.js b/src/common/common.js index 3e2f2054..037c20a2 100644 --- a/src/common/common.js +++ b/src/common/common.js @@ -233,8 +233,8 @@ function setValue(key, value) { uni.setStorageSync(key, value); } -function getValue(key) { - return uni.getStorageSync(key) +function getValue(key, defaultValue=null) { + return uni.getStorageSync(key) ?? defaultValue } function isLogin() { diff --git a/src/common/imgSvg.ts b/src/common/imgSvg.ts index 4dbf34f4..ea02103c 100644 --- a/src/common/imgSvg.ts +++ b/src/common/imgSvg.ts @@ -5,7 +5,7 @@ export const optionSuccessIcon = (color : string) : string => { return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`; }; -export const optionErrorIcon = (color: string='#F5212D') : string => { +export const optionErrorIcon = (color : string = '#F5212D') : string => { const svgXml = ` `.trim(); @@ -19,7 +19,7 @@ export const examinationSheetIcon = () : string => { return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`; }; -export const markIcon = (color: string='#ABABAB') : string => { +export const markIcon = (color : string = '#ABABAB') : string => { const svgXml = ` `.trim(); @@ -34,7 +34,7 @@ export const feedbackIcon = () : string => { }; // 通过考试 -export const passTheExamIcon = (color: string) : string => { +export const passTheExamIcon = (color : string) : string => { const svgXml = ` `.trim(); @@ -42,7 +42,7 @@ export const passTheExamIcon = (color: string) : string => { }; // 完成练习 -export const completeTheExercisesIcon = (color: string) : string => { +export const completeTheExercisesIcon = (color : string) : string => { const svgXml = ` `.trim(); @@ -50,7 +50,7 @@ export const completeTheExercisesIcon = (color: string) : string => { }; // 锁定 -export const lockIcon = (color: string) : string => { +export const lockIcon = (color : string) : string => { const svgXml = ` `.trim(); @@ -66,3 +66,53 @@ export const practiceExitIcon = () : string => { return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`; }; + +// Pk页规则图标 +export const pkRules = () : string => { + const svgXml = ` + +`.trim(); + return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`; +}; + +// Pk页排行榜图标 +export const pkRankingList = () : string => { + const svgXml = ` + +`.trim(); + return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`; +}; + +// Pk页匿名关 +export const pkAnonymousClose = () : string => { + const svgXml = ` + +`.trim(); + return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`; +}; + +// Pk页匿名开 +export const pkAnonymousOpen = () : string => { + const svgXml = ` + +`.trim(); + return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`; +}; + + +// 重置页图标 +export const resetIcon = () : string => { + const svgXml = ` + +`.trim(); + return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`; +}; + + +// pk页切换图标 +export const switchIcon = () : string => { + const svgXml = ` + +`.trim(); + return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`; +}; diff --git a/src/components/cached-avatar/cached-avatar.vue b/src/components/cached-avatar/cached-avatar.vue new file mode 100644 index 00000000..ff1a20b6 --- /dev/null +++ b/src/components/cached-avatar/cached-avatar.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/src/components/examination/es-analysis.vue b/src/components/examination/es-analysis.vue index e1a82d6c..a2de879c 100644 --- a/src/components/examination/es-analysis.vue +++ b/src/components/examination/es-analysis.vue @@ -19,7 +19,7 @@ diff --git a/src/components/points-and-badge/badge.vue b/src/components/points-and-badge/badge.vue new file mode 100644 index 00000000..0754586f --- /dev/null +++ b/src/components/points-and-badge/badge.vue @@ -0,0 +1,17 @@ + + + + + diff --git a/src/components/points-and-badge/check-in.vue b/src/components/points-and-badge/check-in.vue new file mode 100644 index 00000000..8d2341b8 --- /dev/null +++ b/src/components/points-and-badge/check-in.vue @@ -0,0 +1,417 @@ + + + + + diff --git a/src/components/points-and-badge/points.vue b/src/components/points-and-badge/points.vue new file mode 100644 index 00000000..02409e35 --- /dev/null +++ b/src/components/points-and-badge/points.vue @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/src/components/selection-institutions/index.vue b/src/components/selection-institutions/index.vue new file mode 100644 index 00000000..fb0ba731 --- /dev/null +++ b/src/components/selection-institutions/index.vue @@ -0,0 +1,197 @@ + + + + + diff --git a/src/components/selection-institutions/treeSelector.vue b/src/components/selection-institutions/treeSelector.vue new file mode 100644 index 00000000..ef10a915 --- /dev/null +++ b/src/components/selection-institutions/treeSelector.vue @@ -0,0 +1,134 @@ + + + + + diff --git a/src/composables/useZpaging.js b/src/composables/useZpaging.js index faf92378..e7482454 100644 --- a/src/composables/useZpaging.js +++ b/src/composables/useZpaging.js @@ -43,7 +43,8 @@ export default function useZpaging({ } // 搜索 - const search = (value, state) => { + const search = (value, state=true) => { + console.log('搜索', value, state); searchText.value = value; if (state) { reload() diff --git a/src/enum.js b/src/enum.js index 7ec50154..d40c7cb8 100644 --- a/src/enum.js +++ b/src/enum.js @@ -13,7 +13,7 @@ export const GENDER = [{ value: "f", label: "女性", } -] +] // 试题题型 export const SUBJECT_TYPE = [{ value: "SN", @@ -32,4 +32,19 @@ export const SUBJECT_TYPE = [{ label: "问答题", } ] -export const defaultAvatar = '/static/images/me/default_user_avatar.png' \ No newline at end of file +export const defaultAvatar = '/static/images/me/default_user_avatar.png' +export const defaultAnonymousAvatar = '/static/images/competition/competition_default_avatar.png' +export const styleButton = { + style_button_1: { + color: '#FFFFFF', + fontSize: '30rpx', + backgroundColor: '#0066FF', + borderRadius: '8rpx' + }, + style_button_2: { + color: '#0066FF', + fontSize: '30rpx', + backgroundColor: '#E2EDFF', + borderRadius: '8rpx' + } +} \ No newline at end of file diff --git a/src/manifest.json b/src/manifest.json index ce7971c6..3013d5af 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -48,11 +48,13 @@ ] }, /* ios打包配置 */ - "ios" : {}, + "ios" : { + "dSYMs" : false + }, /* SDK配置 */ "sdkConfigs" : {}, "splashscreen" : { - "androidStyle" : "default", + "androidStyle" : "common", "android" : { "hdpi" : "src/static/images/login/open.9.png", "xhdpi" : "src/static/images/login/open.9.png", diff --git a/src/pages.json b/src/pages.json index 675567b4..29a19bfc 100644 --- a/src/pages.json +++ b/src/pages.json @@ -311,6 +311,79 @@ } } }, + { + "path": "pages/competition/index", + "style": { + "navigationBarTitleText": "竞赛列表页", + "app-plus": { + "titleNView": false + } + } + }, + { + "path": "pages/competition/matching", + "style": { + "navigationBarTitleText": "匹配页面", + "app-plus": { + "titleNView": false + } + } + }, + { + "path": "pages/competition/pk", + "style": { + "navigationBarTitleText": "pk页面", + "disableSwipeBack": true, + "app-plus": { + "titleNView": false + } + } + }, + { + "path": "pages/competition/result", + "style": { + "navigationBarTitleText": "pk结果页面", + "app-plus": { + "titleNView": false + } + } + }, + { + "path": "pages/competition/ranking", + "style": { + "navigationBarTitleText": "pk排行榜", + "app-plus": { + "titleNView": false + } + } + }, + { + "path": "pages/pointsAndRank/points", + "style": { + "navigationBarTitleText": "积分变动明细", + "app-plus": { + "titleNView": false + } + } + }, + { + "path": "pages/pointsAndRank/rank", + "style": { + "navigationBarTitleText": "段位列表", + "app-plus": { + "titleNView": false + } + } + }, + { + "path": "pages/pointsAndRank/badge", + "style": { + "navigationBarTitleText": "徽章列表", + "app-plus": { + "titleNView": false + } + } + }, { "path": "pages/course/index", "style": { diff --git a/src/pages/competition/index.vue b/src/pages/competition/index.vue new file mode 100644 index 00000000..ac0b1980 --- /dev/null +++ b/src/pages/competition/index.vue @@ -0,0 +1,193 @@ + + + + + diff --git a/src/pages/competition/js/matching.ts b/src/pages/competition/js/matching.ts new file mode 100644 index 00000000..213b1e23 --- /dev/null +++ b/src/pages/competition/js/matching.ts @@ -0,0 +1,64 @@ +/** + * 生成随机匹配时间 + * 特点:同一时间段生成的时间会比较接近,时间范围可配置 + */ +export const MatchTimeGenerator = (() => { + // 可配置的时间范围(毫秒) + const MIN_TIME = 2000; // 最小时间 + const MAX_TIME = 6000; // 最大时间 + + // 时间窗口:100秒内生成的时间会基于同一基准(可根据需要调整) + const TIME_WINDOW = 100000; + // 同一窗口内的最大偏差(±1.5秒,可根据需要调整) + const MAX_DEVIATION = 1500; + + // 记录上一次生成的基准时间和时间戳 + let lastBaseTime = 0; + let lastTimestamp = 0; + + return { + /** + * 生成匹配时间(毫秒) + * @returns {number} 介于MIN_TIME和MAX_TIME之间的随机数(毫秒) + */ + generate() : number { + const now = Date.now(); + let baseTime : number; + + // 如果超过时间窗口或首次生成,重新计算基准时间 + if (now - lastTimestamp > TIME_WINDOW || lastBaseTime === 0) { + // 生成MIN_TIME到MAX_TIME之间的基准时间(毫秒) + const timeRange = MAX_TIME - MIN_TIME; + baseTime = MIN_TIME + Math.floor(Math.random() * timeRange); + lastBaseTime = baseTime; + lastTimestamp = now; + } else { + // 在同一时间窗口内,基于上次基准时间生成偏差 + baseTime = lastBaseTime; + } + + // 生成偏差值(-MAX_DEVIATION 到 MAX_DEVIATION) + const deviation = Math.floor(Math.random() * (MAX_DEVIATION * 2 + 1)) - MAX_DEVIATION; + let matchTime = baseTime + deviation; + + // 确保结果在配置的范围内 + matchTime = Math.max(MIN_TIME, Math.min(MAX_TIME, matchTime)); + + return matchTime; + }, + + /** + * 生成匹配时间(秒) + * @returns {number} 介于MIN_TIME/1000和MAX_TIME/1000之间的随机数(秒,保留一位小数) + */ + generateInSeconds() : number { + return Math.round(this.generate() / 100) / 10; + } + }; +})(); + +export const formatTime = (seconds : number) => { + const m = Math.floor(seconds / 60); + const s = seconds % 60; + return [m.toString().padStart(2, '0'), s.toString().padStart(2, '0')].join(':'); +}; \ No newline at end of file diff --git a/src/pages/competition/js/random_avatar.ts b/src/pages/competition/js/random_avatar.ts new file mode 100644 index 00000000..e0875f68 --- /dev/null +++ b/src/pages/competition/js/random_avatar.ts @@ -0,0 +1,69 @@ +import AvatarW1 from '@/static/images/competition/avatar/f_1.jpg' +import AvatarW2 from '@/static/images/competition/avatar/f_2.jpg' +import AvatarW3 from '@/static/images/competition/avatar/f_3.jpg' +import AvatarW4 from '@/static/images/competition/avatar/f_4.jpg' +import AvatarM1 from '@/static/images/competition/avatar/m_1.jpg' +import AvatarM2 from '@/static/images/competition/avatar/m_2.jpg' +import AvatarM3 from '@/static/images/competition/avatar/m_3.jpg' +import AvatarM4 from '@/static/images/competition/avatar/m_4.jpg' +import AvatarS1 from '@/static/images/competition/avatar/s_1.jpg' +import AvatarS2 from '@/static/images/competition/avatar/s_2.jpg' +import AvatarS3 from '@/static/images/competition/avatar/s_3.jpg' +import AvatarS4 from '@/static/images/competition/avatar/s_4.jpg' + +// 定义头像类型 +type AvatarType = 'f' | 'm' | 's'; + +// 按类型分组 +const avatarsByType = { + f: [AvatarW1, AvatarW2, AvatarW3, AvatarW4], + m: [AvatarM1, AvatarM2, AvatarM3, AvatarM4], + s: [AvatarS1, AvatarS2, AvatarS3, AvatarS4] +}; + +/** + * 随机获取头像 + * @param type1 第一个头像类型(可选) + * @param type2 第二个头像类型(可选,传入则返回2个头像) + * @returns 单个头像路径(当只传type1或都不传时)或两个头像路径的数组(当传入type2时) + */ +export default function getRandomAvatars( + type1 ?: AvatarType, + type2 ?: AvatarType +) { + // 随机获取指定类型的头像 + const getRandomAvatar = (type ?: AvatarType) => { + // 如果指定了类型且有效,则从该类型中选择 + if (type && avatarsByType[type]) { + const avatars = avatarsByType[type]; + return avatars[Math.floor(Math.random() * avatars.length)]; + } + + // 否则从所有头像中随机选择 + const allAvatars = [ + ...avatarsByType.f, + ...avatarsByType.m, + ...avatarsByType.s + ]; + return allAvatars[Math.floor(Math.random() * allAvatars.length)]; + }; + + // 只传type1或都不传时,返回1个头像 + if (type2 === undefined) { + return getRandomAvatar(type1); + } + + // 传入type2时,返回2个头像 + const avatar1 = getRandomAvatar(type1); + let avatar2; + + // 当两个类型相同时,确保头像不重复 + if (type1 && type1 === type2) { + const sameTypeAvatars = avatarsByType[type1].filter(avatar => avatar !== avatar1); + avatar2 = sameTypeAvatars[Math.floor(Math.random() * sameTypeAvatars.length)]; + } else { + avatar2 = getRandomAvatar(type2); + } + + return [avatar1, avatar2]; +} \ No newline at end of file diff --git a/src/pages/competition/js/tool.js b/src/pages/competition/js/tool.js new file mode 100644 index 00000000..4ea1d197 --- /dev/null +++ b/src/pages/competition/js/tool.js @@ -0,0 +1,20 @@ +export function formatSecondsToMS(seconds) { + + // 尝试将输入转换为数字(处理字符串类型的数字) + const num = Number(seconds); + + // 处理无效输入(转换失败、非数字、负数) + if (isNaN(num) || num < 0) { + return '0-秒'; + } + + // 计算分钟(不进位,直接取整) + const minutes = Math.floor(num / 60); + // 计算剩余秒数(取整) + const remainingSeconds = Math.floor(num % 60); + if (minutes === 0) { + return `${remainingSeconds}秒`; + } + // 拼接结果 + return `${minutes}分${remainingSeconds}秒`; +} \ No newline at end of file diff --git a/src/pages/competition/matching.vue b/src/pages/competition/matching.vue new file mode 100644 index 00000000..5cbb2dde --- /dev/null +++ b/src/pages/competition/matching.vue @@ -0,0 +1,337 @@ + + + + + diff --git a/src/pages/competition/pk.vue b/src/pages/competition/pk.vue new file mode 100644 index 00000000..6fb7c603 --- /dev/null +++ b/src/pages/competition/pk.vue @@ -0,0 +1,803 @@ + + + + + diff --git a/src/pages/competition/ranking.vue b/src/pages/competition/ranking.vue new file mode 100644 index 00000000..44c88751 --- /dev/null +++ b/src/pages/competition/ranking.vue @@ -0,0 +1,399 @@ + + + + + diff --git a/src/pages/competition/result.vue b/src/pages/competition/result.vue new file mode 100644 index 00000000..8dc794a8 --- /dev/null +++ b/src/pages/competition/result.vue @@ -0,0 +1,586 @@ + + + + diff --git a/src/pages/competition/style/matching-loading.scss b/src/pages/competition/style/matching-loading.scss new file mode 100644 index 00000000..1f958557 --- /dev/null +++ b/src/pages/competition/style/matching-loading.scss @@ -0,0 +1,37 @@ +.match-time-div { + font-weight: 400; + font-size: 34rpx; + color: #ffffff; + line-height: 34rpx; + font-style: normal; + margin-bottom: 20rpx; + transition: opacity 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); + opacity: 1; /* 初始显示状态 */ + transform: translateY(0); /* 初始位置 */ + &.hidden { + opacity: 0; /* 完全透明 */ + transform: translateY(40rpx); /* 轻微上移增强淡出效果 */ + pointer-events: none; /* 隐藏后不响应事件 */ + height: 0; /* 可选:完全隐藏高度 */ + overflow: hidden; /* 可选:隐藏溢出内容 */ + } +} +.match-msg-div { + width: 100%; + height: 8vh; + font-weight: normal; + font-size: 72rpx; + color: #ffffff; + display: flex; + justify-content: center; + align-items: flex-end; + transition: opacity 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); + opacity: 1; /* 初始显示状态 */ + transform: translateY(0); /* 初始位置 */ + &.hidden { + opacity: 0; /* 完全透明 */ + transform: translateY(-40rpx); /* 轻微上移增强淡出效果 */ + pointer-events: none; /* 隐藏后不响应事件 */ + overflow: hidden; /* 可选:隐藏溢出内容 */ + } +} diff --git a/src/pages/competition/style/matching-prelude.scss b/src/pages/competition/style/matching-prelude.scss new file mode 100644 index 00000000..05758db2 --- /dev/null +++ b/src/pages/competition/style/matching-prelude.scss @@ -0,0 +1,334 @@ +.select-div { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + transition: opacity 0.3s ease-out; + opacity: 1; /* 初始显示状态 */ + &.hidden { + opacity: 0; /* 完全透明 */ + pointer-events: none; /* 隐藏后不响应事件 */ + height: 0; /* 可选:完全隐藏高度 */ + overflow: hidden; /* 可选:隐藏溢出内容 */ + } +} +.select-institution-msg-div { + width: calc(100% - 228rpx); + font-weight: 400; + font-size: 22rpx; + color: #ffffff; + line-height: 32rpx; + text-align: left; + font-style: normal; + opacity: 0.8; + padding-top: 18rpx; + padding-bottom: 18rpx; +} + +.select-institution-div { + width: calc(100% - 228rpx); + height: 86rpx; + border: 1rpx solid transparent; + position: relative; + font-weight: 400; + font-size: 32rpx; + line-height: 86rpx; + text-align: center; + color: #ffffff; + background-color: rgba(0, 0, 0, 0.4); + z-index: 1; + overflow: hidden; + padding: 0 60rpx 0 20rpx; + white-space: nowrap; + /* 强制不换行 */ + overflow: hidden; + /* 超出部分隐藏 */ + text-overflow: ellipsis; + .img { + // background-color: red; + padding: 16rpx; + position: absolute; + top: 50%; + transform: translateY(-50%); + right: 10rpx; + width: 30rpx; + height: 30rpx; + } +} + +/* 边框高光效果 */ +.select-institution-div::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border: 4rpx solid transparent; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.8), transparent) border-box; + -webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; + + /* 初始位置在左侧外部 */ + background-size: 200% 100%; + + /* 动画:高光从左到右流动 */ + animation: highlightFlow 4s linear infinite; +} + +/* 定义高光流动动画 */ +@keyframes highlightFlow { + 0% { + background-position: -100% 0; + } + 100% { + background-position: 100% 0; + } +} + +/* 环绕版本带呼吸效果 */ +.select-institution-div.full-flow::before { + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.8), + transparent, + transparent, + transparent, + transparent, + transparent, + transparent + ); + background-size: 300% 300%; + animation: fullBorderFlow 2s linear infinite, breathing 1s ease-in-out infinite; +} + +@keyframes fullBorderFlow { + 0% { + background-position: 0 0; + } + 25% { + background-position: 100% 0; + } + 50% { + background-position: 100% 100%; + } + 75% { + background-position: 0 100%; + } + 100% { + background-position: 0 0; + } +} + +.title-div { + width: calc(100% - 80rpx); + margin: 40rpx; + font-size: 32rpx; + font-weight: 500; + color: #ffffff; + line-height: 44rpx; + text-shadow: 0px 0px 19px rgba(0, 0, 0, 0.11); + text-align: center; + font-family: none; + min-height: 88rpx; + margin: calc(var(--status-bar-height) + 40rpx); + + /* 添加过渡动画 */ + transition: opacity 0.3s ease-out, transform 0.3s ease-out; + opacity: 1; /* 初始显示状态 */ + transform: translateY(0); /* 初始位置 */ + &.hidden { + opacity: 0; /* 完全透明 */ + transform: translateY(-10rpx); /* 轻微上移增强淡出效果 */ + pointer-events: none; /* 隐藏后不响应事件 */ + height: 0; /* 可选:完全隐藏高度 */ + overflow: hidden; /* 可选:隐藏溢出内容 */ + } +} + +.avatar-div { + margin-top: 20rpx; + .avatar-img-div { + width: 204rpx; + height: 204rpx; + border: 4rpx solid #ffffff; + border-radius: 50%; + position: relative; + overflow: visible; + /* 修复伪元素显示问题 */ + &.hidden::before { + content: ''; + position: absolute; + /* 使用固定尺寸确保计算正确 */ + width: 230rpx; + height: 230rpx; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + /* 确保在z轴上处于正确层级 */ + z-index: 1; + /* 背景图设置 */ + background-image: url('@/static/images/competition/avatar_surround.png'); + background-size: contain; /* 改用contain确保完整显示 */ + background-repeat: no-repeat; + background-position: center; + /* 旋转动画 */ + animation: variableRotate 2s linear infinite; + } + .img { + width: 100%; + height: 100%; + border-radius: 50%; + position: relative; + z-index: 2; + overflow: hidden; + } + } + + @keyframes variableRotate { + 0% { + transform: translate(-50%, -50%) rotate(0deg); + } + 100% { + transform: translate(-50%, -50%) rotate(360deg); + } + } + + .avatar-name-div { + font-weight: 600; + font-size: 46rpx; + color: #ffffff; + line-height: 64rpx; + text-align: center; + font-style: normal; + padding-top: 16rpx; + padding-bottom: 16rpx; + } +} +.button-div { + .start_button { + width: 569rpx; + height: 92rpx; + background: linear-gradient(#009fff 0%, #0066ff 100%); + border-radius: 58rpx; + font-weight: 500; + font-size: 30rpx; + color: #ffffff; + line-height: 92rpx; + text-align: center; + margin-top: 250rpx; + } + .cancel_button { + width: 246rpx; + height: 92rpx; + border-radius: 58rpx; + font-weight: 500; + font-size: 30rpx; + color: #ffffff; + line-height: 92rpx; + text-align: center; + background: rgba(0, 0, 0, 0.5); + background: linear-gradient(rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.5) 100%); + border-radius: 58rpx; + border: 2rpx solid #dbdbdb; + margin-bottom: 250rpx; /* 向上移动50rpx,可根据需要调整 */ + } + .button-transition { + /* 添加过渡动画属性 */ + transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); + /* 确保边框变化也有过渡效果 */ + box-sizing: border-box; + } +} + +.top-button-div { + display: flex; + justify-content: space-between; + width: 100%; + height: 70rpx; + padding-top: calc(16rpx + var(--status-bar-height)); + font-weight: 500; + position: relative; + margin-right: 10rpx; + .top-icon-div { + display: flex; + align-items: center; + margin-right: 30rpx; + font-weight: 400; + font-size: 26rpx; + color: #ffffff; + text-align: left; + .img1, + .img3 { + width: 40rpx; + height: 40rpx; + margin-right: 6rpx; + } + .img2 { + width: 30rpx; + height: 30rpx; + margin-right: 6rpx; + } + } + .back_div { + position: absolute; + left: 26rpx; + height: 34rpx; + top: calc(10rpx + var(--status-bar-height)); + .back-icon { + position: relative; + width: 50rpx; + height: 50rpx; + cursor: pointer; + display: flex; + align-items: center; + } + + .back-icon::before, + .back-icon::after { + content: ''; + position: absolute; + transition: all 0.3s ease; + } + + .back-icon::after { + top: 25%; + left: 25%; + width: 40%; + height: 40%; + border-top: 4rpx solid #ffffff; + border-left: 4rpx solid #ffffff; + transform: rotate(-45deg); + } + } +} + +.popup { + z-index: 1800; + padding: 0 38rpx; + + .sheet_popup_div { + width: 100%; + height: 100%; + background-color: #ffffff; + padding: 10rpx 34rpx; + border-radius: 34rpx 34rpx 0px 0px; + position: relative; + + .title_div { + width: 100%; + padding: 30rpx 0; + font-family: PingFangSC; + font-weight: 600; + font-size: 30rpx; + color: #000000; + font-style: normal; + border-bottom: 2rpx solid #efefef; + } + .note_div { + padding: 20rpx 0 80rpx 0; + } + } +} diff --git a/src/pages/competition/style/matching-success.scss b/src/pages/competition/style/matching-success.scss new file mode 100644 index 00000000..e7bf1ebe --- /dev/null +++ b/src/pages/competition/style/matching-success.scss @@ -0,0 +1,194 @@ +// 坠落动画配置变量 - 方便调整效果 +$fall-duration: 0.26s; // 动画总时长 +$initial-scale: 2.8; // 初始大小倍数 +$impact-scale: 0.9; // 撞击瞬间的压缩倍数 +$final-scale: 1; // 最终稳定大小 +$primary-color: #ffe271; // 文字颜色 + +.matching-success-text { + margin-top: 10%; + padding-left: 1rem; + // 基础样式 + font-size: 74rpx; + font-weight: bold; + color: #ffe271; + text-align: center; + // 动画属性 + animation: fallDown $fall-duration ease-out forwards; + + // 坠落动画关键帧 + @keyframes fallDown { + 0% { + // 初始状态:高处坠落的起点 + transform: scale($initial-scale); + opacity: 0.8; + } + + 80% { + // 撞击瞬间:略微压缩,增强冲击力 + transform: scale($impact-scale); + opacity: 1; + } + + 100% { + // 稳定状态:恢复正常大小 + transform: scale($final-scale); + opacity: 1; + } + } + + // 增强版:带震动效果的撞击(可选) + &.with-vibration { + animation: fallWithVibration $fall-duration cubic-bezier(0.21, 0.98, 0.6, 0.99) forwards; + + @keyframes fallWithVibration { + 0% { + transform: scale($initial-scale) rotate(0deg); + opacity: 0.8; + } + + 70% { + transform: scale($impact-scale) rotate(2deg); + opacity: 1; + } + + 85% { + transform: scale($final-scale * 1.05) rotate(-1deg); + opacity: 1; + } + + 100% { + transform: scale($final-scale) rotate(0deg); + opacity: 1; + } + } + } +} + +// 可通过修改变量快速调整效果 +// 示例:更重的坠落效果 +.heavy-impact { + $fall-duration: 0.3s; + $initial-scale: 2.5; + $impact-scale: 0.8; + + animation: fallDown $fall-duration ease-out forwards; +} + +.matching-success-msg { + margin-top: 20%; + font-family: Alibaba-PuHuiTi, Alibaba-PuHuiTi; + font-weight: normal; + font-size: 74rpx; + color: #ffffff; + font-weight: 700; + margin-bottom: 10%; +} + +/**匹配成功样式*/ +.matching-success-animation { + display: flex; + width: 100%; + justify-content: space-between; + height: 540rpx; + position: relative; + // overflow: hidden; + .left { + position: absolute; + width: calc(59vw); + left: -10rpx; + top: 0%; + z-index: 3; + } + .right { + position: absolute; + right: -10rpx; + top: 18vw; + width: calc(59vw); + z-index: 2; + } + .img { + width: 100%; + } + // 左侧元素回弹动画 + .animate-left { + animation: leftCollision 0.24s ease-out forwards; + } + @keyframes leftCollision { + 0% { + transform: translateX(-100%); + } // 初始位置 + 90% { + transform: translateX(10rpx); + } // 超过目标位置10rpx(冲刺) + 100% { + transform: translateX(0); + } // 回到目标位置(回弹) + } + + // 右侧元素回弹动画 + .animate-right { + animation: rightCollision 0.24s ease-out forwards; + } + @keyframes rightCollision { + 0% { + transform: translateX(100%); + } // 初始位置 + 90% { + transform: translateX(-10rpx); + } // 超过目标位置10rpx(冲刺) + 100% { + transform: translateX(0); + } // 回到目标位置(回弹) + } + + // 上面的双方头像 + .information-div-left, + .information-div-right { + display: flex; + flex-direction: column; + align-items: center; + + .avatar-img-div { + width: 120rpx; + height: 120rpx; + border: 4rpx solid #ffffff; + border-radius: 50%; + overflow: hidden; + .img { + width: 100%; + height: 100%; + } + } + .name { + font-size: 30rpx; + margin-top: 20rpx; + margin-bottom: 100rpx; + font-weight: 600; + font-size: 36rpx; + color: #ffffff; + } + .org { + font-weight: 400; + font-size: 30rpx; + color: #ffffff; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 160rpx; + } + } + .information-div-left { + position: absolute; + top: 0%; + left: 0%; + transform: translate(24%, -20%); + } + .information-div-right { + position: absolute; + right: 0%; + top: 0%; + transform: translate(-24%, -20%); + } +} diff --git a/src/pages/course/components/touchBtn.vue b/src/pages/course/components/touchBtn.vue index fee712ec..03dddf4b 100644 --- a/src/pages/course/components/touchBtn.vue +++ b/src/pages/course/components/touchBtn.vue @@ -445,6 +445,7 @@ box-shadow: 0 8rpx 20rpx 0 rgba(0, 0, 0, 0.06); text-align: center; line-height: 92rpx; + font-size: 28rpx; color: #000; font-weight: 400; position: relative; diff --git a/src/pages/examination/components/dialog.vue b/src/pages/examination/components/dialog.vue index a66bb3aa..64fd870e 100644 --- a/src/pages/examination/components/dialog.vue +++ b/src/pages/examination/components/dialog.vue @@ -9,6 +9,9 @@ + + {{ msg }} + {{ item?.name || '' }} @@ -25,12 +28,14 @@ dialogConfiguration: { type: Object, required: false - } + }, + }); const emit = defineEmits(['button_click']); const title = ref(''); const html = ref(''); + const msg = ref(''); const buttonList = ref([]); const PopupRef = ref(null); // 点击按钮 @@ -41,9 +46,10 @@ const close = () => PopupRef.value?.close(); const open = (dialogConfiguration) => { // 解构赋值更清晰 - const { title: t, html: h, buttonList: bl = [] } = dialogConfiguration || props.dialogConfiguration || {}; + const { title: t, html: h, msg: m, buttonList: bl = [] } = dialogConfiguration || props.dialogConfiguration || {}; title.value = t || ''; html.value = h || ''; + msg.value = m || ''; buttonList.value = Array.isArray(bl) ? bl : []; // 确保PopupRef存在再调用open @@ -62,7 +68,7 @@ z-index: 1810; width: 100%; height: 100%; - background-color: rgba(0, 0, 0, 0.9); + background-color: rgba(0, 0, 0, 0.5); } .popup_div { diff --git a/src/pages/examination/components/feedback.vue b/src/pages/examination/components/feedback.vue index d701c8ed..8aea1a9c 100644 --- a/src/pages/examination/components/feedback.vue +++ b/src/pages/examination/components/feedback.vue @@ -143,7 +143,7 @@ onUnmounted(() => { removeListener(); }); - + const open = (qnsId) => { feedbackInfo.qnsId = qnsId; openFeedback(true); diff --git a/src/pages/index/dialog.vue b/src/pages/index/dialog.vue index 7b00709f..076a8526 100644 --- a/src/pages/index/dialog.vue +++ b/src/pages/index/dialog.vue @@ -1113,7 +1113,7 @@ height: calc(100% - 772rpx); padding: 12rpx 24rpx; box-sizing: border-box; - + min-height: 300rpx; .talking-scroll-list { height: 100%; } diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue index bc124d6d..e8de8d9b 100644 --- a/src/pages/index/index.vue +++ b/src/pages/index/index.vue @@ -138,7 +138,8 @@ 考试中心 - + + 竞赛练习 diff --git a/src/pages/learningTasks/components/content-item.vue b/src/pages/learningTasks/components/content-item.vue index 39afeb40..9fa7823b 100644 --- a/src/pages/learningTasks/components/content-item.vue +++ b/src/pages/learningTasks/components/content-item.vue @@ -1,5 +1,5 @@ + + diff --git a/src/pages/pointsAndRank/points.vue b/src/pages/pointsAndRank/points.vue new file mode 100644 index 00000000..93d9ef54 --- /dev/null +++ b/src/pages/pointsAndRank/points.vue @@ -0,0 +1,270 @@ + + + + + diff --git a/src/pages/pointsAndRank/rank.vue b/src/pages/pointsAndRank/rank.vue new file mode 100644 index 00000000..4289dc86 --- /dev/null +++ b/src/pages/pointsAndRank/rank.vue @@ -0,0 +1,257 @@ + + + + + diff --git a/src/pages/practice/index.vue b/src/pages/practice/index.vue index be67ffb1..7b0a8d32 100644 --- a/src/pages/practice/index.vue +++ b/src/pages/practice/index.vue @@ -114,7 +114,7 @@ const storageTopicList = ref([]); // 所有题列表 const popup_input_bottom = ref('0px'); const questionNumber = ref(0); // 题号 - const systemInfo = uni.getSystemInfoSync(); + const topic = computed({ get: () => topicList[questionNumber.value], @@ -747,7 +747,7 @@ } } } - + .main_div { display: flex; flex-direction: column; diff --git a/src/pages/setting/index.vue b/src/pages/setting/index.vue index 0b5bfb55..0abe8e10 100644 --- a/src/pages/setting/index.vue +++ b/src/pages/setting/index.vue @@ -67,16 +67,7 @@ const mascotName = computed(() => userInfo.value.mascotInfo?.mascotName || ''); const sexOptions = GENDER; - const anonymousOptions = [ - { - value: 'y', - label: '是' - }, - { - value: 'n', - label: '否' - } - ]; // 性别 + // 去选择百禄 const gotoMascot = () => { diff --git a/src/pagesA.json b/src/pagesA.json index ee081f97..a6daa78c 100644 --- a/src/pagesA.json +++ b/src/pagesA.json @@ -123,7 +123,7 @@ "style": { "navigationBarTitleText": "错题本", "app-plus": { - "titleNView": false + "titleNView": false } } }, @@ -246,7 +246,79 @@ "titleNView": false } } + }, + { + "path": "pages/competition/index", + "style": { + "navigationBarTitleText": "竞赛列表页", + "app-plus": { + "titleNView": false + } + } + }, + { + "path": "pages/competition/matching", + "style": { + "navigationBarTitleText": "匹配页面", + "app-plus": { + "titleNView": false + } + } + }, + { + "path": "pages/competition/pk", + "style": { + "navigationBarTitleText": "pk页面", + "disableSwipeBack": true, + "app-plus": { + "titleNView": false + } + } + }, + { + "path": "pages/competition/result", + "style": { + "navigationBarTitleText": "pk结果页面", + "app-plus": { + "titleNView": false + } + } + }, + { + "path": "pages/competition/ranking", + "style": { + "navigationBarTitleText": "pk排行榜", + "app-plus": { + "titleNView": false + } + } + }, + { + "path": "pages/pointsAndRank/points", + "style": { + "navigationBarTitleText": "积分变动明细", + "app-plus": { + "titleNView": false + } + } + }, + { + "path": "pages/pointsAndRank/rank", + "style": { + "navigationBarTitleText": "段位列表", + "app-plus": { + "titleNView": false + } + } + }, + { + "path": "pages/pointsAndRank/badge", + "style": { + "navigationBarTitleText": "徽章墙", + "app-plus": { + "titleNView": false + } + } } - ] } \ No newline at end of file diff --git a/src/pagesOther.json b/src/pagesOther.json index ac8f95c7..e8cf31c7 100644 --- a/src/pagesOther.json +++ b/src/pagesOther.json @@ -3,11 +3,11 @@ { "path": "pages/index/dialog", "style": { - "navigationBarTitleText": "AI问答", - "app-plus": { - "titleNView": false, - "animationType": "fade-in" - } + "navigationBarTitleText": "AI问答", + "app-plus": { + "titleNView": false, + "animationType": "fade-in" + } } }, { diff --git a/src/static/images/competition/avatar_surround.png b/src/static/images/competition/avatar_surround.png new file mode 100644 index 00000000..cd12d0b1 Binary files /dev/null and b/src/static/images/competition/avatar_surround.png differ diff --git a/src/static/images/competition/bg.png b/src/static/images/competition/bg.png new file mode 100644 index 00000000..c8b35ffa Binary files /dev/null and b/src/static/images/competition/bg.png differ diff --git a/src/static/images/competition/competition_default_avatar.png b/src/static/images/competition/competition_default_avatar.png new file mode 100644 index 00000000..1660ff8c Binary files /dev/null and b/src/static/images/competition/competition_default_avatar.png differ diff --git a/src/static/images/competition/list_empty.png b/src/static/images/competition/list_empty.png new file mode 100644 index 00000000..b197bdf8 Binary files /dev/null and b/src/static/images/competition/list_empty.png differ diff --git a/src/static/images/competition/matching-success-left.png b/src/static/images/competition/matching-success-left.png new file mode 100644 index 00000000..451fbb74 Binary files /dev/null and b/src/static/images/competition/matching-success-left.png differ diff --git a/src/static/images/competition/matching-success-right.png b/src/static/images/competition/matching-success-right.png new file mode 100644 index 00000000..68a5443a Binary files /dev/null and b/src/static/images/competition/matching-success-right.png differ diff --git a/src/static/images/competition/matching-success-text-2.png b/src/static/images/competition/matching-success-text-2.png new file mode 100644 index 00000000..cc7602f1 Binary files /dev/null and b/src/static/images/competition/matching-success-text-2.png differ diff --git a/src/static/images/competition/matching-success-text.png b/src/static/images/competition/matching-success-text.png new file mode 100644 index 00000000..648c7020 Binary files /dev/null and b/src/static/images/competition/matching-success-text.png differ diff --git a/src/static/images/competition/pk_bg_icon.png b/src/static/images/competition/pk_bg_icon.png new file mode 100644 index 00000000..e64e4efa Binary files /dev/null and b/src/static/images/competition/pk_bg_icon.png differ diff --git a/src/static/images/competition/ranking/1@2x.png b/src/static/images/competition/ranking/1@2x.png new file mode 100644 index 00000000..fcfe338c Binary files /dev/null and b/src/static/images/competition/ranking/1@2x.png differ diff --git a/src/static/images/competition/ranking/2@2x.png b/src/static/images/competition/ranking/2@2x.png new file mode 100644 index 00000000..6912627b Binary files /dev/null and b/src/static/images/competition/ranking/2@2x.png differ diff --git a/src/static/images/competition/ranking/3@2x.png b/src/static/images/competition/ranking/3@2x.png new file mode 100644 index 00000000..abb2f860 Binary files /dev/null and b/src/static/images/competition/ranking/3@2x.png differ diff --git a/src/static/images/competition/ranking/ranking_bg.png b/src/static/images/competition/ranking/ranking_bg.png new file mode 100644 index 00000000..a4e36345 Binary files /dev/null and b/src/static/images/competition/ranking/ranking_bg.png differ diff --git a/src/static/images/competition/ranking/ranking_title.png b/src/static/images/competition/ranking/ranking_title.png new file mode 100644 index 00000000..e6426924 Binary files /dev/null and b/src/static/images/competition/ranking/ranking_title.png differ diff --git a/src/static/images/competition/result_bg.png b/src/static/images/competition/result_bg.png new file mode 100644 index 00000000..4181ef18 Binary files /dev/null and b/src/static/images/competition/result_bg.png differ diff --git a/src/static/images/competition/result_huosheng.png b/src/static/images/competition/result_huosheng.png new file mode 100644 index 00000000..d1636c0c Binary files /dev/null and b/src/static/images/competition/result_huosheng.png differ diff --git a/src/static/images/competition/result_loading_mascot.png b/src/static/images/competition/result_loading_mascot.png new file mode 100644 index 00000000..87d1c007 Binary files /dev/null and b/src/static/images/competition/result_loading_mascot.png differ diff --git a/src/static/images/competition/result_loading_msg.png b/src/static/images/competition/result_loading_msg.png new file mode 100644 index 00000000..b49369d2 Binary files /dev/null and b/src/static/images/competition/result_loading_msg.png differ diff --git a/src/static/images/competition/result_pk_bg.png b/src/static/images/competition/result_pk_bg.png new file mode 100644 index 00000000..d7b953d6 Binary files /dev/null and b/src/static/images/competition/result_pk_bg.png differ diff --git a/src/static/images/competition/result_shengli.png b/src/static/images/competition/result_shengli.png new file mode 100644 index 00000000..d02a1f8a Binary files /dev/null and b/src/static/images/competition/result_shengli.png differ diff --git a/src/static/images/competition/result_shibai.png b/src/static/images/competition/result_shibai.png new file mode 100644 index 00000000..c18d305f Binary files /dev/null and b/src/static/images/competition/result_shibai.png differ diff --git a/src/static/images/me/sign-in-button-bg.png b/src/static/images/me/sign-in-button-bg.png new file mode 100644 index 00000000..736d22e1 Binary files /dev/null and b/src/static/images/me/sign-in-button-bg.png differ diff --git a/src/static/images/me/sign-in-button-bg2.png b/src/static/images/me/sign-in-button-bg2.png new file mode 100644 index 00000000..555239e1 Binary files /dev/null and b/src/static/images/me/sign-in-button-bg2.png differ diff --git a/src/static/images/pointsAndRank/badge-bg.png b/src/static/images/pointsAndRank/badge-bg.png new file mode 100644 index 00000000..0c800a1a Binary files /dev/null and b/src/static/images/pointsAndRank/badge-bg.png differ diff --git a/src/static/images/pointsAndRank/badge-icon.png b/src/static/images/pointsAndRank/badge-icon.png new file mode 100644 index 00000000..6dcf5b5e Binary files /dev/null and b/src/static/images/pointsAndRank/badge-icon.png differ diff --git a/src/static/images/pointsAndRank/badge-title-left.png b/src/static/images/pointsAndRank/badge-title-left.png new file mode 100644 index 00000000..0e03e19d Binary files /dev/null and b/src/static/images/pointsAndRank/badge-title-left.png differ diff --git a/src/static/images/pointsAndRank/badge-title-right.png b/src/static/images/pointsAndRank/badge-title-right.png new file mode 100644 index 00000000..ad3f1f7f Binary files /dev/null and b/src/static/images/pointsAndRank/badge-title-right.png differ diff --git a/src/static/images/pointsAndRank/badge-wheat-left.png b/src/static/images/pointsAndRank/badge-wheat-left.png new file mode 100644 index 00000000..e70ccac7 Binary files /dev/null and b/src/static/images/pointsAndRank/badge-wheat-left.png differ diff --git a/src/static/images/pointsAndRank/badge-wheat-right.png b/src/static/images/pointsAndRank/badge-wheat-right.png new file mode 100644 index 00000000..88331a46 Binary files /dev/null and b/src/static/images/pointsAndRank/badge-wheat-right.png differ diff --git a/src/static/images/pointsAndRank/bg.png b/src/static/images/pointsAndRank/bg.png new file mode 100644 index 00000000..7bcccbf4 Binary files /dev/null and b/src/static/images/pointsAndRank/bg.png differ diff --git a/src/types/global.d.ts b/src/types/global.d.ts new file mode 100644 index 00000000..049d9855 --- /dev/null +++ b/src/types/global.d.ts @@ -0,0 +1,20 @@ +// 声明图片文件模块 +declare module '*.jpg' { + const src: string; + export default src; +} + +declare module '*.jpeg' { + const src: string; + export default src; +} + +declare module '*.png' { + const src: string; + export default src; +} + +declare module '*.gif' { + const src: string; + export default src; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..ae61526a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + // Vue 项目常用配置(根据实际版本调整) + "moduleResolution": "NodeNext", // 或 "Node" + "module": "NodeNext", // 与 moduleResolution 保持一致 + "target": "ES2022", + "jsx": "preserve", + "lib": ["ES2022", "DOM"], + "baseUrl": ".", // 表示以 tsconfig.json 所在目录为基准 + "types": ["vue"], // 确保包含 vue 类型定义 + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] +} \ No newline at end of file diff --git a/vite.config.js b/vite.config.js index 6f7b2c3d..e2ccf4bc 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,8 +1,14 @@ -import { defineConfig } from 'vite' +import { + defineConfig +} from 'vite' import uni from '@dcloudio/vite-plugin-uni' -// https://vitejs.dev/config/ +import path from 'path' export default defineConfig({ - plugins: [ - uni(), - ], -}) + plugins: [uni()], + // 新增服务器配置,指定启动端口 + server: { + port: 5174, + strictPort: true, // 可选:端口被占用时直接报错(避免自动切换端口) + open: false // 可选:启动后自动打开浏览器(按需开启) + } +}) \ No newline at end of file