x
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
# 开发环境
|
||||
ENV = 'development'
|
||||
# 'development'
|
||||
|
||||
|
||||
# VITE_APP_BASE_API_Url = 'https://aits.jlbank.com.cn:7001'
|
||||
|
||||
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7002'
|
||||
|
||||
|
||||
|
||||
# VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7002'
|
||||
|
||||
|
||||
# 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_TRAAPP = 'http://25.64.32.150:9601'
|
||||
# dev
|
||||
#VITE_APP_BASE_H5_API_Url = 'http://25.18.122.65:7001'
|
||||
# sit
|
||||
# VITE_APP_BASE_H5_API_Url = 'http://25.18.122.91:9786'
|
||||
@@ -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'
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# 杨航开发环境
|
||||
ENV = 'development.yh'
|
||||
# 'development.yh'
|
||||
|
||||
# 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_API_Url = 'https://aitstest.jlbank.com.cn:7002'
|
||||
# VITE_APP_BASE_H5_API_Url_TRAEXAM = 'http://25.64.32.154:9604'
|
||||
# VITE_APP_BASE_H5_API_Url_TRAPRACTICE = 'http://25.64.32.156:9603'
|
||||
# VITE_APP_BASE_H5_API_Url_TRAAPP = 'http://25.64.32.154:9601'
|
||||
# VITE_APP_BASE_H5_API_Url_TRAAPP = 'http://25.64.32.158:9601'
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# 开发环境
|
||||
ENV = 'development'
|
||||
# 'development'
|
||||
|
||||
VITE_APP_BASE_API_Url = ''
|
||||
@@ -0,0 +1,5 @@
|
||||
# 生产环境
|
||||
ENV = 'production'
|
||||
|
||||
# base api
|
||||
VITE_APP_BASE_API_Url = 'https://aits.jlbank.com.cn:7001'
|
||||
@@ -0,0 +1,5 @@
|
||||
# SIT环境
|
||||
ENV = 'sit'
|
||||
|
||||
# base api
|
||||
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7002'
|
||||
@@ -0,0 +1,5 @@
|
||||
# UAT环境
|
||||
ENV = 'uat'
|
||||
|
||||
# base api
|
||||
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7003'
|
||||
@@ -0,0 +1,26 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
package-lock
|
||||
package-lock.json
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
unpackage
|
||||
*.local
|
||||
build
|
||||
release
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.svn
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -0,0 +1,9 @@
|
||||
npm install --registry=http://25.12.10.69:8081/repository/aliyun-npm/
|
||||
|
||||
npm 淘宝源下载 :npm config set registry https://registry.npmmirror.com
|
||||
|
||||
npm config set registry http://25.12.10.69:8081/repository/aliyun-npm/
|
||||
|
||||
|
||||
npm install --registry=https://registry.npmmirror.com
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script>
|
||||
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
|
||||
CSS.supports('top: constant(a)'))
|
||||
document.write(
|
||||
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
||||
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
|
||||
</script>
|
||||
<title></title>
|
||||
<!--preload-links-->
|
||||
<!--app-context-->
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"><!--app-html--></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "tra-app",
|
||||
"version": "0.0.1",
|
||||
"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",
|
||||
"build:app-plus:dev": "uni build -p app-plus --mode development",
|
||||
"build:app-plus:develop": "uni build -p app-plus --mode development",
|
||||
"build:app-plus:sit": "uni build -p app-plus --mode sit",
|
||||
"build:app-plus:uat": "uni build -p app-plus --mode uat",
|
||||
"build:app-plus:prod": "uni build -p app-plus",
|
||||
"build:h5:dev": "uni build -p h5 --mode development_h5",
|
||||
"build:h5": "uni build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dcloudio/uni-app": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-app-harmony": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-app-plus": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-components": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-h5": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-mp-alipay": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-mp-baidu": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-mp-harmony": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-mp-jd": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-mp-kuaishou": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-mp-lark": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-mp-qq": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-mp-toutiao": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-mp-weixin": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-mp-xhs": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-quickapp-webview": "3.0.0-4060620250520001",
|
||||
"crypto-js": "^3.1.9-1",
|
||||
"dompurify": "^3.3.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"pinia": "^2.3.1",
|
||||
"terser": "^5.42.0",
|
||||
"uuid": "^11.1.0",
|
||||
"vue": "^3.5.11",
|
||||
"vue-i18n": "^9.1.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dcloudio/types": "^3.4.8",
|
||||
"@dcloudio/uni-automator": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-cli-shared": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-stacktracey": "3.0.0-4060620250520001",
|
||||
"@dcloudio/vite-plugin-uni": "3.0.0-4060620250520001",
|
||||
"@vue/runtime-core": "^3.4.21",
|
||||
"sass": "1.77.0",
|
||||
"vite": "5.2.8"
|
||||
}
|
||||
}
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
/// <reference types='@dcloudio/types' />
|
||||
import 'vue'
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
type Hooks = App.AppInstance & Page.PageInstance;
|
||||
|
||||
interface ComponentCustomOptions extends Hooks {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<script>
|
||||
export default {
|
||||
onLaunch: function () {
|
||||
uni.onTabBarMidButtonTap(() => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/index/dialog',
|
||||
animationType: 'slide-in-bottom'
|
||||
});
|
||||
});
|
||||
},
|
||||
onShow: function () {
|
||||
|
||||
},
|
||||
onHide: function () {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* 注意要写在第一行,同时给style标签加入lang="scss"属性 */
|
||||
// @import "@/uni_modules/uview-ui/index.scss";
|
||||
|
||||
@font-face {
|
||||
font-family: 'PingFangSC';
|
||||
src: url('@/static/font/PingFang-SC.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
view {
|
||||
box-sizing: border-box;
|
||||
font-family: 'PingFangSC';
|
||||
}
|
||||
|
||||
// 解决uview离线环境下没有图标
|
||||
@font-face {
|
||||
font-family: 'uicon-iconfont';
|
||||
src: url('@/static/font/font_2225171_8kdcwk4po24.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.max_page {
|
||||
min-height: 100vh;
|
||||
}
|
||||
.mr-10 {
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
.mb-8 {
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
.pt-8 {
|
||||
padding-top: 8rpx;
|
||||
}
|
||||
.mb-10 {
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
.mb-18 {
|
||||
margin-bottom: 18rpx;
|
||||
}
|
||||
.mb-64 {
|
||||
margin-bottom: 64rpx;
|
||||
}
|
||||
.mb-30 {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
.mb-36 {
|
||||
margin-bottom: 36rpx;
|
||||
}
|
||||
.mb-4 {
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
.fz-30 {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
.fz-28 {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.fz-24 {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.w_100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.h_100 {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.flex-jcsb {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.flex-c-c {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.tar {
|
||||
text-align: right;
|
||||
}
|
||||
.fw-600 {
|
||||
font-weight: 600;
|
||||
}
|
||||
.fw-400 {
|
||||
font-weight: 400;
|
||||
}
|
||||
.img_100 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.color-666{
|
||||
color: #666666;
|
||||
}
|
||||
.fl1 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.font_pf {
|
||||
font-family: 'PingFangSC';
|
||||
}
|
||||
|
||||
.ellipsis-text {
|
||||
white-space: nowrap;
|
||||
/* 强制不换行 */
|
||||
overflow: hidden;
|
||||
/* 超出部分隐藏 */
|
||||
text-overflow: ellipsis;
|
||||
/* 超出部分显示省略号 */
|
||||
}
|
||||
|
||||
/* 多行文本溢出 */
|
||||
.multi-line-ellipsis {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
/* 显示的行数 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.no-overflow {
|
||||
overflow: hidden;
|
||||
}
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.maoh{
|
||||
margin-right: 8rpx;
|
||||
margin-left: 2rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"version": "1",
|
||||
"prompt": "template",
|
||||
"title": "个人信息保护提示",
|
||||
"message": "欢迎使用吉AI学!<br/> 请你务必审慎阅读、充分理解<a href=\"https://aits.jlbank.com.cn:7001/traapp/dfAgtInfo/queryValidDfAgtHtml\">《用户隐私协议》</a>各条款,帮助您了解我们为您提供的服务、我们如何处理个人信息以及您享有的权利。我们会严格按照相关法律法规要求,采取各种安全措施来保护您的个人信息。<br/> 如果你同意,请点击下面按钮开始接受我们的服务。<br/>1.为了保障软件的安全运行和账户安全,我们会申请手机您的设备信息、IP地址、WLAN MAC地址。<br/>2.上传或拍摄图片,需要使用您的媒体影音、图片、视频、音频、相机、等权限。<br/>3.为了实现AI问答、课程学习等APP内功能,我们需要使用您的麦克风权限。",
|
||||
"buttonAccept": "同意并接受",
|
||||
"buttonRefuse": "暂不同意",
|
||||
"hrefLoader": "system",
|
||||
"backToExit":"true",
|
||||
"second": {
|
||||
"title": "确认提示",
|
||||
"message": " 进入应用前,你需先同意<a href=\"https://aits.jlbank.com.cn:7001/traapp/dfAgtInfo/queryValidDfAgtHtml\">《用户隐私协议》</a>,否则将退出应用。",
|
||||
"buttonAccept": "同意并继续",
|
||||
"buttonRefuse": "退出应用"
|
||||
},
|
||||
"disagreeMode":{
|
||||
"support": false,
|
||||
"loadNativePlugins": false,
|
||||
"visitorEntry": false,
|
||||
"showAlways": false
|
||||
},
|
||||
"styles": {
|
||||
"backgroundColor": "#fff",
|
||||
"borderRadius":"5px",
|
||||
"title": {
|
||||
"color": "#000"
|
||||
},
|
||||
"buttonAccept": {
|
||||
"color": "#F91B59"
|
||||
},
|
||||
"buttonRefuse": {
|
||||
"color": "#333"
|
||||
},
|
||||
"buttonVisitor": {
|
||||
"color": "#00ffff"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import request from '@/api/request'
|
||||
|
||||
|
||||
// 1.本月/本年/累计 学习信息查询接口
|
||||
export const queryStudyStatistics = (data) => {
|
||||
return request({
|
||||
url: '/traapp/traStudyAnalysis/queryStudyStatistics',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 课程推荐查询接口
|
||||
export const queryRecmdCrsInfo = (data) => {
|
||||
return request({
|
||||
url: '/traapp/traStudyAnalysis/queryRecmdCrsInfo',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 4.维度分析查询接口
|
||||
export const queryDimesionInfo = (data) => {
|
||||
return request({
|
||||
url: '/traapp/traStudyAnalysis/queryDimesionInfo',
|
||||
method: 'post',
|
||||
timeout: 60000,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 5.学习习惯查询接口
|
||||
export const queryHabitInfo = (data) => {
|
||||
return request({
|
||||
url: '/traapp/traStudyAnalysis/queryHabitInfo',
|
||||
method: 'post',
|
||||
timeout: 60000,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 6.学习建议查询接口
|
||||
export const querySuggestInfo = (data) => {
|
||||
return request({
|
||||
url: '/traapp/traStudyAnalysis/querySuggestInfo',
|
||||
method: 'post',
|
||||
timeout: 60000,
|
||||
data
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
|
||||
import { getToken } from '@/common/common.js'
|
||||
import { get_base_url } from '@/api/request'
|
||||
|
||||
const base_url = get_base_url();
|
||||
const token = getToken();
|
||||
// 基础配置
|
||||
const baseConfig = {
|
||||
enableChunked: true, // 必须开启分块传输
|
||||
responseType: 'arraybuffer', // 微信小程序需用arraybuffer
|
||||
timeout: 30000 // 超时时间延长
|
||||
}
|
||||
|
||||
// 流式请求核心实现
|
||||
function streamRequest(options) {
|
||||
const {
|
||||
url,
|
||||
method = 'GET',
|
||||
data,
|
||||
headers = {},
|
||||
onChunk,
|
||||
onComplete,
|
||||
onError
|
||||
} = options
|
||||
|
||||
// headers['Accept'] = 'text/event-stream'
|
||||
headers['Content-Type'] = 'text/event-stream'
|
||||
headers['summary'] = token
|
||||
|
||||
// 创建请求任务
|
||||
const requestTask = uni.request({
|
||||
url,
|
||||
method,
|
||||
data,
|
||||
header: headers,
|
||||
responseType: 'arraybuffer',
|
||||
...baseConfig,
|
||||
success: (res) => {
|
||||
console.log(res.data)
|
||||
onComplete?.(res)
|
||||
},
|
||||
fail: (err) => {
|
||||
onError?.(err)
|
||||
}
|
||||
})
|
||||
console.log(requestTask)
|
||||
// 流式数据处理器
|
||||
let bufferCache = ''
|
||||
requestTask.onChunkReceived((res) => {
|
||||
try {
|
||||
// ArrayBuffer转字符串(兼容多平台)
|
||||
const uint8Array = new Uint8Array(res.data)
|
||||
const chunkText = bufferCache +
|
||||
String.fromCharCode.apply(null, uint8Array)
|
||||
|
||||
// 处理SSE格式数据(data:开头)
|
||||
const events = chunkText.split('\n\n')
|
||||
bufferCache = events.pop() || '' // 缓存不完整数据
|
||||
|
||||
events.forEach(event => {
|
||||
if (event.startsWith('data:')) {
|
||||
onChunk?.(event.substring(5).trim())
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
onError?.(e)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
abort: () => requestTask.abort()
|
||||
}
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
export const demoUsage = (data) => {
|
||||
const controller = streamRequest({
|
||||
url: `${base_url}/trastudy/intgask/askAbout`,
|
||||
data: data,
|
||||
onChunk: (chunk) => {
|
||||
console.log('收到数据块:', chunk)
|
||||
// 实时更新UI逻辑
|
||||
},
|
||||
onComplete: () => console.log('传输完成'),
|
||||
onError: (err) => console.error('错误:', err)
|
||||
})
|
||||
|
||||
// 需要中断时调用
|
||||
// controller.abort()
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import request from '@/api/request'
|
||||
|
||||
// 获取list
|
||||
export const leaderBoardApi = (data) => {
|
||||
return request({
|
||||
url: '/traapp/myLeaderBoard/leaderBoard ',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,272 @@
|
||||
import {
|
||||
get_base_url,
|
||||
REQUES_ERROR_CODES,
|
||||
goto_login_fun
|
||||
} from '@/api/request'
|
||||
import {
|
||||
getToken
|
||||
} from '@/common/common.js'
|
||||
import common from '@/common/common';
|
||||
|
||||
|
||||
export const uploadFile = (file, data) => {
|
||||
const url = '/traoss/tras3/upload';
|
||||
const base_url = get_base_url(url);
|
||||
const token = getToken();
|
||||
const header = {};
|
||||
console.log('file', file);
|
||||
if (token)
|
||||
header['summary'] = token
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: `${base_url}${url}`,
|
||||
header: header,
|
||||
filePath: file,
|
||||
name: 'file',
|
||||
formData: data,
|
||||
success(result) {
|
||||
try {
|
||||
const res = JSON.parse(result.data);
|
||||
if (res.rtnCode === '0000') {
|
||||
resolve(res.body);
|
||||
} else {
|
||||
reject(new Error(res.msg));
|
||||
}
|
||||
} catch (error) {
|
||||
reject(new Error('Failed to parse response'));
|
||||
}
|
||||
},
|
||||
fail(error) {
|
||||
console.log('error', error);
|
||||
reject(new Error('Upload failed'));
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
export const getPreviewFileUrl = (id) => {
|
||||
return get_base_url() + `/traoss/tras3/show/${id}`
|
||||
}
|
||||
// 对象存储下载 /traoss/tras3/show/{文件ID}
|
||||
export function downloadFile(id) {
|
||||
const token = getToken();
|
||||
const header = {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
};
|
||||
if (token)
|
||||
header['summary'] = token
|
||||
let baseUrl = get_base_url();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.downloadFile({
|
||||
url: `${baseUrl}/traoss/tras3/show/${id}`,
|
||||
header,
|
||||
success(result) {
|
||||
resolve(result)
|
||||
},
|
||||
fail(error) {
|
||||
reject(new Error(error));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 上传音频 通用
|
||||
export const commonUploadVoiceFile = (file, url, data = {}, fileKey = 'voice') => {
|
||||
const base_url = get_base_url(url);
|
||||
const token = getToken();
|
||||
const header = {
|
||||
"Content-Type": "multipart/form-data; charset=UTF-8",
|
||||
};
|
||||
if (token)
|
||||
header['summary'] = token
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: `${base_url}${url}`,
|
||||
header: header,
|
||||
filePath: file,
|
||||
name: fileKey,
|
||||
formData: data,
|
||||
timeout: 60000,
|
||||
success(result) {
|
||||
try {
|
||||
console.log(result)
|
||||
const res = JSON.parse(result.data);
|
||||
if (res.rtnCode === '0000') {
|
||||
return resolve(res);
|
||||
} else if (REQUES_ERROR_CODES['NO_LOGIN'].includes(res.rtnCode)) {
|
||||
goto_login_fun()
|
||||
return reject('NO_LOGIN');
|
||||
} else if (res.rtnCode === '0004') {
|
||||
return reject('N');
|
||||
}
|
||||
return reject('Other');
|
||||
} catch (error) {
|
||||
reject(new Error('Failed to parse response'));
|
||||
}
|
||||
},
|
||||
fail(error) {
|
||||
return reject('Other');
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
// 上传音频 翻译文本
|
||||
export const uploadVoiceFile = (file) => {
|
||||
const url = '/trastudy/traStdyInfo/audioTranscriptions';
|
||||
const base_url = get_base_url(url);
|
||||
const token = getToken();
|
||||
const header = {
|
||||
"Content-Type": "multipart/form-data; charset=UTF-8",
|
||||
};
|
||||
if (token)
|
||||
header['summary'] = token
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: `${base_url}${url}`,
|
||||
header: header,
|
||||
filePath: file,
|
||||
name: 'voice',
|
||||
success(result) {
|
||||
try {
|
||||
const res = JSON.parse(result.data);
|
||||
if (res.rtnCode === '0000') {
|
||||
resolve(result);
|
||||
} else {
|
||||
reject(new Error(res.message));
|
||||
}
|
||||
} catch (error) {
|
||||
reject(new Error('Failed to parse response'));
|
||||
}
|
||||
},
|
||||
fail(error) {
|
||||
reject(new Error('Upload failed'));
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
// 获取音频 通用
|
||||
export function downloadVoiceFile(url) {
|
||||
const token = getToken();
|
||||
const header = {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
};
|
||||
if (token)
|
||||
header['summary'] = token
|
||||
let baseUrl = get_base_url(url);
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.downloadFile({
|
||||
url: `${baseUrl}${url}`,
|
||||
header,
|
||||
timeout: 180000,
|
||||
success(result) {
|
||||
console.log(result)
|
||||
resolve(result)
|
||||
},
|
||||
fail(error) {
|
||||
console.log(error)
|
||||
reject(new Error(error));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
// 获取音频 通用参数
|
||||
export function downloadVoiceParams(url) {
|
||||
const token = getToken();
|
||||
const header = {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
};
|
||||
if (token)
|
||||
header['summary'] = token
|
||||
let baseUrl = get_base_url(url);
|
||||
return {
|
||||
url: `${baseUrl}${url}`,
|
||||
header,
|
||||
timeout: 180000,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function getAudioByText(data) {
|
||||
const token = getToken();
|
||||
const header = {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
};
|
||||
if (token)
|
||||
header['summary'] = token
|
||||
let baseUrl = get_base_url();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url: `${baseUrl}/trastudy/traStdyInfo/readContent`,
|
||||
header,
|
||||
method: "POST",
|
||||
data,
|
||||
responseType: "arraybuffer",
|
||||
success(result) {
|
||||
if (result.statusCode === 200) {
|
||||
resolve(result.data)
|
||||
} else {
|
||||
reject(new Error(result))
|
||||
}
|
||||
},
|
||||
fail(error) {
|
||||
reject(new Error(error));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 对象存储预览 /traoss/tras3/show/{文件ID}
|
||||
export function previewFile(id, responseType = 'arraybuffer') {
|
||||
const token = getToken();
|
||||
const header = {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
};
|
||||
if (token)
|
||||
header['summary'] = token
|
||||
let baseUrl = get_base_url();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url: `${baseUrl}/traoss/tras3/show/${id}`,
|
||||
header,
|
||||
responseType,
|
||||
success(result) {
|
||||
resolve(result)
|
||||
},
|
||||
fail(error) {
|
||||
reject(new Error(error));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
// 缩略图预览 /traoss/tras3/getCacheThumbImage/{fileId}/{width}/{height}
|
||||
export function previewCacheThumbImage(id, width = 30, height = 30) {
|
||||
const token = getToken();
|
||||
const header = {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
};
|
||||
if (token)
|
||||
header['summary'] = token
|
||||
let baseUrl = get_base_url();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url: `${baseUrl}/traoss/tras3/getCacheThumbImage/${id}/${width}/${height}`,
|
||||
header,
|
||||
success(result) {
|
||||
if (result.data.rtnCode === '0000') {
|
||||
let base64 = `data:image/png;base64,` + result.data.body
|
||||
resolve(base64)
|
||||
} else {
|
||||
reject({
|
||||
error: result.data.message
|
||||
})
|
||||
}
|
||||
},
|
||||
fail(error) {
|
||||
reject(new Error(error));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
import request from '@/api/request'
|
||||
|
||||
const base_url = '/traapp'
|
||||
const course_base_url = '/trastudy'
|
||||
|
||||
|
||||
// 获取课程分类树接口
|
||||
export const getCourseTypeListApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/app/traCrsCatalogInfo/courseQueryTraCrsCatalogInfoList',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 获取生产线分类树接口
|
||||
export const getPrdLineTypeListApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/app/traPrdLineInfo/courseQueryTraPrdLineInfoList',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 获取标签分类树 接口
|
||||
export const getTraTagListApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/app/traTagCatalogInfo/queryTraTagCatalogInfoList',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 全部模块
|
||||
export const queryCrsInfoByCatalogApi = (data) => {
|
||||
return request({
|
||||
url: course_base_url + '/crsInfo/queryCrsInfoByCatalog',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 生产线模块-
|
||||
export const queryCrsInfoByPrdLineApi = (data) => {
|
||||
return request({
|
||||
url: course_base_url + '/crsInfo/queryCrsInfoByPrdLine',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 最新
|
||||
export const queryCrsInfoByLastnewApi = (data) => {
|
||||
return request({
|
||||
url: course_base_url + '/crsInfo/queryCrsInfoByLastnew',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 最热
|
||||
export const queryCrsInfoByPopularApi = (data) => {
|
||||
return request({
|
||||
url: course_base_url + '/crsInfo/queryCrsInfoByPopular',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 推荐
|
||||
export const queryCrsInfoByRecmdApi = (data) => {
|
||||
return request({
|
||||
url: course_base_url + '/crsInfo/queryCrsInfoByRecmd',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import request from '@/api/request'
|
||||
|
||||
const module_url = '/traapp'
|
||||
|
||||
// AITS-A-4006--APP课程详情查询-含知识点学习状态数据
|
||||
export const getCourseDetailApi = (data) => {
|
||||
return request({
|
||||
url: module_url + '/crsInfo/queryCrsInfoDetail',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// AITS-A-4007--APP课程评价列表数据查询(20250723预计废弃)
|
||||
export const qryTraCrsBbsDataByCrsNum = (data) => {
|
||||
return request({
|
||||
url: module_url + '/traCrsBbsInfo/qryTraCrsBbsDataByCrsNum',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
//王洋新写
|
||||
export const queryTraCrsBbsInfoByCrsId = (data) => {
|
||||
return request({
|
||||
url: module_url + '/traCrsBbsInfo/queryTraCrsBbsInfoByCrsId',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// AITS-A-4008--APP课程评价下回复评论时刷新二级评论列表数据接口
|
||||
export const qryChildTraCrsBbsInfo = (data) => {
|
||||
return request({
|
||||
url: module_url + '/traCrsBbsInfo/qryChildTraCrsBbsInfo',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// AITS-A-4009--APP课程评价回复评论接口
|
||||
export const replyTraCrsBbsInfo = (data) => {
|
||||
return request({
|
||||
url: module_url + '/traCrsBbsInfo/replyTraCrsBbsInfo',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// AITS-A-4010--APP课程评价接口
|
||||
export const publishCrsEvaluation = (data) => {
|
||||
return request({
|
||||
url: module_url + '/traCrsBbsInfo/publishCrsEvaluation',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 考试排行榜/traexam/traCrsPapers/queryTraCrsRankingList
|
||||
export const queryTraCrsRankingList = (data) => {
|
||||
return request({
|
||||
url: '/traexam/traCrsPapers/queryTraCrsRankingList',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import request from '@/api/request'
|
||||
const base_url = '/traexam'
|
||||
|
||||
|
||||
/**学习*/
|
||||
// 学习列表页
|
||||
export const queryStdyRecordPaging = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traRecord/queryStdyRecordPaging',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
// 学习单项列表 crsId
|
||||
export const queryStdyBatchByCrsId = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traRecord/queryStdyBatchByCrsId',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 学习详情 通过 stdyId 查询
|
||||
export const queryTraStudyExecuteRecord = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traCrsPapers/queryTraStudyExecuteRecord',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
/**练习*/
|
||||
// 练习列表页
|
||||
export const queryPracticeRecordPaging = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traRecord/queryPracticeRecordPaging',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
// 练习每一项列表
|
||||
export const queryPracticeHis = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traRecord/queryPracticeHis',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
// 练习详情 通过 exrId 查询
|
||||
export const queryPracticeRecordByExrId = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traCrsPractice/queryPracticeRecordByExrId',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**考试*/
|
||||
export const queryCrsExamRecordPaging = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traRecord/queryCrsExamRecordPaging',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
// 考试每一项列表
|
||||
export const queryCrsExamHisByExamId = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traRecord/queryCrsExamHisByExamId',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**考试中心考试*/
|
||||
// 考试列表页
|
||||
export const queryExamRecordPaging = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traRecord/queryExamRecordPaging',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
// 考试每一项列表
|
||||
export const queryExamHisByExamId = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traRecord/queryExamHisByExamId',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**竞赛*/
|
||||
|
||||
// 查询竞赛记录
|
||||
export const queryTraCompetitionRecordPaging = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traCompetitionInfo/queryTraCompetitionRecordPaging',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
// 查询竞赛答题列表
|
||||
export const queryTraCompetitionRecordInfoPaging = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traCompetitionInfo/queryTraCompetitionRecordInfoPaging',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import request from '@/api/request'
|
||||
|
||||
|
||||
|
||||
// 获取问题list
|
||||
export const queryHotQuestionApi = (data) => {
|
||||
return request({
|
||||
// url: '/traask/traAskchat/queryHotQuestion',
|
||||
url: '/traask/traAskchat/queryHotQuestions',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 获取问答历史
|
||||
export const queryAskBatchSessionPaging = (data) => {
|
||||
return request({
|
||||
url: '/traask/traAskchatSession/queryAskBatchSessionPaging',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 问答详情 /traAskchatSession/queryAskHisByBatchIdPaging
|
||||
export const queryAskHisByBatchIdPaging = (data) => {
|
||||
return request({
|
||||
url: '/traask/traAskchatSession/queryAskHisByBatchId',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 问答反馈
|
||||
export const insertTraAskFeedback = (data) => {
|
||||
return request({
|
||||
url: '/traask/traAskFeedback/insertTraAskFeedback',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 取消反馈 取消踩 /traAskFeedback/updateDownvoteStat
|
||||
export const updateDownvoteStat = (data) => {
|
||||
return request({
|
||||
url: '/traask/traAskFeedback/updateDownvoteStat',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 赞 /取消赞/traAskFeedback/updateDownvoteStat
|
||||
export const updateLikeStat = (data) => {
|
||||
return request({
|
||||
url: '/traask/traAskFeedback/updateLikeStat',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// example /traask/traTestchat/chatVoice
|
||||
export const chatVoice = (data) => {
|
||||
return request({
|
||||
url: '/traask/traTestchat/chatVoice',
|
||||
method: 'post',
|
||||
timeout: 120000,
|
||||
data
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
|
||||
import CryptoJS from 'crypto-js'// 引入AES加密库 npm install crypto-js@3.1.9-1 -save
|
||||
// import { JSEncrypt } from 'jsencrypt'; // npm install jsencrypt -save
|
||||
|
||||
const RsaPublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCZf8e1tzYTSr7KciAHH2KrE3O13ftuv3rHRRz7MztYXirMdLquocTNSJ5BAoj1H3V8fvFtfmN6BkuB6XnQrbY5heOzZZQWvO4qAa0EktwkLQUwCkYJfaQNc1Iw4wZFt4VO7U8+LLo+IO7jtxmaNIBqJm8laDc1C6zw2ETZD4bQ6wIDAQAB"
|
||||
const iv = CryptoJS.enc.Utf8.parse('1234567812345678');
|
||||
//定义rsa加密类
|
||||
// const crypt = new JSEncrypt();
|
||||
// crypt.setPublicKey(RsaPublicKey); // 设置公钥
|
||||
|
||||
|
||||
// 生成一个随机的128位(16字节)AES密钥
|
||||
const generateAESKey = () => {
|
||||
return CryptoJS.lib.WordArray.random(16);
|
||||
}
|
||||
// 转化生成AES密钥成base64字符串
|
||||
const getGenerateAESKeyBase64 = (aesKey) => aesKey.toString(CryptoJS.enc.Base64)
|
||||
|
||||
// 执行rsa加密
|
||||
const encryptRSA = (str) => crypt.encrypt(str)
|
||||
// 执行rsa解密
|
||||
const decryptRSA = (str) => crypt.decrypt(str)
|
||||
|
||||
// 执行aes加密
|
||||
export const encryptAES = (data, aesKeyBase64, iv) => {
|
||||
const key = CryptoJS.enc.Utf8.parse(aesKeyBase64);
|
||||
return CryptoJS.AES.encrypt(JSON.stringify(data), key, {
|
||||
iv: iv,
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.Pkcs7
|
||||
}).toString();
|
||||
}
|
||||
// aes解密
|
||||
const decryptAES = (ciphertext, aesKeyBase64, iv) => {
|
||||
const key = CryptoJS.enc.Utf8.parse(aesKeyBase64);
|
||||
const bytes = CryptoJS.AES.decrypt(ciphertext, key, {
|
||||
iv: iv,
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.Pkcs7
|
||||
});
|
||||
return JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
|
||||
}
|
||||
|
||||
// 加密过程
|
||||
export const encryptData = (data={}) => {
|
||||
const aesKey = generateAESKey(); // 获取随机密钥key
|
||||
const aesKeyBase64 = getGenerateAESKeyBase64(aesKey) // 变成base64
|
||||
const key = encryptRSA(aesKeyBase64)
|
||||
const ciphertext = encryptAES(data, aesKeyBase64, iv);
|
||||
const decryptedData = decryptAES(ciphertext, aesKeyBase64, iv);
|
||||
return {text:ciphertext, key:key, aesKeyBase64:aesKeyBase64}
|
||||
}
|
||||
|
||||
// 解密过程
|
||||
export const decryptData= (aesStr, aesKey) => {
|
||||
return decryptAES(aesStr, aesKey, iv)
|
||||
}
|
||||
|
||||
/**
|
||||
* AES加密
|
||||
* @param plainText 明文
|
||||
* @param keyInBase64Str base64编码后的key
|
||||
* @returns {string} base64编码后的密文
|
||||
*/
|
||||
export function encryptByAES(plainText, keyInBase64Str) {
|
||||
let key = CryptoJS.enc.Base64.parse(keyInBase64Str);
|
||||
let encrypted = CryptoJS.AES.encrypt(plainText, key, {
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
});
|
||||
// 这里的encrypted不是字符串,而是一个CipherParams对象
|
||||
return encrypted.ciphertext.toString(CryptoJS.enc.Base64);
|
||||
}
|
||||
|
||||
/**
|
||||
* AES解密
|
||||
* @param cipherText 密文
|
||||
* @param keyInBase64Str base64编码后的key
|
||||
* @return 明文
|
||||
*/
|
||||
export function decryptByAES(cipherText, keyInBase64Str) {
|
||||
let key = CryptoJS.enc.Base64.parse(keyInBase64Str);
|
||||
// 返回的是一个Word Array Object,其实就是Java里的字节数组
|
||||
let decrypted = CryptoJS.AES.decrypt(cipherText, key, {
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
});
|
||||
|
||||
return decrypted.toString(CryptoJS.enc.Utf8);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import request from '@/api/request'
|
||||
import { getToken } from '@/common/common.js'
|
||||
import { get_base_url } from '@/api/request'
|
||||
const base_url = '/traexam'
|
||||
|
||||
// 开始考试(试卷信息)
|
||||
export const startTraExamPapersInfo = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traExamPapers/startTraExamPapersInfo',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
// 提交试卷
|
||||
export const commitPaperExam = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traExamPapers/commitPaperExam',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 考试中心考试查分
|
||||
export const queryPaperExamScore = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traExamPapers/queryPaperExamScore',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
// 根据试卷id查询详情
|
||||
export const queryTraExamPapersByPapersId = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traExamPapers/queryTraExamPapersByPapersId',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// ↑↑↑↑↑↑↑↑↑考试中心考试↑↑↑↑↑↑↑
|
||||
|
||||
// ↓↓↓↓↓↓↓↓↓课程考试↓↓↓↓↓↓↓↓
|
||||
|
||||
export const startExamByCrs = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traCrsPapers/startExamByCrs',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
// 提交课程考试
|
||||
export const commitCrsExam = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traCrsPapers/commitCrsExam',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
// 试卷考试查分
|
||||
export const queryCrsExamScore = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traCrsPapers/queryCrsExamScore',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 试卷考试 排行榜
|
||||
export const queryPaperExamRankingList = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traExamPapers/queryPaperExamRankingList',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 反馈问题
|
||||
export const saveTraQnsFeedback = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traCrsPapers/saveTraQnsFeedback',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 考试过程中提交结果,后端用来缓存(考试中心考试和课程考试共用这个缓存接口)
|
||||
export const commitPaperCache = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traExamPapers/commitPaperCache',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import request from '@/api/request'
|
||||
const base_url = '/trastudy'
|
||||
|
||||
// 课程收藏分页查询
|
||||
export const queryTraPersonalCollectionCrsPaging = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPersonalCollection/queryTraPersonalCollectionCrsPaging',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 任务收藏分页查询
|
||||
export const queryTraPersonalCollectionTaskPaging = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPersonalCollection/queryTraPersonalCollectionTaskPaging',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 根据收藏ID删除收藏
|
||||
export const deleteTraPersonalCollectionById = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPersonalCollection/deleteTraPersonalCollectionById',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
新增任务收藏
|
||||
入参:
|
||||
collectTyp 收藏类型 必须 String 01--课程,02--任务
|
||||
collectBusId 收藏业务ID 必须 String
|
||||
**/
|
||||
export const insertTraPersonalCollectionTask = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPersonalCollection/insertTraPersonalCollectionTask',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
import request from '@/api/request'
|
||||
|
||||
|
||||
// 获取首页统计数据方法(查询个人学习统计信息 )
|
||||
export const getStatisticsData = (data) => {
|
||||
/**
|
||||
userId 学员ID String
|
||||
stdtTmLen 本年学习时长 double
|
||||
accmPracticeCnt 累计训练次数 int
|
||||
accmExamCnt 累计考试次数 int
|
||||
**/
|
||||
return request({
|
||||
url: '/traapp/traStdyInfo/queryTraStdyStatisticsPerson',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 最新课程查询接口
|
||||
export const queryCrsInfoByLastnew = (data) => {
|
||||
/**
|
||||
crsNum 课程编号 String
|
||||
crsName 课程名称 String
|
||||
tagId 课程分类 String
|
||||
prdLineId 生产线分类 String
|
||||
issuTm 发布日期 String
|
||||
imgId 课程封面ID String
|
||||
accmStdyCnt 累计学习次数 int
|
||||
evalGrade 课程评分 Bigdecimal
|
||||
tagCataList 课程标签集 List<Object>
|
||||
tagId 标签ID String
|
||||
tagName 标签名称 String
|
||||
**/
|
||||
return request({
|
||||
url: '/traapp/mycrs/queryCrsInfoByLastnew',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 热门课程查询接口
|
||||
export const queryCrsInfoByPopular = (data) => {
|
||||
/**
|
||||
tagId 标签ID(可选填),为空时查全部 必输项:false 类型:String
|
||||
出参:
|
||||
tagId 标签ID(可选填),为空时查全部 必输项:false 类型:String
|
||||
出参:
|
||||
crsId 课程ID StringcrsNum 课程编号 String
|
||||
crsName 课程名称 String
|
||||
tagId 课程分类 String
|
||||
prdLineId 生产线分类 String
|
||||
issuTm 发布日期 String
|
||||
imgId 课程封面ID String
|
||||
accmStdyCnt 累计学习次数 int
|
||||
evalGrade 课程评分 Bigdecimal
|
||||
tagCataList 课程标签集 List<Object>
|
||||
tagId 标签ID String
|
||||
tagName 标签名称 String
|
||||
**/
|
||||
return request({
|
||||
url: '/traapp/mycrs/queryCrsInfoByPopular',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 推荐课程查询接口
|
||||
export const queryCrsInfoByRecmd = (data) => {
|
||||
/**
|
||||
crsNum 课程编号 String
|
||||
crsName 课程名称 String
|
||||
tagId 课程分类 String
|
||||
prdLineId 生产线分类 String
|
||||
issuTm 发布日期 String
|
||||
imgId 课程封面ID String
|
||||
accmStdyCnt 累计学习次数 int
|
||||
evalGrade 课程评分 Bigdecimal
|
||||
tagCataList 课程标签集 List<Object>
|
||||
tagId 标签ID String
|
||||
tagName 标签名称 String
|
||||
**/
|
||||
return request({
|
||||
url: '/traapp/mycrs/queryCrsInfoByRecmd',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import request from '@/api/request'
|
||||
|
||||
const base_url = '/traapp'
|
||||
|
||||
|
||||
// 学习时长
|
||||
export const getLeaderBoardStdyTmApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/myLeaderBoard/stdyTmLen',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 练习次数
|
||||
export const getLeaderBoardPractCntApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/myLeaderBoard/practCnt',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 考试次数
|
||||
export const getLeaderBoardExamCntApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/myLeaderBoard/examCnt',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 通关课程数
|
||||
export const getLeaderBoardCrsCntApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/myLeaderBoard/crsCnt',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import request from '@/api/request'
|
||||
const base_url = '/trastudy'
|
||||
|
||||
// 查询所有、未完成、已完成的任务(分页)
|
||||
export const queryAllTraTaskInfoByUserId = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traTaskInfo/queryAllTraTaskInfoByUserId',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 查询当前用户的课程完成情况
|
||||
export const queryAllTraTaskCrsInfo = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traTaskInfo/queryAllTraTaskCrsInfo',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 根据任务id查询这一条
|
||||
export const queryTraTaskInfoByTaskId = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traTaskInfo/queryTraTaskInfoByTaskId',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,239 @@
|
||||
import request from '@/api/request'
|
||||
import common from "@/common/common";
|
||||
import {
|
||||
queryCurrentValidMascotInfo
|
||||
} from "@/api/mascot.js"
|
||||
|
||||
import {
|
||||
queryCurrentStatus
|
||||
} from '@/api/pointsAndRank.js';
|
||||
|
||||
import {
|
||||
setToken,
|
||||
setUserInfo,
|
||||
getUserInfo,
|
||||
getToken
|
||||
} from "@/common/common";
|
||||
import {
|
||||
get_base_url
|
||||
} from '@/api/request.js'
|
||||
|
||||
// 账号登录接口
|
||||
export const useAccountLoginApp = (data) => request({
|
||||
url: '/traapp/access/userLogin',
|
||||
method: 'post',
|
||||
data
|
||||
}).then(async ({
|
||||
rtnCode,
|
||||
body
|
||||
}) => {
|
||||
if (rtnCode === '0000') {
|
||||
setToken(body)
|
||||
const res = await Promise.all([queryCurrentUser(), queryCurrentValidMascotInfo()
|
||||
// queryCurrentStatus()
|
||||
])
|
||||
const userInfo = getUserInfo()
|
||||
const mascotInfo = res[1]
|
||||
setUserInfo({
|
||||
...userInfo,
|
||||
'mascotInfo': mascotInfo
|
||||
})
|
||||
// 积分状态
|
||||
// const pointsStatus = res[2].body
|
||||
const pointsStatus = {}
|
||||
common.setValue('points_data', {
|
||||
badgeNum: pointsStatus['badgeNum'] ?? 0, // 徽章数
|
||||
currentPoints: pointsStatus['currentPoints'] ?? 0, // 当前积分
|
||||
currentRankAddr: pointsStatus['ossAddr'] ?? '', // 当前段位图片
|
||||
currentRankName: pointsStatus['currentRankName'] ?? '未知', // 当前段位名称
|
||||
})
|
||||
return {
|
||||
mascotState: !mascotInfo.mascotId
|
||||
}
|
||||
}
|
||||
return Promise.reject({
|
||||
rtnCode,
|
||||
body
|
||||
})
|
||||
}).catch(res => {
|
||||
return Promise.reject(res)
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取个人信息
|
||||
* 入参
|
||||
gender f是女 m男
|
||||
imageAddr: 图片
|
||||
loginName ID
|
||||
userName 姓名
|
||||
*/
|
||||
export const queryCurrentUser = (data) => request({
|
||||
url: '/traapp/access/queryCurrentUser',
|
||||
method: 'post',
|
||||
data
|
||||
}).then(res => {
|
||||
// deptId: ""
|
||||
// deptName: null
|
||||
// loginName: "ADMIN" // todo id
|
||||
// orgId: "00E200"
|
||||
// orgName: "金融科技部"
|
||||
// userId: "USER001"
|
||||
// userName: "ADMIN" / todo name
|
||||
// userStatus: "01"
|
||||
const userInfo = getUserInfo() ?? {}
|
||||
setUserInfo({
|
||||
...userInfo,
|
||||
...res.body
|
||||
})
|
||||
// console.log('预加载头像');
|
||||
const base_url = get_base_url()
|
||||
const token = getToken()
|
||||
// 预加载头像
|
||||
// #ifdef APP-PLUS
|
||||
// 更新用户信息中的头像路径
|
||||
const updateUserAvatar = (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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 生成唯一文件名
|
||||
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`;
|
||||
};
|
||||
|
||||
// 下载并保存头像
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 检查文件是否存在
|
||||
const checkFileExists = (filePath) => {
|
||||
return new Promise((resolve) => {
|
||||
uni.getFileInfo({
|
||||
filePath,
|
||||
success: () => resolve(true),
|
||||
fail: () => resolve(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 主逻辑
|
||||
(async () => {
|
||||
try {
|
||||
const pathName = res.body.imageAddr
|
||||
if (pathName) {
|
||||
const fileUrl = base_url + pathName + '?tk=' + token
|
||||
console.log('fileUrl', fileUrl);
|
||||
// 生成保存路径
|
||||
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
|
||||
|
||||
return res.body
|
||||
})
|
||||
|
||||
//获取隐私协议
|
||||
export const getYsxy = (data) => request({
|
||||
url: '/traapp/dfAgtInfo/queryValidDfAgt',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
|
||||
//获取服务条款
|
||||
export const getFutk = (data) => request({
|
||||
url: '/traapp/dfAgtInfo/queryValidDfAgtTerm',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
|
||||
// 获取验证码(作废)
|
||||
export const getLoginAppCheckCode = (data) => request({
|
||||
url: '/traapp/access/checkCode',
|
||||
method: 'get',
|
||||
data
|
||||
})
|
||||
|
||||
// 退出登录
|
||||
export const loginOut = (data) => request({
|
||||
url: '/traapp/access/loginOut',
|
||||
method: 'post',
|
||||
data
|
||||
}).then(() => {
|
||||
setToken(null)
|
||||
setUserInfo(null)
|
||||
common.setValue('me_statistics_data', null) // 我的页统计数据缓存
|
||||
common.setValue('index_statistics_data', null) // 首页统计数据缓存
|
||||
common.setValue('points_data', null) // 首页统计数据缓存
|
||||
})
|
||||
@@ -0,0 +1,112 @@
|
||||
import request from '@/api/request'
|
||||
import {
|
||||
get_base_url
|
||||
} from '@/api/request.js'
|
||||
import { getToken } from '@/common/common.js'
|
||||
const app_url = '/traapp'
|
||||
|
||||
|
||||
/**
|
||||
* 查询吉祥物列表
|
||||
* 出参:
|
||||
mascotId 吉祥物ID String
|
||||
mascotName 吉祥物名称 String
|
||||
mascotNameEn 吉祥物英文名称 String
|
||||
mascotDesc 吉祥物简介 String
|
||||
mascotNo 吉祥物编号 String
|
||||
imgAddr 吉祥物图片 String
|
||||
imgThumbAddr 吉祥物缩略图 String
|
||||
showOrder 显示顺序 int
|
||||
*/
|
||||
export const queryValidTraMascotInfo = (data) => {
|
||||
return request({
|
||||
url: app_url + '/traMascotInfo/queryValidTraMascotInfo',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置吉祥物
|
||||
* 入参:
|
||||
mascotId 必输项:true 类型:String
|
||||
*/
|
||||
export const setTraMascotInfo = (data) => {
|
||||
return request({
|
||||
url: app_url + '/traMascotInfo/setTraMascotInfo',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 查询当前已经选择的吉祥物
|
||||
// 检查文件是否存在
|
||||
const checkFileExists = (filePath) => {
|
||||
return new Promise((resolve) => {
|
||||
uni.getFileInfo({
|
||||
filePath,
|
||||
success: () => resolve(true),
|
||||
fail: () => resolve(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前已经选择的吉祥物
|
||||
* 出参:
|
||||
mascotId 吉祥物ID String
|
||||
mascotName 吉祥物名称 String
|
||||
mascotNameEn 吉祥物英文名称 String
|
||||
mascotDesc 吉祥物简介 String
|
||||
mascotNo 吉祥物编号 String
|
||||
imgAddr 吉祥物图片 String
|
||||
imgThumbAddr 吉祥物缩略图 String
|
||||
showOrder 显示顺序 int
|
||||
*/
|
||||
export const queryCurrentValidMascotInfo = (data) => {
|
||||
return request({
|
||||
url: app_url + '/traMascotInfo/queryCurrentValidMascotInfo',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
}).then(({
|
||||
body
|
||||
}) => {
|
||||
if (body.imgAddr) {
|
||||
console.log('body.imgAddrmascotNo');
|
||||
const base_url = get_base_url()
|
||||
const token = getToken()
|
||||
const pathName = body.imgAddr
|
||||
const fileUrl = base_url + pathName + '?tk=' + token
|
||||
const localFilePath = '_doc/mascot/' + body.mascotNo + '.png'
|
||||
console.log('fileUrl', fileUrl);
|
||||
// 将 Promise 回调函数声明为 async
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// 现在可以在这里使用 await 了
|
||||
const exists = await checkFileExists(localFilePath)
|
||||
if (exists) {
|
||||
// console.log('已存在,无需下载吉祥物')
|
||||
resolve({...body, imgAddr:localFilePath})
|
||||
} else {
|
||||
// console.log('开始下载吉祥物:', fileUrl)
|
||||
const dtask = plus.downloader.createDownload(fileUrl, {
|
||||
filename: localFilePath
|
||||
}, (d, status) => {
|
||||
if (status === 200) {
|
||||
console.log("保存路径:", d.filename)
|
||||
resolve({...body, imgAddr:d.filename})
|
||||
} else {
|
||||
console.error("文件下载失败:", status)
|
||||
reject(new Error(`下载失败,状态码: ${status}`))
|
||||
}
|
||||
})
|
||||
|
||||
dtask.start()
|
||||
}
|
||||
})
|
||||
}
|
||||
return body
|
||||
})
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
import request from '@/api/request'
|
||||
const base_url = '/trastudy'
|
||||
|
||||
// 查询当前用户的所有消息通知 (分页)
|
||||
export const queryTraPersonalMessageNoticePaging = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPersonalMessageNotice/queryTraPersonalMessageNoticePaging',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 读取单一消息
|
||||
export const readNotice = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPersonalMessageNotice/readNotice',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 一键读取所有消息(不展示)
|
||||
export const oneTimeRead = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPersonalMessageNotice/oneTimeRead',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
export const noticeMessageCount = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPersonalMessageNotice/noticeMessageCount',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
export const queryMessageNoticeClass = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPersonalMessageNotice/queryMessageNoticeClass',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 按照分类一键读取消息
|
||||
export const oneTimeReadByNoticeTyp = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPersonalMessageNotice/oneTimeReadByNoticeTyp',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
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) => {
|
||||
// base_url
|
||||
return request({
|
||||
url: base_url + '/traPointsInfo/addPointsByTask',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
// return request({
|
||||
// url: '/test/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
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import request from '@/api/request'
|
||||
const base_url = '/traapp'
|
||||
|
||||
// String actId,活动id
|
||||
export const queryPointsActivityDetail = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPointsActivity/queryPointsActivityDetail',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export const addPointsExchange = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPointsActivity/addPointsExchange',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
import request from '@/api/request'
|
||||
const base_url = '/traexam'
|
||||
|
||||
// 1.开始练习(课程练习)
|
||||
export const startPracticeByCrs = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traCrsPractice/startPracticeByCrs',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 2.课程练习答题
|
||||
export const practiceAnswer = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traCrsPractice/practiceAnswer',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 3.课程练习状态变更
|
||||
export const practiceCommit = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traCrsPractice/practiceCommit',
|
||||
method: 'post',
|
||||
suppressErrors:true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 4.课程练习记录
|
||||
export const queryPracticeRecordByExrId = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traCrsPractice/queryPracticeRecordByExrId',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 5.练习时候查询问答题结果
|
||||
export const queryCrsPracticeAnswerResult = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traCrsPractice/queryCrsPracticeAnswerResult',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import request from '@/api/request'
|
||||
const base_url = '/trastudy'
|
||||
|
||||
|
||||
export const queryTraStdyInfoPreviewPaging = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traStdyInfoPreview/queryTraStdyInfoPreviewPaging',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
export const queryTraListPreviewPaging = (data) => {
|
||||
return request({
|
||||
url: '/trapractice/traPartnerInfo/queryTraPartnerPreviewPaging',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,154 @@
|
||||
import {
|
||||
getToken
|
||||
} from '@/common/common.js'
|
||||
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 (['development', 'development.rd', 'development.yh', 'development.ty'].includes(ENV.MODE)) { // 开发环境
|
||||
// #ifdef H5
|
||||
if (url) {
|
||||
let _str = (url.split('/')[0] || url.split('/')[1])?.toUpperCase()
|
||||
return ENV[`VITE_APP_BASE_H5_API_Url_${_str}`] || ENV.VITE_APP_BASE_H5_API_Url || ENV
|
||||
.VITE_APP_BASE_API_Url
|
||||
} else {
|
||||
return ENV.VITE_APP_BASE_H5_API_Url || ENV.VITE_APP_BASE_API_Url
|
||||
}
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 请求错误码
|
||||
export const REQUES_ERROR_CODES = {
|
||||
NO_LOGIN: ['QQ0005', '0005'] //登录
|
||||
};
|
||||
|
||||
// 去登录弹窗状态
|
||||
let no_login_show_modal_state = false
|
||||
// 去登录方法
|
||||
export const goto_login_fun = (from = 'http') => {
|
||||
if (no_login_show_modal_state) return;
|
||||
no_login_show_modal_state = true
|
||||
let page_route = ''
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length >= 1) {
|
||||
const page = pages[pages.length - 1];
|
||||
page_route = page.route
|
||||
}
|
||||
console.log('当前页路由', page_route);
|
||||
if (page_route === 'pages/login/login') return;
|
||||
common.hideLoading()
|
||||
common.show('未登录', '现在去登录', false).then(() => {
|
||||
// common.navigateTo('/pages/login/login')
|
||||
if (from === 'socket' && page_route !== '') {
|
||||
common.redirectTo('/pages/login/login?path=' + page_route)
|
||||
} else {
|
||||
common.redirectTo('/pages/login/login')
|
||||
// common.navigateTo('/pages/login/login')
|
||||
}
|
||||
|
||||
}).finally(() => {
|
||||
no_login_show_modal_state = false
|
||||
})
|
||||
}
|
||||
|
||||
export default function request({
|
||||
url,
|
||||
method,
|
||||
data,
|
||||
meta,
|
||||
isStream,
|
||||
suppressErrors = false,
|
||||
toastErrors = true,
|
||||
header = {},
|
||||
timeout = 6000,
|
||||
baseUrl = ''
|
||||
}) {
|
||||
const base_url = get_base_url(url);
|
||||
|
||||
const headers_base = {
|
||||
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
...(header || {})
|
||||
}
|
||||
|
||||
let token = getToken()
|
||||
if (token) {
|
||||
headers_base['summary'] = token // 让每个请求携带令牌
|
||||
}
|
||||
let that = this
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url: `${baseUrl || base_url}${url}`,
|
||||
data: data,
|
||||
method: method,
|
||||
header: headers_base,
|
||||
timeout: timeout,
|
||||
success: (response) => {
|
||||
if (isStream) { // 流式传输,直接返回
|
||||
return resolve(response);
|
||||
}
|
||||
// suppressErrors
|
||||
// 模拟拦截器功能
|
||||
const res = response.data
|
||||
if (res?.rtnCode === '0000') {
|
||||
return resolve(res)
|
||||
} else if (res?.rtnCode === '1001') { // 为前后端约定不公共拦截的码值(小树20250903定)
|
||||
return reject(res)
|
||||
} else if (res?.rtnCode === '0002' && res?.message) {
|
||||
if (toastErrors) {
|
||||
common.msg(res.message)
|
||||
}
|
||||
return reject(response)
|
||||
}
|
||||
//到这里下面全是异常的处理
|
||||
if (suppressErrors) { // 静默的接口,报错也不处理,因为下面有公共处理
|
||||
return reject(response)
|
||||
}
|
||||
if (!res) {
|
||||
common.msg("系统异常.")
|
||||
return reject()
|
||||
}
|
||||
// 0005 QQ0005 登录已过期
|
||||
if (REQUES_ERROR_CODES['NO_LOGIN'].includes(res.rtnCode)) {
|
||||
goto_login_fun()
|
||||
}
|
||||
if (res?.rtnCode === '9999') {
|
||||
common.msg("系统异常.")
|
||||
}
|
||||
return reject(res)
|
||||
},
|
||||
fail(res) {
|
||||
console.log('请求错误', res, url, base_url);
|
||||
return reject(res)
|
||||
}
|
||||
});
|
||||
})
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import request from '@/api/request'
|
||||
const base_url = '/traapp'
|
||||
|
||||
// 搜索
|
||||
export const appSearchCrsApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traSearchHis/appSearchCrs',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,242 @@
|
||||
import { goto_login_fun } from '@/api/request'
|
||||
export default class WebSocketUtil {
|
||||
/**
|
||||
* WebSocket工具类,用于管理WebSocket连接
|
||||
* @param {string} url - WebSocket服务器地址
|
||||
* @param {Object} options - 配置选项
|
||||
* @param {number} [options.maxReconnectCount=5] - 最大重连次数
|
||||
* @param {number} [options.reconnectInterval=3000] - 重连间隔时间(ms)
|
||||
* @param {number} [options.heartbeatInterval=30000] - 心跳间隔时间(ms)
|
||||
* @param {string|Object} [options.heartbeatMsg='ping'] - 心跳消息
|
||||
*/
|
||||
constructor(url, options = {}) {
|
||||
this.url = url;
|
||||
this.options = options;
|
||||
this.socketTask = null; // WebSocket任务实例
|
||||
this.reconnectTimer = null; // 重连计时器
|
||||
this.reconnectCount = 0; // 当前重连次数
|
||||
this.maxReconnectCount = options.maxReconnectCount || 5; // 最大重连次数
|
||||
this.reconnectInterval = options.reconnectInterval || 3000; // 重连间隔(ms)
|
||||
this.heartbeatTimer = null; // 心跳计时器
|
||||
this.heartbeatInterval = options.heartbeatInterval || 30000; // 心跳间隔(ms)
|
||||
this.heartbeatMsg = options.heartbeatMsg || 'ping'; // 心跳消息
|
||||
this.isClose = false; // 手动关闭
|
||||
this.callbacks = {
|
||||
open: [], // 连接打开回调
|
||||
message: [], // 消息接收回调
|
||||
close: [], // 连接关闭回调
|
||||
error: [] // 错误处理回调
|
||||
};
|
||||
this.init(true); // 初始化WebSocket连接
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化WebSocket连接
|
||||
*/
|
||||
init(flag = false) {
|
||||
if(flag) this.reconnectCount = 0
|
||||
this.isClose = false; // 手动关闭
|
||||
// 创建WebSocket连接
|
||||
this.socketTask = uni.connectSocket({
|
||||
url: this.url,
|
||||
header: this.options.header || {},
|
||||
protocols: this.options.protocols || [],
|
||||
success: () => {
|
||||
// console.log('WebSocket连接创建成功');
|
||||
},
|
||||
fail: (err) => {
|
||||
// console.error('WebSocket连接创建失败', err);
|
||||
this.triggerEvent('error', err);
|
||||
this.tryReconnect(); // 连接失败时尝试重连
|
||||
}
|
||||
});
|
||||
|
||||
// 监听WebSocket连接打开事件
|
||||
this.socketTask.onOpen(() => {
|
||||
// console.log('WebSocket连接已打开');
|
||||
this.reconnectCount = 0; // 重置重连计数
|
||||
clearInterval(this.reconnectTimer); // 清除重连计时器
|
||||
// this.startHeartbeat(); // 启动心跳机制
|
||||
this.triggerEvent('open'); // 触发连接打开事件
|
||||
});
|
||||
|
||||
// 监听WebSocket消息接收事件
|
||||
this.socketTask.onMessage((res) => {
|
||||
if(res.data){
|
||||
const { rtnCode } = JSON.parse(res.data)
|
||||
// console.log('收到WebSocket消息', JSON.parse(res.data) );
|
||||
if(['0005', 'QQ0005'].includes(rtnCode)) {
|
||||
clearInterval(this.reconnectTimer);
|
||||
this.reconnectTimer = null
|
||||
this.close()
|
||||
goto_login_fun('socket');
|
||||
return
|
||||
}
|
||||
}else{
|
||||
clearInterval(this.reconnectTimer); // 清除重连计时器
|
||||
this.reconnectTimer = null
|
||||
goto_login_fun('socket');
|
||||
return
|
||||
}
|
||||
this.triggerEvent('message', res); // 触发消息接收事件
|
||||
});
|
||||
|
||||
// 监听WebSocket连接关闭事件
|
||||
this.socketTask.onClose((res) => {
|
||||
// console.log('WebSocket连接已关闭', res);
|
||||
clearInterval(this.heartbeatTimer); // 清除心跳计时器
|
||||
this.triggerEvent('close', res); // 触发连接关闭事件
|
||||
this.tryReconnect(); // 尝试重连
|
||||
});
|
||||
|
||||
// 监听WebSocket错误事件
|
||||
this.socketTask.onError((err) => {
|
||||
console.error('WebSocket发生错误', err);
|
||||
clearInterval(this.heartbeatTimer); // 清除心跳计时器
|
||||
this.triggerEvent('error', err); // 触发错误事件
|
||||
this.tryReconnect(); // 尝试重连
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册事件回调
|
||||
* @param {string} event - 事件名称(open/message/close/error)
|
||||
* @param {Function} callback - 回调函数
|
||||
* @returns {WebSocketUtil} - 返回当前实例,支持链式调用
|
||||
*/
|
||||
on(event, callback) {
|
||||
if (this.callbacks[event]) {
|
||||
this.callbacks[event].push(callback);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件回调
|
||||
* @param {string} event - 事件名称(open/message/close/error)
|
||||
* @param {Function} callback - 要移除的回调函数
|
||||
* @returns {WebSocketUtil} - 返回当前实例,支持链式调用
|
||||
*/
|
||||
off(event, callback) {
|
||||
if (this.callbacks[event]) {
|
||||
this.callbacks[event] = this.callbacks[event].filter(cb => cb !== callback);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发事件回调
|
||||
* @param {string} event - 事件名称
|
||||
* @param {any} [data] - 传递给回调函数的数据
|
||||
*/
|
||||
triggerEvent(event, data) {
|
||||
if (this.callbacks[event]) {
|
||||
this.callbacks[event].forEach(callback => callback(data));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过WebSocket发送消息
|
||||
* @param {string|Object} message - 要发送的消息,可以是字符串或对象
|
||||
* @returns {WebSocketUtil} - 返回当前实例,支持链式调用
|
||||
*/
|
||||
send(message, num = 1) {
|
||||
if (this.socketTask && this.getStatus() === 1) {
|
||||
// 发送消息,对象会自动转换为JSON字符串
|
||||
this.socketTask.send({
|
||||
data: typeof message === 'string' ? message : JSON.stringify(message),
|
||||
success: () => {
|
||||
// console.log('WebSocket消息发送成功');
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('WebSocket消息发送失败', err);
|
||||
this.triggerEvent('error', err);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error('WebSocket连接未打开,无法发送消息');
|
||||
this.triggerEvent('error', new Error('WebSocket连接未打开'));
|
||||
if(num === 10) return
|
||||
setTimeout(()=>{
|
||||
this.send(message, num + 1)
|
||||
},500)
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭WebSocket连接
|
||||
* @param {number} [code=1000] - 关闭码
|
||||
* @param {string} [reason=''] - 关闭原因
|
||||
* @returns {WebSocketUtil} - 返回当前实例,支持链式调用
|
||||
*/
|
||||
close(code = 1000, reason = '') {
|
||||
this.isClose = true
|
||||
if (this.socketTask) {
|
||||
clearInterval(this.heartbeatTimer); // 清除心跳计时器
|
||||
clearInterval(this.reconnectTimer); // 清除重连计时器
|
||||
// 关闭WebSocket连接
|
||||
this.socketTask.close({
|
||||
code,
|
||||
reason,
|
||||
success: () => {
|
||||
console.log('WebSocket连接正在关闭');
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('WebSocket关闭失败', err);
|
||||
this.triggerEvent('error', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取WebSocket连接状态
|
||||
* @returns {number} - 连接状态:0-连接中,1-已连接,2-连接关闭中,3-已关闭,-1-未知
|
||||
*/
|
||||
getStatus() {
|
||||
if (this.socketTask) {
|
||||
try {
|
||||
return this.socketTask.readyState;
|
||||
} catch (e) {
|
||||
console.error('获取WebSocket状态失败', e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试重新连接WebSocket
|
||||
*/
|
||||
tryReconnect() {
|
||||
if(this.isClose) return
|
||||
if (this.reconnectCount < this.maxReconnectCount) {
|
||||
clearInterval(this.reconnectTimer);
|
||||
this.reconnectTimer = setTimeout(() => {
|
||||
this.reconnectCount++;
|
||||
console.log(`尝试重新连接WebSocket (${this.reconnectCount}/${this.maxReconnectCount})`);
|
||||
this.init(); // 重新初始化WebSocket连接
|
||||
}, this.reconnectInterval);
|
||||
} else {
|
||||
console.error('达到最大重连次数,停止尝试');
|
||||
this.triggerEvent('error', new Error('达到最大重连次数'));
|
||||
this.reconnectTimer = null
|
||||
this.close()
|
||||
goto_login_fun('socket')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动心跳机制
|
||||
*/
|
||||
startHeartbeat() {
|
||||
clearInterval(this.heartbeatTimer);
|
||||
this.heartbeatTimer = setInterval(() => {
|
||||
if (this.getStatus() === 1) {
|
||||
this.send(this.heartbeatMsg); // 发送心跳消息
|
||||
}
|
||||
}, this.heartbeatInterval);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import request from '@/api/request'
|
||||
|
||||
const base_url = '/trapractice'
|
||||
|
||||
// 获取分类树接口
|
||||
export const queryTraPartnerCatelogList = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPartnerCatelog/queryTraPartnerCatelogList',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 获取标签分类树 接口
|
||||
export const getTraTagListApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPartnerTag/queryTraTagCatalogInfoList',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 全部模块
|
||||
export const queryCrsInfoByCatalogApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPartnerInfo/queryTraPartnerInfoPaging',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// id 查详情/trapractice/traPartnerInfo/queryTraPartnerInfoById
|
||||
export const queryTraPartnerInfoById = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPartnerInfo/queryTraPartnerInfoById',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 陪练角色列表 traId
|
||||
export const queryTraPartnerCharacterInfoList = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPartnerCharacterInfo/queryTraPartnerCharacterInfoList',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 陪练角色详情 + 对话详情 /traPartnerCharacterInfo/queryTraPartnerCharacterInfoById
|
||||
export const queryTraPartnerCharacterInfoById = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPartnerCharacterInfo/queryTraPartnerCharacterInfoById',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 结束陪练 /trapractice/traPartnerChatReport/partnerChatReportTrigger?execId=
|
||||
export const partnerChatReportTrigger = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPartnerChatReport/partnerChatReportTrigger',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 问答提示/trapractice/traPartnerChat/partnerChatPrompt
|
||||
export const partnerChatPrompt = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPartnerChat/partnerChatPrompt',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 获取报告traPartnerChatReport/partnerChatReport
|
||||
export const partnerChatReport = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPartnerChatReport/partnerChatReport',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 反馈
|
||||
export const insertTraPartnerFeedback = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPartnerFeedback/insertTraPartnerFeedback',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
// 查询是否可以进入trapractice/traPartnerInfo/checkTraPartnerInfoById
|
||||
export const checkTraPartnerInfoById = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPartnerInfo/checkTraPartnerInfoById',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,206 @@
|
||||
import request from '@/api/request'
|
||||
import { getToken } from '@/common/common.js'
|
||||
import { get_base_url } from '@/api/request'
|
||||
const base_url = '/trastudy'
|
||||
|
||||
// 获取课程学习信息接口
|
||||
export const getCourseStudyInfoApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traStdyInfo/queryTraStdyProgressAndTeacher',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
// 获取课程学习 知识点 接口
|
||||
export const queryTraStdyBatchWaitParagraphInfoApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traStdyInfo/queryTraStdyBatchWaitParagraphInfo',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
// 重新开始 删除学习信息
|
||||
export const deleteTraStdyInfoByIdApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traStdyInfo/deleteTraStdyInfoById',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 重新学习
|
||||
export const afreshStudyCourseApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traStdyInfo/afreshStudyCourse',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
// 学习段落完成
|
||||
export const studyParagraphOverApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traStdyInfo/studyParagraphOver',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
// 回答问题 /traStdyInfo/studyAnswerTextUp
|
||||
export const studyAnswerTextUpApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traStdyInfo/studyAnswerTextUp',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
// 获取问题信息/traStdyInfo/queryTraStdyBatchWaitQuestionInfo
|
||||
export const queryTraStdyBatchWaitQuestionInfoApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traStdyInfo/queryTraStdyBatchWaitQuestionInfo',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
// 上传语音翻译i文本 /traStdyInfo/audioTranscriptions
|
||||
export const audioTranscriptionsApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traStdyInfo/audioTranscriptions',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 撤回消息
|
||||
export const withdrawalAnswerApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traStdyInfo/withdrawalAnswer',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
// 异步完成回答
|
||||
// 入参:
|
||||
// qstLogId 必输项:true 类型:String
|
||||
// 出参:
|
||||
// qstLogId 问题日志ID String
|
||||
// qnsId 问题ID String
|
||||
// processStat 状态 String
|
||||
// message 处理消息 String
|
||||
|
||||
export const waitMakeEvaluateApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traStdyInfo/waitMakeEvaluate',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
// 查询问题评价
|
||||
// /traStdyInfo/queryQuestionEvalate
|
||||
export const queryQuestionEvalateApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traStdyInfo/queryQuestionEvalate',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// /trastudy/intgask/askAbout
|
||||
// 流式获取文本
|
||||
export const getAskAboutRawApi = (data) => {
|
||||
// return request({
|
||||
// baseUrl:'http://25.18.122.76:3030',
|
||||
// url: '/api/v1/prediction/a402cf58-9f64-479b-a9f8-c527300a7905',
|
||||
// isStream: true,
|
||||
// header: {
|
||||
// 'Accept':'text/event-stream',
|
||||
// 'content-type': 'application/json; charset=UTF-8',
|
||||
// 'Authorization': 'Bearer PjadGPFSHDg6F8ZRekROe-QxS2fPXlWRsmvP5Mp0vxA'
|
||||
// },
|
||||
// method: 'post',
|
||||
// toastErrors: false,
|
||||
// data
|
||||
// });
|
||||
return request({
|
||||
url: base_url + '/intgask/askAbout',
|
||||
header: {
|
||||
'Accept':'text/event-stream',
|
||||
'content-type': 'text/event-stream; charset=UTF-8',
|
||||
},
|
||||
responseType: 'arrayBuffer',
|
||||
method: 'get',
|
||||
isStream: true,
|
||||
timeout: 30000,
|
||||
toastErrors: false,
|
||||
data
|
||||
})
|
||||
};
|
||||
|
||||
// 学习开始
|
||||
export const studyStartApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traStdychat/studyStart',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
// 学习结束
|
||||
export const studyEndApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traStdychat/studyEnd',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 学习 对话
|
||||
export const textChartApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traStdychat/textChart',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 预览 对话
|
||||
export const textChartPreviewApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traStdyInfoPreview/previewCrsInfo',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export const previewAfreshStudyCourse = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traStdyInfo/previewAfreshStudyCourse',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 设置学习顾问音色/traStdyTeacher/setStudyTeacher
|
||||
export const setStudyTeacherApi = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traStdyTeacher/setStudyTeacher',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import request from '@/api/request'
|
||||
|
||||
export const prepareApi = (data) => {
|
||||
return request({
|
||||
url: '/trapractice/traPartnerVoiceCall/voiceCallPrepare' ,
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data,
|
||||
baseUrl: 'http://25.64.32.157:9603'
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import request from '@/api/request'
|
||||
|
||||
const module_url = '/traexam'
|
||||
|
||||
// 1.查询考试列表分页
|
||||
export const queryTraExamPapersPage = (data) => {
|
||||
return request({
|
||||
url: module_url + '/traExamPapers/queryTraExamPapersPage',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import request from '@/api/request'
|
||||
const base_url = '/trapractice'
|
||||
|
||||
|
||||
/**练习*/
|
||||
// 练习列表页
|
||||
export const queryPracticeRecordPaging = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPartnerExecuteHis/queryTraPartnerExecutePaging',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data:{
|
||||
traType:'01',
|
||||
...data
|
||||
}
|
||||
});
|
||||
};
|
||||
// 练习每一项列表
|
||||
export const queryPracticeHis = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPartnerExecuteHis/queryTraPartnerExecuteHisPaging',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
// 练习详情 通过 exrId 查询
|
||||
export const queryPracticeRecordByExrId = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traCrsPractice/queryPracticeRecordByExrId',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**考试*/
|
||||
export const queryCrsExamRecordPaging = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPartnerExecuteHis/queryTraPartnerExecutePaging',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data:{
|
||||
traType:'02',
|
||||
...data
|
||||
}
|
||||
});
|
||||
};
|
||||
// 考试每一项列表
|
||||
export const queryCrsExamHisByExamId = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPartnerExecuteHis/queryTraPartnerExecuteHisPaging',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import request from '@/api/request'
|
||||
|
||||
|
||||
// 我的-统计信息
|
||||
export const queryTraStdyStatisticsSelf = (data) => {
|
||||
/**
|
||||
userId 学员ID String
|
||||
stdtTmLen 累计学习时长 double
|
||||
accmTaskCnt 累计完成任务 int
|
||||
accmPoint 累计获得积分 int
|
||||
**/
|
||||
return request({
|
||||
url: '/traapp/traStdyInfo/queryTraStdyStatisticsSelf',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 更新性别
|
||||
export const updateDfSysUserExtandInfoGender = (data) => {
|
||||
/**
|
||||
|
||||
**/
|
||||
return request({
|
||||
url: '/traapp/dfSysUserInfo/updateDfSysUserExtandInfoGender ',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 更新照片
|
||||
export const updateDfSysUserExtandInfoImageAddr = (data) => {
|
||||
return request({
|
||||
url: '/traapp/dfSysUserInfo/updateDfSysUserExtandInfoImageAddr',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 设置匿名 是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
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import request from '@/api/request'
|
||||
const base_url = '/traexam'
|
||||
|
||||
// 错题本列表页
|
||||
export const queryTraMistakesCollectionPaging = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traMistakesCollection/queryTraMistakesCollectionPaging',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 错题订正
|
||||
export const mistakesCorrect = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traMistakesCollection/mistakesCorrect',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 错题本id集合
|
||||
export const queryTraMistakesCollection = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traMistakesCollection/queryTraMistakesCollection',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 根据ID查题
|
||||
export const queryMistakesAnswerByMisId = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traMistakesCollection/queryMistakesAnswerByMisId ',
|
||||
method: 'post',
|
||||
toastErrors: false,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 根据ID查题
|
||||
export const queryMistakesCorrectByMisId = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traMistakesCollection/queryMistakesCorrectByMisId',
|
||||
method: 'post',
|
||||
toastErrors: false,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,385 @@
|
||||
import {
|
||||
get_base_url
|
||||
} from '@/api/request';
|
||||
// #ifdef APP-PLUS
|
||||
import showDialog from '@/common/dialogMessage'
|
||||
import common from '@/common/common'
|
||||
// #endif
|
||||
function msg(title, duration = 1000) {
|
||||
uni.showToast({
|
||||
title: title,
|
||||
duration: duration,
|
||||
icon: 'none',
|
||||
mask: true,
|
||||
});
|
||||
}
|
||||
|
||||
function show(title, msg, showCancel = true, cancelText = '取消', confirmText = '确定') {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 创建通用参数对象
|
||||
const dialogOptions = {
|
||||
title,
|
||||
content: msg,
|
||||
showCancel,
|
||||
cancelText,
|
||||
confirmText,
|
||||
success: (res) => {
|
||||
// 统一处理成功回调,解析为布尔值
|
||||
resolve(!!res.confirm);
|
||||
}
|
||||
};
|
||||
// #ifdef APP-PLUS
|
||||
if (getPhoneEnvBool('AND')) {
|
||||
// 安卓环境使用showDialog,补充cancel回调
|
||||
showDialog({
|
||||
...dialogOptions,
|
||||
cancel: () => {
|
||||
console.log('用户点击取消');
|
||||
resolve(false); // 取消时也返回Promise结果
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 其他环境使用uni.showModal,补充fail回调
|
||||
uni.showModal({
|
||||
...dialogOptions,
|
||||
fail: (res) => {
|
||||
reject(res);
|
||||
}
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
uni.showModal({
|
||||
...dialogOptions,
|
||||
fail: (res) => {
|
||||
reject(res);
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
});
|
||||
}
|
||||
|
||||
const loading = (title = '加载中', mask = true) => uni.showLoading({
|
||||
title: title,
|
||||
mask: mask
|
||||
})
|
||||
const hideLoading = () => uni.hideLoading()
|
||||
|
||||
// 根据姓名的长度生成对应的带星号格式
|
||||
export const formatName = (name) => {
|
||||
|
||||
// 处理空值或非字符串情况
|
||||
if (!name || typeof name !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
const familyName = name.charAt(0); // 取姓氏(第一个字符)
|
||||
const givenNameLength = name.length - 1; // 名字部分的长度
|
||||
|
||||
// 根据名字长度计算星号数量,最多4个
|
||||
const starCount = Math.min(givenNameLength, 3);
|
||||
const stars = '*'.repeat(starCount);
|
||||
|
||||
return familyName + stars;
|
||||
}
|
||||
|
||||
// 防抖工具函数
|
||||
function debounce(fn, wait = 300) {
|
||||
let isLocked = false; // 锁定状态标记
|
||||
return function(...args) {
|
||||
// 如果处于锁定状态,直接返回不执行
|
||||
if (isLocked) {
|
||||
return;
|
||||
}
|
||||
// 执行函数
|
||||
fn.apply(this, args);
|
||||
|
||||
// 锁定,防止再次执行
|
||||
isLocked = true;
|
||||
|
||||
// 等待指定时间后解锁
|
||||
setTimeout(() => {
|
||||
isLocked = false;
|
||||
}, wait);
|
||||
};
|
||||
}
|
||||
|
||||
// 保留当前页面,跳转到应用内的某个页面,使用uni.navigateBack可以返回到原页面。
|
||||
const navigateTo = debounce(function(url, data = {}) {
|
||||
uni.navigateTo({
|
||||
url: url,
|
||||
...data
|
||||
});
|
||||
})
|
||||
|
||||
// 关闭所有页面,打开到应用内的某个页面。
|
||||
const reLaunch = url => {
|
||||
uni.reLaunch({
|
||||
url: url
|
||||
});
|
||||
}
|
||||
// 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。
|
||||
const switchTab = url => {
|
||||
uni.switchTab({
|
||||
url: url
|
||||
});
|
||||
}
|
||||
// 关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages() 获取当前的页面栈,决定需要返回几层。
|
||||
const navigateBack = (delta = 1, fun = () => {}, data = {}) => {
|
||||
uni.navigateBack({
|
||||
delta: delta,
|
||||
...data,
|
||||
success() {
|
||||
fun()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//关闭当前页面,跳转到应用内的某个页面。
|
||||
const redirectTo = (url, obj = {}) => uni.redirectTo({
|
||||
url: url,
|
||||
...obj
|
||||
});
|
||||
|
||||
// 时间转换时间戳
|
||||
const dateToTime = (dateString) => {
|
||||
let date = new Date(dateString);
|
||||
let timestamp = date.getTime();
|
||||
return timestamp
|
||||
}
|
||||
|
||||
// 数字格式化
|
||||
const formatDate2 = (timestamp, format = 'yyyy-MM-dd HH:mm:ss') => {
|
||||
if (!timestamp)
|
||||
return ''
|
||||
let date
|
||||
try {
|
||||
if (timestamp) {
|
||||
if (typeof timestamp == 'number' && timestamp <= 9999999999) {
|
||||
timestamp *= 1000
|
||||
}
|
||||
date = new Date(timestamp)
|
||||
} else {
|
||||
date = new Date()
|
||||
}
|
||||
} catch (e) {
|
||||
return ''
|
||||
}
|
||||
let day = date.getDate()
|
||||
let month = date.getMonth() + 1
|
||||
let year = date.getFullYear()
|
||||
let hours = date.getHours()
|
||||
let minutes = date.getMinutes()
|
||||
let seconds = date.getSeconds()
|
||||
|
||||
// 替换日期格式中的占位符
|
||||
return format
|
||||
.replace('yyyy', year)
|
||||
.replace('MM', month.toString().padStart(2, '0'))
|
||||
.replace('dd', day.toString().padStart(2, '0'))
|
||||
.replace('HH', hours.toString().padStart(2, '0'))
|
||||
.replace('mm', minutes.toString().padStart(2, '0'))
|
||||
.replace('ss', seconds.toString().padStart(2, '0'))
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 获取token
|
||||
export const getToken = () => uni.getStorageSync('token')
|
||||
export const setToken = data => uni.setStorageSync('token', data)
|
||||
export const setUserInfo = data => uni.setStorageSync('userInfo', data)
|
||||
export const getUserInfo = (key = '') => {
|
||||
const userInfo = uni.getStorageSync('userInfo')
|
||||
return '' === key ? userInfo : userInfo[key];
|
||||
}
|
||||
// 数字转换,把传入的秒转换为HH:mm:ss格式
|
||||
export const formatSeconds = (seconds) => {
|
||||
// 处理非数字或负数情况
|
||||
if (typeof seconds !== 'number' || isNaN(seconds) || seconds < 0) {
|
||||
return '00:00:00';
|
||||
}
|
||||
|
||||
// 计算小时、分钟和剩余秒数
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const remainingSeconds = Math.floor(seconds % 60);
|
||||
|
||||
// 补零函数:将数字转为两位数字符串
|
||||
const padZero = (num) => num.toString().padStart(2, '0');
|
||||
|
||||
// 拼接成00:00:00格式
|
||||
return `${padZero(hours)}:${padZero(minutes)}:${padZero(remainingSeconds)}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* 判断当前运行平台是否匹配指定标识
|
||||
* @param {string} flag - 平台标识:'IOS' 表示苹果iOS平台,'AND' 表示安卓平台
|
||||
* @returns {boolean} 如果当前平台匹配指定标识返回true,否则返回false
|
||||
*/
|
||||
export const getPhoneEnvBool = (flag) => {
|
||||
// 获取当前设备的平台信息
|
||||
|
||||
const {
|
||||
platform
|
||||
} = uni.getSystemInfoSync();
|
||||
|
||||
// 平台与标识的映射关系
|
||||
const platformMap = {
|
||||
ios: 'IOS',
|
||||
android: 'AND',
|
||||
};
|
||||
|
||||
// 根据当前平台返回匹配结果
|
||||
return platformMap[platform] === flag;
|
||||
};
|
||||
|
||||
|
||||
// 将对象和字符串相互转换
|
||||
export const objToJson = obj => encodeURLComponent('page_data', obj)
|
||||
export const jsonToObj = () => uni.getStorageSync('page_data')
|
||||
|
||||
// 将对象存入缓存
|
||||
export const setPageCache = (name, obj) => {
|
||||
uni.setStorageSync(name, obj)
|
||||
}
|
||||
|
||||
// 将对象取出并且删除缓存
|
||||
export const getPageCache = name => {
|
||||
const cache = uni.getStorageSync(name)
|
||||
uni.removeStorageSync(name)
|
||||
return cache
|
||||
}
|
||||
|
||||
function setValue(key, value) {
|
||||
uni.setStorageSync(key, value);
|
||||
}
|
||||
|
||||
function getValue(key, defaultValue = null) {
|
||||
return uni.getStorageSync(key) ?? defaultValue
|
||||
}
|
||||
|
||||
function isLogin() {
|
||||
return !!getToken();
|
||||
}
|
||||
|
||||
// 封装键盘高度变化处理
|
||||
export function setupKeyboardHeightListener(updateCallback) {
|
||||
// #ifndef APP-PLUS
|
||||
return () => {};
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
// 获取系统信息
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
// 计算安全区域底部高度
|
||||
const safeAreaBottomHeight = systemInfo.screenHeight - systemInfo.safeArea.bottom;
|
||||
|
||||
// 定义处理函数
|
||||
const handleKeyboardHeightChange = (res) => {
|
||||
let keyboardHeight = res.height;
|
||||
|
||||
// 仅在iOS端补偿安全区域高度
|
||||
if (systemInfo.platform === 'ios') {
|
||||
keyboardHeight = Math.max(0, keyboardHeight - safeAreaBottomHeight); // 避免负数
|
||||
}
|
||||
|
||||
// 通过回调函数更新外部的响应式变量
|
||||
updateCallback(keyboardHeight);
|
||||
};
|
||||
|
||||
// 监听键盘高度变化
|
||||
uni.onKeyboardHeightChange(handleKeyboardHeightChange);
|
||||
|
||||
// 返回取消监听的函数,方便组件卸载时清理
|
||||
return () => {
|
||||
uni.offKeyboardHeightChange(handleKeyboardHeightChange);
|
||||
};
|
||||
// #endif
|
||||
|
||||
}
|
||||
|
||||
let isDownloading = false
|
||||
export const previewFileFnc = ({
|
||||
fileId,
|
||||
getFileSrc,
|
||||
fileName
|
||||
}) => {
|
||||
let userInfo = getUserInfo()
|
||||
let userParams = '/' + (userInfo?.loginName ?? '')
|
||||
navigateTo('/pages/index/preview?fileId=' + fileId + '&fileApiSrc=' + getFileSrc);
|
||||
// if (getPhoneEnvBool('AND')) {
|
||||
// navigateTo('/pages/index/preview?fileId=' + fileId + '&fileApiSrc=' + getFileSrc);
|
||||
// } else {
|
||||
// if (isDownloading) return
|
||||
// const base_url = get_base_url(getFileSrc);
|
||||
// let fileUrl = base_url + getFileSrc + fileId + userParams;
|
||||
// const localFilePath = '_doc/pdf_view/' + fileName + '.pdf';
|
||||
// isDownloading = true
|
||||
// downloadFileFnc(fileUrl, localFilePath).then((res) => {
|
||||
// uni.openDocument({
|
||||
// filePath: res,
|
||||
// showMenu: true,
|
||||
// success: function() {
|
||||
// console.log('打开文档成功');
|
||||
// }
|
||||
// })
|
||||
// }).finally(() => {
|
||||
// setTimeout(() => {
|
||||
// isDownloading = false
|
||||
// }, 1000)
|
||||
// });
|
||||
// }
|
||||
}
|
||||
const downloadFileFnc = async (fileUrl, localFilePath) => {
|
||||
try {
|
||||
//#ifdef APP-PLUS
|
||||
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();
|
||||
});
|
||||
// #endif
|
||||
//#ifdef H5
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve();
|
||||
});
|
||||
// #endif
|
||||
} catch (error) {
|
||||
console.error('创建目录失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export default {
|
||||
getPageCache,
|
||||
setPageCache,
|
||||
objToJson,
|
||||
jsonToObj,
|
||||
hideLoading,
|
||||
loading,
|
||||
msg,
|
||||
show,
|
||||
redirectTo,
|
||||
navigateTo,
|
||||
navigateBack,
|
||||
formatDate2,
|
||||
setValue,
|
||||
getValue,
|
||||
isLogin,
|
||||
reLaunch,
|
||||
switchTab,
|
||||
dateToTime,
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
export default function showDialog(options) {
|
||||
|
||||
const view = new plus.nativeObj.View('customModal', {
|
||||
top: '0',
|
||||
left: '0',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
backgroundColor: 'rgba(0,0,0,0.1)',
|
||||
position: 'fixed'
|
||||
});
|
||||
|
||||
const screenWidth = plus.screen.resolutionWidth;
|
||||
const screenHeight = plus.screen.resolutionHeight;
|
||||
const borderRadius = 10;
|
||||
const contentPadding = 12; // 内容左右内边距
|
||||
const lineHeight = 17; // 单行文本行高
|
||||
const maxLines = 2; // 最大行数
|
||||
const fontSize = 14; // 字体大小
|
||||
|
||||
// 判断内容和标题是否存在
|
||||
const hasContent = options.content && options.content.trim() !== '';
|
||||
const hasTitle = options.title && options.title.trim() !== '';
|
||||
|
||||
// 弹窗宽度计算
|
||||
const modalWidth = Math.min(0.65 * screenWidth, 500);
|
||||
const modalLeft = (screenWidth - modalWidth) / 2;
|
||||
|
||||
// 内容区域可用宽度
|
||||
const contentAvailableWidth = modalWidth - 2 * contentPadding - 10;
|
||||
|
||||
// 动态计算各部分高度
|
||||
const titleHeight = hasTitle ? 20 : 0;
|
||||
const titleToContentSpacing = (hasTitle && hasContent) ? 10 : 0;
|
||||
const contentHeight = hasContent ? (lineHeight * maxLines) : 0;
|
||||
const contentToBtnSpacing = 10;
|
||||
const btnHeight = 46;
|
||||
|
||||
// 弹窗总高度
|
||||
let modalHeight = contentPadding + 6 + titleHeight + titleToContentSpacing + contentHeight + contentToBtnSpacing + btnHeight;
|
||||
modalHeight = options.showCancel ? Math.min(modalHeight, 300) : Math.min(modalHeight, 250);
|
||||
|
||||
// 位置计算
|
||||
const modalTop = (screenHeight - modalHeight) / 2;
|
||||
const contentAreaTop = modalTop + contentPadding + 6;
|
||||
const titleTop = hasTitle ? contentAreaTop : 0;
|
||||
|
||||
|
||||
let contentTop;
|
||||
if (hasContent) {
|
||||
contentTop = hasTitle ? (titleTop + titleHeight + titleToContentSpacing) : contentAreaTop;
|
||||
} else {
|
||||
contentTop = hasTitle ? (titleTop + titleHeight) : contentAreaTop;
|
||||
}
|
||||
|
||||
const btnTop = hasContent
|
||||
? (contentTop + contentHeight + contentToBtnSpacing)
|
||||
: (hasTitle
|
||||
? (titleTop + titleHeight + contentToBtnSpacing)
|
||||
: (contentAreaTop + (modalHeight - btnHeight - contentPadding))); // 既无标题也无内容时
|
||||
|
||||
// 绘制弹窗背景
|
||||
view.draw([{
|
||||
tag: 'rect',
|
||||
id: 'bg',
|
||||
position: { top: modalTop + 'px', left: modalLeft + 'px', width: modalWidth + 'px', height: modalHeight + 'px' },
|
||||
rectStyles: { color: '#fff', radius: borderRadius + 'px' }
|
||||
}]);
|
||||
|
||||
// 绘制标题和内容
|
||||
const elements = [];
|
||||
|
||||
// 标题(仅当存在时)
|
||||
if (hasTitle) {
|
||||
elements.push({
|
||||
tag: 'font',
|
||||
id: 'title',
|
||||
text: options.title,
|
||||
position: { top: titleTop + 'px', left: modalLeft + 'px', width: modalWidth + 'px', height: titleHeight + 'px' },
|
||||
textStyles: {
|
||||
color: '#000',
|
||||
size: '18px',
|
||||
align: 'center',
|
||||
fontWeight: 'bold',
|
||||
verticalAlign: 'middle'
|
||||
}
|
||||
});
|
||||
}
|
||||
if (hasContent) {
|
||||
const text = options.content;
|
||||
const maxLines = 2; // 最大显示行数
|
||||
const lineHeightPx = lineHeight;
|
||||
const leftPos = modalLeft + contentPadding;
|
||||
const textWidth = contentAvailableWidth;
|
||||
const fontSizePx = fontSize;
|
||||
|
||||
// 按 \n 分割段落
|
||||
const paragraphs = text.split('\n');
|
||||
let lines = [];
|
||||
|
||||
// 遍历每个段落,计算换行
|
||||
for (let para of paragraphs) {
|
||||
if (lines.length >= maxLines) break;
|
||||
|
||||
// 估算每行能容纳的字符数(中文按 1.2 倍宽度计算)
|
||||
const avgCharWidth = fontSizePx * (isChinese(para) ? 1.2 : 0.6);
|
||||
const maxCharsPerLine = Math.floor(textWidth / avgCharWidth);
|
||||
|
||||
let currentLine = '';
|
||||
for (let i = 0; i < para.length; i++) {
|
||||
const char = para[i];
|
||||
currentLine += char;
|
||||
|
||||
// 如果当前行字符数超过限制,换行
|
||||
if (currentLine.length >= maxCharsPerLine) {
|
||||
lines.push(currentLine);
|
||||
currentLine = '';
|
||||
|
||||
if (lines.length >= maxLines) break;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加剩余部分
|
||||
if (currentLine && lines.length < maxLines) {
|
||||
lines.push(currentLine);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果超出最大行数,最后一行加 "..."
|
||||
if (lines.length > maxLines) {
|
||||
lines = lines.slice(0, maxLines);
|
||||
const lastLine = lines[maxLines - 1];
|
||||
lines[maxLines - 1] = lastLine.substring(0, lastLine.length - 3) + '...';
|
||||
}
|
||||
|
||||
// 绘制每一行文本(确保行高正确)
|
||||
lines.forEach((line, index) => {
|
||||
const topPos = contentTop + index * lineHeightPx;
|
||||
|
||||
elements.push({
|
||||
tag: 'font',
|
||||
id: 'content_line_' + index,
|
||||
text: line,
|
||||
position: {
|
||||
top: topPos + 'px',
|
||||
left: leftPos + 'px',
|
||||
width: textWidth + 'px',
|
||||
height: lineHeightPx + 'px'
|
||||
},
|
||||
textStyles: {
|
||||
color: '#111',
|
||||
size: fontSizePx + 'px',
|
||||
align: 'center',
|
||||
verticalAlign: 'top',
|
||||
lineHeight: lineHeightPx + 'px'
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 判断是否主要是中文
|
||||
function isChinese(text) {
|
||||
return /[\u4e00-\u9fa5]/.test(text);
|
||||
}
|
||||
|
||||
// 分割线
|
||||
elements.push({
|
||||
tag: 'rect',
|
||||
id: 'divider',
|
||||
position: { top: btnTop + 'px', left: modalLeft + 'px', width: modalWidth + 'px', height: '0.5px' },
|
||||
rectStyles: { color: '#b3b3b3' }
|
||||
});
|
||||
|
||||
view.draw(elements);
|
||||
|
||||
// 绘制按钮(不变)
|
||||
if (options.showCancel) {
|
||||
const btnWidth = Math.floor((modalWidth - 2 * borderRadius) / 2);
|
||||
view.draw([
|
||||
{ tag: 'rect', id: 'cancelBtn', position: { top: (btnTop + 2) + 'px', left: (modalLeft + borderRadius) + 'px', width: btnWidth + 'px', height: (btnHeight - 3) + 'px' }, rectStyles: { color: '#fff' } },
|
||||
{ tag: 'font', id: 'cancelText', text: options.cancelText || '取消', position: { top: btnTop + 'px', left: (modalLeft + borderRadius) + 'px', width: btnWidth + 'px', height: btnHeight + 'px' }, textStyles: { color: '#007AFF', size: '16px', align: 'center', verticalAlign: 'middle' } },
|
||||
{ tag: 'rect', id: 'btnDivider', position: { top: btnTop + 'px', left: (modalLeft + borderRadius + btnWidth) + 'px', width: '0.5px', height: btnHeight + 'px' }, rectStyles: { color: '#b3b3b3' } },
|
||||
{ tag: 'rect', id: 'confirmBtn', position: { top: (btnTop + 2) + 'px', left: (modalLeft + borderRadius + btnWidth + 1) + 'px', width: btnWidth + 'px', height: (btnHeight - 3) + 'px' }, rectStyles: { color: '#fff' } },
|
||||
{ tag: 'font', id: 'confirmText', text: options.confirmText || '确定', position: { top: btnTop + 'px', left: (modalLeft + borderRadius + btnWidth + 1) + 'px', width: btnWidth + 'px', height: btnHeight + 'px' }, textStyles: { color: '#007AFF', size: '16px', align: 'center', verticalAlign: 'middle' } }
|
||||
]);
|
||||
} else {
|
||||
view.draw([
|
||||
{ tag: 'rect', id: 'confirmBtn', position: { top: (btnTop + 2) + 'px', left: (modalLeft + borderRadius) + 'px', width: (modalWidth - 2 * borderRadius) + 'px', height: (btnHeight - 3) + 'px' }, rectStyles: { color: '#fff' } },
|
||||
{ tag: 'font', id: 'confirmText', text: options.confirmText || '确定', position: { top: btnTop + 'px', left: (modalLeft + borderRadius) + 'px', width: (modalWidth - 2 * borderRadius) + 'px', height: btnHeight + 'px' }, textStyles: { color: '#007AFF', size: '16px', align: 'center', verticalAlign: 'middle' } }
|
||||
]);
|
||||
}
|
||||
|
||||
// 点击事件(不变)
|
||||
view.addEventListener("click", function(e) {
|
||||
const x = e.clientX;
|
||||
const y = e.clientY;
|
||||
if (options.showCancel) {
|
||||
const btnWidth = Math.floor((modalWidth - 2 * borderRadius) / 2);
|
||||
const cancelBtnLeft = modalLeft + borderRadius;
|
||||
if (y > btnTop && y < btnTop + btnHeight) {
|
||||
if (x >= cancelBtnLeft && x < cancelBtnLeft + btnWidth) {
|
||||
options.cancel?.();
|
||||
view.close();
|
||||
} else if (x >= cancelBtnLeft + btnWidth + 1 && x < cancelBtnLeft + 2 * btnWidth + 1) {
|
||||
options.success?.({ confirm: true });
|
||||
view.close();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const btnLeft = modalLeft + borderRadius;
|
||||
if (y > btnTop && y < btnTop + btnHeight && x >= btnLeft && x < btnLeft + (modalWidth - 2 * borderRadius)) {
|
||||
options.success?.({ confirm: true });
|
||||
view.close();
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
|
||||
view.show();
|
||||
|
||||
return {
|
||||
close: () => {
|
||||
view.close();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
export const optionSuccessIcon = (color : string) : string => {
|
||||
const svgXml = `
|
||||
<svg t="1754618739937" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6322" width="64" height="64"><path d="M843.693959 293.609061 425.255869 712.056362 186.145026 472.947566 66.579883 592.504522 425.255869 951.165158 963.260126 413.174204Z" fill="${color}" p-id="6323"></path></svg>
|
||||
`.trim();
|
||||
|
||||
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
|
||||
};
|
||||
export const optionErrorIcon = (color : string = '#F5212D') : string => {
|
||||
const svgXml = `
|
||||
<svg t="1754619171929" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6505" width="64" height="64"><path d="M910.336 186.368l-72.704-72.704L512 439.808 186.368 113.664 113.664 186.368 439.808 512l-326.144 325.632 72.704 72.704L512 584.192l325.632 326.144 72.704-72.704L584.192 512l326.144-325.632z" fill="${color}" p-id="6506"></path></svg>
|
||||
`.trim();
|
||||
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
|
||||
};
|
||||
|
||||
export const examinationSheetIcon = () : string => {
|
||||
const svgXml = `
|
||||
<svg t="1754630871725" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6324" width="64" height="64"><path d="M802.107077 51.2a145.289846 145.289846 0 0 1 145.092923 145.526154v630.547692a145.289846 145.289846 0 0 1-145.092923 145.526154H221.892923a145.329231 145.329231 0 0 1-145.092923-145.526154V196.726154A145.289846 145.289846 0 0 1 221.892923 51.2h580.214154z m-3.387077 76.8H225.28a71.837538 71.837538 0 0 0-71.561846 67.741538l-0.118154 4.253539v624.009846a71.837538 71.837538 0 0 0 67.465846 71.876923l4.214154 0.118154h573.44c38.203077 0 69.356308-29.932308 71.561846-67.741538l0.118154-4.253539V200.034462a71.876923 71.876923 0 0 0-67.465846-71.876924l-4.214154-0.157538zM729.6 512a38.4 38.4 0 1 1 0 76.8h-230.4a38.4 38.4 0 1 1 0-76.8h230.4z m-384 0a38.4 38.4 0 1 1 0 76.8h-51.2a38.4 38.4 0 1 1 0-76.8h51.2z m384-204.8a38.4 38.4 0 1 1 0 76.8h-230.4a38.4 38.4 0 1 1 0-76.8h230.4z m-384 0a38.4 38.4 0 1 1 0 76.8h-51.2a38.4 38.4 0 0 1 0-76.8h51.2z" p-id="6325"></path></svg>
|
||||
`.trim();
|
||||
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
|
||||
};
|
||||
|
||||
export const markIcon = (color : string = '#ABABAB') : string => {
|
||||
const svgXml = `
|
||||
<svg t="1754987745092" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13983" width="64" height="64"><path d="M693.01933598 59.45166004c-24.99339629-24.99339629-65.5162717-24.99339629-90.50966799 0-24.99339629 24.99339629-24.99339629 65.5162717 0 90.50966799l22.627417 22.627417-181.19328425 181.19328425c-78.50370195-71.13564929-199.8637317-68.83684515-275.5976964 6.89711955l-33.94112549 33.94112549a8 8 0 0 0 0 11.3137085l208.59650045 208.59650045-175.36248174 175.36248174a8 8 0 0 0 0 11.3137085l56.5685425 56.56854249a8 8 0 0 0 11.3137085 0L410.88373029 682.41273427l208.59650045 208.59650045a8 8 0 0 0 11.3137085 0l33.9411255-33.9411255c76.21055466-76.21055466 78.05893179-198.62346641 5.54583848-277.07484246L851.41125497 398.86291501l22.627417 22.627417c24.99339629 24.99339629 65.5162717 24.99339629 90.50966799 0 24.99339629-24.99339629 24.99339629-65.5162717 0-90.50966799L693.01933598 59.45166004z" fill="${color}" p-id="13984"></path></svg>
|
||||
`.trim();
|
||||
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
|
||||
};
|
||||
|
||||
export const feedbackIcon = () : string => {
|
||||
const svgXml = `
|
||||
<svg t="1754962550365" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6478" width="64" height="64"><path d="M512 853.333333C323.4816 853.333333 170.666667 700.5184 170.666667 512S323.4816 170.666667 512 170.666667s341.333333 152.814933 341.333333 341.333333-152.814933 341.333333-341.333333 341.333333z m0-51.2a290.133333 290.133333 0 1 0 0-580.266666 290.133333 290.133333 0 0 0 0 580.266666z m-25.6-429.738666a25.6 25.6 0 1 1 51.2 0v248.2176a25.6 25.6 0 0 1-51.2 0v-248.251734zM512 729.1904a31.061333 31.061333 0 1 1 0-62.088533 31.061333 31.061333 0 0 1 0 62.088533z" fill="#999999" p-id="6479"></path></svg>
|
||||
`.trim();
|
||||
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
|
||||
};
|
||||
|
||||
// 通过考试
|
||||
export const passTheExamIcon = (color : string) : string => {
|
||||
const svgXml = `
|
||||
<svg t="1755670289043" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6974" width="64" height="64"><path d="M97.878 483.853c0-117.062-0.108-160.999-0.108-278.062 0-101.472 56.328-108.082 110.483-108.082h441.933c85.128 0 111.453 28.968 111.453 110.481v55.241c0 43.562-55.419 38.57-55.419 0V208.19c0-40.918-13.309-55.24-56.035-55.24H208.252c-45.746 0-55.241 6.203-55.241 55.24v552.407c0 30.278 16.572 55.241 55.241 55.241 67.004 0 43.482 0.026 110.484 0 33.77 0 36.252 55.241 0 55.241s-63.711 0.44-131.306 0.17c-39.867-0.163-72.883-23.774-84.643-60.322-3.292-10.223-4.694-21.525-4.694-32.34-0.323-116.658-0.215-178.076-0.215-294.734" fill="${color}" p-id="6975"></path><path d="M649.644 926.291c-152.344-0.027-275.666-123.537-275.666-276.122 0-152.586 123.915-276.259 276.529-276.042 152.616 0.215 275.884 124.022 275.722 276.878-0.16 152.236-123.807 275.314-276.585 275.286m0.756-496.868c-121.109-0.271-220.641 98.828-220.91 220.045-0.27 121.541 98.345 220.934 219.561 221.287 121.704 0.324 221.505-98.208 221.775-219.047 0.324-122.594-98.236-221.99-220.426-222.285m-55.455-166.362c44.02 0.027 37.978 55.979 0 55.979h-331.45c-41.431 0-39.704-55.979 0-55.979 108.756-0.027 289.155-0.027 331.45 0zM318.736 428.782c44.021 0.028 37.979 55.98 0 55.98h-55.242c-41.431 0-39.704-55.98 0-55.98 108.757-0.027 12.948-0.027 55.242 0z" fill="${color}" p-id="6976"></path><path d="M785.502 547.22c-12.496-12.498-32.758-12.498-45.254 0L602.151 685.316l-42.191-42.191c-12.498-12.496-32.758-12.496-45.256 0-12.496 12.497-12.496 32.759 0 45.256l64.818 64.818c6.249 6.248 14.438 9.372 22.628 9.372s16.379-3.124 22.628-9.372a32.424 32.424 0 0 0 2.402-2.695 32.304 32.304 0 0 0 2.689-2.397l155.633-155.633c12.497-12.496 12.497-32.758 0-45.254z" fill="${color}" p-id="6977"></path></svg>
|
||||
`.trim();
|
||||
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
|
||||
};
|
||||
|
||||
// 完成练习
|
||||
export const completeTheExercisesIcon = (color : string) : string => {
|
||||
const svgXml = `
|
||||
<svg t="1755670357132" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7127" width="64" height="64"><path d="M832 704a42.666667 42.666667 0 0 0-42.666667 42.666667v106.666666H170.666667V170.666667h618.666666v226.133333a42.666667 42.666667 0 0 0 85.333334 0V166.4A83.2 83.2 0 0 0 789.333333 85.333333H170.666667a83.2 83.2 0 0 0-85.333334 81.066667v691.2A83.413333 83.413333 0 0 0 170.666667 938.666667h618.666666a83.413333 83.413333 0 0 0 85.333334-81.066667V746.666667a42.666667 42.666667 0 0 0-42.666667-42.666667z" fill="${color}" p-id="7128"></path><path d="M682.666667 341.333333a42.666667 42.666667 0 0 0-42.666667-42.666666H298.666667a42.666667 42.666667 0 0 0 0 85.333333h341.333333a42.666667 42.666667 0 0 0 42.666667-42.666667zM298.666667 512a42.666667 42.666667 0 0 0 0 85.333333h213.333333a42.666667 42.666667 0 0 0 0-85.333333zM921.386667 491.733333a42.666667 42.666667 0 0 0-60.373334 0L649.813333 704l-60.16-60.373333A42.666667 42.666667 0 0 0 529.28 704l90.453333 90.453333a42.666667 42.666667 0 0 0 60.373334 0l241.28-242.346666a42.666667 42.666667 0 0 0 0-60.373334z" fill="${color}" p-id="7129"></path></svg>
|
||||
`.trim();
|
||||
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
|
||||
};
|
||||
|
||||
// 锁定
|
||||
export const lockIcon = (color : string) : string => {
|
||||
const svgXml = `
|
||||
<svg t="1755683025230" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6630" width="64" height="64"><path d="M787.136 395.466H373.113c0.036-0.579 0.173-1.128 0.173-1.716V267.63c0-81.153 72.891-147.174 162.483-147.174 86.561 0 162.488 64.414 162.488 137.851 0 15.446 12.523 27.969 27.969 27.969 15.446 0 27.969-12.523 27.969-27.969 0-105.043-100.027-193.789-218.426-193.789-120.439 0-218.422 91.118-218.422 203.112v126.12c0 0.588 0.137 1.138 0.173 1.716h-14.467c-82.25 0-149.168 66.918-149.168 149.168v284.402c0 71.971 58.551 130.522 130.522 130.522h502.728c71.971 0 130.522-58.551 130.522-130.522V525.988c0.001-71.971-58.55-130.522-130.521-130.522z m74.584 433.57c0 41.125-33.459 74.584-74.584 74.584H284.408c-41.125 0-74.584-33.459-74.584-74.584V544.634c0-51.409 41.822-93.23 93.23-93.23h484.082c41.125 0 74.584 33.459 74.584 74.584v303.048z" fill="${color}" p-id="6631"></path><path d="M613.108 649.541H458.436c-15.446 0-27.969 12.523-27.969 27.969 0 15.446 12.523 27.969 27.969 27.969h154.672c15.446 0 27.969-12.523 27.969-27.969 0-15.446-12.523-27.969-27.969-27.969z" fill="${color}" p-id="6632"></path></svg>
|
||||
`.trim();
|
||||
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
|
||||
};
|
||||
|
||||
|
||||
// 练习退出图标
|
||||
export const practiceExitIcon = () : string => {
|
||||
const svgXml = `
|
||||
<svg t="1756432094912" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7026" width="32" height="32"><path d="M833.633836 551.096437 481.955218 551.096437c-16.207124 0-29.359678-13.019525-29.359678-29.322839 0-16.188704 13.057387-29.321816 29.359678-29.321816l359.371834 0-74.718749-74.719772c-11.435447-11.414981-11.473309-29.894867 0.057305-41.424458 11.453866-11.453866 30.124088-11.340279 41.407062-0.038886l109.329011 109.310591c5.555529 1.069355 10.881838 3.703345 15.196097 8.018627 6.681166 6.681166 9.468652 15.691377 8.399297 24.319895 1.069355 8.630564-1.718131 17.640776-8.399297 24.321942-4.314259 4.315282-9.640568 6.987135-15.196097 8.018627l-109.329011 109.309568c-11.281951 11.30037-29.953196 11.413957-41.407062-0.039909-11.530614-11.530614-11.491729-30.009478-0.057305-41.423435L833.633836 551.096437zM745.933425 238.32392l0-97.704253c0-43.2572-35.029818-78.230736-78.230736-78.230736l-508.36931 0c-43.181475 0-78.230736 35.011398-78.230736 78.230736l0 742.759641c0 43.256176 35.011398 78.230736 78.230736 78.230736l508.36931 0c43.181475 0 78.230736-35.050284 78.230736-78.230736l0-97.704253c0-16.188704-13.134135-29.321816-29.341259-29.321816-16.208147 0-29.341259 13.133112-29.341259 29.321816l0 78.230736c0 21.571295-17.600867 39.059598-38.925544 39.059598l-469.635123 0c-21.49557 0-38.943964-17.450441-38.943964-39.059598l0-703.852516c0-21.533432 17.600867-39.019689 38.943964-39.019689l469.635123 0c21.515013 0 38.925544 17.410532 38.925544 39.019689l0 78.269621c0 16.187681 13.133112 29.321816 29.341259 29.321816C732.79929 267.645736 745.933425 254.511601 745.933425 238.32392z" fill="#ffffff" p-id="7027"></path></svg>
|
||||
`.trim();
|
||||
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
|
||||
};
|
||||
|
||||
|
||||
// Pk页规则图标
|
||||
export const pkRules = () : string => {
|
||||
const svgXml = `
|
||||
<svg t="1760676829470" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7500" width="64" height="64"><path d="M512 69.963c248.05 0 445.217 197.167 445.217 445.217S760.05 960.398 512 960.398 66.783 763.23 66.783 515.18 263.95 69.963 512 69.963m0-63.603C232.15 6.36 3.18 235.33 3.18 515.18 3.18 795.031 232.15 1024 512 1024s508.82-228.969 508.82-508.82C1020.82 235.33 791.85 6.36 512 6.36z" fill="#ffffff" p-id="7501"></path><path d="M512 432.497c-38.161 0-63.602 25.44-63.602 57.242v273.49c0 31.802 25.44 57.243 63.602 57.243 38.161 0 63.602-25.44 63.602-57.242V489.74c0-31.802-25.44-57.243-63.602-57.243z m0-95.404c38.161 0 63.602-25.44 63.602-63.602 0-38.162-25.44-63.603-63.602-63.603-38.161 0-63.602 25.441-63.602 63.603 0 38.161 25.44 63.602 63.602 63.602z" fill="#ffffff" p-id="7502"></path></svg>
|
||||
`.trim();
|
||||
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
|
||||
};
|
||||
|
||||
// Pk页排行榜图标
|
||||
export const pkRankingList = () : string => {
|
||||
const svgXml = `
|
||||
<svg t="1760676897154" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7659" width="64" height="64"><path d="M323.9 326.9H215.8c-14.9 0-27 12.1-27 27v459.2c0 14.9 12.1 27 27 27h108.1c14.9 0 27-12.1 27-27V353.9c0-14.9-12.1-27-27-27zM567 488.9H459c-14.9 0-27 12.1-27 27v297.2c0 14.9 12.1 27 27 27h108c14.9 0 27-12.1 27-27V515.9c0-14.9-12-27-27-27zM810.2 191.8H702.1c-14.9 0-27 12.1-27 27v594.3c0 14.9 12.1 27 27 27h108.1c14.9 0 27-12.1 27-27V218.8c0-14.9-12.1-27-27-27z" p-id="7660" fill="#ffffff"></path></svg>
|
||||
`.trim();
|
||||
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
|
||||
};
|
||||
|
||||
// Pk页匿名关
|
||||
export const pkAnonymousClose = () : string => {
|
||||
const svgXml = `
|
||||
<svg t="1760677024029" class="icon" viewBox="0 0 1292 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8345" width="64" height="64"><path d="M1188.53945615 539.42278643a46.73094873 46.73094873 0 0 0 0-55.20152813C979.91575068 197.74436416 724.94179737 84.96193203 470.63406904 166.33659834a486.48725859 486.48725859 0 0 0-86.32376894 35.69064258L256.91849844 75.92030263a48.25374961 48.25374961 0 0 0-67.81222178 0 47.06406123 47.06406123 0 0 0 0 67.09840841L300.03279541 252.47001641c-17.22668379 12.37275615-34.31060508 24.7455123-51.15658887 39.02177021A1053.25466924 1053.25466924 0 0 0 71.1843919 484.2212583a46.68336123 46.68336123 0 0 0 0 55.20152813c163.55832011 224.61311338 356.90643018 343.58192315 558.67753212 343.58192402a542.49777422 542.49777422 0 0 0 245.64679893-60.91203076l127.10627666 125.63106328a47.68269873 47.68269873 0 0 0 67.81222177-67.09840928l-110.40305536-108.97542949a992.43781348 992.43781348 0 0 0 228.51529013-232.22711777z m-558.48718212 248.40687568c-161.79758174 0-320.21644922-95.17504834-460.07618204-276.00763974a952.03600576 952.03600576 0 0 1 140.66872119-147.99719971 675.74284102 675.74284102 0 0 1 58.58024239-42.8287711l87.46586894 86.60929307a197.8213377 197.8213377 0 0 0-30.31325332 104.21667774 202.86561533 202.86561533 0 0 0 203.67460284 201.77110195 200.20071357 200.20071357 0 0 0 105.5491286-30.45601495l68.00257179 67.57428428a440.51770987 440.51770987 0 0 1-173.55170039 37.11826846z m101.69453935-242.22049717l-135.67203106-134.19681767a100.74278847 100.74278847 0 0 1 33.97749171-6.18637852 105.35877862 105.35877862 0 0 1 101.55177685 140.38319619z m-67.76463427 67.0984084a114.210058 114.210058 0 0 1-33.97749258 5.71050351 107.119517 107.119517 0 0 1-107.690567-106.59605448 101.69453848 101.69453848 0 0 1 5.99602852-33.3112667z m226.61178925 89.9404207l-87.27551894-86.60929394a196.53647432 196.53647432 0 0 0 30.31325243-104.21667773 202.96079033 202.96079033 0 0 0-309.31890556-171.31508614L456.31022451 272.93265195c2.47455088-0.95175088 4.75875263-2.37937588 7.37606602-3.33112676 12.08723115-4.75875263 24.26963731-9.5175044 36.40445595-13.32450615 203.34149033-65.19490752 411.9176083 25.69726318 589.75218545 255.54500333a847.91450303 847.91450303 0 0 1-199.43931357 190.82597167z" fill="#ffffff" p-id="8346"></path></svg>
|
||||
`.trim();
|
||||
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
|
||||
};
|
||||
|
||||
// Pk页匿名开
|
||||
export const pkAnonymousOpen = () : string => {
|
||||
const svgXml = `
|
||||
<svg t="1763361315669" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7687" width="64" height="64"><path d="M512 832c-156.448 0-296.021333-98.730667-418.410667-291.605333a52.938667 52.938667 0 0 1 0-56.789334C215.978667 290.730667 355.552 192 512 192c156.448 0 296.021333 98.730667 418.410667 291.605333a52.938667 52.938667 0 0 1 0 56.789334C808.021333 733.269333 668.448 832 512 832z m0-576c-129.514667 0-249.461333 83.850667-360.117333 256C262.538667 684.149333 382.485333 768 512 768c129.514667 0 249.461333-83.850667 360.117333-256C761.461333 339.850667 641.514667 256 512 256z m0 405.333333c-83.210667 0-150.666667-66.858667-150.666667-149.333333S428.789333 362.666667 512 362.666667s150.666667 66.858667 150.666667 149.333333S595.210667 661.333333 512 661.333333z m0-64c47.552 0 86.101333-38.208 86.101333-85.333333S559.552 426.666667 512 426.666667c-47.552 0-86.101333 38.208-86.101333 85.333333s38.549333 85.333333 86.101333 85.333333z" fill="#ffffff" p-id="7688"></path></svg>
|
||||
`.trim();
|
||||
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
|
||||
};
|
||||
|
||||
|
||||
// 重置页图标
|
||||
export const resetIcon = () : string => {
|
||||
const svgXml = `
|
||||
<svg t="1760952234942" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7688" width="64" height="64"><path d="M931.07 384.75a368 368 0 0 0-704 95.25H64l192 192 192-192H288.91C312 333.51 439.12 221.13 592 221.13c169.21 0 306.87 137.66 306.87 306.87S761.21 834.87 592 834.87a307.37 307.37 0 0 1-194.56-69.55 30.57 30.57 0 0 0-38.79 47.25 368.1 368.1 0 0 0 572.42-427.82z" fill="#333333" p-id="7689"></path></svg>
|
||||
`.trim();
|
||||
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
|
||||
};
|
||||
|
||||
|
||||
// pk页切换图标
|
||||
export const switchIcon = () : string => {
|
||||
const svgXml = `
|
||||
<svg t="1761037923256" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7561" width="32" height="32"><path d="M900.4 424.87c19.47 0 37.03-11.73 44.49-29.73 7.46-17.98 3.33-38.7-10.43-52.48L713.97 122.19c-7.3-7.3-19.12-7.3-26.42 0l-41.69 41.69c-7.3 7.3-7.3 19.13 0 26.42l138.28 138.27H86.32c-10.19 0-18.46 8.26-18.46 18.46v59.39c0 10.19 8.26 18.46 18.46 18.46H900.4zM937.65 598.72H123.8c-19.47 0-37.03 11.73-44.49 29.73-7.46 17.98-3.33 38.7 10.43 52.48l220.49 220.48c7.3 7.3 19.12 7.3 26.42 0l41.69-41.69c7.3-7.3 7.3-19.13 0-26.42L240.06 695.02h697.59c10.32 0 18.68-8.37 18.68-18.68v-58.93c0-10.32-8.36-18.69-18.68-18.69z" p-id="7562" fill="#e6e6e6"></path></svg>
|
||||
`.trim();
|
||||
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
|
||||
};
|
||||
|
||||
|
||||
// 左右切换箭头
|
||||
export const arrowRightIcon = (color : string = '#F5212D') : string => {
|
||||
const svgXml = `
|
||||
<svg t="1762762748602" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7658" width="64" height="64" fill="${color}"><path d="M462.08 192a47.36 47.36 0 0 0 0 64l256 256-256 256a47.36 47.36 0 1 0 64 64l288.64-288.64A50.56 50.56 0 0 0 832 512a47.36 47.36 0 0 0-14.08-33.28L529.28 192a47.36 47.36 0 0 0-67.2 0z" p-id="7659"></path><path d="M206.08 192a47.36 47.36 0 0 0 0 64l256 256-256 256a47.36 47.36 0 1 0 64 64l291.84-286.08A50.56 50.56 0 0 0 576 512a47.36 47.36 0 0 0-14.08-33.28L273.28 192a47.36 47.36 0 0 0-67.2 0z" p-id="7660" fill="${color}"></path></svg>
|
||||
`.trim();
|
||||
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
// 设置科技鹿
|
||||
import {
|
||||
getUserInfo
|
||||
} from "@/common/common.js"
|
||||
import common from "@/common/common.js"
|
||||
export const setMascot = () => {
|
||||
const mascotState = false
|
||||
if (!mascotState) {
|
||||
try {
|
||||
const userInfo = getUserInfo()
|
||||
const mascotInfo = userInfo?.mascotInfo
|
||||
if (mascotInfo) {
|
||||
uni.setTabBarStyle({
|
||||
midButton: {
|
||||
iconPath: "/static/images/icon/fawn-" + (mascotInfo?.mascotNo || '1') + ".png",
|
||||
width: "86px",
|
||||
height: "86px",
|
||||
iconWidth: "90px"
|
||||
},
|
||||
success: () => {
|
||||
common.setValue('mascotState', true)
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
return mascotState
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* 本模块封装了Android、iOS的应用权限判断、打开应用权限设置界面、以及位置系统服务是否开启
|
||||
*/
|
||||
|
||||
var isIos
|
||||
// #ifdef APP-PLUS
|
||||
isIos = (plus.os.name == "iOS")
|
||||
// #endif
|
||||
|
||||
// 判断推送权限是否开启
|
||||
function judgeIosPermissionPush() {
|
||||
var result = false;
|
||||
var UIApplication = plus.ios.import("UIApplication");
|
||||
var app = UIApplication.sharedApplication();
|
||||
var enabledTypes = 0;
|
||||
if (app.currentUserNotificationSettings) {
|
||||
var settings = app.currentUserNotificationSettings();
|
||||
enabledTypes = settings.plusGetAttribute("types");
|
||||
console.log("enabledTypes1:" + enabledTypes);
|
||||
if (enabledTypes == 0) {
|
||||
console.log("推送权限没有开启");
|
||||
} else {
|
||||
result = true;
|
||||
console.log("已经开启推送功能!")
|
||||
}
|
||||
plus.ios.deleteObject(settings);
|
||||
} else {
|
||||
enabledTypes = app.enabledRemoteNotificationTypes();
|
||||
if (enabledTypes == 0) {
|
||||
console.log("推送权限没有开启!");
|
||||
} else {
|
||||
result = true;
|
||||
console.log("已经开启推送功能!")
|
||||
}
|
||||
console.log("enabledTypes2:" + enabledTypes);
|
||||
}
|
||||
plus.ios.deleteObject(app);
|
||||
plus.ios.deleteObject(UIApplication);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 判断定位权限是否开启
|
||||
function judgeIosPermissionLocation() {
|
||||
var result = false;
|
||||
var cllocationManger = plus.ios.import("CLLocationManager");
|
||||
var status = cllocationManger.authorizationStatus();
|
||||
result = (status != 2)
|
||||
console.log("定位权限开启:" + result);
|
||||
// 以下代码判断了手机设备的定位是否关闭,推荐另行使用方法 checkSystemEnableLocation
|
||||
/* var enable = cllocationManger.locationServicesEnabled();
|
||||
var status = cllocationManger.authorizationStatus();
|
||||
console.log("enable:" + enable);
|
||||
console.log("status:" + status);
|
||||
if (enable && status != 2) {
|
||||
result = true;
|
||||
console.log("手机定位服务已开启且已授予定位权限");
|
||||
} else {
|
||||
console.log("手机系统的定位没有打开或未给予定位权限");
|
||||
} */
|
||||
plus.ios.deleteObject(cllocationManger);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 判断麦克风权限是否开启
|
||||
function judgeIosPermissionRecord() {
|
||||
var result = false;
|
||||
var avaudiosession = plus.ios.import("AVAudioSession");
|
||||
var avaudio = avaudiosession.sharedInstance();
|
||||
var permissionStatus = avaudio.recordPermission();
|
||||
console.log("permissionStatus:" + permissionStatus);
|
||||
if (permissionStatus == 1684369017 || permissionStatus == 1970168948) {
|
||||
console.log("麦克风权限没有开启");
|
||||
} else {
|
||||
result = true;
|
||||
console.log("麦克风权限已经开启");
|
||||
}
|
||||
plus.ios.deleteObject(avaudiosession);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 判断相机权限是否开启
|
||||
function judgeIosPermissionCamera() {
|
||||
var result = false;
|
||||
var AVCaptureDevice = plus.ios.import("AVCaptureDevice");
|
||||
var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide');
|
||||
console.log("authStatus:" + authStatus);
|
||||
if (authStatus == 3) {
|
||||
result = true;
|
||||
console.log("相机权限已经开启");
|
||||
} else {
|
||||
console.log("相机权限没有开启");
|
||||
}
|
||||
plus.ios.deleteObject(AVCaptureDevice);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 判断相册权限是否开启
|
||||
function judgeIosPermissionPhotoLibrary() {
|
||||
var result = false;
|
||||
var PHPhotoLibrary = plus.ios.import("PHPhotoLibrary");
|
||||
var authStatus = PHPhotoLibrary.authorizationStatus();
|
||||
console.log("authStatus:" + authStatus);
|
||||
if (authStatus == 3) {
|
||||
result = true;
|
||||
console.log("相册权限已经开启");
|
||||
} else {
|
||||
console.log("相册权限没有开启");
|
||||
}
|
||||
plus.ios.deleteObject(PHPhotoLibrary);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 判断通讯录权限是否开启
|
||||
function judgeIosPermissionContact() {
|
||||
var result = false;
|
||||
var CNContactStore = plus.ios.import("CNContactStore");
|
||||
var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0);
|
||||
if (cnAuthStatus == 3) {
|
||||
result = true;
|
||||
console.log("通讯录权限已经开启");
|
||||
} else {
|
||||
console.log("通讯录权限没有开启");
|
||||
}
|
||||
plus.ios.deleteObject(CNContactStore);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 判断日历权限是否开启
|
||||
function judgeIosPermissionCalendar() {
|
||||
var result = false;
|
||||
var EKEventStore = plus.ios.import("EKEventStore");
|
||||
var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(0);
|
||||
if (ekAuthStatus == 3) {
|
||||
result = true;
|
||||
console.log("日历权限已经开启");
|
||||
} else {
|
||||
console.log("日历权限没有开启");
|
||||
}
|
||||
plus.ios.deleteObject(EKEventStore);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 判断备忘录权限是否开启
|
||||
function judgeIosPermissionMemo() {
|
||||
var result = false;
|
||||
var EKEventStore = plus.ios.import("EKEventStore");
|
||||
var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(1);
|
||||
if (ekAuthStatus == 3) {
|
||||
result = true;
|
||||
console.log("备忘录权限已经开启");
|
||||
} else {
|
||||
console.log("备忘录权限没有开启");
|
||||
}
|
||||
plus.ios.deleteObject(EKEventStore);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Android权限查询
|
||||
function requestAndroidPermission(permissionID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
plus.android.requestPermissions(
|
||||
[permissionID], // 理论上支持多个权限同时查询,但实际上本函数封装只处理了一个权限的情况。有需要的可自行扩展封装
|
||||
function(resultObj) {
|
||||
var result = 0;
|
||||
for (var i = 0; i < resultObj.granted.length; i++) {
|
||||
var grantedPermission = resultObj.granted[i];
|
||||
console.log('已获取的权限:' + grantedPermission);
|
||||
result = 1
|
||||
}
|
||||
for (var i = 0; i < resultObj.deniedPresent.length; i++) {
|
||||
var deniedPresentPermission = resultObj.deniedPresent[i];
|
||||
console.log('拒绝本次申请的权限:' + deniedPresentPermission);
|
||||
result = 0
|
||||
}
|
||||
for (var i = 0; i < resultObj.deniedAlways.length; i++) {
|
||||
var deniedAlwaysPermission = resultObj.deniedAlways[i];
|
||||
console.log('永久拒绝申请的权限:' + deniedAlwaysPermission);
|
||||
result = -1
|
||||
}
|
||||
resolve(result);
|
||||
// 若所需权限被拒绝,则打开APP设置界面,可以在APP设置界面打开相应权限
|
||||
// if (result != 1) {
|
||||
// gotoAppPermissionSetting()
|
||||
// }
|
||||
},
|
||||
function(error) {
|
||||
console.log('申请权限错误:' + error.code + " = " + error.message);
|
||||
resolve({
|
||||
code: error.code,
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 使用一个方法,根据参数判断权限
|
||||
function judgeIosPermission(permissionID) {
|
||||
if (permissionID == "location") {
|
||||
return judgeIosPermissionLocation()
|
||||
} else if (permissionID == "camera") {
|
||||
return judgeIosPermissionCamera()
|
||||
} else if (permissionID == "photoLibrary") {
|
||||
return judgeIosPermissionPhotoLibrary()
|
||||
} else if (permissionID == "record") {
|
||||
return judgeIosPermissionRecord()
|
||||
} else if (permissionID == "push") {
|
||||
return judgeIosPermissionPush()
|
||||
} else if (permissionID == "contact") {
|
||||
return judgeIosPermissionContact()
|
||||
} else if (permissionID == "calendar") {
|
||||
return judgeIosPermissionCalendar()
|
||||
} else if (permissionID == "memo") {
|
||||
return judgeIosPermissionMemo()
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 跳转到**应用**的权限页面
|
||||
function gotoAppPermissionSetting() {
|
||||
if (isIos) {
|
||||
var UIApplication = plus.ios.import("UIApplication");
|
||||
var application2 = UIApplication.sharedApplication();
|
||||
var NSURL2 = plus.ios.import("NSURL");
|
||||
// var setting2 = NSURL2.URLWithString("prefs:root=LOCATION_SERVICES");
|
||||
var setting2 = NSURL2.URLWithString("app-settings:");
|
||||
application2.openURL(setting2);
|
||||
|
||||
plus.ios.deleteObject(setting2);
|
||||
plus.ios.deleteObject(NSURL2);
|
||||
plus.ios.deleteObject(application2);
|
||||
} else {
|
||||
// console.log(plus.device.vendor);
|
||||
var Intent = plus.android.importClass("android.content.Intent");
|
||||
var Settings = plus.android.importClass("android.provider.Settings");
|
||||
var Uri = plus.android.importClass("android.net.Uri");
|
||||
var mainActivity = plus.android.runtimeMainActivity();
|
||||
var intent = new Intent();
|
||||
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||
var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
|
||||
intent.setData(uri);
|
||||
mainActivity.startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查系统的设备服务是否开启
|
||||
// var checkSystemEnableLocation = async function () {
|
||||
function checkSystemEnableLocation() {
|
||||
if (isIos) {
|
||||
var result = false;
|
||||
var cllocationManger = plus.ios.import("CLLocationManager");
|
||||
var result = cllocationManger.locationServicesEnabled();
|
||||
console.log("系统定位开启:" + result);
|
||||
plus.ios.deleteObject(cllocationManger);
|
||||
return result;
|
||||
} else {
|
||||
var context = plus.android.importClass("android.content.Context");
|
||||
var locationManager = plus.android.importClass("android.location.LocationManager");
|
||||
var main = plus.android.runtimeMainActivity();
|
||||
var mainSvr = main.getSystemService(context.LOCATION_SERVICE);
|
||||
var result = mainSvr.isProviderEnabled(locationManager.GPS_PROVIDER);
|
||||
console.log("系统定位开启:" + result);
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
export default ({
|
||||
judgeIosPermission: judgeIosPermission,
|
||||
requestAndroidPermission: requestAndroidPermission,
|
||||
checkSystemEnableLocation: checkSystemEnableLocation,
|
||||
gotoAppPermissionSetting: gotoAppPermissionSetting
|
||||
})
|
||||
@@ -0,0 +1,94 @@
|
||||
import {
|
||||
getPhoneEnvBool
|
||||
} from '@/common/common.js'
|
||||
|
||||
import common from '@/common/common.js'
|
||||
const IsAndEnv = getPhoneEnvBool('AND')
|
||||
import permission from '@/common/permission.js'
|
||||
|
||||
/**
|
||||
* 录音权限工具类
|
||||
* 统一处理安卓和iOS的录音权限状态:同意、拒绝、未设置
|
||||
*/
|
||||
const RecordPermission = {
|
||||
hasRecordPermission: false,
|
||||
/**
|
||||
* 获取当前录音权限状态
|
||||
* @returns {Promise<'granted'|'denied'|'undetermined'>} 权限状态
|
||||
*/
|
||||
getPermissionState() {
|
||||
// #ifdef APP-PLUS
|
||||
const authSetting = uni.getAppAuthorizeSetting();
|
||||
const state = authSetting.microphoneAuthorized;
|
||||
switch (state) {
|
||||
case 'authorized':
|
||||
return 'authorized';
|
||||
case 'denied':
|
||||
return 'denied';
|
||||
case 'not determined':
|
||||
return 'undetermined';
|
||||
case 'config error':
|
||||
return 'undetermined';
|
||||
default:
|
||||
return 'undetermined';
|
||||
}
|
||||
// #endif
|
||||
// 非APP环境默认返回以获取方便h5调试
|
||||
return 'undetermined';
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查并请求录音权限
|
||||
* @returns {Promise<boolean>} 是否获得录音权限
|
||||
*/
|
||||
requestPermission() {
|
||||
if (this.hasRecordPermission) {
|
||||
console.log('直接通过');
|
||||
return Promise.resolve('authorized');
|
||||
}
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const state = this.getPermissionState();
|
||||
// 已同意权限
|
||||
console.log('权限状态', state);
|
||||
if (state === 'authorized') {
|
||||
this.hasRecordPermission = true; // 缓存权限状态
|
||||
resolve('authorized')
|
||||
};
|
||||
if (state === 'denied') {
|
||||
const result = await common.show(
|
||||
'提示信息',
|
||||
'当前页面需要使用录音权限,是否前往开启?'
|
||||
)
|
||||
if (result) {
|
||||
// 前往权限设置页面
|
||||
uni.openAppAuthorizeSetting();
|
||||
reject('open_app_authorize_setting')
|
||||
} else {
|
||||
// 用户取消,返回上一页
|
||||
reject('cancel')
|
||||
}
|
||||
}
|
||||
|
||||
if (state === 'undetermined') {
|
||||
// #ifdef APP-PLUS
|
||||
if (IsAndEnv) {
|
||||
// 安卓需要再次请求权限
|
||||
const permResult = await permission.requestAndroidPermission(
|
||||
'android.permission.RECORD_AUDIO');
|
||||
if(permResult === 1) {
|
||||
resolve('need_request');
|
||||
} else {
|
||||
reject('permResultx')
|
||||
}
|
||||
} else {
|
||||
resolve('need_request');
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
};
|
||||
|
||||
export default RecordPermission;
|
||||
@@ -0,0 +1,174 @@
|
||||
// 定义正则表达式
|
||||
const regexes = {
|
||||
email: /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/,
|
||||
phone: /^(?:(?:\+|00)86)?1\d{10}$/,
|
||||
url: /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/,
|
||||
ip: /^((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))$/,
|
||||
uuid: /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/,
|
||||
required: /^.+$/,
|
||||
is_number: /^\d+(\.\d+)?$/,
|
||||
is_chinese: /^[\u4e00-\u9fa5]+$/,
|
||||
is_realname: /^(?:[\u4e00-\u9fa5·]{2,50})$/,
|
||||
is_english: /^[A-Za-z]+$/,
|
||||
is_id_card: /^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|10|11|12)(?:0[1-9]|[1-2]\d|30|31)\d{3}[\dXx]$/,
|
||||
is_bank_card: /^[1-9]\d{9,29}$/,
|
||||
is_car_id: /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-HJ-NP-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]$/,
|
||||
is_username: /^.{5,18}$/,
|
||||
is_password: /^.{4,18}$/,
|
||||
is_email: /^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/
|
||||
};
|
||||
|
||||
// 邮箱
|
||||
export const checkEmail = (rule, sId, callback) => {
|
||||
let _idRe = regexes.is_usis_emailername
|
||||
if (!_idRe.test(sId)) {
|
||||
callback(new Error('邮箱格式不正确'));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
// 用户名
|
||||
export const checkUserName = (rule, sId, callback) => {
|
||||
let _idRe = regexes.is_username
|
||||
if (!_idRe.test(sId)) {
|
||||
callback(new Error('用户名格式不正确'));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
// 密码
|
||||
export const checkPassword = (rule, sId, callback) => {
|
||||
let _idRe = regexes.is_password
|
||||
if (!_idRe.test(sId)) {
|
||||
callback(new Error('密码格式不正确'));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
// 车牌号
|
||||
export const checkCarId = (rule, sId, callback) => {
|
||||
let _idRe = regexes.is_car_id
|
||||
if (!_idRe.test(sId)) {
|
||||
callback(new Error('车牌号格式不正确'));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
// 银行卡
|
||||
export const checkBankCard = (rule, sId, callback) => {
|
||||
let _idRe = regexes.is_bank_card
|
||||
if (!_idRe.test(sId)) {
|
||||
callback(new Error('银行卡号格式不正确'));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
// 验证手机号
|
||||
export const checkPhone = (rule, sId, callback) => {
|
||||
let _idRe = regexes.phone
|
||||
if (!_idRe.test(sId)) {
|
||||
callback(new Error('手机号不正确'));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
// 验证姓名
|
||||
export const checkName = (rule, sId, callback) => {
|
||||
let _idRe = regexes.is_chinese
|
||||
if (!_idRe.test(sId)) {
|
||||
callback(new Error('姓名不正确'));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
export const checkRealName = (rule, sId, callback) => {
|
||||
let _idRe = regexes.is_realname
|
||||
if (!_idRe.test(sId)) {
|
||||
callback(new Error('姓名不正确'));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
// 验证身份证号
|
||||
export const checkIdCard = (rule, sId, callback) => {
|
||||
if (!sId) {
|
||||
return callback(new Error('身份证号不能为空'));
|
||||
}
|
||||
let _idRe =
|
||||
/^\d{6}((((((19|20)\d{2})(0[13-9]|1[012])(0[1-9]|[12]\d|30))|(((19|20)\d{2})(0[13578]|1[02])31)|((19|20)\d{2})02(0[1-9]|1\d|2[0-8])|((((19|20)([13579][26]|[2468][048]|0[48]))|(2000))0229))\d{3})|((((\d{2})(0[13-9]|1[012])(0[1-9]|[12]\d|30))|((\d{2})(0[13578]|1[02])31)|((\d{2})02(0[1-9]|1\d|2[0-8]))|(([13579][26]|[2468][048]|0[048])0229))\d{2}))(\d|X|x)$/
|
||||
if (!_idRe.test(sId)) {
|
||||
callback(new Error('⾝份证长度或格式错误'));
|
||||
}
|
||||
//⾝份证城市
|
||||
const aCity = {
|
||||
11: "北京",
|
||||
12: "天津",
|
||||
13: "河北",
|
||||
14: "山西",
|
||||
15: "内蒙古",
|
||||
21: "辽宁",
|
||||
22: "吉林",
|
||||
23: "黑龙江",
|
||||
31: "上海",
|
||||
32: "江苏",
|
||||
33: "浙江",
|
||||
34: "安徽",
|
||||
35: "福建",
|
||||
36: "江西",
|
||||
37: "山东",
|
||||
41: "河南",
|
||||
42: "湖北",
|
||||
43: "湖南",
|
||||
44: "广东",
|
||||
45: "广西",
|
||||
46: "海南",
|
||||
50: "重庆",
|
||||
51: "四川",
|
||||
52: "贵州",
|
||||
53: "云南",
|
||||
54: "西藏",
|
||||
61: "陕西",
|
||||
62: "甘肃",
|
||||
63: "青海",
|
||||
64: "宁夏",
|
||||
65: "新疆",
|
||||
71: "台湾",
|
||||
81: "香港",
|
||||
82: "澳门",
|
||||
91: "国外"
|
||||
};
|
||||
if (!aCity[parseInt(sId.substr(0, 2))]) {
|
||||
callback(new Error('身份证地区有误'));
|
||||
}
|
||||
// 出生期验证
|
||||
const sBirthday = (sId.substr(6, 4) + "-" + Number(sId.substr(10, 2)) + "-" + Number(sId.substr(12, 2)))
|
||||
.replace(/-/g, "/")
|
||||
const d = new Date(sBirthday)
|
||||
if (sBirthday != (d.getFullYear() + "/" + (d.getMonth() + 1) + "/" + d.getDate())) {
|
||||
callback(new Error('出生日期有误'));
|
||||
}
|
||||
// ⾝份证号码校验
|
||||
if (sId.length == 18) {
|
||||
let sum = 0
|
||||
const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2],
|
||||
codes = "10X98765432"
|
||||
for (let i = 0; i < sId.length - 1; i++) {
|
||||
sum += sId[i] * weights[i];
|
||||
}
|
||||
const last = codes[sum % 11]; //计算出来的最后⼀位⾝份证号码
|
||||
if (sId[sId.length - 1] != last) {
|
||||
callback(new Error('身份证号校验码有误'));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
export const validator = {
|
||||
checkName,
|
||||
checkIdCard,
|
||||
checkPhone,
|
||||
checkCarId,
|
||||
checkBankCard,
|
||||
checkUserName,
|
||||
checkPassword,
|
||||
checkEmail
|
||||
}
|
||||
|
||||
export default regexes;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="30px" height="30px" viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#FFFFFF;stroke-width:2.4306;stroke-miterlimit:10;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M17.1,24.2h-12c-0.2,0-0.3-0.2-0.3-0.3v-9.3c0-0.2,0.2-0.3,0.3-0.3h12c0.2,0,0.3,0.2,0.3,0.3v9.3
|
||||
C17.5,24.1,17.3,24.2,17.1,24.2z"/>
|
||||
<path class="st0" d="M16.6,5.4c4.8,0,8.7,3.9,8.7,8.7"/>
|
||||
<polyline class="st0" points="19.3,10.1 14.9,5.6 19.3,1.2 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 791 B |
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<image class="img_100" v-if="showLocalImage" :src="imagePath"></image>
|
||||
<image-preview v-else-if="showNetworkImage" class="img_100" :src="imageAddr"></image-preview>
|
||||
<image class="img_100" v-else :src="defaultAvatar"></image>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, watch, onMounted } from 'vue';
|
||||
import { defaultAvatar } from '@/enum.js';
|
||||
import { getUserInfo } from '@/common/common';
|
||||
|
||||
const imagePath = ref('');
|
||||
const imageAddr = ref('');
|
||||
|
||||
// 状态标识
|
||||
const showLocalImage = ref(false);
|
||||
const showNetworkImage = ref(false);
|
||||
|
||||
// 初始化用户信息
|
||||
const initUserInfo = () => {
|
||||
const info = getUserInfo();
|
||||
imagePath.value = info.imagePath || '';
|
||||
imageAddr.value = info.imageAddr || '';
|
||||
validateAvatarSources();
|
||||
};
|
||||
|
||||
// 验证头像来源
|
||||
const validateAvatarSources = () => {
|
||||
if (imagePath.value) {
|
||||
showLocalImage.value = true;
|
||||
showNetworkImage.value = false;
|
||||
} else if (imageAddr.value) {
|
||||
showLocalImage.value = false;
|
||||
showNetworkImage.value = true;
|
||||
} else {
|
||||
showLocalImage.value = false;
|
||||
showNetworkImage.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 强制刷新方法 - 重新获取用户信息并刷新头像
|
||||
const forceRefreshAvatar = () => {
|
||||
// 清空当前图片信息
|
||||
imagePath.value = '';
|
||||
imageAddr.value = '';
|
||||
// 重新获取用户信息
|
||||
initUserInfo();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initUserInfo();
|
||||
});
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
forceRefreshAvatar
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<view class="course-item-box" @click="clickItem">
|
||||
<view class="item-title">{{itemInfo.crsName}}</view>
|
||||
<view class="img-box-view">
|
||||
<ImagePreview class="img-box" :imgId="itemInfo.imgId" :isCache="true" width="225" height="127">
|
||||
</ImagePreview>
|
||||
<view class="item-grade">{{itemInfo.evalGrade}}分</view>
|
||||
<!-- <image class="item-grade" mode="aspectFill"></view> -->
|
||||
</view>
|
||||
<view class="cont-box">
|
||||
<scroll-view class="item-tags" :scroll-x="true">
|
||||
<view class="item-tag-view">
|
||||
<view :class="['item-tag', this.activeTag.includes(item.tagId)?'active-tag':'']" v-for="item in tagsShowList">
|
||||
{{item.tagName}}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="item-publish">更新:{{itemInfo.mtTime}}</view>
|
||||
<view class="item-num">已学:{{itemInfo.accmStdyCnt}}人次</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ImagePreview from '@/components/image-preview/image-preview.vue'
|
||||
export default {
|
||||
name: "course-item",
|
||||
components: {
|
||||
ImagePreview
|
||||
},
|
||||
props: {
|
||||
itemInfo: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
crsName: '',
|
||||
tagCataList: [],
|
||||
publicTime: "",
|
||||
learnedNum: ''
|
||||
})
|
||||
},
|
||||
activeTag: {
|
||||
default: () => [],
|
||||
type: Array
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
activeTag: {
|
||||
handler() {},
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tagsShowList() {
|
||||
if (!this.activeTag) return this.itemInfo.tagCataList || []
|
||||
let _arr = (this.itemInfo.tagCataList || []).filter(t => this.activeTag.includes(t.tagId))
|
||||
let _arr1 = (this.itemInfo.tagCataList || []).filter(t => !this.activeTag.includes(t.tagId))
|
||||
return _arr.concat(_arr1)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickItem() {
|
||||
this.$emit('clickItem', this.itemInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.course-item-box {
|
||||
padding: 12rpx 15rpx 8rpx;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
|
||||
.img-box-view {
|
||||
width: 225rpx;
|
||||
height: 127rpx;
|
||||
float: left;
|
||||
position: relative;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 10rpx #c3c3c3;
|
||||
|
||||
.img-box {
|
||||
width: 225rpx;
|
||||
height: 127rpx;
|
||||
}
|
||||
|
||||
.item-grade {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 62rpx;
|
||||
height: 32rpx;
|
||||
background-color: #ff581c;
|
||||
top: 20rpx;
|
||||
border-radius: 8rpx 0 0 8rpx;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
// padding-right: 10rpx;
|
||||
font-size: 22rpx;
|
||||
line-height: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.item-title {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 44rpx;
|
||||
font-size: 29rpx;
|
||||
font-weight: 500;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.cont-box {
|
||||
width: calc(100% - 225rpx);
|
||||
box-sizing: border-box;
|
||||
padding: 0 10rpx 0 20rpx;
|
||||
float: left;
|
||||
|
||||
.item-tags {
|
||||
margin-bottom: 14rpx;
|
||||
|
||||
.item-tag-view {
|
||||
white-space: nowrap;
|
||||
height: 44rpx;
|
||||
|
||||
.item-tag {
|
||||
height: 38rpx;
|
||||
line-height: 34rpx;
|
||||
box-sizing: border-box;
|
||||
font-size: 20rpx;
|
||||
background-color: #fff;
|
||||
color: #06f;
|
||||
border: 2rpx solid #06f;
|
||||
padding: 0 20rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 20rpx;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 200rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.active-tag {
|
||||
// background-color: #ffa150;
|
||||
color: #ffa150;
|
||||
border: 2rpx solid #ffa150;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-publish,
|
||||
.item-num {
|
||||
height: 32rpx;
|
||||
line-height: 32rpx;
|
||||
font-size: 20rpx;
|
||||
color: rgb(102, 102, 102);
|
||||
margin-top: 6rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.item-publish {
|
||||
margin-top: 0rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<view class="item">
|
||||
<view class="top">
|
||||
<view class="type">
|
||||
{{ subject_type_text }}
|
||||
</view>
|
||||
<view class="score">5分</view>
|
||||
<view class="fl1"></view>
|
||||
<view class="right">
|
||||
<view class="mark_and_feedback_div">
|
||||
<image
|
||||
:src="markIcon(props.item.mark ? '#0066FF' : '#ABABAB')"
|
||||
class="img"
|
||||
@click="mark_click()"
|
||||
></image>
|
||||
<image :src="feedbackIcon()" class="img" @click="feedback_click()"></image>
|
||||
</view>
|
||||
<!-- <view class="right_text">展开</view>
|
||||
<uv-icon class="arrow-icon" name="arrow-right"></uv-icon> -->
|
||||
</view>
|
||||
</view>
|
||||
<view class="question_text">
|
||||
{{ subject_data.qnsContent }}
|
||||
</view>
|
||||
<view class="option_div">
|
||||
<questionVue @changePlay="changePlay" :dataInfo="talkingInfo" :activeVoiceId="questionValue"></questionVue>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, nextTick, computed, toRaw, watch } from 'vue';
|
||||
import questionVue from './question.vue';
|
||||
import radioSubjectItem from './radio-subject-item.vue';
|
||||
import { markIcon, feedbackIcon } from '@/common/imgSvg';
|
||||
import { SUBJECT_TYPE } from '@/enum.js';
|
||||
const questionValue = ref('');
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
activeVoiceId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
voicePathValue: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
const emit = defineEmits(['subject_click']);
|
||||
const talkingInfo = ref({
|
||||
id: '3',
|
||||
intrctWay: '02',
|
||||
intrctContent: '中的i id一二三。',
|
||||
talkingText: '中的i id一二三。',
|
||||
talkingVoice: ''
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.voicePathValue,
|
||||
(val) => {
|
||||
if (val) {
|
||||
talkingInfo.value = {
|
||||
id: '3',
|
||||
intrctWay: '02',
|
||||
intrctContent: '中的i id一二三。',
|
||||
talkingText: '中的i id一二三。',
|
||||
talkingVoice: val
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
const feedback_click = () => {
|
||||
emit('subject_click', 'feedback', props.item);
|
||||
};
|
||||
const mark_click = () => {
|
||||
emit('subject_click', 'mark', props.item);
|
||||
};
|
||||
|
||||
const customStyle = {
|
||||
borderRadius: '16rpx',
|
||||
width: '292rpx',
|
||||
backgroundColor: '#0066FF',
|
||||
border: '1rpx solid rgb(12, 123, 245)'
|
||||
};
|
||||
|
||||
const subject_data = reactive({
|
||||
qnsId: '',
|
||||
qnsContent: '',
|
||||
qnsTyp: ''
|
||||
});
|
||||
Object.assign(subject_data, {
|
||||
qnsId: props.item.qnsId,
|
||||
qnsContent: props.item.qnsContent,
|
||||
qnsTyp: props.item.qnsTyp
|
||||
});
|
||||
// 题类型
|
||||
const subject_type_text = computed(() => SUBJECT_TYPE.find((res) => subject_data.qnsTyp === res.value)?.label || '');
|
||||
const changePlay = (id) => {
|
||||
console.log('changePlay');
|
||||
questionValue.value = id;
|
||||
// emit('handleEvents', 'changePlay', props.talkingInfo, id)
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mark_and_feedback_div {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
.img {
|
||||
width: 38rpx;
|
||||
height: 38rpx;
|
||||
margin-left: 4rpx;
|
||||
padding: 4rpx;
|
||||
}
|
||||
}
|
||||
.option_div {
|
||||
padding-top: 46rpx;
|
||||
}
|
||||
|
||||
.item {
|
||||
// height: 170rpx;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 40rpx;
|
||||
|
||||
.top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.type {
|
||||
width: 108rpx;
|
||||
height: 46rpx;
|
||||
background: #267eff;
|
||||
border-radius: 8rpx;
|
||||
font-family: PingFangSC;
|
||||
font-weight: 500;
|
||||
font-size: 25rpx;
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
line-height: 46rpx;
|
||||
}
|
||||
|
||||
.score {
|
||||
margin-left: 24rpx;
|
||||
font-family: ArialMT;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.right_text {
|
||||
font-family: PingFangSC;
|
||||
font-weight: 600;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 40rpx;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.question_text {
|
||||
font-family: PingFangSC;
|
||||
font-weight: 500;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
line-height: 48rpx;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<view class="analysis_div" @click.stop="click_option()" :class="status">
|
||||
<view class="line">
|
||||
<text class="tr">维度评分:</text>
|
||||
<text class="td">{{ dimension_all_score }}</text>
|
||||
|
||||
<view class="td_success_error_img">
|
||||
<image :src="select_status_icon" class="img"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="line" v-for="item in evalSettingVos">
|
||||
<text class="tr">{{ item.gradeDimens }}:</text>
|
||||
<text class="td">{{ item.anlyGrade }}</text>
|
||||
<view class="your_answer" v-if="item.dimensCode === '01'">
|
||||
<rich-text :nodes="highlightedText" class="content" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, nextTick, computed, watch } from 'vue';
|
||||
import { optionErrorIcon, optionSuccessIcon } from '@/common/imgSvg';
|
||||
|
||||
const emit = defineEmits(['click_option']);
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
required: false
|
||||
},
|
||||
judgeResult: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
anserResult: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const evalSettingVos = computed(() => props.value.evalSettingVos);
|
||||
const status = computed(() => (props.judgeResult ? 'success' : 'error'));
|
||||
const select_status_icon = computed(() => {
|
||||
if (props.judgeResult) {
|
||||
return optionSuccessIcon('#15B859');
|
||||
} else {
|
||||
return optionErrorIcon();
|
||||
}
|
||||
});
|
||||
|
||||
const keywords = computed(() => {
|
||||
if (!props.value.relKeyword) return [];
|
||||
return props.value.relKeyword.split(/[,、/]/).map((key) => key.trim());
|
||||
});
|
||||
|
||||
const highlightedText = computed(() => {
|
||||
// 如果没有关键词,直接返回原文本
|
||||
if (!keywords.value.length) return '你的答案:' + props.anserResult;
|
||||
// 构建正则表达式,匹配所有关键词(不区分大小写)
|
||||
// 对关键词进行转义,避免特殊字符影响正则
|
||||
const escapedKeywords = keywords.value.map((keyword) => keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
||||
|
||||
// 创建正则表达式,g表示全局匹配,i表示不区分大小写
|
||||
const regex = new RegExp(`(${escapedKeywords.join('|')})`, 'gi');
|
||||
|
||||
// 替换匹配到的关键词,用span包裹并添加高亮样式
|
||||
return '你的答案:' + props.anserResult.replace(regex, (match) => `<span class="highlight">${match}</span>`);
|
||||
});
|
||||
const dimension_all_score = computed(() => {
|
||||
return (
|
||||
evalSettingVos.value
|
||||
// 先通过map提取每个item的anlyGrade,不存在或非数字则取0
|
||||
.map((item) => (item?.anlyGrade && typeof item.anlyGrade === 'number' ? item.anlyGrade : 0))
|
||||
// 再通过reduce累加所有数值
|
||||
.reduce((total, current) => total + current, 0)
|
||||
);
|
||||
});
|
||||
|
||||
const click_option = () => {
|
||||
emit('click_option', props.analysisVo);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.success {
|
||||
background-color: #e7f8ed;
|
||||
.td {
|
||||
color: #15b859;
|
||||
}
|
||||
:deep(.highlight) {
|
||||
color: #15b859;
|
||||
}
|
||||
}
|
||||
.error {
|
||||
background-color: #fce8e9;
|
||||
.td {
|
||||
color: #f5212d;
|
||||
}
|
||||
:deep(.highlight) {
|
||||
color: #f5212d;
|
||||
}
|
||||
}
|
||||
.your_answer {
|
||||
:deep(.highlight) {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.analysis_div {
|
||||
margin-top: 30rpx;
|
||||
|
||||
border-radius: 16rpx;
|
||||
padding: 22rpx 15rpx 15rpx 34rpx;
|
||||
.line {
|
||||
margin: 12rpx 0;
|
||||
position: relative;
|
||||
}
|
||||
.tr {
|
||||
font-weight: 600;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
}
|
||||
.td {
|
||||
font-weight: 600;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
.your_answer {
|
||||
font-weight: 500;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
line-height: 46rpx;
|
||||
text-align: left;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
.td_success_error_img {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
.img {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
padding-right: 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,399 @@
|
||||
<template>
|
||||
<view class="talking-line">
|
||||
<view class="talking-info" v-if="status === '02'">
|
||||
<view :class="['talking-gif', isPlaying ? 'is-play' : '']">
|
||||
<!-- <CommonLoading color="#fff" /> -->
|
||||
</view>
|
||||
<view class="right-time">{{ voiceTime }}</view>
|
||||
<view class="talking-cont">
|
||||
<text :class="['talking-text translate-text loading']" v-show="textShow">
|
||||
{{ showContent }}
|
||||
</text>
|
||||
<view class="talking-text translate-text" v-if="textLoading">
|
||||
<CommonLoading color="#fff" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="btns-cont">
|
||||
<view class="play-btn">
|
||||
<image
|
||||
v-show="!isPlaying"
|
||||
class="icon"
|
||||
@click="playVoice"
|
||||
src="@/static/images/course/play-blue.png"
|
||||
style="width: 100%; height: 100%"
|
||||
></image>
|
||||
<image
|
||||
v-show="isPlaying"
|
||||
class="icon"
|
||||
@click.stop="playVoice"
|
||||
src="@/static/images/course/stop-blue.png"
|
||||
style="width: 100%; height: 100%"
|
||||
></image>
|
||||
</view>
|
||||
<view class="text-btn" v-if="!textShow" @click.stop="translateVoice()">文</view>
|
||||
<view class="fl1"></view>
|
||||
<view
|
||||
v-show="!props.isPreview && props.button_again_show"
|
||||
class="btns-cont-button-text"
|
||||
@click.stop="buutton_click('again')"
|
||||
>
|
||||
重答
|
||||
</view>
|
||||
<view
|
||||
v-show="!props.isPreview && props.button_complete_show"
|
||||
class="btns-cont-button-text"
|
||||
@click.stop="buutton_click('complete')"
|
||||
>
|
||||
完成
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 01纯文字 -->
|
||||
<view class="talking-info talking-info-text" v-else-if="status === '01'">
|
||||
<view class="talking-cont">
|
||||
<text :class="['talking-text translate-text loading']">
|
||||
{{ showContent }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="btns-cont" v-show="!props.isPreview">
|
||||
<view class="fl1"></view>
|
||||
<view v-if="props.button_again_show" class="btns-cont-button-text" @click.stop="buutton_click('again')">
|
||||
重答
|
||||
</view>
|
||||
<view
|
||||
v-if="props.button_complete_show"
|
||||
class="btns-cont-button-text"
|
||||
@click.stop="buutton_click('complete')"
|
||||
>
|
||||
完成
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="talking-info talking-info-loading" v-else-if="status === '00'">
|
||||
<view :class="['talking-gif']">
|
||||
<CommonLoading color="#fff" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, unref, ref, toRefs, watch, onMounted, nextTick } from 'vue';
|
||||
import { onUnload } from '@dcloudio/uni-app';
|
||||
import { useAudio } from './useAudio.js';
|
||||
import { downloadFile } from '@/api/common.js';
|
||||
const props = defineProps({
|
||||
dataInfo: {
|
||||
default: () => ({}),
|
||||
type: Object
|
||||
},
|
||||
text: {
|
||||
default: '',
|
||||
type: String
|
||||
},
|
||||
voice: {
|
||||
// 当前激活的语音
|
||||
default: '',
|
||||
type: String
|
||||
},
|
||||
activeVoiceId: {
|
||||
default: '',
|
||||
type: String
|
||||
},
|
||||
defaultDisplayText: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
status: {
|
||||
default: '',
|
||||
type: String
|
||||
},
|
||||
isPreview: {
|
||||
// 是否是预览语音
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
button_again_show: {
|
||||
// 是否显示重答按钮
|
||||
default: true,
|
||||
type: Boolean
|
||||
},
|
||||
button_complete_show: {
|
||||
// 是否显示重答按钮
|
||||
default: true,
|
||||
type: Boolean
|
||||
}
|
||||
});
|
||||
|
||||
const innerAudio = useAudio();
|
||||
|
||||
const status = computed(() => props.status);
|
||||
watch(
|
||||
() => props.status,
|
||||
(val) => {
|
||||
if (val === '') {
|
||||
textShow.value = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
const emit = defineEmits(['changePlay', 'buutton_click']);
|
||||
|
||||
// 中间显示的文字
|
||||
const showContent = computed(() => {
|
||||
return props.text || props.dataInfo.anserResult || '';
|
||||
});
|
||||
|
||||
const isPlaying = innerAudio.isPlaying;
|
||||
const textLoading = ref(false);
|
||||
const textShow = ref(props.defaultDisplayText); // 翻译文字展示状态
|
||||
const voiceTime = ref('');
|
||||
const itemVoice = ref('');
|
||||
|
||||
// 按钮点击
|
||||
const buutton_click = (type) => {
|
||||
if (type === 'again') {
|
||||
nextTick(() => {
|
||||
textShow.value = true;
|
||||
});
|
||||
setTimeout(() => {
|
||||
voiceTime.value = '';
|
||||
}, 0);
|
||||
}
|
||||
emit('buutton_click', type, props.dataInfo);
|
||||
};
|
||||
|
||||
// 翻译语音
|
||||
const translateVoice = (type) => {
|
||||
if (!textShow.value) {
|
||||
textLoading.value = true;
|
||||
setTimeout(() => {
|
||||
textShow.value = true;
|
||||
textLoading.value = false;
|
||||
}, 500);
|
||||
} else {
|
||||
textShow.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const playVoice = () => {
|
||||
console.log('点击播放');
|
||||
innerAudio.playAudio();
|
||||
};
|
||||
let unsubscribe = () => {};
|
||||
onMounted(() => {
|
||||
console.log('question的onMounted');
|
||||
// 2. 监听 Store 中 isPlaying 的变化,实时同步
|
||||
// unsubscribe = innerAudio.$subscribe((mutation, state) => {
|
||||
// isPlaying.value = state.isPlaying;
|
||||
// });
|
||||
// 注册播放完成回调
|
||||
innerAudio.onAudioEnded(() => {
|
||||
console.log('播放完成(通过回调)');
|
||||
setTimeout(() => {
|
||||
isPlaying.value = false;
|
||||
}, 60);
|
||||
});
|
||||
// 注册播放错误回调
|
||||
innerAudio.onAudioError((error) => {
|
||||
console.error('播放错误(通过回调):', error);
|
||||
});
|
||||
// 声音加载完毕
|
||||
innerAudio.onAudioLoaded((e) => {
|
||||
console.log('声音加载完毕', e.duration);
|
||||
// 计算原始时长
|
||||
const originalTime = Math.ceil(e.duration) || 1;
|
||||
let displayTime
|
||||
if(originalTime >= 118 && originalTime <= 122) {
|
||||
// 判断是否在118-123秒范围内
|
||||
displayTime = 120
|
||||
} else if(originalTime >= 58 && originalTime <= 62) {
|
||||
// 判断是否在58-62秒范围内
|
||||
displayTime = 60
|
||||
} else {
|
||||
displayTime = originalTime
|
||||
}
|
||||
// 赋值显示
|
||||
console.log('displayTime', displayTime);
|
||||
voiceTime.value = displayTime + 's';
|
||||
});
|
||||
watch(
|
||||
() => props.dataInfo.voicePath,
|
||||
(newVal) => {
|
||||
console.log('加载声音', props.dataInfo);
|
||||
if (newVal === '' && props.dataInfo.ossKey) {
|
||||
// 去下载oss内容
|
||||
console.log('真下载oss内容');
|
||||
downloadFile(props.dataInfo.ossKey).then((res) => {
|
||||
voiceTime.value = '';
|
||||
innerAudio.setAudioSrc(res.tempFilePath);
|
||||
});
|
||||
} else if (newVal === undefined) {
|
||||
innerAudio.stopAudio();
|
||||
} else {
|
||||
voiceTime.value = '';
|
||||
innerAudio.setAudioSrc(newVal);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
});
|
||||
onUnload(() => {
|
||||
// 1. 停止当前播放(优先处理,避免音频继续播放)
|
||||
innerAudio.stopAudio();
|
||||
// 2. 移除当前组件注册的回调(避免内存泄漏)
|
||||
innerAudio.removeCallbacks();
|
||||
|
||||
unsubscribe();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.talking-line {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.talking-info {
|
||||
padding: 20rpx;
|
||||
border-radius: 15rpx;
|
||||
background-color: #06f;
|
||||
padding-bottom: 30rpx;
|
||||
position: relative;
|
||||
color: #fff;
|
||||
width: 100%;
|
||||
|
||||
.talking-gif {
|
||||
width: 200rpx;
|
||||
height: 38rpx;
|
||||
background: url(@/static/images/course/voice-gray.png) no-repeat;
|
||||
background-size: contain;
|
||||
padding-left: 168rpx;
|
||||
|
||||
&.is-play {
|
||||
background: url(@/static/images/course/voice-white.gif) no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.right-time {
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: 20rpx;
|
||||
font-size: 28rpx;
|
||||
height: 38rpx;
|
||||
line-height: 38rpx;
|
||||
}
|
||||
|
||||
&.talking-info-loading {
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
|
||||
&.talking-info-text {
|
||||
padding-bottom: 20rpx;
|
||||
width: auto;
|
||||
min-width: calc(100vw * 0.45);
|
||||
// float: right;
|
||||
.talking-cont {
|
||||
// margin-top: 0rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.talking-cont {
|
||||
// margin-top: 16rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.talking-text {
|
||||
font-size: 28rpx;
|
||||
line-height: 48rpx;
|
||||
margin-bottom: 20rpx;
|
||||
word-break: break-all;
|
||||
white-space: pre-line;
|
||||
overflow: hidden;
|
||||
|
||||
&.translate-text {
|
||||
min-height: 48rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btns-cont {
|
||||
overflow: hidden;
|
||||
border-top: 3rpx solid #1f79ff;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 20rpx;
|
||||
margin-top: 14rpx;
|
||||
.replay-btn {
|
||||
float: left;
|
||||
width: 46rpx;
|
||||
height: 46rpx;
|
||||
overflow: hidden;
|
||||
margin-right: 16rpx;
|
||||
margin-top: 25rpx;
|
||||
}
|
||||
|
||||
.play-btn {
|
||||
width: 46rpx;
|
||||
height: 46rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.text-btn {
|
||||
width: 46rpx;
|
||||
height: 46rpx;
|
||||
overflow: hidden;
|
||||
line-height: 46rpx;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
margin-right: 16rpx;
|
||||
background-color: #67a4ff;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.text-right-btn {
|
||||
float: right;
|
||||
padding: 0 10rpx;
|
||||
height: 46rpx;
|
||||
overflow: hidden;
|
||||
line-height: 46rpx;
|
||||
text-align: center;
|
||||
margin-left: 16rpx;
|
||||
margin-top: 25rpx;
|
||||
}
|
||||
|
||||
.next-btn {
|
||||
float: right;
|
||||
padding: 0 60rpx 0 20rpx;
|
||||
height: 62rpx;
|
||||
line-height: 62rpx;
|
||||
font-size: 30rpx;
|
||||
background-color: #06f;
|
||||
color: #fff;
|
||||
border-radius: 8rpx;
|
||||
margin-top: 16rpx;
|
||||
position: relative;
|
||||
|
||||
.icon-iamge {
|
||||
width: 25rpx;
|
||||
height: 25rpx;
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: 50%;
|
||||
margin-top: -12rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.btns-cont-button-text {
|
||||
font-weight: 400;
|
||||
font-size: 30rpx;
|
||||
color: #ffffff;
|
||||
line-height: 30rpx;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
margin-left: 18rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<view class="option_item" @click.stop="click_option()" :class="select_status">
|
||||
<view class="option_text">
|
||||
{{props.analysisVo.itemNo}}. {{props.analysisVo.itemContent}}
|
||||
</view>
|
||||
<image :src="select_status_icon" class="img"></image>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
reactive,
|
||||
onMounted,
|
||||
nextTick,
|
||||
computed,
|
||||
toRaw
|
||||
} from 'vue'
|
||||
import {
|
||||
optionErrorIcon,
|
||||
optionSuccessIcon
|
||||
} from '@/common/imgSvg'
|
||||
const emit = defineEmits(['click_option'])
|
||||
const props = defineProps({
|
||||
analysisVo: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
my_answer: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
const select_status = computed(() => {
|
||||
// 是错误答案但是被用户错误选中的为红
|
||||
const my_answer_array = props.my_answer;
|
||||
if(my_answer_array.includes(props.analysisVo.itemId) && props.analysisVo.correctFlag === 'E') return 'error';
|
||||
// 是正确答案 被用户错误选中的为绿色
|
||||
if(my_answer_array.includes(props.analysisVo.itemId) && props.analysisVo.correctFlag === 'R') return 'success';
|
||||
// 只有被我选中,不知是正确还是错误的为蓝
|
||||
if(my_answer_array.includes(props.analysisVo.itemId)) return 'primary';
|
||||
// 什么都不是者为灰色
|
||||
return 'info';
|
||||
})
|
||||
const select_status_icon = computed(() => {
|
||||
if (select_status.value === 'primary') {
|
||||
return optionSuccessIcon('#0066FF')
|
||||
} else if (select_status.value === 'success') {
|
||||
return optionSuccessIcon('#15B859')
|
||||
} else if (select_status.value === 'error') {
|
||||
return optionErrorIcon()
|
||||
}
|
||||
|
||||
})
|
||||
const click_option = () => {
|
||||
emit('click_option', props.analysisVo)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.success {
|
||||
color: #15B859;
|
||||
background: #E7F8ED;
|
||||
}
|
||||
|
||||
.primary {
|
||||
color: #333333;
|
||||
background: #ECF3FE;
|
||||
}
|
||||
|
||||
.info {
|
||||
color: #333333;
|
||||
background: #F9F9F9;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #F5212D;
|
||||
background: #FCE8E9;
|
||||
}
|
||||
|
||||
.option_item {
|
||||
|
||||
width: 100%;
|
||||
min-height: 108rpx;
|
||||
border-radius: 15rpx;
|
||||
font-family: PingFangSC;
|
||||
font-weight: 500;
|
||||
font-size: 30rpx;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
|
||||
margin-top: 32rpx;
|
||||
padding: 10rpx 10rpx 10rpx 52rpx;
|
||||
|
||||
|
||||
// display: -webkit-box;
|
||||
// -webkit-box-orient: vertical;
|
||||
// -webkit-line-clamp: 2;
|
||||
/* 显示的行数 */
|
||||
overflow: hidden;
|
||||
.option_text {
|
||||
flex: 1;
|
||||
// white-space: nowrap;
|
||||
/* 强制不换行 */
|
||||
overflow: hidden;
|
||||
/* 超出部分隐藏 */
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.img {
|
||||
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
padding-right: 20rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,214 @@
|
||||
<template>
|
||||
<view class="item">
|
||||
<view class="top">
|
||||
<view class="type">
|
||||
{{subject_type_text}}
|
||||
</view>
|
||||
<view class="score" v-if="mode !== 'study'">
|
||||
5分
|
||||
</view>
|
||||
<view class="fl1" v-if="mode !== 'study'">
|
||||
|
||||
</view>
|
||||
<view class="right" v-if="mode !== 'study'">
|
||||
<view class="mark_and_feedback_div">
|
||||
<image :src="markIcon(props.item.mark?'#0066FF':'#ABABAB')" class="img" @click="mark_click()"></image>
|
||||
<image :src="feedbackIcon()" class="img" @click="feedback_click()"></image>
|
||||
</view>
|
||||
<!-- <view class="right_text">展开</view>
|
||||
<uv-icon class="arrow-icon" name="arrow-right"></uv-icon> -->
|
||||
</view>
|
||||
</view>
|
||||
<view class="question_text">
|
||||
{{subject_data.qnsContent}}
|
||||
</view>
|
||||
<view class="option_div">
|
||||
<radioSubjectItem v-for="(analysisVo,index) in subject_data.itemVos" @click_option="click_option" :key="index"
|
||||
:analysisVo="analysisVo" :my_answer="props.item.my_answer" :mode="props.mode">
|
||||
</radioSubjectItem>
|
||||
</view>
|
||||
<view class="option_div_button" v-if="subject_data.qnsTyp==='MU' && ['practice','study'].includes(props.mode) && !props.isPreview">
|
||||
<uv-button type="primary" class="send_evaluate" :custom-style="customStyle" :throttleTime="300"
|
||||
@click="muCheckbox" :hairline="false">确定</uv-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
reactive,
|
||||
onMounted,
|
||||
nextTick,
|
||||
computed,
|
||||
toRaw
|
||||
} from 'vue'
|
||||
import radioSubjectItem from './radio-subject-item.vue'
|
||||
import {
|
||||
markIcon,
|
||||
feedbackIcon
|
||||
} from '@/common/imgSvg'
|
||||
import {
|
||||
SUBJECT_TYPE
|
||||
} from '@/enum.js'
|
||||
const customStyle = {
|
||||
borderRadius: '16rpx',
|
||||
width: '292rpx',
|
||||
backgroundColor: '#0066FF',
|
||||
border: '1rpx solid rgb(12, 123, 245)',
|
||||
}
|
||||
const emit = defineEmits(['subject_click'])
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
// 是否预览模式
|
||||
isPreview:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
const feedback_click = () => {
|
||||
emit('subject_click', 'feedback', props.item)
|
||||
}
|
||||
const mark_click = () => {
|
||||
emit('subject_click', 'mark', props.item)
|
||||
}
|
||||
|
||||
const subject_data = reactive({
|
||||
qnsId: '',
|
||||
qnsContent: '',
|
||||
qnsTyp: '',
|
||||
itemVos: props.item.itemVos.map(res => ({
|
||||
...res,
|
||||
})),
|
||||
})
|
||||
Object.assign(subject_data, {
|
||||
qnsId: props.item.qnsId,
|
||||
qnsContent: props.item.qnsContent,
|
||||
qnsTyp: props.item.qnsTyp,
|
||||
})
|
||||
const item_answer = ref(props.item?.my_answer.split(','))
|
||||
// 题类型
|
||||
const subject_type_text = computed(() => SUBJECT_TYPE.find(res => subject_data.qnsTyp === res.value)?.label || '')
|
||||
// 点击答题
|
||||
const click_option = (analysisVo) => {
|
||||
|
||||
// // 无论什么类型如果表里已经有了,再次点击就是弹出
|
||||
if (props.item.my_answer.includes(analysisVo.itemId)) {
|
||||
// props.item.my_answer = props.item.my_answer.filter(id => id !== analysisVo.itemId);
|
||||
} else if (props.item.qnsTyp === 'SN') { // 单选
|
||||
// 单选插入,并且移除其他
|
||||
// props.item.my_answer[0] = analysisVo.itemId
|
||||
muCheckbox()
|
||||
} else if (props.item.qnsTyp === 'MU') { // 多选题
|
||||
// 多选直接插入
|
||||
// props.item.my_answer.push(analysisVo.itemId)
|
||||
} else if (props.item.qnsTyp === 'JD') { // 判断题
|
||||
// 判断插入,并且移除其他
|
||||
// props.item.my_answer = [analysisVo.itemId]
|
||||
muCheckbox()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const muCheckbox = () => {
|
||||
emit('subject_click', 'answer', {
|
||||
qnsTyp: props.item.qnsTyp,
|
||||
qnsId: props.item.qnsId,
|
||||
answer: props.item.my_answer,
|
||||
answerItems: subject_data.itemVos.filter(t=> props.item.my_answer.includes(t.itemId))
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mark_and_feedback_div {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
|
||||
.img {
|
||||
width: 38rpx;
|
||||
height: 38rpx;
|
||||
margin-left: 4rpx;
|
||||
padding: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.option_div_button {
|
||||
width: 100%;
|
||||
height: 84rpx;
|
||||
padding-top: 30rpx;
|
||||
// background-color: red;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.item {
|
||||
// height: 170rpx;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 40rpx;
|
||||
|
||||
.top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.type {
|
||||
width: 108rpx;
|
||||
height: 46rpx;
|
||||
background: #267EFF;
|
||||
border-radius: 8rpx;
|
||||
font-family: PingFangSC;
|
||||
font-weight: 500;
|
||||
font-size: 25rpx;
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
line-height: 46rpx;
|
||||
}
|
||||
|
||||
.score {
|
||||
margin-left: 24rpx;
|
||||
font-family: ArialMT;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.right_text {
|
||||
font-family: PingFangSC;
|
||||
font-weight: 600;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.question_text {
|
||||
font-family: PingFangSC;
|
||||
font-weight: 500;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
line-height: 48rpx;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,541 @@
|
||||
<template>
|
||||
<view class="item">
|
||||
<view class="top">
|
||||
<view class="type">{{ subject_type_text }}</view>
|
||||
<view class="score" v-if="['settlement', 'examination', 'pk_in'].includes(props.mode)">{{ item.tgtGrade }}分</view>
|
||||
<view class="fl1"></view>
|
||||
<view class="right">
|
||||
<!-- 反馈中心 -->
|
||||
<slot name="title-right"></slot>
|
||||
<view class="mark_and_feedback_div" v-if="['examination', 'practice'].includes(props.mode)">
|
||||
<image
|
||||
v-if="props.mode === 'examination'"
|
||||
:src="markIcon(props.item.mark ? '#e8b531' : '#ABABAB')"
|
||||
class="img"
|
||||
@click="mark_click()"
|
||||
></image>
|
||||
<image
|
||||
:src="feedbackIcon()"
|
||||
class="img"
|
||||
@click="feedback_click()"
|
||||
v-if="props.mode2 !== 'mistake'"
|
||||
></image>
|
||||
</view>
|
||||
<template v-if="['mistake'].includes(props.mode)">
|
||||
<view class="expand_button_div" @click="toggleExpand()">
|
||||
<!-- <view class="right_text">{{animationState?'收起':'展开'}}</view> -->
|
||||
<view class="toggle-container">
|
||||
<view class="toggle-text" :class="{ active: !animationState }">展开</view>
|
||||
<view class="toggle-text" :class="{ active: animationState }">收起</view>
|
||||
</view>
|
||||
<uv-icon
|
||||
class="click_text_icon"
|
||||
name="arrow-right"
|
||||
:class="{ click_text_icon_action: animationState }"
|
||||
color="#0066FF"
|
||||
></uv-icon>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
<view class="question_text">
|
||||
{{ subject_data.qnsContent }}
|
||||
</view>
|
||||
<view :style="{ height: animationHeight }" :class="{ collapse_wrapper: props.mode === 'mistake' }" ref="wrapperRef">
|
||||
<view class="collapse-content" ref="contentRef">
|
||||
<view class="option_div">
|
||||
<template v-if="qnsTyp === 'ES'">
|
||||
<questionVue
|
||||
:status="subject_data.es_status"
|
||||
:dataInfo="props.item?.my_answer"
|
||||
:isPreview="props.isPreview"
|
||||
:activeVoiceId="subject_data.qnsId"
|
||||
@buutton_click="questionButtonClick"
|
||||
:button_complete_show="['practice', 'pk_in'].includes(props.mode)"
|
||||
></questionVue>
|
||||
</template>
|
||||
<template v-else>
|
||||
<radioSubjectItem
|
||||
v-for="(analysisVo, index) in subject_data.itemVos"
|
||||
@click_option="click_option"
|
||||
:key="index"
|
||||
:analysisVo="analysisVo"
|
||||
:my_answer="item_answer"
|
||||
></radioSubjectItem>
|
||||
</template>
|
||||
</view>
|
||||
<view
|
||||
class="option_div_button"
|
||||
v-if="qnsTyp === 'MU' && ['practice', 'study', 'pk_in'].includes(props.mode) && !props.isPreview && clearResult"
|
||||
>
|
||||
<uv-button
|
||||
type="primary"
|
||||
class="send_evaluate"
|
||||
:custom-style="customStyle"
|
||||
:throttleTime="300"
|
||||
@click="muBtnClick"
|
||||
:hairline="false"
|
||||
>
|
||||
确定
|
||||
</uv-button>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="analysis_div"
|
||||
v-if="
|
||||
!clearResult &&
|
||||
['study', 'mistake', 'settlement', 'practice', 'practice_record', 'pk_in'].includes(props.mode)
|
||||
"
|
||||
>
|
||||
<view class="analysis_title" v-if="qnsTyp !== 'ES'">
|
||||
<view class="success_answer">正确答案:{{ correctResultLabel.join('') }}</view>
|
||||
<view class="your_answer">
|
||||
你的答案:
|
||||
<text :class="right_or_wrong_class ? 'success' : 'error'">{{ myResultLabel.join('') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="reference_answer" v-if="qnsTyp === 'ES'">
|
||||
<view class="reference_answer_title">你的答案:</view>
|
||||
{{ subject_data.anserResult ?? '' }}
|
||||
</view>
|
||||
<esAnalysis
|
||||
v-if="qnsTyp === 'ES'"
|
||||
:value="subject_data.itemVos[0]"
|
||||
:judgeResult="judgeResultStatus"
|
||||
:anserResult="subject_data.anserResult"
|
||||
></esAnalysis>
|
||||
|
||||
<!-- 问答题专属参考答案 -->
|
||||
<view class="reference_answer" v-if="qnsTyp === 'ES'">
|
||||
<view class="reference_answer_title">参考答案:</view>
|
||||
{{ subject_data.itemVos[0]?.itemAnswer ?? '' }}
|
||||
</view>
|
||||
<view class="analysis_note" v-if="props.mode !== 'pk_in'">试题解析:{{ subject_data?.analyContent }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, nextTick, computed, getCurrentInstance, watch } from 'vue';
|
||||
import radioSubjectItem from './radio-subject-item.vue';
|
||||
import esAnalysis from './es-analysis.vue';
|
||||
import questionVue from './question.vue';
|
||||
import { markIcon, feedbackIcon } from '@/common/imgSvg';
|
||||
import { SUBJECT_TYPE } from '@/enum.js';
|
||||
import common from '@/common/common';
|
||||
|
||||
// 多选的确定按钮的样式
|
||||
const customStyle = {
|
||||
borderRadius: '16rpx',
|
||||
width: '292rpx',
|
||||
backgroundColor: '#0066FF',
|
||||
border: '1rpx solid rgb(12, 123, 245)'
|
||||
};
|
||||
|
||||
const emit = defineEmits(['subject_click', 'update:modelValue']);
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [Array, Object],
|
||||
default: () => []
|
||||
},
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: (value) => {
|
||||
// 考试: examination
|
||||
// 练习: practice
|
||||
// 错题本: mistake
|
||||
// 学习: study
|
||||
// 考试结算页 settlement
|
||||
// 练习记录页 practice_record, 'practice_record'
|
||||
return ['examination', 'practice', 'mistake', 'study', 'settlement', 'practice_record', 'pk_in'].includes(value);
|
||||
}
|
||||
},
|
||||
// 是否预览模式
|
||||
isPreview: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否清除正确答案
|
||||
clearResult: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
mode2: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
|
||||
// 状态管理
|
||||
const isExpanded = ref(false);
|
||||
const wrapperRef = ref(null);
|
||||
const contentRef = ref(null);
|
||||
const judgeResultStatus = computed(() => props.item.judgeResult === 'P');
|
||||
|
||||
// 不是错题本就默认不折叠,错题本默认折叠
|
||||
const animationState = ref(props.mode !== 'mistake' ? true : false);
|
||||
const animationHeight = ref(props.mode !== 'mistake' ? '' : '0px');
|
||||
|
||||
const modelValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val)
|
||||
});
|
||||
|
||||
let answerText = props.item?.my_answer || props.item.anserResult || [];
|
||||
// item_answer 是答题结果,可以是对象 数组 字符串类型
|
||||
const item_answer = ref(typeof answerText === 'string' ? answerText.split(',') : answerText);
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
console.log('modelValue', newVal);
|
||||
item_answer.value = typeof newVal === 'string' ? newVal.split(',') : newVal;
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
// watch(
|
||||
// item_answer,
|
||||
// (newVal) => {
|
||||
// console.log('item_answer', newVal);
|
||||
// emit('update:modelValue', newVal);
|
||||
// },
|
||||
// { deep: true }
|
||||
// );
|
||||
|
||||
const feedback_click = () => {
|
||||
emit('subject_click', 'feedback', props.item);
|
||||
};
|
||||
|
||||
const instance = getCurrentInstance(); // 获取当前组件实例
|
||||
|
||||
// 切换展开/折叠状态(兼容H5和App)
|
||||
const toggleExpand = async () => {
|
||||
console.log('切换展开/折叠状态', animationHeight.value, animationState.value);
|
||||
if (!animationState.value) {
|
||||
animationState.value = true;
|
||||
const query = uni.createSelectorQuery().in(instance);
|
||||
query
|
||||
.select('.collapse-content')
|
||||
.boundingClientRect((rect) => {
|
||||
animationHeight.value = `${rect.height}px`;
|
||||
console.log('rect', rect);
|
||||
})
|
||||
.exec();
|
||||
} else {
|
||||
animationState.value = false;
|
||||
animationHeight.value = '0px';
|
||||
}
|
||||
};
|
||||
const queryRect = () => {
|
||||
// 组件内部一般用this.$uvGetRect,对外的为getRect,二者功能一致,名称不同
|
||||
return new Promise((resolve) => {
|
||||
this.$uvGetRect(`#${this.elId}`).then((size) => {
|
||||
resolve(size);
|
||||
});
|
||||
});
|
||||
};
|
||||
const mark_click = () => {
|
||||
emit('subject_click', 'mark', props.item);
|
||||
};
|
||||
// 单选 SN, 多选 MU,判断题 JD,简答 ES
|
||||
const qnsTyp = computed(() => props.item.qnsTyp);
|
||||
// 正确答案选项
|
||||
const correctResultLabel = computed(() =>
|
||||
subject_data.value.itemVos.filter((res) => res.correctFlag === 'R').map((res) => res.itemNo)
|
||||
);
|
||||
// 我的答案选项
|
||||
const myResultLabel = computed(() => {
|
||||
if (!props.item.anserResult) return ['无'];
|
||||
const anserResultArray = props.item.anserResult.split(',');
|
||||
return subject_data.value.itemVos.filter((res) => anserResultArray.includes(res.itemId)).map((res) => res.itemNo);
|
||||
});
|
||||
|
||||
// 判断选项是否完全正确
|
||||
const right_or_wrong_class = computed(
|
||||
() =>
|
||||
correctResultLabel.value.length === myResultLabel.value.length &&
|
||||
myResultLabel.value.every((res) => correctResultLabel.value.includes(res))
|
||||
);
|
||||
|
||||
const subject_data = computed({
|
||||
get: () => {
|
||||
return {
|
||||
qnsId: props.item.qnsId,
|
||||
qnsContent: props.item.qnsContent,
|
||||
qnsTyp: props.item.qnsTyp,
|
||||
es_status: props.item.es_status,
|
||||
analyContent: props.item.analyContent,
|
||||
anserResult: props.item.anserResult,
|
||||
itemVos: (props.item.itemVos ?? []).map((res) => ({
|
||||
...res,
|
||||
correctFlag: props.clearResult ? '' : res.correctFlag
|
||||
}))
|
||||
};
|
||||
},
|
||||
set: (val) => {}
|
||||
});
|
||||
|
||||
// 语音播放组件小按钮
|
||||
const questionButtonClick = (type, submitObj) => {
|
||||
console.log('type', type, submitObj);
|
||||
// 判断点击的是什么
|
||||
if (type === 'complete') {
|
||||
// 完成
|
||||
} else if (type === 'again') {
|
||||
// 重答
|
||||
}
|
||||
emit('subject_click', type, {
|
||||
qnsTyp: props.item.qnsTyp,
|
||||
qnsId: props.item.qnsId,
|
||||
answer: submitObj,
|
||||
answerItems: []
|
||||
});
|
||||
};
|
||||
|
||||
// 题类型
|
||||
const subject_type_text = computed(() => SUBJECT_TYPE.find((res) => qnsTyp.value === res.value)?.label || '');
|
||||
// 点击答题
|
||||
const click_option = (analysisVo) => {
|
||||
if (props.isPreview || !props.clearResult) return;
|
||||
|
||||
// 无论什么类型如果表里已经有了,再次点击就是弹出
|
||||
if (item_answer.value.includes(analysisVo.itemId)) {
|
||||
item_answer.value = item_answer.value.filter((id) => id !== analysisVo.itemId);
|
||||
modelValue.value = item_answer.value;
|
||||
if (!['study'].includes(props.mode)) {
|
||||
muCheckbox('choose');
|
||||
}
|
||||
} else if (props.item.qnsTyp === 'SN') {
|
||||
// 单选
|
||||
// 单选插入,并且移除其他
|
||||
item_answer.value[0] = analysisVo.itemId;
|
||||
modelValue.value = item_answer.value;
|
||||
muCheckbox('choose');
|
||||
} else if (props.item.qnsTyp === 'MU') {
|
||||
// 多选题
|
||||
// 多选直接插入
|
||||
item_answer.value.push(analysisVo.itemId);
|
||||
modelValue.value = item_answer.value;
|
||||
if (!['study'].includes(props.mode)) {
|
||||
muCheckbox('choose');
|
||||
}
|
||||
} else if (props.item.qnsTyp === 'JD') {
|
||||
// 判断题
|
||||
// 判断插入,并且移除其他
|
||||
item_answer.value = [analysisVo.itemId];
|
||||
modelValue.value = item_answer.value;
|
||||
muCheckbox('choose');
|
||||
}
|
||||
};
|
||||
|
||||
const muBtnClick = () => {
|
||||
if (item_answer.value.length === 0) {
|
||||
common.msg('请至少选择一个选项!');
|
||||
return;
|
||||
}
|
||||
muCheckbox('answer');
|
||||
};
|
||||
|
||||
const muCheckbox = (type = 'answer') => {
|
||||
emit('subject_click', type, {
|
||||
qnsTyp: props.item.qnsTyp,
|
||||
qnsId: props.item.qnsId,
|
||||
answer: item_answer.value,
|
||||
answerItems: subject_data.value.itemVos.filter((t) => item_answer.value.includes(t.itemId))
|
||||
});
|
||||
};
|
||||
|
||||
// 初始化动画
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.expand_button_div {
|
||||
/* 相对定位,让两个文本重叠 */
|
||||
// display: inline-block;
|
||||
// cursor: pointer;
|
||||
}
|
||||
.toggle-container {
|
||||
position: relative;
|
||||
height: 42rpx;
|
||||
width: 60rpx;
|
||||
font-size: 30rpx;
|
||||
line-height: 42rpx;
|
||||
}
|
||||
.toggle-text {
|
||||
/* 绝对定位使文本重叠 */
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
/* 初始状态:非激活文本透明且轻微下移 */
|
||||
opacity: 0;
|
||||
// transform: translateY(15rpx);
|
||||
/* 添加过渡动画 */
|
||||
transition: all 0.2s ease-out;
|
||||
/* 确保文本占据空间一致 */
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 激活状态:文本可见且位置正常 */
|
||||
.toggle-text.active {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
z-index: 1; /* 确保激活文本在上方 */
|
||||
}
|
||||
|
||||
.collapse_wrapper {
|
||||
overflow: hidden; /* 关键:隐藏超出部分 */
|
||||
// height: 0; /* 初始高度为0 */
|
||||
transition: height 0.3s ease-out;
|
||||
}
|
||||
.click_text_icon_action {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
.click_text_icon {
|
||||
transition: transform 0.3s ease-out;
|
||||
}
|
||||
|
||||
.mark_and_feedback_div {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
|
||||
.img {
|
||||
width: 38rpx;
|
||||
height: 38rpx;
|
||||
margin-left: 4rpx;
|
||||
padding: 4rpx;
|
||||
}
|
||||
}
|
||||
.expand_button_div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 90rpx;
|
||||
|
||||
justify-content: space-between;
|
||||
}
|
||||
.option_div {
|
||||
padding-top: 20rpx;
|
||||
}
|
||||
.option_div_button {
|
||||
width: 100%;
|
||||
height: 84rpx;
|
||||
padding-top: 30rpx;
|
||||
// background-color: red;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.analysis_div {
|
||||
.analysis_title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 32rpx 0 0 0;
|
||||
|
||||
.success_answer {
|
||||
}
|
||||
|
||||
.your_answer {
|
||||
.success {
|
||||
color: #15b859;
|
||||
}
|
||||
.error {
|
||||
color: #f5212d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.analysis_note {
|
||||
font-family: PingFangSC;
|
||||
font-weight: 500;
|
||||
font-size: 30rpx;
|
||||
color: #666666;
|
||||
line-height: 48rpx;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
margin-top: 28rpx;
|
||||
}
|
||||
.reference_answer {
|
||||
.reference_answer_title {
|
||||
font-weight: 700;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
text-align: left;
|
||||
line-height: 30rpx;
|
||||
margin: 30rpx 0;
|
||||
}
|
||||
font-weight: 400;
|
||||
font-size: 30rpx;
|
||||
color: #666666;
|
||||
line-height: 48rpx;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
.item {
|
||||
// height: 170rpx;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 40rpx;
|
||||
|
||||
.top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.type {
|
||||
width: 108rpx;
|
||||
height: 46rpx;
|
||||
background: #267eff;
|
||||
border-radius: 8rpx;
|
||||
font-family: PingFangSC;
|
||||
font-weight: 500;
|
||||
font-size: 25rpx;
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
line-height: 46rpx;
|
||||
}
|
||||
|
||||
.score {
|
||||
margin-left: 24rpx;
|
||||
font-family: ArialMT;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.right_text {
|
||||
font-family: PingFangSC;
|
||||
font-weight: 400;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
line-height: 40px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.question_text {
|
||||
font-family: PingFangSC;
|
||||
font-weight: 500;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
line-height: 48rpx;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,468 @@
|
||||
<template>
|
||||
<view class="talk-btns">
|
||||
<view class="touch-btn" @click="disabled_click">
|
||||
<uv-button
|
||||
class="touch-btn"
|
||||
@touchstart="startRecord"
|
||||
@touchend="stopRecord(false)"
|
||||
@touchmove="handleMove"
|
||||
v-if="!keyboardIsShow"
|
||||
:customStyle="{ height: '92rpx' }"
|
||||
>
|
||||
按住说话
|
||||
</uv-button>
|
||||
<uv-input
|
||||
v-if="keyboardIsShow"
|
||||
class="input-box"
|
||||
ref="inputRef"
|
||||
v-model="inputText"
|
||||
:maxlength="props.textValueLength"
|
||||
:adjustPosition="false"
|
||||
placeholderStyle="color:#999999;font-size:26rpx"
|
||||
placeholder="请输入文字"
|
||||
:disabled="props.isDisabled"
|
||||
></uv-input>
|
||||
</view>
|
||||
|
||||
<view class="talk-btn-box">
|
||||
<image
|
||||
class="talk-btn-icon"
|
||||
v-show="!keyboardIsShow"
|
||||
src="@/static/images/course/jianpan.png"
|
||||
mode=""
|
||||
@click="changeBtn"
|
||||
></image>
|
||||
<image
|
||||
class="talk-btn-icon"
|
||||
v-show="keyboardIsShow && !inputText"
|
||||
src="@/static/images/course/record.png"
|
||||
mode=""
|
||||
@click="changeBtn"
|
||||
></image>
|
||||
<image
|
||||
class="talk-btn-icon"
|
||||
v-show="keyboardIsShow && !!inputText"
|
||||
src="@/static/images/course/send.png"
|
||||
mode=""
|
||||
@click.stop="showKeyboard"
|
||||
></image>
|
||||
</view>
|
||||
<uni-popup ref="popupRef" class="popup">
|
||||
<view class="overlay-box">
|
||||
<view class="circle-bg-box">
|
||||
<view class="icon-box">
|
||||
<image src="@/static/images/course/talk-icon.png"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tips-box">松开发送</view>
|
||||
<view class="btns-box">
|
||||
<view :class="['close-btn', isOut ? 'is-out' : '']" id="circleBox" @click="closePopup()">
|
||||
取消
|
||||
<image class="btn-box" v-if="!isOut" src="@/static/images/course/close.png" mode=""></image>
|
||||
<image class="btn-box" v-else src="@/static/images/course/close-active.png" mode=""></image>
|
||||
</view>
|
||||
</view>
|
||||
<view :class="['talk-dialog', isOut ? 'is-out' : '']">
|
||||
<image src="@/static/images/course/voice-white.gif" mode="" class="gif-box"></image>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, nextTick, watch, reactive } from 'vue';
|
||||
import common from '@/common/common';
|
||||
import { onHide } from '@dcloudio/uni-app';
|
||||
import permission from '@/common/permission.js';
|
||||
import { getPhoneEnvBool } from '@/common/common.js';
|
||||
import { initRecord } from '@/composables/useExamSubject.js';
|
||||
const emit = defineEmits(['submit']);
|
||||
const props = defineProps({
|
||||
isDisabled: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
isLoading: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
loadingText: {
|
||||
default: '发送回答中,请稍后再试!',
|
||||
type: String
|
||||
},
|
||||
disabledText: {
|
||||
default: '请稍后再试!',
|
||||
type: String
|
||||
},
|
||||
canAnswer: {
|
||||
default: true,
|
||||
type: Boolean
|
||||
},
|
||||
canAnswerText: {
|
||||
default: '当前状态不能发送内容!',
|
||||
type: String
|
||||
},
|
||||
textValueLength: {
|
||||
type: Number,
|
||||
default: 500
|
||||
},
|
||||
minRecordDuration: {
|
||||
// 最小录音间隔
|
||||
type: Number,
|
||||
default: 1000
|
||||
}
|
||||
});
|
||||
|
||||
// 键盘还是语音
|
||||
const keyboardIsShow = ref(false);
|
||||
const popupRef = ref(null);
|
||||
const inputText = ref('');
|
||||
let startTime = 0;
|
||||
|
||||
// not_started 未开始, preparing 准备中, in_recording 录音中,ending 结束中
|
||||
const recordingStatus = ref('not_started');
|
||||
// 获取全局唯一的录音管理器 recorderManager
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
const recorderManager = uni.getRecorderManager();
|
||||
// #endif
|
||||
|
||||
// const innerAudioContext = uni.createInnerAudioContext();
|
||||
const startTimer = ref(null); // 延迟启动的定时器ID
|
||||
const startFlag = ref(1);
|
||||
const startRecord = async (obj) => {
|
||||
try {
|
||||
await initRecord();
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
if (props.isDisabled) return;
|
||||
// 开始录音
|
||||
isClickOut.value = false
|
||||
console.log('开始录音', recordingStatus.value);
|
||||
if (recordingStatus.value !== 'not_started') return;
|
||||
touchY.value = obj.changedTouches[0].pageY;
|
||||
startTimer.value = setTimeout(() => {
|
||||
console.log('延迟开始');
|
||||
recordingStatus.value = 'preparing';
|
||||
popupRef.value.open();
|
||||
recorderManager.start({ duration: 59000 });
|
||||
startTime = Date.now();
|
||||
}, 80);
|
||||
};
|
||||
// 结束录音
|
||||
const stopRecord = (status = false) => {
|
||||
console.log('结束录音stopRecord', status);
|
||||
if (props.isDisabled) return;
|
||||
if (startTimer.value) {
|
||||
clearTimeout(startTimer.value);
|
||||
startTimer.value = null;
|
||||
}
|
||||
if (status) {
|
||||
nextTick(() => {
|
||||
popupRef.value.close();
|
||||
});
|
||||
} else {
|
||||
const endTime = Date.now();
|
||||
const times = Math.max(500 - endTime + startTime, 0);
|
||||
setTimeout(() => {
|
||||
console.log('停止录音');
|
||||
recorderManager?.stop();
|
||||
|
||||
popupRef.value?.close();
|
||||
}, times);
|
||||
}
|
||||
nextTick(() => (recordingStatus.value = 'not_started'));
|
||||
};
|
||||
const closePopup = () => {
|
||||
console.log('点击关闭');
|
||||
isClickOut.value = true
|
||||
stopRecord(false)
|
||||
|
||||
}
|
||||
onMounted(async () => {
|
||||
// #ifdef APP-PLUS
|
||||
recorderManager.onStop(function (res) {
|
||||
console.log('录音文件临时位置', res.tempFilePath, isOut.value, isClickOut.value);
|
||||
if (isOut.value || isClickOut.value) return; // 被划掉的语音
|
||||
const endTime = Date.now();
|
||||
const recordDuration = endTime - startTime;
|
||||
console.log('说话时长', recordDuration);
|
||||
if (props.minRecordDuration > recordDuration) {
|
||||
nextTick(() => common.msg('说话时间太短,没有听清!'));
|
||||
// console.log('说话时间太短,没有听清');
|
||||
return;
|
||||
}
|
||||
popupRef.value?.close();
|
||||
recordingStatus.value = 'not_started';
|
||||
stopRecord(true); // 在去主动执行一次关闭
|
||||
console.log('录音结束,时长:', recordDuration, '秒');
|
||||
emit('submit', 'voice', res.tempFilePath);
|
||||
});
|
||||
recorderManager.onError(function (err) {
|
||||
isClickOut.value = false
|
||||
console.log('录音错误', err);
|
||||
});
|
||||
recorderManager.onStart(function (res) {
|
||||
// 检测是否已经抬手结束了
|
||||
console.log('检测是否已经抬手结束了', recordingStatus.value);
|
||||
if (recordingStatus.value !== 'preparing') {
|
||||
stopRecord();
|
||||
}
|
||||
|
||||
startTime = Date.now();
|
||||
isClickOut.value = false
|
||||
recordingStatus.value = 'in_recording';
|
||||
});
|
||||
// #endif
|
||||
});
|
||||
onHide(() => {
|
||||
closePopup()
|
||||
console.log('onHide');
|
||||
})
|
||||
|
||||
|
||||
const touchY = ref(0);
|
||||
const windowInfo = uni.getWindowInfo();
|
||||
// 安全区域信息
|
||||
const safeArea = windowInfo.safeArea;
|
||||
// 计算是否超出按下盒子位置
|
||||
const isOut = computed(() => {
|
||||
const btnHeight = (windowInfo.screenWidth / 750) * 234;
|
||||
return touchY.value < safeArea.bottom - btnHeight;
|
||||
});
|
||||
const isClickOut = ref(false);
|
||||
|
||||
const activeNum = ref(1);
|
||||
const handleMove = (obj) => {
|
||||
touchY.value = obj.changedTouches[0].pageY;
|
||||
};
|
||||
const clearinputText = (text = '') => {
|
||||
const old_text = inputText.value;
|
||||
inputText.value = text;
|
||||
return old_text;
|
||||
};
|
||||
|
||||
// 发送文本
|
||||
const showKeyboard = () => {
|
||||
if (keyboardIsShow.value) {
|
||||
const inputTextTram = inputText.value.trim();
|
||||
if (inputTextTram) {
|
||||
emit('submit', 'text', inputTextTram);
|
||||
} else {
|
||||
common.msg('不能发送空白消息!');
|
||||
}
|
||||
} else {
|
||||
keyboardIsShow.value = !keyboardIsShow.value;
|
||||
}
|
||||
};
|
||||
// 切换键盘语音
|
||||
const changeBtn = () => {
|
||||
if (props.isDisabled) return;
|
||||
keyboardIsShow.value = !keyboardIsShow.value;
|
||||
inputText.value = '';
|
||||
};
|
||||
// 禁用时点击给的提示
|
||||
const disabled_click = () => {
|
||||
console.log('禁用时点击给的提示');
|
||||
if (props.isDisabled) common.msg(props.disabledText);
|
||||
};
|
||||
|
||||
// 外面调用放弃这次
|
||||
const give_up = () => {
|
||||
console.log('调用结束语音回答');
|
||||
stopRecord(false)
|
||||
};
|
||||
defineExpose({ clearinputText, give_up });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.talk-btns {
|
||||
height: 150rpx;
|
||||
background-color: #fff;
|
||||
padding: 25rpx 30rpx 0;
|
||||
position: relative;
|
||||
|
||||
.touch-btn {
|
||||
height: 92rpx;
|
||||
box-shadow: 0 8rpx 20rpx 0 rgba(0, 0, 0, 0.06);
|
||||
text-align: center;
|
||||
line-height: 92rpx;
|
||||
color: #000;
|
||||
font-weight: 400;
|
||||
position: relative;
|
||||
|
||||
.input-box {
|
||||
width: 100%;
|
||||
height: 92rpx;
|
||||
line-height: 92rpx;
|
||||
text-align: left;
|
||||
box-sizing: border-box;
|
||||
padding: 0 40rpx 0 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.talk-btn-box {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 120rpx;
|
||||
right: 0;
|
||||
top: 0;
|
||||
|
||||
.talk-btn-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
position: absolute;
|
||||
right: 60rpx;
|
||||
top: 50%;
|
||||
margin-top: -20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.overlay-box {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
// justify-content: flex-end;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
|
||||
.circle-bg-box {
|
||||
height: 234rpx;
|
||||
background: url(@/static/images/course/talking.png) no-repeat;
|
||||
background-size: cover;
|
||||
position: relative;
|
||||
|
||||
.icon-box {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin-left: -17rpx;
|
||||
margin-top: -24rpx;
|
||||
width: 34rpx;
|
||||
height: 48rpx;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tips-box {
|
||||
height: 40rpx;
|
||||
line-height: 40rpx;
|
||||
color: #c0c0c0;
|
||||
text-align: center;
|
||||
margin: 12rpx 0 24rpx;
|
||||
}
|
||||
|
||||
.btns-box {
|
||||
height: 270rpx;
|
||||
// padding: 0 120rpx;
|
||||
width: 154rpx;
|
||||
margin: 0 auto 100rpx;
|
||||
overflow: hidden;
|
||||
font-size: 28rpx;
|
||||
text-align: center;
|
||||
line-height: 116rpx;
|
||||
|
||||
.close-btn {
|
||||
float: left;
|
||||
height: 100%;
|
||||
width: 154rpx;
|
||||
overflow: hidden;
|
||||
color: #999;
|
||||
|
||||
&.is-out {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.translate-btn {
|
||||
float: right;
|
||||
height: 100%;
|
||||
width: 154rpx;
|
||||
overflow: hidden;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.btn-box {
|
||||
width: 100%;
|
||||
height: 154rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.talk-dialog {
|
||||
width: 506rpx;
|
||||
height: 234rpx;
|
||||
margin: 0 auto;
|
||||
background-color: #06f;
|
||||
border-radius: 32rpx;
|
||||
position: relative;
|
||||
|
||||
&.is-out {
|
||||
background-color: #ff3e49;
|
||||
|
||||
&::after {
|
||||
background-color: #ff3e49;
|
||||
}
|
||||
}
|
||||
|
||||
.gif-box {
|
||||
position: absolute;
|
||||
width: 200rpx;
|
||||
height: 80rpx;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
|
||||
background-color: #06f;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
bottom: -22rpx;
|
||||
left: 50%;
|
||||
margin-left: -22rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.white-bg-box {
|
||||
height: 365rpx;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
padding: 58rpx 36rpx;
|
||||
|
||||
.box-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
line-height: 46rpx;
|
||||
margin-bottom: 46rpx;
|
||||
}
|
||||
|
||||
.box-btns {
|
||||
height: 46rpx;
|
||||
line-height: 46rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: #999;
|
||||
|
||||
.is-active {
|
||||
color: #06f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .uv-input__content__field-wrapper__field {
|
||||
padding-right: 60rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,225 @@
|
||||
import {
|
||||
computed,
|
||||
reactive,
|
||||
nextTick,
|
||||
ref
|
||||
} from 'vue';
|
||||
|
||||
|
||||
// const innerAudioContext = uni.createInnerAudioContext();
|
||||
// // 当前播放状态
|
||||
const audioStopFun = ref(null)
|
||||
|
||||
|
||||
export const useAudio = () => {
|
||||
|
||||
const audioInstance = uni.createInnerAudioContext();
|
||||
const callbacks = reactive({
|
||||
onEnded: null, // 播放完成回调
|
||||
onError: null, // 播放错误回调
|
||||
onLoaded: null, // 音频加载完成回调(可获取时长)
|
||||
onStop: null
|
||||
})
|
||||
let isInitialized = false;
|
||||
const isPlaying = ref(false)
|
||||
// 加载状态
|
||||
const isLoading = ref(false)
|
||||
// 当前播放的音频地址
|
||||
const currentSrc = ref('')
|
||||
const errorMsg = ref('')
|
||||
|
||||
// 初始化音频实例(确保全局唯一)
|
||||
const initAudio = () => {
|
||||
console.log('初始化音频实例(确保全局唯一)');
|
||||
// if (isInitialized) return;
|
||||
// isInitialized = true;
|
||||
bindAudioEvents();
|
||||
}
|
||||
|
||||
|
||||
|
||||
const bindAudioEvents = () => {
|
||||
if (!audioInstance) return;
|
||||
// 播放开始事件
|
||||
audioInstance.onPlay(() => {
|
||||
isPlaying.value = true;
|
||||
isLoading.value = false;
|
||||
errorMsg.value = '';
|
||||
});
|
||||
|
||||
audioInstance.onStop(() => {
|
||||
console.log('音频停止事件');
|
||||
isPlaying.value = false;
|
||||
isLoading.value = false;
|
||||
// 清空地址避免残留
|
||||
|
||||
// 触发自定义回调
|
||||
if (callbacks.onStop && typeof callbacks.onStop === 'function') {
|
||||
callbacks.onStop();
|
||||
}
|
||||
});
|
||||
|
||||
// 播放结束事件
|
||||
audioInstance.onEnded(() => {
|
||||
console.log('播放结束事件');
|
||||
|
||||
// 清空地址避免残留
|
||||
// if (audioInstance) {
|
||||
// audioInstance.src = '';
|
||||
// }
|
||||
// 触发自定义回调
|
||||
if (callbacks.onEnded && typeof callbacks.onEnded === 'function') {
|
||||
callbacks.onEnded();
|
||||
}
|
||||
});
|
||||
|
||||
// 音频可以播放时触发(此时通常能获取到时长)
|
||||
audioInstance.onCanplay(() => {
|
||||
// 尝试获取时长(部分环境需要延迟一点)
|
||||
// console.log('尝试获取时长(部分环境需要延迟一点)');
|
||||
isLoading.value = false;
|
||||
setTimeout(() => {
|
||||
const duration = audioInstance.duration || 0;
|
||||
console.log('尝试获duration', duration);
|
||||
if (duration > 0) {
|
||||
// 触发加载完成回调(此时已获取时长)
|
||||
if (callbacks.onLoaded && typeof callbacks.onLoaded === 'function') {
|
||||
callbacks.onLoaded({
|
||||
duration: duration
|
||||
});
|
||||
}
|
||||
}
|
||||
}, 100); // 短暂延迟确保时长已加载
|
||||
});
|
||||
|
||||
// 错误事件
|
||||
audioInstance.onError((err) => {
|
||||
isPlaying.value = false;
|
||||
isLoading.value = false;
|
||||
console.error('音频错误:', err);
|
||||
if (callbacks.onError && typeof callbacks.onError === 'function') {
|
||||
callbacks.onError(err); // 把错误信息传给外部
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 新增:预设置音频地址并加载(此时不播放,仅获取时长)
|
||||
const setAudioSrc = (src) => {
|
||||
console.log('预设置音频地址并加载', src);
|
||||
if (!src || src === currentSrc.value) return; // 地址不变则不重复加载
|
||||
|
||||
isLoading.value = true;
|
||||
currentSrc.value = src;
|
||||
errorMsg.value = '';
|
||||
|
||||
try {
|
||||
// 先停止当前播放并清空旧地址
|
||||
// audioInstance.stop();
|
||||
// audioInstance.src = '';
|
||||
// 设置新地址并加载(不自动播放)
|
||||
audioInstance.src = src;
|
||||
// audioInstance.load(); // 手动触发加载
|
||||
} catch (err) {
|
||||
isLoading.value = false;
|
||||
errorMsg.value = `设置音频失败: ${err.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 修改:播放当前已设置的音频(需先调用setAudioSrc)
|
||||
const playAudio = async (src) => {
|
||||
console.log('播放当前已设置的音频', currentSrc.value);
|
||||
if (!currentSrc.value) {
|
||||
errorMsg.value = '请先设置音频地址';
|
||||
return;
|
||||
}
|
||||
// 已在播放则暂停
|
||||
console.log('已在播放则暂停', isPlaying.value);
|
||||
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
if (isPlaying.value) {
|
||||
pauseAudio();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('当前是是否暂停或停止状态', audioStopFun.value?.paused);
|
||||
if (!audioStopFun.value?.paused) { // 当前是是否暂停或停止状态,true 表示暂停或停止,false 表示正在播放
|
||||
audioStopFun.value?.stop();
|
||||
wait(100)
|
||||
}
|
||||
|
||||
// 执行播放
|
||||
try {
|
||||
// 哪个函数的实例正在播放,就把他挂在全局
|
||||
audioStopFun.value = audioInstance
|
||||
audioInstance.play();
|
||||
isPlaying.value = true;
|
||||
isLoading.value = false;
|
||||
} catch (err) {
|
||||
isLoading.value = false;
|
||||
errorMsg.value = `播放失败: ${err.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 暂停播放
|
||||
const pauseAudio = () => {
|
||||
if (audioInstance && isPlaying.value) {
|
||||
console.log('暂停播放');
|
||||
audioInstance.pause();
|
||||
isPlaying.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 停止播放并重置状态
|
||||
const stopAudio = () => {
|
||||
|
||||
if (audioInstance && isPlaying.value) {
|
||||
audioInstance.stop();
|
||||
// todo
|
||||
}
|
||||
}
|
||||
|
||||
// 移除所有回调(组件卸载时调用)
|
||||
const removeCallbacks = () => {
|
||||
callbacks.onEnded = null;
|
||||
callbacks.onError = null;
|
||||
callbacks.onLoaded = null;
|
||||
callbacks.onStop = null;
|
||||
}
|
||||
// 注册播放完成回调
|
||||
const onAudioEnded = (callback) => {
|
||||
if (typeof callback === 'function') {
|
||||
callbacks.onEnded = callback;
|
||||
}
|
||||
}
|
||||
// 注册播放错误回调
|
||||
const onAudioError = (callback) => {
|
||||
if (typeof callback === 'function') {
|
||||
callbacks.onError = callback;
|
||||
}
|
||||
}
|
||||
// 新增:注册音频加载完成回调(用于获取时长)
|
||||
const onAudioLoaded = (callback) => {
|
||||
if (typeof callback === 'function') {
|
||||
callbacks.onLoaded = callback;
|
||||
}
|
||||
}
|
||||
const onAudioStop = (callback) => {
|
||||
if (typeof callback === 'function') {
|
||||
callbacks.onStop = callback;
|
||||
}
|
||||
}
|
||||
initAudio();
|
||||
|
||||
return {
|
||||
isPlaying,
|
||||
playAudio,
|
||||
pauseAudio,
|
||||
stopAudio,
|
||||
setAudioSrc,
|
||||
removeCallbacks,
|
||||
onAudioEnded,
|
||||
onAudioError,
|
||||
onAudioLoaded,
|
||||
onAudioStop
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<view class="popup-view" v-if="modelValue">
|
||||
<view class="close-preview" @click="emit('update:modelValue', false)" >X</view>
|
||||
<view class="popup-view-preview" v-if="showView">
|
||||
<web-view :src="showUrl" :fullscreen="true" class="web-view-class" :webview-styles="{
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}"></web-view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else></view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
unref,
|
||||
onMounted,
|
||||
computed,
|
||||
watch
|
||||
} from 'vue'
|
||||
import {
|
||||
getToken,
|
||||
getUserInfo
|
||||
} from "@/common/common.js"
|
||||
import {
|
||||
downloadFile
|
||||
} from "@/api/common.js"
|
||||
import {
|
||||
get_base_url
|
||||
} from '@/api/request.js'
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
fileId: {
|
||||
default: '',
|
||||
type: String
|
||||
},
|
||||
fileName: {
|
||||
default: '',
|
||||
type: String
|
||||
}
|
||||
})
|
||||
watch(()=>props.modelValue,(val)=>{
|
||||
if(val){
|
||||
openPdfFn()
|
||||
}else{
|
||||
showView.value = false
|
||||
}
|
||||
})
|
||||
const userInfo = ref(getUserInfo());
|
||||
const userParams = '/' + (unref(userInfo)?.userName??'') + ' ' + (unref(userInfo)?.loginName??'');
|
||||
const base_url = get_base_url('/traask/traFilePreview/')
|
||||
const showUrl = ref('')
|
||||
const viewerUrl = ref('/static/pdf-view/web/viewer.html')
|
||||
const showView = ref(false)
|
||||
const openPdfFn = (data) => {
|
||||
const token = getToken();
|
||||
const header = {};
|
||||
let _id = data?.fileId || props.fileId
|
||||
let fileUrl = base_url + '/traask/traFilePreview/' + _id + userParams
|
||||
if (token)
|
||||
header['summary'] = token
|
||||
uni.downloadFile({
|
||||
//需要预览的文件地址
|
||||
url: fileUrl,
|
||||
header: header,
|
||||
success: (res) => {
|
||||
//下载成功,得到文件临时地址
|
||||
console.log('下载成功', res.tempFilePath);
|
||||
|
||||
//条件编译,若为h5端则直接赋值文件地址
|
||||
// #ifdef H5
|
||||
let newUrl = res.tempFilePath
|
||||
// #endif
|
||||
|
||||
//条件编译,若为App端,则需要将本地文件系统URL转换为平台绝对路径
|
||||
// #ifdef APP-PLUS
|
||||
let newUrl = plus.io.convertLocalFileSystemURL(res.tempFilePath)
|
||||
// #endif
|
||||
showUrl.value = viewerUrl.value + '?file=' + newUrl
|
||||
showView.value = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.popup-view{
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
.popup-view-preview{
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
z-index: 100;
|
||||
position: relative;
|
||||
}
|
||||
.close-preview{
|
||||
position: fixed;
|
||||
right: 20rpx;
|
||||
top: calc(var(--status-bar-height) + 20rpx);
|
||||
color: #000;
|
||||
z-index: 1000;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<view style="line-height: 0;">
|
||||
<image :src="imgUrlShow" :mode="mode" @click="showDetail" style="width: 100%; height: 100%"></image>
|
||||
<uni-popup ref="PopupRef" class="popup" mask-background-color="rgba(0,0,0,.2)" background-color="rgba(0,0,0,.2)" @change="changeShow">
|
||||
<view class="overlay-box" @click="closePopup" @touchmove.stop>
|
||||
<image class="detail-img" :src="imgUrlShow" mode="aspectFit"></image>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { previewFile, previewCacheThumbImage } from '@/api/common.js';
|
||||
import { get_base_url } from '@/api/request.js';
|
||||
import { useStorageStore } from '@/store/storage.js';
|
||||
import { getToken } from '@/common/common.js'
|
||||
|
||||
// const storageStore = useStorageStore()
|
||||
export default {
|
||||
name: 'image-preview',
|
||||
props: {
|
||||
imgId: {
|
||||
default: '',
|
||||
type: String
|
||||
},
|
||||
src: {
|
||||
default: '',
|
||||
type: String
|
||||
},
|
||||
width: {
|
||||
default: 50,
|
||||
type: [String, Number]
|
||||
},
|
||||
height: {
|
||||
default: 50,
|
||||
type: [String, Number]
|
||||
},
|
||||
isCache: {
|
||||
// 获取缩略图
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'aspectFill'
|
||||
},
|
||||
previewDetail: {
|
||||
default: '',
|
||||
type: [String, Boolean]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
imgUrlShow: '',
|
||||
watching: false,
|
||||
base_url: get_base_url(),
|
||||
showModel: false,
|
||||
token: getToken()
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
storageStore: () => useStorageStore(),
|
||||
imageObj() {
|
||||
return this.storageStore?.imgObj || {};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
imgId: {
|
||||
handler(newval, oldval) {
|
||||
if (newval.indexOf('/') === 0) {
|
||||
this.imgUrlShow = this.base_url + newval;
|
||||
return;
|
||||
}
|
||||
if (newval.indexOf('http') === 0) {
|
||||
this.imgUrlShow = newval;
|
||||
return;
|
||||
}
|
||||
if (newval && this.watching) {
|
||||
this.getPreviewUrlBlob(newval);
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
},
|
||||
src: {
|
||||
handler(newval, oldval) {
|
||||
if (newval && this.watching) {
|
||||
this.imgUrlShow = this.base_url + this.src + '?tk=' + this.token;;
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
},
|
||||
showModel(val){
|
||||
if(!val){
|
||||
this.storageStore.setTouchBtnZIndex(12);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeShow(data){
|
||||
if(!data.show){
|
||||
this.storageStore.setTouchBtnZIndex(12);
|
||||
}else{
|
||||
this.storageStore.setTouchBtnZIndex(-1);
|
||||
}
|
||||
},
|
||||
closePopup() {
|
||||
this.showModel = false;
|
||||
this.$refs.PopupRef.close();
|
||||
},
|
||||
showDetail() {
|
||||
if (!!this.previewDetail) {
|
||||
this.showModel = true;
|
||||
this.$refs.PopupRef.open();
|
||||
}
|
||||
},
|
||||
getPreviewUrlBlob(id) {
|
||||
if (!id) {
|
||||
this.imgUrlShow = '';
|
||||
return;
|
||||
}
|
||||
if (this.isCache) {
|
||||
let _cache = this.imageObj[`/${id}/${this.width}/${this.height}`] || '';
|
||||
if (_cache) {
|
||||
this.imgUrlShow = _cache;
|
||||
return;
|
||||
}
|
||||
previewCacheThumbImage(id, this.width, this.height).then((res) => {
|
||||
this.imgUrlShow = res;
|
||||
this.storageStore.setStorage('image', `/${id}/${this.width}/${this.height}`, res);
|
||||
});
|
||||
} else {
|
||||
let _cache = this.imageObj[`${id}`] || '';
|
||||
if (_cache) {
|
||||
this.imgUrlShow = _cache;
|
||||
return;
|
||||
}
|
||||
previewFile(id).then((result) => {
|
||||
//#ifdef APP-PLUS
|
||||
let _header = result.header['Content-Type'].split(';')[0];
|
||||
// #endif
|
||||
//#ifndef APP-PLUS
|
||||
let _header = result.header['content-type'].split(';')[0];
|
||||
// #endif
|
||||
let base64 = `data:${_header};base64,` + uni.arrayBufferToBase64(result.data);
|
||||
this.imgUrlShow = base64;
|
||||
this.storageStore.setStorage('image', id, base64);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.imgId) {
|
||||
if (this.imgId.indexOf('/') === 0) {
|
||||
this.imgUrlShow = this.base_url + this.imgId + '?tk=' + this.token;
|
||||
} else if (this.imgId.indexOf('http') === 0) {
|
||||
this.imgUrlShow = this.imgId + '?tk=' + this.token;
|
||||
} else {
|
||||
this.getPreviewUrlBlob(this.imgId);
|
||||
}
|
||||
}
|
||||
this.watching = true;
|
||||
if (this.src) {
|
||||
this.imgUrlShow = this.base_url + this.src + '?tk=' + this.token;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.overlay-box {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 999;
|
||||
background-color: rgba(0, 0, 0, 0.65);
|
||||
.detail-img {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 100vw;
|
||||
max-height: 100%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,301 @@
|
||||
<template>
|
||||
<!-- <uni-popup ref="PopupRef" class="popup" mask-background-color="rgba(0,0,0,.2)" @change="changeShow">
|
||||
<view class="overlay-box" @click="playEnded" v-if="fileType === 'video'">
|
||||
<video class="video-cont-center"
|
||||
:id="`video`+ fileId"
|
||||
ref="playerRef"
|
||||
v-if="showModel && fileUrlShow"
|
||||
:src="fileUrlShow"
|
||||
:controls="true"
|
||||
@error="playError"
|
||||
@ended="playEnded"
|
||||
object-fit="contain"></video>
|
||||
</view>
|
||||
<view class="overlay-box" @click="playEnded" @touchmove.stop v-if="fileType === 'image'">
|
||||
<image class="detail-img" :src="fileUrlShow" mode="aspectFill"></image>
|
||||
</view>
|
||||
</uni-popup> -->
|
||||
<uv-overlay :show="showModel" opacity=".6" @click="showModel = false">
|
||||
<view class="overlay-box" @click="playEnded" v-if="fileType === 'video'">
|
||||
<video class="video-cont-center" :id="`video` + fileId" ref="playerRef" v-if="showModel && fileUrlShow" :src="fileUrlShow" :controls="true" @error="playError" @ended="playEnded" object-fit="contain"></video>
|
||||
</view>
|
||||
<view class="overlay-box" @click="playEnded" @touchmove.stop v-if="fileType === 'image'">
|
||||
<image class="detail-img" :src="fileUrlShow" mode="aspectFill"></image>
|
||||
</view>
|
||||
</uv-overlay>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { downloadFile, getPreviewFileUrl } from '@/api/common.js'
|
||||
import { useStorageStore } from '@/store/storage.js'
|
||||
import { get_base_url } from '@/api/request.js'
|
||||
import imagePreviewVue from './image-preview.vue'
|
||||
export default {
|
||||
name: 'video-preview',
|
||||
components: { imagePreviewVue },
|
||||
props: {
|
||||
// fileId:{
|
||||
// default:'',
|
||||
// type:String
|
||||
// },
|
||||
// src:{
|
||||
// default:'',
|
||||
// type:String
|
||||
// },
|
||||
// fileType:{
|
||||
// default:'image',
|
||||
// type:String
|
||||
// }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showModel: false,
|
||||
fileUrlShow: '',
|
||||
watching: false,
|
||||
videoContext: null,
|
||||
base_url: get_base_url()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
storageStore: () => useStorageStore(),
|
||||
videoObj() {
|
||||
return this.storageStore?.videoObj || {}
|
||||
},
|
||||
imageObj() {
|
||||
return this.storageStore?.imgObj || {}
|
||||
},
|
||||
showPreviewDetail() {
|
||||
return !!this.storageStore?.getShowPreviewDetail
|
||||
},
|
||||
fileType() {
|
||||
return this.storageStore?.getFileType
|
||||
},
|
||||
fileId() {
|
||||
return this.storageStore?.getFileId
|
||||
},
|
||||
src() {
|
||||
return this.storageStore?.getFileSrc
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
fileId: {
|
||||
handler(newval, oldval) {
|
||||
if (this.fileType === 'image') {
|
||||
if (newval.indexOf('/') === 0) {
|
||||
this.fileUrlShow = this.base_url + newval
|
||||
return
|
||||
}
|
||||
if (newval.indexOf('http') === 0) {
|
||||
this.fileUrlShow = newval
|
||||
return
|
||||
}
|
||||
if (newval && this.watching) {
|
||||
this.getImagePreviewUrlBlob(newval)
|
||||
}
|
||||
}
|
||||
if (this.fileType === 'video') {
|
||||
if (newval.indexOf('http') === 0) {
|
||||
this.fileUrlShow = newval
|
||||
} else {
|
||||
if (newval && this.watching) {
|
||||
this.getPreviewUrlBlob(newval)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.videoContext = uni.createVideoContext(`video` + this.fileId)
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
},
|
||||
src: {
|
||||
handler(newval, oldval) {
|
||||
if (newval.indexOf('http') === 0) {
|
||||
this.fileUrlShow = newval
|
||||
} else {
|
||||
this.fileUrlShow = this.base_url + newval
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
},
|
||||
showPreviewDetail(val) {
|
||||
if (val) {
|
||||
this.showModelNow()
|
||||
} else {
|
||||
// this.$refs.PopupRef.close()
|
||||
this.showModel = false
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getImagePreviewUrlBlob(id) {
|
||||
if (!id) {
|
||||
this.fileUrlShow = ''
|
||||
return
|
||||
}
|
||||
if (this.isCache) {
|
||||
let _cache = this.imageObj[`/${id}/${this.width}/${this.height}`] || ''
|
||||
if (_cache) {
|
||||
this.fileUrlShow = _cache
|
||||
return
|
||||
}
|
||||
previewCacheThumbImage(id, this.width, this.height).then(res => {
|
||||
this.fileUrlShow = res
|
||||
this.storageStore.setStorage('image', `/${id}/${this.width}/${this.height}`, res)
|
||||
})
|
||||
} else {
|
||||
let _cache = this.imageObj[`${id}`] || ''
|
||||
if (_cache) {
|
||||
this.fileUrlShow = _cache
|
||||
return
|
||||
}
|
||||
previewFile(id).then(result => {
|
||||
//#ifdef APP-PLUS
|
||||
let _header = result.header['Content-Type'].split(';')[0]
|
||||
// #endif
|
||||
//#ifndef APP-PLUS
|
||||
let _header = result.header['content-type'].split(';')[0]
|
||||
// #endif
|
||||
let base64 = `data:${_header};base64,` + uni.arrayBufferToBase64(result.data)
|
||||
this.fileUrlShow = base64
|
||||
this.storageStore.setStorage('image', id, base64)
|
||||
})
|
||||
}
|
||||
},
|
||||
changeShow(data) {
|
||||
if (!data.show) {
|
||||
this.storageStore.setTouchBtnZIndex(12)
|
||||
} else {
|
||||
this.storageStore.setTouchBtnZIndex(-1)
|
||||
}
|
||||
},
|
||||
showModelNow() {
|
||||
// this.$refs.PopupRef.open()
|
||||
this.showModel = true
|
||||
if (this.fileType === 'image') {
|
||||
if (this.fileId) {
|
||||
if (this.fileId.indexOf('/') === 0) {
|
||||
this.fileUrlShow = this.base_url + this.fileId
|
||||
} else if (this.fileId.indexOf('http') === 0) {
|
||||
this.fileUrlShow = this.fileId
|
||||
} else {
|
||||
this.getPreviewUrlBlob(this.fileId)
|
||||
}
|
||||
}
|
||||
this.watching = true
|
||||
if (this.src) {
|
||||
this.fileUrlShow = this.base_url + this.src
|
||||
}
|
||||
}
|
||||
if (this.fileType === 'video') {
|
||||
if (this.fileId) {
|
||||
let _newDate = new Date().getTime()
|
||||
if (this.fileId.indexOf('http') === 0) {
|
||||
this.fileUrlShow = this.fileId + '?t=' + _newDate
|
||||
} else {
|
||||
this.getPreviewUrlBlob(this.fileId)
|
||||
}
|
||||
this.videoContext = uni.createVideoContext(`video` + this.fileId)
|
||||
}
|
||||
}
|
||||
},
|
||||
playError(info) {
|
||||
console.log('err:', info)
|
||||
},
|
||||
playEnded() {
|
||||
this.storageStore.setShowPreviewDetail({
|
||||
status: false,
|
||||
fileType: '',
|
||||
fileId: '',
|
||||
fileSrc: ''
|
||||
})
|
||||
},
|
||||
getPreviewUrlBlob(id) {
|
||||
if (!id) {
|
||||
this.fileUrlShow = ''
|
||||
return
|
||||
}
|
||||
let _cache = this.videoObj[id] || ''
|
||||
if (_cache) {
|
||||
this.fileUrlShow = _cache
|
||||
return
|
||||
}
|
||||
let _newDate = new Date().getTime()
|
||||
this.fileUrlShow = getPreviewFileUrl(this.fileId) + '?t=' + _newDate
|
||||
// downloadFile(id).then(res=>{
|
||||
// this.fileUrlShow = res.tempFilePath
|
||||
// this.storageStore.setStorage('video', id, this.fileUrlShow)
|
||||
// })
|
||||
},
|
||||
getPreviewFileUrlNow(id) {
|
||||
let _newDate = new Date().getTime()
|
||||
if (id.indexOf('http') === 0) {
|
||||
this.fileUrlShow = id + '?t=' + _newDate
|
||||
return
|
||||
}
|
||||
if (id && this.watching) {
|
||||
this.getPreviewUrlBlob(id)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.watching = true
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.video-view-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #fafafa !important;
|
||||
position: relative;
|
||||
}
|
||||
.img-box {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.preview-pic {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.overlay-box .video-cont-center {
|
||||
width: 100vw;
|
||||
max-height: 100%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.overlay-box {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 999;
|
||||
// background-color: rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
|
||||
.overlay-box {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 999;
|
||||
// background-color: rgba(0, 0, 0, 0.65);
|
||||
.detail-img {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 100vw;
|
||||
max-height: 100%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,296 @@
|
||||
<template>
|
||||
<uni-popup ref="PopupRef" class="popup" mask-background-color="rgba(0,0,0,.2)" @change="changeShow">
|
||||
<view class="overlay-box" @click="playEnded" v-if="fileType === 'video'">
|
||||
<video class="video-cont-center"
|
||||
:id="`video`+ fileId"
|
||||
ref="playerRef"
|
||||
v-if="showModel && fileUrlShow"
|
||||
:src="fileUrlShow"
|
||||
:controls="true"
|
||||
@error="playError"
|
||||
@ended="playEnded"
|
||||
object-fit="contain"></video>
|
||||
</view>
|
||||
<view class="overlay-box" @click="playEnded" @touchmove.stop v-if="fileType === 'image'">
|
||||
<image class="detail-img" :src="fileUrlShow" mode="aspectFit"></image>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { downloadFile,getPreviewFileUrl } from'@/api/common.js'
|
||||
import { useStorageStore } from "@/store/storage.js"
|
||||
import { get_base_url } from '@/api/request.js'
|
||||
import imagePreviewVue from './image-preview.vue'
|
||||
export default {
|
||||
name:"video-preview",
|
||||
components:{imagePreviewVue},
|
||||
props:{
|
||||
// fileId:{
|
||||
// default:'',
|
||||
// type:String
|
||||
// },
|
||||
// src:{
|
||||
// default:'',
|
||||
// type:String
|
||||
// },
|
||||
// fileType:{
|
||||
// default:'image',
|
||||
// type:String
|
||||
// }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showModel: false,
|
||||
fileUrlShow:'',
|
||||
watching:false,
|
||||
videoContext: null,
|
||||
base_url: get_base_url()
|
||||
};
|
||||
},
|
||||
computed:{
|
||||
storageStore:() => useStorageStore(),
|
||||
videoObj() {
|
||||
return this.storageStore?.videoObj || {}
|
||||
},
|
||||
imageObj() {
|
||||
return this.storageStore?.imgObj || {};
|
||||
},
|
||||
showPreviewDetail() {
|
||||
return !!this.storageStore?.getShowPreviewDetail
|
||||
},
|
||||
fileType(){
|
||||
return this.storageStore?.getFileType
|
||||
},
|
||||
fileId(){
|
||||
return this.storageStore?.getFileId
|
||||
},
|
||||
src(){
|
||||
return this.storageStore?.getFileSrc
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
fileId: {
|
||||
handler(newval, oldval) {
|
||||
if(this.fileType === 'image'){
|
||||
if (newval.indexOf('/') === 0) {
|
||||
this.fileUrlShow = this.base_url + newval;
|
||||
return;
|
||||
}
|
||||
if (newval.indexOf('http') === 0) {
|
||||
this.fileUrlShow = newval;
|
||||
return;
|
||||
}
|
||||
if (newval && this.watching) {
|
||||
this.getImagePreviewUrlBlob(newval);
|
||||
}
|
||||
}
|
||||
if(this.fileType === 'video'){
|
||||
if(newval.indexOf('http') === 0){
|
||||
this.fileUrlShow = newval
|
||||
}else{
|
||||
if (newval && this.watching) {
|
||||
this.getPreviewUrlBlob(newval);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.videoContext = uni.createVideoContext(`video`+ this.fileId)
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
src: {
|
||||
handler(newval, oldval) {
|
||||
if(newval.indexOf('http') === 0){
|
||||
this.fileUrlShow = newval
|
||||
}else{
|
||||
this.fileUrlShow = this.base_url + newval
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
showPreviewDetail(val){
|
||||
if(val){
|
||||
this.showModelNow()
|
||||
} else {
|
||||
this.$refs.PopupRef.close()
|
||||
this.showModel = false
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getImagePreviewUrlBlob(id) {
|
||||
if (!id) {
|
||||
this.fileUrlShow = '';
|
||||
return;
|
||||
}
|
||||
if (this.isCache) {
|
||||
let _cache = this.imageObj[`/${id}/${this.width}/${this.height}`] || '';
|
||||
if (_cache) {
|
||||
this.fileUrlShow = _cache;
|
||||
return;
|
||||
}
|
||||
previewCacheThumbImage(id, this.width, this.height).then((res) => {
|
||||
this.fileUrlShow = res;
|
||||
this.storageStore.setStorage('image', `/${id}/${this.width}/${this.height}`, res);
|
||||
});
|
||||
} else {
|
||||
let _cache = this.imageObj[`${id}`] || '';
|
||||
if (_cache) {
|
||||
this.fileUrlShow = _cache;
|
||||
return;
|
||||
}
|
||||
previewFile(id).then((result) => {
|
||||
//#ifdef APP-PLUS
|
||||
let _header = result.header['Content-Type'].split(';')[0];
|
||||
// #endif
|
||||
//#ifndef APP-PLUS
|
||||
let _header = result.header['content-type'].split(';')[0];
|
||||
// #endif
|
||||
let base64 = `data:${_header};base64,` + uni.arrayBufferToBase64(result.data);
|
||||
this.fileUrlShow = base64;
|
||||
this.storageStore.setStorage('image', id, base64);
|
||||
});
|
||||
}
|
||||
},
|
||||
changeShow(data){
|
||||
if(!data.show){
|
||||
this.storageStore.setTouchBtnZIndex(12);
|
||||
}else{
|
||||
this.storageStore.setTouchBtnZIndex(-1);
|
||||
}
|
||||
},
|
||||
showModelNow(){
|
||||
this.$refs.PopupRef.open()
|
||||
this.showModel = true
|
||||
if(this.fileType === 'image'){
|
||||
if (this.fileId) {
|
||||
if (this.fileId.indexOf('/') === 0) {
|
||||
this.fileUrlShow = this.base_url + this.fileId;
|
||||
} else if (this.fileId.indexOf('http') === 0) {
|
||||
this.fileUrlShow = this.fileId;
|
||||
} else {
|
||||
this.getPreviewUrlBlob(this.fileId);
|
||||
}
|
||||
}
|
||||
this.watching = true;
|
||||
if (this.src) {
|
||||
this.fileUrlShow = this.base_url + this.src;
|
||||
}
|
||||
}
|
||||
if(this.fileType === 'video'){
|
||||
if (this.fileId) {
|
||||
let _newDate = new Date().getTime()
|
||||
if(this.fileId.indexOf('http') === 0){
|
||||
this.fileUrlShow = this.fileId + '?t=' + _newDate
|
||||
}else{
|
||||
this.getPreviewUrlBlob(this.fileId);
|
||||
}
|
||||
this.videoContext = uni.createVideoContext(`video`+ this.fileId)
|
||||
}
|
||||
}
|
||||
},
|
||||
playError(info){
|
||||
console.log('err:',info)
|
||||
},
|
||||
playEnded(){
|
||||
this.storageStore.setShowPreviewDetail({
|
||||
status: false,
|
||||
fileType: '',
|
||||
fileId: '',
|
||||
fileSrc: ''
|
||||
})
|
||||
},
|
||||
getPreviewUrlBlob(id) {
|
||||
if (!id) {
|
||||
this.fileUrlShow = ''
|
||||
return
|
||||
}
|
||||
let _cache = this.videoObj[id] || ''
|
||||
if(_cache){
|
||||
this.fileUrlShow = _cache;
|
||||
return
|
||||
}
|
||||
let _newDate = new Date().getTime()
|
||||
this.fileUrlShow = getPreviewFileUrl(this.fileId)+'?t=' + _newDate
|
||||
// downloadFile(id).then(res=>{
|
||||
// this.fileUrlShow = res.tempFilePath
|
||||
// this.storageStore.setStorage('video', id, this.fileUrlShow)
|
||||
// })
|
||||
},
|
||||
getPreviewFileUrlNow(id){
|
||||
let _newDate = new Date().getTime()
|
||||
if(id.indexOf('http') === 0){
|
||||
this.fileUrlShow = id + '?t=' + _newDate
|
||||
return
|
||||
}
|
||||
if (id && this.watching) {
|
||||
this.getPreviewUrlBlob(id);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.watching = true
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.playEnded()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.video-view-box{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #fafafa !important;
|
||||
position: relative;
|
||||
}
|
||||
.img-box{
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.preview-pic{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.overlay-box .video-cont-center{
|
||||
width: 100vw;
|
||||
max-height: 100%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.overlay-box{
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 999;
|
||||
background-color: rgba(0, 0, 0, .65);
|
||||
}
|
||||
|
||||
.overlay-box {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 999;
|
||||
background-color: rgba(0, 0, 0, 0.65);
|
||||
.detail-img {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: calc(100% - 40rpx);
|
||||
height: calc(100% - 100rpx);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<view v-bind="$attrs" class="video-view-box">
|
||||
<imagePreviewVue :imgId="picId" class="preview-pic" v-if="picId" @click="showModelNow"/>
|
||||
<imagePreviewVue :src="picSrc" class="preview-pic" v-if="picSrc" @click="showModelNow"/>
|
||||
<view class="img-box">
|
||||
<image
|
||||
class="icon"
|
||||
src="@/static/images/course/play.png"
|
||||
style="width: 100%;height: 100%;"
|
||||
></image>
|
||||
</view>
|
||||
<!-- <uv-overlay :show="showModel" opacity=".6" @click.stop="showModel = false"> -->
|
||||
<uni-popup ref="PopupRef" class="popup" mask-background-color="rgba(0,0,0,.2)" @change="changeShow">
|
||||
<view class="overlay-box" @click="playEnded">
|
||||
<video class="video-cont-center"
|
||||
:id="`video`+ fileId"
|
||||
ref="playerRef"
|
||||
v-if="showModel && fileUrlShow"
|
||||
:src="fileUrlShow"
|
||||
:controls="true"
|
||||
@error="playError"
|
||||
@ended="playEnded"
|
||||
object-fit="contain"></video>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { downloadFile,getPreviewFileUrl } from'@/api/common.js'
|
||||
import { useStorageStore } from "@/store/storage.js"
|
||||
import { get_base_url } from '@/api/request.js'
|
||||
import imagePreviewVue from './image-preview.vue'
|
||||
export default {
|
||||
name:"video-preview",
|
||||
components:{ imagePreviewVue },
|
||||
props:{
|
||||
fileId:{
|
||||
default:'',
|
||||
type:String
|
||||
},
|
||||
picId:{
|
||||
default:'',
|
||||
type:String
|
||||
},
|
||||
picSrc:{
|
||||
default:'',
|
||||
type:String
|
||||
},
|
||||
src:{
|
||||
default:'',
|
||||
type:String
|
||||
},
|
||||
previewDetail: {
|
||||
default: false,
|
||||
type: [String, Boolean]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showModel: false,
|
||||
fileUrlShow:'',
|
||||
watching:false,
|
||||
videoContext: null,
|
||||
base_url: get_base_url()
|
||||
};
|
||||
},
|
||||
computed:{
|
||||
storageStore:() => useStorageStore(),
|
||||
videoObj() {
|
||||
return this.storageStore?.videoObj||{}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
fileId: {
|
||||
handler(newval, oldval) {
|
||||
if(newval.indexOf('http') === 0){
|
||||
this.fileUrlShow = newval
|
||||
}else{
|
||||
if (newval && this.watching) {
|
||||
this.getPreviewUrlBlob(newval);
|
||||
}
|
||||
}
|
||||
this.videoContext = uni.createVideoContext(`video`+ this.fileId)
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
src: {
|
||||
handler(newval, oldval) {
|
||||
// if (newval && this.watching) {
|
||||
this.fileUrlShow = this.base_url + newval
|
||||
console.log('this.fileUrlShow', this.fileUrlShow);
|
||||
// }
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
changeShow(data){
|
||||
if(!data.show){
|
||||
this.storageStore.setTouchBtnZIndex(12);
|
||||
}else{
|
||||
this.storageStore.setTouchBtnZIndex(-1);
|
||||
}
|
||||
},
|
||||
showModelNow(){
|
||||
if (!!this.previewDetail) {
|
||||
this.$refs.PopupRef.open()
|
||||
this.showModel = true
|
||||
}
|
||||
},
|
||||
playError(info){
|
||||
console.log('err:',info)
|
||||
},
|
||||
playEnded(){
|
||||
this.$refs.PopupRef.close()
|
||||
this.showModel = false
|
||||
},
|
||||
getPreviewUrlBlob(id) {
|
||||
if (!id) {
|
||||
this.fileUrlShow = ''
|
||||
return
|
||||
}
|
||||
let _cache = this.videoObj[id] || ''
|
||||
if(_cache){
|
||||
this.fileUrlShow = _cache;
|
||||
return
|
||||
}
|
||||
let _newDate = new Date().getTime()
|
||||
this.fileUrlShow = getPreviewFileUrl(this.fileId)+'?t=' + _newDate
|
||||
// downloadFile(id).then(res=>{
|
||||
// this.fileUrlShow = res.tempFilePath
|
||||
// this.storageStore.setStorage('video', id, this.fileUrlShow)
|
||||
// })
|
||||
},
|
||||
getPreviewFileUrlNow(id){
|
||||
let _newDate = new Date().getTime()
|
||||
if(id.indexOf('http') === 0){
|
||||
this.fileUrlShow = id + '?t=' + _newDate
|
||||
return
|
||||
}
|
||||
if (id && this.watching) {
|
||||
this.getPreviewUrlBlob(id);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.fileId) {
|
||||
let _newDate = new Date().getTime()
|
||||
if(this.fileId.indexOf('http') === 0){
|
||||
this.fileUrlShow = this.fileId + '?t=' + _newDate
|
||||
}else{
|
||||
this.getPreviewUrlBlob(this.fileId);
|
||||
}
|
||||
this.videoContext = uni.createVideoContext(`video`+ this.fileId)
|
||||
}
|
||||
this.watching = true
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.video-view-box{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #fafafa !important;
|
||||
position: relative;
|
||||
}
|
||||
.img-box{
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.preview-pic{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.overlay-box .video-cont-center{
|
||||
width: 100vw;
|
||||
max-height: 100%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.overlay-box{
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 999;
|
||||
background-color: rgba(0, 0, 0, .65);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<view style="width: 100%;" class="no-data">
|
||||
<view class="flex-c-c">
|
||||
<image src="@/static/images/common/default_img.png" mode="aspectFit" style="width:400rpx"></image>
|
||||
</view>
|
||||
<view class="text">
|
||||
<slot ></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.no-data{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.text{
|
||||
|
||||
color: #666666;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,352 @@
|
||||
<template>
|
||||
<view :class="['loading', type]">
|
||||
<view></view>
|
||||
<view></view>
|
||||
<view></view>
|
||||
<view></view>
|
||||
<view></view>
|
||||
<view></view>
|
||||
<view></view>
|
||||
<view></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed } from 'vue'
|
||||
|
||||
export default {
|
||||
name:"loading",
|
||||
props:{
|
||||
type:{
|
||||
default: 'cr',// cl cr easy
|
||||
type: String
|
||||
},
|
||||
size: {
|
||||
default: '32',
|
||||
type: [String, Number]
|
||||
},
|
||||
color: {
|
||||
default: 'black',
|
||||
type: String
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
loadingWidth(){
|
||||
return this.size + 'rpx'
|
||||
},
|
||||
easyLoadingWidth(){
|
||||
return Number(this.size) * 15 / 16 + 'rpx'
|
||||
},
|
||||
easyLoadingBorderWidth(){
|
||||
return Number(this.size) / 16 + 'rpx'
|
||||
},
|
||||
loadingColor(){
|
||||
return this.color
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
.loading.easy {
|
||||
width: v-bind(easyLoadingWidth);
|
||||
height: v-bind(easyLoadingWidth);
|
||||
border-style: solid;
|
||||
border-color: v-bind(loadingColor);
|
||||
border-width: v-bind(easyLoadingBorderWidth);
|
||||
border-top-color: transparent;
|
||||
border-radius: 100%;
|
||||
animation: circle infinite 0.75s linear;
|
||||
}
|
||||
|
||||
// 转转转动画
|
||||
@keyframes circle {
|
||||
0% {
|
||||
transform: rotate(0);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.loading,
|
||||
.loading.cl > view {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.loading.cl {
|
||||
display: block;
|
||||
font-size: 0;
|
||||
color: v-bind(loadingColor);
|
||||
}
|
||||
|
||||
.loading.cl.la-dark {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.loading.cl > view {
|
||||
display: inline-block;
|
||||
float: none;
|
||||
background-color: currentColor;
|
||||
border: 0 solid currentColor;
|
||||
}
|
||||
|
||||
.loading.cl {
|
||||
width: v-bind(loadingWidth);
|
||||
height: v-bind(loadingWidth);
|
||||
}
|
||||
|
||||
.loading.cl > view {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 25%;
|
||||
height: 25%;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 100%;
|
||||
animation: ball-spin-clockwise 1s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.loading.cl > view:nth-child(1) {
|
||||
top: 5%;
|
||||
left: 50%;
|
||||
animation-delay: -0.875s;
|
||||
}
|
||||
|
||||
.loading.cl > view:nth-child(2) {
|
||||
top: 18.1801948466%;
|
||||
left: 81.8198051534%;
|
||||
animation-delay: -0.75s;
|
||||
}
|
||||
|
||||
.loading.cl > view:nth-child(3) {
|
||||
top: 50%;
|
||||
left: 95%;
|
||||
animation-delay: -0.625s;
|
||||
}
|
||||
|
||||
.loading.cl > view:nth-child(4) {
|
||||
top: 81.8198051534%;
|
||||
left: 81.8198051534%;
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
|
||||
.loading.cl > view:nth-child(5) {
|
||||
top: 94.9999999966%;
|
||||
left: 50.0000000005%;
|
||||
animation-delay: -0.375s;
|
||||
}
|
||||
|
||||
.loading.cl > view:nth-child(6) {
|
||||
top: 81.8198046966%;
|
||||
left: 18.1801949248%;
|
||||
animation-delay: -0.25s;
|
||||
}
|
||||
|
||||
.loading.cl > view:nth-child(7) {
|
||||
top: 49.9999750815%;
|
||||
left: 5.0000051215%;
|
||||
animation-delay: -0.125s;
|
||||
}
|
||||
|
||||
.loading.cl > view:nth-child(8) {
|
||||
top: 18.179464974%;
|
||||
left: 18.1803700518%;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.loading.cl.la-sm {
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
.loading.cl.la-sm > view {
|
||||
width:12.5%;
|
||||
height:12.5%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.loading.cl.la-2x {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.loading.cl.la-2x > view {
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.loading.cl.la-3x {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
}
|
||||
|
||||
.loading.cl.la-3x > view {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
@keyframes ball-spin-clockwise {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
20% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
80% {
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
}
|
||||
}
|
||||
|
||||
.loading.cr,
|
||||
.loading.cr > view {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.loading.cr {
|
||||
display: block;
|
||||
font-size: 0;
|
||||
color: v-bind(loadingColor);
|
||||
}
|
||||
|
||||
.loading.cr.la-dark {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.loading.cr > view {
|
||||
display: inline-block;
|
||||
float: none;
|
||||
background-color: currentColor;
|
||||
border: 0 solid currentColor;
|
||||
}
|
||||
|
||||
.loading.cr {
|
||||
width: v-bind(loadingWidth);
|
||||
height: v-bind(loadingWidth);
|
||||
}
|
||||
|
||||
.loading.cr > view {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 25%;
|
||||
height: 25%;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 100%;
|
||||
animation: ball-spin 1s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.loading.cr > view:nth-child(1) {
|
||||
top: 5%;
|
||||
left: 50%;
|
||||
animation-delay: -1.125s;
|
||||
}
|
||||
|
||||
.loading.cr > view:nth-child(2) {
|
||||
top: 18.1801948466%;
|
||||
left: 81.8198051534%;
|
||||
animation-delay: -1.25s;
|
||||
}
|
||||
|
||||
.loading.cr > view:nth-child(3) {
|
||||
top: 50%;
|
||||
left: 95%;
|
||||
animation-delay: -1.375s;
|
||||
}
|
||||
|
||||
.loading.cr > view:nth-child(4) {
|
||||
top: 81.8198051534%;
|
||||
left: 81.8198051534%;
|
||||
animation-delay: -1.5s;
|
||||
}
|
||||
|
||||
.loading.cr > view:nth-child(5) {
|
||||
top: 94.9999999966%;
|
||||
left: 50.0000000005%;
|
||||
animation-delay: -1.625s;
|
||||
}
|
||||
|
||||
.loading.cr > view:nth-child(6) {
|
||||
top: 81.8198046966%;
|
||||
left: 18.1801949248%;
|
||||
animation-delay: -1.75s;
|
||||
}
|
||||
|
||||
.loading.cr > view:nth-child(7) {
|
||||
top: 49.9999750815%;
|
||||
left: 5.0000051215%;
|
||||
animation-delay: -1.875s;
|
||||
}
|
||||
|
||||
.loading.cr > view:nth-child(8) {
|
||||
top: 18.179464974%;
|
||||
left: 18.1803700518%;
|
||||
animation-delay: -2s;
|
||||
}
|
||||
|
||||
.loading.cr.la-sm {
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
.loading.cr.la-sm > view {
|
||||
width:12.5%;
|
||||
height:12.5%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.loading.cr.la-2x {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.loading.cr.la-2x > view {
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.loading.cr.la-3x {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
}
|
||||
|
||||
.loading.cr.la-3x > view {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
@keyframes ball-spin {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
20% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
80% {
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<view>
|
||||
<rich-text :nodes="mdText"></rich-text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MarkdownIt from 'markdown-it';
|
||||
// import DOMPurify from 'dompurify'
|
||||
export default {
|
||||
name:"markdown-preview",
|
||||
props: {
|
||||
mdString: {
|
||||
default: '',
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
contentHtml:'',
|
||||
mdObj:null
|
||||
};
|
||||
},
|
||||
watch:{
|
||||
mdString: {
|
||||
handler(val){
|
||||
this.contentHtml = this.parseContent(val)
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
mdText(){
|
||||
// let _endtext = DOMPurify.sanitize(this.contentHtml)
|
||||
return this.contentHtml
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
parseContent(text){
|
||||
if(this.mdObj){
|
||||
return this.mdObj.render(text)
|
||||
}else{
|
||||
setTimeout(()=>{
|
||||
return this.parseContent(text)
|
||||
},500)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
this.mdObj = new MarkdownIt({
|
||||
html: true,
|
||||
linkify: true,
|
||||
typographer: true
|
||||
})
|
||||
this.contentHtml = this.parseContent(this.mdString)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
pre{
|
||||
background-color: #f4f4f4;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
overflow: auto;
|
||||
}
|
||||
code{
|
||||
font-family: monospace;
|
||||
background-color: #e8e8e8;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
p,span,div,li{
|
||||
word-break: break-all;
|
||||
line-height: 50rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div
|
||||
class="skeleton-container"
|
||||
:style="containerStyle"
|
||||
>
|
||||
<div
|
||||
v-for="(item, index) in skeletonItems"
|
||||
:key="index"
|
||||
class="skeleton-item"
|
||||
:style="getItemStyle(item)"
|
||||
>
|
||||
<!-- 内部内容根据类型渲染 -->
|
||||
<slot v-if="item.type === 'custom' && index === 0" :item="item" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, toRefs } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
// 定义骨架屏项目类型
|
||||
interface SkeletonItem {
|
||||
type?: 'rect' | 'circle' | 'custom' // 形状类型
|
||||
width?: string | number // 宽度
|
||||
height?: string | number // 高度
|
||||
radius?: string | number // 圆角半径,仅rect类型有效
|
||||
margin?: string | number // 间距
|
||||
animation?: boolean // 是否启用动画
|
||||
}
|
||||
|
||||
// 组件属性
|
||||
const props = defineProps({
|
||||
// 骨架屏项目配置
|
||||
items: {
|
||||
type: Array as PropType<SkeletonItem[]>,
|
||||
default: () => [
|
||||
{ type: 'rect', width: '100%', height: '200px', animation: true },
|
||||
{ type: 'rect', width: '80%', height: '30px', margin: '15px 0', animation: true },
|
||||
{ type: 'rect', width: '60%', height: '30px', margin: '15px 0', animation: true }
|
||||
]
|
||||
},
|
||||
// 骨架屏主色调
|
||||
baseColor: {
|
||||
type: String,
|
||||
default: '#f2f2f2'
|
||||
},
|
||||
// 骨架屏高光色
|
||||
highlightColor: {
|
||||
type: String,
|
||||
default: '#e0e0e0'
|
||||
},
|
||||
// 是否显示骨架屏
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
|
||||
// 计算容器样式
|
||||
const containerStyle = computed(() => {
|
||||
return {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start'
|
||||
}
|
||||
})
|
||||
|
||||
// 获取每个骨架屏项目的样式
|
||||
const getItemStyle = (item: SkeletonItem) => {
|
||||
const style: Record<string, string | number> = {
|
||||
background: props.baseColor,
|
||||
margin: item.margin || 0,
|
||||
animation: props.show && item.animation
|
||||
? 'skeleton-loading 1.5s infinite'
|
||||
: 'none'
|
||||
}
|
||||
|
||||
// 根据类型设置不同样式
|
||||
if (item.type === 'rect') {
|
||||
style.width = item.width || '100%'
|
||||
style.height = item.height || '30px'
|
||||
style.borderRadius = item.radius || '4px'
|
||||
} else if (item.type === 'circle') {
|
||||
style.width = item.width || item.height || '30px'
|
||||
style.height = item.height || item.width || '30px'
|
||||
style.borderRadius = '50%'
|
||||
} else if (item.type === 'custom') {
|
||||
// 自定义类型由插槽处理
|
||||
}
|
||||
|
||||
return style
|
||||
}
|
||||
|
||||
// 注册骨架屏动画
|
||||
const style = document.createElement('style')
|
||||
style.innerHTML = `
|
||||
@keyframes skeleton-loading {
|
||||
0% { background-position: -468px 0 }
|
||||
100% { background-position: 468px 0 }
|
||||
}
|
||||
.skeleton-item {
|
||||
background: linear-gradient(90deg,
|
||||
${props.baseColor} 25%,
|
||||
${props.highlightColor} 50%,
|
||||
${props.baseColor} 75%
|
||||
);
|
||||
background-size: 468px 104px;
|
||||
background-repeat: no-repeat;
|
||||
overflow: hidden;
|
||||
}
|
||||
`
|
||||
document.head.appendChild(style)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.skeleton-container {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<view class="nav-bar-div" >
|
||||
<view class="nav-bar" v-if="is_fixed" :style="{ backgroundColor: backgroundColor }"></view>
|
||||
<view class="nav-bar-seat" v-if="is_seat" :style="{ backgroundColor: backgroundColor }"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
is_fixed: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
is_seat: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: 'transparent'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.nav-bar {
|
||||
width: 100%;
|
||||
height: var(--status-bar-height);
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.nav-bar-seat {
|
||||
width: 100%;
|
||||
height: var(--status-bar-height);
|
||||
}
|
||||
|
||||
.nav-bar-div {
|
||||
z-index: 1900;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<view class="components_custom_file_upload_div pic-cell">
|
||||
<view v-for="(item,index) in fileList" class="img_div pic-item" @click="pic_click(item, index)">
|
||||
<image-preview :imgId="item.fileId" class="img" mode="aspectFill"></image-preview>
|
||||
</view>
|
||||
<view @tap="upload" v-show="show_upload_button">
|
||||
<view class=" add_icon"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
reactive,
|
||||
onMounted,
|
||||
computed,
|
||||
watch
|
||||
} from 'vue'
|
||||
import common from '@/common/common'
|
||||
|
||||
import {
|
||||
uploadFile
|
||||
} from '@/api/common'
|
||||
const show_upload_button = computed(() => {
|
||||
return !props.readonly && props.limit > fileList.length
|
||||
})
|
||||
const pic_click = (item, index) => {
|
||||
common.show('提示', '是否删除图片' + (index + 1)).then(res => {
|
||||
if (res) {
|
||||
fileList.splice(index, 1)
|
||||
emits('update:modelValue', fileList.map(res => res.fileId))
|
||||
}
|
||||
})
|
||||
}
|
||||
// 定义props
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 9
|
||||
},
|
||||
mark: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
fileType: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return ['jpg', 'png']
|
||||
}
|
||||
},
|
||||
})
|
||||
// 状态管理
|
||||
const fileList = reactive([])
|
||||
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
fileList.length = 0
|
||||
if (Array.isArray(newVal)) {
|
||||
fileList.push(...newVal.map(res => ({
|
||||
fileId: res
|
||||
})))
|
||||
}
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
})
|
||||
// 定义emits
|
||||
const emits = defineEmits(['update:modelValue'])
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
})
|
||||
const showPicture = (urls, current) => {
|
||||
// const urlsList = urls.map((url) => imgPrefix(url));
|
||||
|
||||
// uni.previewImage({
|
||||
// current: current || 0,
|
||||
// urls: urlsList,
|
||||
// longPressActions: {
|
||||
// // itemList: ['发送给朋友', '保存图片', '收藏'],
|
||||
// itemList: ['保存图片'],
|
||||
// success: function(data) {
|
||||
// uni.saveImageToPhotosAlbum({
|
||||
// filePath: urlsList[data.index],
|
||||
// success: function() {
|
||||
// // that.$common.msg('下载成功')
|
||||
// }
|
||||
// });
|
||||
// },
|
||||
// fail: function(err) {}
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
// 方法
|
||||
const upload = () => {
|
||||
uni.chooseImage({
|
||||
count: props.limit,
|
||||
sizeType: ['original', 'compressed'],
|
||||
sourceType: ['camera', 'album'],
|
||||
success: async ({
|
||||
tempFilePaths
|
||||
}) => {
|
||||
const valid = tempFilePaths.every(url => {
|
||||
if (props.fileType.length === 0) return true;
|
||||
console.log(url);
|
||||
const lastDotIndex = url.lastIndexOf('.')
|
||||
if (lastDotIndex === -1) return false;
|
||||
const extension = url.slice(lastDotIndex + 1).split('?')[0].toLowerCase()
|
||||
return props.fileType.includes(extension)
|
||||
})
|
||||
if (!valid) return common.msg('您选择的图片格式有误。')
|
||||
console.log(valid)
|
||||
const data = {
|
||||
bizScen: 'S3005'
|
||||
}
|
||||
const upload_map = tempFilePaths.map((file) => uploadFile(file, data))
|
||||
Promise.all(upload_map).then(res => {
|
||||
fileList.push(...res.splice(0, props.limit - fileList.length))
|
||||
emits('update:modelValue', fileList.map(res => res.fileId))
|
||||
|
||||
}).catch(() => {
|
||||
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.components_custom_file_upload_div {
|
||||
display: flex;
|
||||
|
||||
.img_div {
|
||||
overflow: hidden;
|
||||
border: 2rpx solid #C0C0C0;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pic-cell {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.pic-item {
|
||||
margin-right: 30rpx;
|
||||
width: 170rpx;
|
||||
height: 170rpx;
|
||||
border-radius: 8rpx;
|
||||
// background-color: red;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.add_icon {
|
||||
position: relative;
|
||||
width: 170rpx;
|
||||
height: 170rpx;
|
||||
border: 2rpx dashed #C0C0C0;
|
||||
border-radius: 8rpx;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
background-color: #333;
|
||||
top: calc(50% - 15rpx);
|
||||
left: 50%;
|
||||
width: 2rpx;
|
||||
height: 30rpx;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
background-color: #333;
|
||||
top: 50%;
|
||||
left: calc(50% - 15rpx);
|
||||
width: 30rpx;
|
||||
height: 2rpx;
|
||||
transform: translateY(50%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,319 @@
|
||||
<template>
|
||||
<view class="">
|
||||
<uni-popup ref="popup" background-color="#222222" :animation="true">
|
||||
<view class="popup-content" @touchmove.stop :style="{ opacity }">
|
||||
<view class="max-div">
|
||||
<view class="title_div">
|
||||
<image class="img" src="@/static/images/pointsAndRank/badge-wheat-left.png"></image>
|
||||
<view class="title">{{ title }}</view>
|
||||
<image class="img" src="@/static/images/pointsAndRank/badge-wheat-right.png"></image>
|
||||
</view>
|
||||
<view class="points-div">
|
||||
<view class="doct_icon left" @click="doct_click(-1)" v-show="multiple">
|
||||
<view class="doct_icon_div">
|
||||
<view class="fl1"></view>
|
||||
<uv-icon size="28" color="#999999" name="arrow-left"></uv-icon>
|
||||
</view>
|
||||
</view>
|
||||
<swiper class="swiper" :current="current">
|
||||
<swiper-item class="swiper-item-div" v-for="item in badgesList">
|
||||
<view class="img-div">
|
||||
<image-preview :src="item.pointsBadgeImg" class="img" mode="aspectFit"></image-preview>
|
||||
</view>
|
||||
<view class="points-title-div">{{ item.pointsBadgeName }}</view>
|
||||
<view class="points-msg-div" v-if="item.remark">{{ item.remark }}</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<view class="doct_icon right" @click="doct_click(1)" v-show="multiple">
|
||||
<view class="doct_icon_div">
|
||||
<uv-icon size="28" color="#999999" name="arrow-right"></uv-icon>
|
||||
<view class="fl1"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="button-div">
|
||||
<uv-button
|
||||
class="button"
|
||||
:text="multiple ? '一起收下' : '开心收下'"
|
||||
:custom-style="customStyle"
|
||||
:color="buttonColor"
|
||||
@click="clickFun(true)"
|
||||
></uv-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onUnmounted } from 'vue';
|
||||
import { optionErrorIcon, optionSuccessIcon } from '@/common/imgSvg';
|
||||
import { addPointsByCheckIn, queryCheckIn } from '@/api/pointsAndRank.js';
|
||||
import common from '@/common/common';
|
||||
import { arrowRightIcon } from '@/common/imgSvg';
|
||||
import { nextTick } from 'process';
|
||||
const popup = ref(null);
|
||||
const current = ref(0);
|
||||
const opacity = ref(1);
|
||||
let CloseComplete;
|
||||
const badgesList = ref([]);
|
||||
const pointsList = ref([]);
|
||||
const multiple = computed(() => badgesList.value.length > 1);
|
||||
const titleNumberText = ['', '两', '三', '四', '五', '六', '七', '八', '九', '十'];
|
||||
|
||||
const title = computed(() =>
|
||||
badgesList.value.length === 1 ? '恭喜你获得徽章' : `恭喜你获得${titleNumberText[badgesList.value.length - 1]}枚徽章`
|
||||
);
|
||||
const buttonColor = 'linear-gradient( 270deg, #F81758 0%, #FFA062 100%)';
|
||||
const customStyle = {
|
||||
borderRadius: '40rpx'
|
||||
};
|
||||
|
||||
const doct_click = (step) => {
|
||||
// 计算新索引
|
||||
let newIndex = current.value + step;
|
||||
// 获取最大索引(注意:如果有 n 个滑块,最大索引是 n-1)
|
||||
const maxIndex = badgesList.value.length - 1;
|
||||
|
||||
// 限制索引在 0 到 maxIndex 之间
|
||||
if (newIndex < 0) {
|
||||
newIndex = 0;
|
||||
} else if (newIndex > maxIndex) {
|
||||
newIndex = maxIndex;
|
||||
}
|
||||
console.log('current.value', current.value);
|
||||
// 更新当前索引
|
||||
current.value = newIndex;
|
||||
};
|
||||
|
||||
const emits = defineEmits(['confirm']);
|
||||
|
||||
// 退出签到
|
||||
const close = () => {
|
||||
opacity.value = 0;
|
||||
nextTick(() => {
|
||||
popup.value.close();
|
||||
});
|
||||
};
|
||||
// 点击
|
||||
const clickFun = (status = true) => {
|
||||
emits('confirm', status, pointsList.value, badgesList.value, () => {});
|
||||
CloseComplete(status, pointsList.value, badgesList.value);
|
||||
};
|
||||
|
||||
const open = (points = [], badges = [], complete = () => {}) => {
|
||||
if (!Array.isArray(badges) || badges.length === 0) {
|
||||
console.error('badges 不是有效的数组或为空');
|
||||
return;
|
||||
}
|
||||
console.log('badges', badges);
|
||||
CloseComplete = complete;
|
||||
badgesList.value = badges;
|
||||
pointsList.value = points;
|
||||
opacity.value = 1;
|
||||
console.log('成功');
|
||||
uni.$emit('update_me_data', true);
|
||||
popup.value.open('center');
|
||||
};
|
||||
onUnmounted(() => {});
|
||||
defineExpose({ open, close });
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.points-div {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.swiper {
|
||||
height: 700rpx;
|
||||
flex: 1;
|
||||
margin: 0 50rpx;
|
||||
// background-color: red;
|
||||
.swiper-item-div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// justify-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
.points-title-div {
|
||||
margin-top: 40rpx;
|
||||
// position: absolute;
|
||||
// bottom: 60rpx;
|
||||
}
|
||||
.points-msg-div {
|
||||
margin: 30rpx 20rpx 0 20rpx;
|
||||
// position: absolute;
|
||||
// bottom: 0rpx;
|
||||
}
|
||||
.img-div {
|
||||
margin-top: 60rpx;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2rpx solid #2d2d2d; /* 中间圆环颜色 */
|
||||
border-radius: 50%;
|
||||
width: 400rpx;
|
||||
height: 400rpx;
|
||||
&::before {
|
||||
z-index: 1;
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 2rpx solid #2d2d2d; /* 圆环颜色和粗细 */
|
||||
border-radius: 50%; /* 圆形 */
|
||||
transform: scale(0.85);
|
||||
}
|
||||
|
||||
&::after {
|
||||
z-index: 1;
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 4rpx solid #2d2d2d; /* 中间圆环颜色 */
|
||||
border-radius: 50%;
|
||||
transform: scale(0.75);
|
||||
}
|
||||
|
||||
.img {
|
||||
position: relative;
|
||||
z-index: 999;
|
||||
width: 408rpx;
|
||||
height: 408rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.doct_icon {
|
||||
width: 220rpx;
|
||||
height: 220rpx;
|
||||
padding: 0%;
|
||||
display: flex;
|
||||
background-color: #2d2d2d;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
.doct_icon_div {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
width: 60%;
|
||||
// background-color: red;
|
||||
}
|
||||
&.left {
|
||||
left: 0;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
&.right {
|
||||
right: 0;
|
||||
transform: translateX(50%);
|
||||
}
|
||||
// width: 8vw;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
.points-title-div {
|
||||
// height: 120rpx;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 500;
|
||||
font-size: 36rpx;
|
||||
color: #dec38c;
|
||||
text-align: center;
|
||||
}
|
||||
.points-msg-div {
|
||||
// height: 120rpx;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 500;
|
||||
font-size: 26rpx;
|
||||
color: #dec38c;
|
||||
text-align: center;
|
||||
}
|
||||
.button-div {
|
||||
height: 80rpx;
|
||||
width: 442rpx;
|
||||
margin: 74rpx auto 0 auto;
|
||||
::v-deep .uv-button__text {
|
||||
font-size: 34rpx !important;
|
||||
}
|
||||
::v-deep .uv-button {
|
||||
height: 90rpx;
|
||||
width: 442rpx;
|
||||
}
|
||||
}
|
||||
.continuous-check-in-div {
|
||||
display: flex;
|
||||
border-bottom: 2rpx solid #efefef;
|
||||
margin-bottom: 16rpx;
|
||||
padding-top: 8rpx;
|
||||
height: 128rpx;
|
||||
.img {
|
||||
width: 112rpx;
|
||||
height: 102rpx;
|
||||
}
|
||||
.text {
|
||||
margin-left: 16rpx;
|
||||
font-weight: 400;
|
||||
font-size: 30rpx;
|
||||
height: 82rpx;
|
||||
color: #333333;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
.number {
|
||||
font-weight: 600;
|
||||
color: #0066ff;
|
||||
font-family: Arial Black;
|
||||
}
|
||||
}
|
||||
.title_div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 54rpx;
|
||||
height: 68rpx;
|
||||
.img {
|
||||
width: 26rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
.title {
|
||||
font-family: AlimamaShuHeiTi, AlimamaShuHeiTi;
|
||||
font-weight: bold;
|
||||
color: #d88f50;
|
||||
margin: 0rpx 30rpx;
|
||||
text-align: center;
|
||||
font-family: AlimamaShuHeiTi, AlimamaShuHeiTi;
|
||||
font-weight: bold;
|
||||
font-size: 46rpx;
|
||||
background: linear-gradient(0deg, #ffe5b7 0%, #cc716d 100%);
|
||||
/* 将背景裁剪为文字形状 */
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
/* 将文字本身颜色设为透明(显示背景渐变) */
|
||||
color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 99999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
// padding: 40rpx 0;
|
||||
}
|
||||
.max-div {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
margin-top: calc(var(--status-bar-height) + 13vw);
|
||||
margin-bottom: 120rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,484 @@
|
||||
<template>
|
||||
<view class="">
|
||||
<uni-popup ref="popup">
|
||||
<view class="popup-content" @touchmove.stop>
|
||||
<view class="fl1"></view>
|
||||
<view class="max-div">
|
||||
<view class="popup_back_div" @click="close">
|
||||
<image :src="optionErrorIcon('#C8BBB7')" class="img"></image>
|
||||
</view>
|
||||
<view class="title_div">
|
||||
<image class="img" src="@/static/images/pointsAndRank/badge-wheat-left.png"></image>
|
||||
<view class="title">每日签到</view>
|
||||
<image class="img" src="@/static/images/pointsAndRank/badge-wheat-right.png"></image>
|
||||
</view>
|
||||
<view class="continuous-check-in-div">
|
||||
<image class="img" src="@/static/images/pointsAndRank/badge-icon.png"></image>
|
||||
<text class="text">
|
||||
您已连续签到
|
||||
<text class="number">{{ continuousDay }}</text>
|
||||
天
|
||||
</text>
|
||||
</view>
|
||||
<scroll-view scroll-y="true" class="scroll-Y" :show-scrollbar="false">
|
||||
<view class="calendar-div" :class="{ expand: expand }">
|
||||
<view
|
||||
class="calendar-line"
|
||||
v-for="(line, lineIndex) in dateGroups"
|
||||
v-show="nowLine === lineIndex || expand"
|
||||
>
|
||||
<view
|
||||
class="calendar-day"
|
||||
v-for="(day, dayIndex) in line.days"
|
||||
:class="{ hide: day.index > 30, expand: expand }"
|
||||
>
|
||||
<view class="calendar-day-bg" :class="[day.type, isSigned ? 'signed' : 'no-signed']">
|
||||
<image
|
||||
v-if="day.type === 'past' || (day.type === 'today' && isSigned)"
|
||||
:src="optionSuccessIcon('#FFFFFF')"
|
||||
class="img"
|
||||
></image>
|
||||
<text v-else>+{{ day.text }}</text>
|
||||
<view class="check-in-tip">签</view>
|
||||
</view>
|
||||
<view class="calendar-day-title">
|
||||
{{ day.date }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="switch-div" @click="switchStatus()">
|
||||
<view class="back_div" :class="expand ? 'up' : 'down'">
|
||||
<view class="back-icon"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="button-div">
|
||||
<uv-button
|
||||
class="button"
|
||||
:text="isSigned ? '已签到' : '签到'"
|
||||
loadingText="签到中"
|
||||
:custom-style="customStyle"
|
||||
:color="buttonColor"
|
||||
@click="checkIn"
|
||||
:disabled="isSigned"
|
||||
:loading="checkInLoading"
|
||||
></uv-button>
|
||||
</view>
|
||||
</view>
|
||||
<view class="fl1"></view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { optionErrorIcon, optionSuccessIcon } from '@/common/imgSvg';
|
||||
import { addPointsByCheckIn, queryCheckIn } from '@/api/pointsAndRank.js';
|
||||
import common from '@/common/common';
|
||||
const popup = ref(null);
|
||||
const buttonColor = 'linear-gradient( 270deg, #E0914A 0%, #EDBE7F 100%)';
|
||||
const customStyle = {
|
||||
borderRadius: '40rpx' //圆角
|
||||
};
|
||||
let checkInClickCallback;
|
||||
let checkInSuccessCallback;
|
||||
|
||||
const dateGroups = ref([]);
|
||||
const expand = ref(false); // 展开状态
|
||||
const isSigned = ref(false); // 今日签到状态
|
||||
const continuousDay = ref(0); // 连续签到
|
||||
const continuousDay30 = computed(() => {
|
||||
// 连续签到取30的天数
|
||||
const mod = continuousDay.value % 30;
|
||||
// 关键:取模为0时返回30(代表满30天),否则返回mod(1-29天)
|
||||
return mod === 0 ? (continuousDay.value === 0 ? 0 : 30) : mod;
|
||||
});
|
||||
// 判断一下当前的签到显示在第几周
|
||||
const nowLine = computed(() => {
|
||||
// 计算当前需要显示图标的天数(已签到则是连续天数,未签到则是下一天)
|
||||
const currentDay = isSigned.value ? continuousDay30.value : continuousDay30.value + 1;
|
||||
// 按每周7天分行,行数从0开始(例如:1-7天 → 第0行,8-14天 → 第1行...)
|
||||
// 注意:如果天数从1开始(不是0),需要减1再计算
|
||||
const line = Math.floor((currentDay - 1) / 7);
|
||||
console.log('nowLine', Math.max(line, 0)); // 避免输出 -1
|
||||
return Math.max(line, 0); // 核心修复:-1 会被转为 0
|
||||
});
|
||||
|
||||
const emits = defineEmits(['checkInFun']);
|
||||
// 切换显示状态
|
||||
const switchStatus = () => {
|
||||
expand.value = !expand.value;
|
||||
};
|
||||
// 退出签到
|
||||
const close = () => {
|
||||
popup.value.close();
|
||||
typeof checkInClickCallback === 'function' &&
|
||||
checkInClickCallback({
|
||||
status: false
|
||||
});
|
||||
};
|
||||
const checkInLoading = ref(false);
|
||||
// 签到
|
||||
const checkIn = () => {
|
||||
if (checkInLoading.value) return;
|
||||
checkInLoading.value = true;
|
||||
// 函数执行成功的回调
|
||||
checkInClickCallback({
|
||||
status: true,
|
||||
days: continuousDay.value,
|
||||
successCb: ({ days, res }) => {
|
||||
console.log('签到成功', res);
|
||||
checkInLoading.value = false;
|
||||
isSigned.value = true;
|
||||
continuousDay.value = days;
|
||||
generate(); // 签到完重新计算日历样式
|
||||
checkInSuccessCallback(true, days, res.body.points, res.body.pointsBadges);
|
||||
},
|
||||
failCb: (res) => {
|
||||
checkInLoading.value = false;
|
||||
console.log('签到失败', res);
|
||||
common.msg('签到失败,请稍后再试');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 生成35天日期列表(分5组,每组7天),包含类型标记和text字段
|
||||
* 以"前X天"为第一天,第1天显示1,每7天显示5,第30天显示30
|
||||
* @param {number} X - 前X天的天数(0-35之间的整数,总天数=35)
|
||||
* @returns {Array<{
|
||||
* groupIndex: number, // 组索引(0-4)
|
||||
* days: Array<{
|
||||
* date: string, // mm-dd格式日期
|
||||
* type: 'past' | 'today' | 'future', // 日期类型
|
||||
* index: number, // 相对今天的索引(负数为过去,正数为未来)
|
||||
* text: string // 显示文本(1/5/30)
|
||||
* }>
|
||||
* }>} 分组后的日期列表
|
||||
*/
|
||||
function generateGroupedDateList(X) {
|
||||
if (X < 0 || X > 35 || !Number.isInteger(X)) {
|
||||
throw new Error('X必须是0到35之间的整数');
|
||||
}
|
||||
const allDays = [];
|
||||
const today = new Date();
|
||||
const todayDate = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
||||
|
||||
// 生成前X天到后(35-X)天的所有日期(共35天)
|
||||
for (let i = -X; i <= 35 - X - 1; i++) {
|
||||
const currentDate = new Date(today);
|
||||
currentDate.setDate(today.getDate() + i);
|
||||
const pureDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate());
|
||||
|
||||
// 确定日期类型
|
||||
let type;
|
||||
if (pureDate < todayDate) type = 'past';
|
||||
else if (pureDate > todayDate) type = 'future';
|
||||
else type = 'today';
|
||||
|
||||
// 格式化日期
|
||||
const month = String(currentDate.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(currentDate.getDate()).padStart(2, '0');
|
||||
|
||||
// 计算当前是第几天(以"前X天"为第1天)
|
||||
const dayNumber = i - -X + 1; // 核心计算:转换为从1开始的序列
|
||||
|
||||
// 计算text字段
|
||||
let text = '1'; // 默认显示1
|
||||
if (dayNumber === 30) {
|
||||
text = '20'; // 第30天显示30
|
||||
} else if ((dayNumber % 30) % 7 === 0) {
|
||||
text = '5'; // 每7天(第7、14、21、28、35天)显示5
|
||||
}
|
||||
|
||||
allDays.push({
|
||||
date: `${month}.${day}`,
|
||||
type,
|
||||
index: i - -X + 1,
|
||||
text,
|
||||
i
|
||||
});
|
||||
}
|
||||
|
||||
// 每7天分为一组,共5组
|
||||
const groupedList = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const start = i * 7;
|
||||
const end = start + 7;
|
||||
groupedList.push({
|
||||
groupIndex: i,
|
||||
days: allDays.slice(start, end)
|
||||
});
|
||||
}
|
||||
|
||||
return groupedList;
|
||||
}
|
||||
// 计算日历显示的日子
|
||||
const generate = () => {
|
||||
try {
|
||||
const subtractNum = isSigned.value ? 1 : 0;
|
||||
const X = Math.max(continuousDay30.value - subtractNum, 0);
|
||||
console.log('取', X);
|
||||
dateGroups.value = generateGroupedDateList(X);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
}
|
||||
};
|
||||
const open = (isSignedStatus, days, onCheckInClick = () => {}, onCheckInSuccess = () => {}) => {
|
||||
isSigned.value = isSignedStatus; // 今日签到状态
|
||||
continuousDay.value = days; // 连续签到
|
||||
// isSigned.value = false; // 今日签到状态
|
||||
// continuousDay.value = 30; // 连续签到
|
||||
checkInClickCallback = onCheckInClick;
|
||||
checkInSuccessCallback = onCheckInSuccess;
|
||||
// 示例:X=10时,以"10天前"为第1天,生成35天列表
|
||||
generate();
|
||||
|
||||
popup.value.open();
|
||||
};
|
||||
defineExpose({ open, close });
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// 日期
|
||||
.calendar-day {
|
||||
margin-top: 10rpx;
|
||||
text-align: center;
|
||||
// 隐藏展开时超过30天的样式
|
||||
&.hide.expand {
|
||||
.calendar-day-title {
|
||||
width: 70rpx;
|
||||
color: #fff;
|
||||
}
|
||||
.calendar-day-bg {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-day-bg {
|
||||
width: 70rpx;
|
||||
height: 84rpx;
|
||||
border-radius: 8rpx;
|
||||
// 前面日期,代表已签到
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
font-family: Arial, Arial;
|
||||
font-size: 32rpx;
|
||||
letter-spacing: -4rpx;
|
||||
.img {
|
||||
width: 32rpx;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
&.past {
|
||||
background-color: #ffd07c;
|
||||
}
|
||||
|
||||
// 未来日期,代表未签到
|
||||
&.future {
|
||||
color: #d88f50;
|
||||
font-weight: bold;
|
||||
background: #ffe6b8;
|
||||
border: 4rpx solid #ffd58a;
|
||||
}
|
||||
// 当前已签到和未签到
|
||||
&.signed.today {
|
||||
background-color: #ffd07c;
|
||||
}
|
||||
&.no-signed.today {
|
||||
color: #d88f50;
|
||||
font-weight: bold;
|
||||
background: #ffe6b8;
|
||||
border: 4rpx solid #ffd58a;
|
||||
}
|
||||
// 当天的签到小图标
|
||||
.check-in-tip {
|
||||
display: none;
|
||||
}
|
||||
&.today.no-signed > .check-in-tip {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
font-size: 20rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid #fff;
|
||||
box-shadow: 0 0 4rpx #fef5e6;
|
||||
color: #fff;
|
||||
font-weight: 400;
|
||||
background-color: #f8831c;
|
||||
|
||||
display: flex;
|
||||
justify-content: center; /* 水平居中 */
|
||||
align-items: center; /* 垂直居中 */
|
||||
/* 2. 解决文字被边框挤压:清除默认内边距,重置行高 */
|
||||
padding: 0; /* 清除默认内边距(部分元素默认有padding) */
|
||||
line-height: 1; /* 行高与字体大小一致,避免垂直方向多余空间 */
|
||||
/* 3. 旋转30度(transform 旋转属性) */
|
||||
transform: translate(30%, -40%) rotate(-30deg);
|
||||
}
|
||||
}
|
||||
.calendar-day-title {
|
||||
margin: 4rpx 0;
|
||||
font-family: ArialMT;
|
||||
font-size: 22rpx;
|
||||
color: #999999;
|
||||
line-height: 28rpx;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-Y {
|
||||
/**
|
||||
* 26rpx 主盒子内间距
|
||||
* 68rpx 标题高度
|
||||
* 128rpx 日历高度
|
||||
* 16rpx 日历下外间距
|
||||
* 92rpx 下方按钮盒子高度
|
||||
* 80rpx 切换盒子高度
|
||||
*/
|
||||
height: calc(100% - 26rpx - 68rpx - 128rpx - 16rpx - 92rpx - 80rpx - 8rpx);
|
||||
}
|
||||
.switch-div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 60rpx;
|
||||
margin: 10rpx 30%;
|
||||
.back_div {
|
||||
.back-icon {
|
||||
position: relative;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
cursor: pointer;
|
||||
}
|
||||
.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: 2rpx solid #bfbfbf;
|
||||
border-left: 2rpx solid #bfbfbf;
|
||||
}
|
||||
&.up {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
&.down {
|
||||
transform: rotate(-135deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
.calendar-div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 130rpx;
|
||||
overflow: hidden;
|
||||
transition: height 0.3s ease;
|
||||
&.expand {
|
||||
height: 650rpx;
|
||||
}
|
||||
.calendar-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: 130rpx;
|
||||
}
|
||||
}
|
||||
.button-div {
|
||||
height: 92rpx;
|
||||
width: 442rpx;
|
||||
margin: 0 auto;
|
||||
.button {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.continuous-check-in-div {
|
||||
display: flex;
|
||||
border-bottom: 2rpx solid #efefef;
|
||||
margin-bottom: 16rpx;
|
||||
padding-top: 8rpx;
|
||||
height: 128rpx;
|
||||
.img {
|
||||
width: 112rpx;
|
||||
height: 102rpx;
|
||||
}
|
||||
.text {
|
||||
margin-left: 16rpx;
|
||||
font-weight: 400;
|
||||
font-size: 30rpx;
|
||||
height: 82rpx;
|
||||
color: #333333;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
.number {
|
||||
font-weight: 600;
|
||||
color: #0066ff;
|
||||
font-family: Arial Black;
|
||||
}
|
||||
}
|
||||
.title_div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 34rpx;
|
||||
height: 68rpx;
|
||||
.img {
|
||||
width: 26rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
.title {
|
||||
font-family: AlimamaShuHeiTi, AlimamaShuHeiTi;
|
||||
font-weight: bold;
|
||||
font-size: 46rpx;
|
||||
color: #d88f50;
|
||||
margin: 0rpx 30rpx;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.popup_back_div {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
right: 20rpx;
|
||||
.img {
|
||||
margin: 10rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 40rpx 0;
|
||||
}
|
||||
.max-div {
|
||||
width: 80vw;
|
||||
background-color: #fefbfa;
|
||||
border-radius: 38rpx;
|
||||
position: relative;
|
||||
padding: 26rpx;
|
||||
overflow: hidden;
|
||||
margin-top: var(--status-bar-height);
|
||||
margin-bottom: 120rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<view class="">
|
||||
<uni-popup ref="popup">
|
||||
<view class="popup-content" @touchmove.stop>
|
||||
<view class="fl1"></view>
|
||||
<view class="max-div">
|
||||
<view class="popup_back_div" @click="clickFun(false)">
|
||||
<image :src="optionErrorIcon('#C8BBB7')" class="img"></image>
|
||||
</view>
|
||||
<view class="title_div">
|
||||
<image class="img" src="@/static/images/pointsAndRank/badge-wheat-left.png"></image>
|
||||
<view class="title">恭喜你获得积分</view>
|
||||
<image class="img" src="@/static/images/pointsAndRank/badge-wheat-right.png"></image>
|
||||
</view>
|
||||
|
||||
<view class="points-div">
|
||||
<view class="points-img-div">
|
||||
<image class="img_100" src="@/static/images/pointsAndRank/like.png" mode=""></image>
|
||||
</view>
|
||||
<view class="points-num-div">+{{showData.num}}</view>
|
||||
<view class="points-msg-div" v-show="showData.message"> {{showData.message}}</view>
|
||||
</view>
|
||||
<view class="button-div">
|
||||
<uv-button
|
||||
class="button"
|
||||
text="开心收下"
|
||||
:custom-style="customStyle"
|
||||
:color="buttonColor"
|
||||
@click="clickFun(true)"
|
||||
></uv-button>
|
||||
</view>
|
||||
</view>
|
||||
<view class="fl1"></view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { optionErrorIcon, optionSuccessIcon } from '@/common/imgSvg';
|
||||
import { addPointsByCheckIn, queryCheckIn } from '@/api/pointsAndRank.js';
|
||||
import common from '@/common/common';
|
||||
|
||||
const showData = ref({
|
||||
num: 1,
|
||||
message:''
|
||||
});
|
||||
const badgesList =ref([]);
|
||||
const pointsList = ref([]);
|
||||
const popup = ref(null);
|
||||
let CloseComplete
|
||||
const buttonColor = 'linear-gradient( 270deg, #F81758 0%, #FFA062 100%)';
|
||||
const customStyle = {
|
||||
borderRadius: '40rpx' //圆角
|
||||
};
|
||||
|
||||
const emits = defineEmits(['confirm']);
|
||||
const open = (points=[], badges=[], complete = () => {}) => {
|
||||
if (!Array.isArray(points) || points.length === 0) {
|
||||
// console.error('points 不是有效的数组或为空');
|
||||
// 积分为空直接调用完成
|
||||
emits(
|
||||
'confirm',
|
||||
status,
|
||||
points,
|
||||
badges
|
||||
);
|
||||
complete(false, points, badges)
|
||||
return;
|
||||
}
|
||||
CloseComplete = complete
|
||||
badgesList.value = badges
|
||||
// 解构出第一项和剩余元素(原数组不变)
|
||||
const [firstItem, ...remainingPoints] = points;
|
||||
showData.value['num'] = firstItem['changePoints'];
|
||||
showData.value['message'] = firstItem['message'];
|
||||
pointsList.value = remainingPoints;
|
||||
uni.$emit('update_me_data', true);
|
||||
popup.value.open();
|
||||
};
|
||||
const close = () => {
|
||||
popup.value.close();
|
||||
};
|
||||
|
||||
// 点击
|
||||
const clickFun = (status = true) => {
|
||||
emits(
|
||||
'confirm',
|
||||
status,
|
||||
pointsList.value,
|
||||
badgesList.value,
|
||||
() => {
|
||||
|
||||
}
|
||||
);
|
||||
CloseComplete(status, pointsList.value, badgesList.value)
|
||||
};
|
||||
defineExpose({ open, close });
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.points-div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 10rpx 0 0 0;
|
||||
.points-img-div {
|
||||
width: 320rpx;
|
||||
height: 278rpx;
|
||||
}
|
||||
.points-num-div {
|
||||
font-family: Arial, Arial;
|
||||
font-weight: normal;
|
||||
font-size: 108rpx;
|
||||
color: #333333;
|
||||
line-height: 108rpx;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
.points-msg-div {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 30rpx;
|
||||
color: #ae723b;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.button-div {
|
||||
height: 92rpx;
|
||||
width: 442rpx;
|
||||
margin: 40rpx auto 20rpx auto;
|
||||
|
||||
.button {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.title_div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 34rpx;
|
||||
height: 68rpx;
|
||||
.img {
|
||||
width: 26rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
.title {
|
||||
font-family: AlimamaShuHeiTi, AlimamaShuHeiTi;
|
||||
font-weight: bold;
|
||||
font-size: 46rpx;
|
||||
color: #d88f50;
|
||||
margin: 0rpx 30rpx;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.popup_back_div {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
right: 20rpx;
|
||||
.img {
|
||||
margin: 10rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 40rpx 0;
|
||||
}
|
||||
.max-div {
|
||||
width: 80vw;
|
||||
background-color: #fefbfa;
|
||||
border-radius: 38rpx;
|
||||
position: relative;
|
||||
padding: 26rpx;
|
||||
overflow: hidden;
|
||||
margin-top: var(--status-bar-height);
|
||||
margin-bottom: 120rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
完成课程学习 首次完成1分 每天最多获取5分 完成课程学习 crsId 02 完成调用 杨航
|
||||
完成课程练习 首次完成1分 每天最多获取5分 完成课程练习 crsId 04 完成练习调用 田岩 已完成
|
||||
通过课程考试 首次完成1分 每天最多获取5分 通过课程考试 crsId 03 通过调用 田岩 已完成
|
||||
完成考试中心考试 首次完成1分 每天最多获取5分 完成考试中心考试 papersId 05 完成调用 田岩 已完成
|
||||
完成竞赛PK 每次1分 每天最多获取5分 完成竞赛PK 11 完成调用 田岩 已完成
|
||||
获取AI问答 次数徽章 06 建立ws完成调用 杨航
|
||||
通过AI陪练练习 每次1分 每天最多获取5分 通过AI陪练练习 07 通过调用 杨航
|
||||
通过AI陪练考试 每次1分 每天最多获取5分 通过AI陪练考试 08 通过调用 杨航
|
||||
签到 每日1积分,
|
||||
第七天5积分,
|
||||
第30天20积分,
|
||||
断签从第一天计算。 签到完成 田岩
|
||||
*/
|
||||
import {
|
||||
addPointsByTask
|
||||
} from '@/api/pointsAndRank.js'
|
||||
import {
|
||||
ref,
|
||||
nextTick
|
||||
} from 'vue'
|
||||
import {
|
||||
pop_up_time
|
||||
} from '@/enum';
|
||||
|
||||
export default function usePointsAndBadge() {
|
||||
const badgeRef = ref(null);
|
||||
const pointsRef = ref(null);
|
||||
/**
|
||||
* 处理积分和徽章弹窗逻辑(提取的独立方法)
|
||||
* @param {number} points - 积分值
|
||||
* @param {Array} pointsBadges - 积分关联的徽章列表
|
||||
* @param {Function} complete - 完成回调函数
|
||||
* @param {any} res - 接口返回数据
|
||||
*/
|
||||
const handlePointsBadgePopup = (points, pointsBadges, complete = () => {}, res = {}) => {
|
||||
// AI问答仅弹徽章弹窗
|
||||
if (points?.length > 0) {
|
||||
// 普通场景:先弹积分弹窗,再弹徽章弹窗
|
||||
pointsRef.value?.open(points, pointsBadges, (status, pointsList, badgesList) => {
|
||||
pointsRef.value.close();
|
||||
// 有徽章则弹徽章弹窗,否则直接完成
|
||||
if (badgesList?.length > 0) {
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
badgeRef.value.open(pointsList, badgesList, () => {
|
||||
badgeRef.value.close();
|
||||
complete(true, '积分获取成功', res);
|
||||
});
|
||||
}, pop_up_time);
|
||||
});
|
||||
} else {
|
||||
complete(true, '积分获取成功', res);
|
||||
}
|
||||
});
|
||||
} else if (pointsBadges?.length > 0) {
|
||||
badgeRef.value.open([], pointsBadges, () => {
|
||||
badgeRef.value.close();
|
||||
complete(true, '积分获取成功', res);
|
||||
});
|
||||
}else{
|
||||
complete(false, '积分接口返回数据异常', res);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 积分和徽章处理主方法
|
||||
* @param {string} type - 任务类型(04:课程练习,06:AI问答,其他:普通任务)
|
||||
* @param {string|null} taskId - 任务ID
|
||||
* @param {Function} complete - 完成回调(status, message, data)
|
||||
*/
|
||||
const pointAndBadgesRun = async (type, taskId = '', complete = () => {}) => {
|
||||
|
||||
try {
|
||||
console.log('调用积分接口');
|
||||
const res = await addPointsByTask({
|
||||
type,
|
||||
taskId
|
||||
});
|
||||
|
||||
// 防御性处理:确保res.body存在
|
||||
if (!res?.body) {
|
||||
complete(false, '积分接口返回数据异常', res);
|
||||
return;
|
||||
}
|
||||
const {
|
||||
points,
|
||||
pointsBadges,
|
||||
message = ''
|
||||
} = res.body;
|
||||
|
||||
// 分支逻辑:按类型处理
|
||||
switch (type) {
|
||||
case '04': // 04:课程练习,自定义返回逻辑,不弹弹窗
|
||||
complete(true, '', res.body); // 补充回调,避免调用方等待
|
||||
return;
|
||||
case '06': // 06:AI问答,仅弹徽章弹窗
|
||||
if (pointsBadges?.length > 0) {
|
||||
handlePointsBadgePopup(points, pointsBadges, complete, res);
|
||||
} else {
|
||||
complete(true, '', res);
|
||||
}
|
||||
return;
|
||||
// 其他类型:弹积分+徽章弹窗
|
||||
default:
|
||||
handlePointsBadgePopup(points, pointsBadges, complete, res);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
// 细化错误信息:区分网络错误/接口错误
|
||||
const errorMsg = error?.message || '积分获取失败';
|
||||
complete(false, `积分获取失败:${errorMsg}`, error);
|
||||
}
|
||||
};
|
||||
return {
|
||||
pointAndBadgesRun,
|
||||
handlePointsBadgePopup,
|
||||
badgeRef,
|
||||
pointsRef
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<uv-input
|
||||
v-model="inputValue"
|
||||
:customStyle="fixedCustomStyles"
|
||||
placeholderStyle="color:#999999;font-size:26rpx"
|
||||
:placeholder="placeholder"
|
||||
:adjustPosition="false"
|
||||
:confirmType="confirmType"
|
||||
:maxlength="20"
|
||||
@confirm="search"
|
||||
>
|
||||
<template #suffix><uv-icon name="search" color="#2c8ef2" size="24" @click="search(inputValue)"></uv-icon></template>
|
||||
</uv-input>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed, watch } from 'vue';
|
||||
const fixedCustomStyles = {
|
||||
backgroundColor: '#F1F5FA',
|
||||
paddingLeft: '40rpx',
|
||||
paddingTop: '5px',
|
||||
paddingBottom: '5px',
|
||||
border: 'none'
|
||||
};
|
||||
// 定义props
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String, // 输入框通常是字符串类型,这里修改为String更合理
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
confirmType: {
|
||||
type: String,
|
||||
default: 'search'
|
||||
},
|
||||
|
||||
});
|
||||
// 定义emits
|
||||
const emits = defineEmits(['update:modelValue', 'search']);
|
||||
|
||||
// 定义组件内部的inputValue,并与props.modelValue关联
|
||||
const inputValue = ref(props.modelValue);
|
||||
|
||||
// 监听inputValue变化,同步到父组件
|
||||
watch(inputValue, (newVal) => {
|
||||
emits('update:modelValue', newVal);
|
||||
});
|
||||
|
||||
// 监听父组件传来的modelValue变化,同步到组件内部
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
inputValue.value = newVal;
|
||||
}
|
||||
);
|
||||
const clearText = () => {
|
||||
console.log('清除搜索结果');
|
||||
inputValue.value = ''
|
||||
};
|
||||
const search = () => {
|
||||
emits('search', inputValue.value);
|
||||
};
|
||||
defineExpose({clearText})
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<uni-popup ref="popupRef" class="popup">
|
||||
<view class="tree-selector-div">
|
||||
<view class="title">
|
||||
<view class="title-text">机构选择</view>
|
||||
<view class="back_div" @click="openDialog(false)">
|
||||
<image :src="optionErrorIcon('#000000')" class="img"></image>
|
||||
</view>
|
||||
<uv-tabs
|
||||
class="uvTabs"
|
||||
:list="selectedPaths"
|
||||
:lineWidth="40"
|
||||
lineColor="#0066FF"
|
||||
:activeStyle="{ color: '#0066FF', fontSize: '30rpx' }"
|
||||
keyName="orgName"
|
||||
:current="selectedPaths.length - 1"
|
||||
@click="tabsClick"
|
||||
></uv-tabs>
|
||||
</view>
|
||||
|
||||
<scroll-view
|
||||
ref="scrollRef"
|
||||
scroll-with-animation
|
||||
:scroll-into-view="scrollIntoId"
|
||||
class="tree-selector-scroll"
|
||||
scroll-x="true"
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<treeSelector :node="orgTree"></treeSelector>
|
||||
</scroll-view>
|
||||
<view class="button-div">
|
||||
<view class="button">
|
||||
<image class="img" :src="resetIcon()"></image>
|
||||
<view class="" @click="click_button('reset')">重置</view>
|
||||
</view>
|
||||
|
||||
<view class="button" @click="click_button('all')">全部</view>
|
||||
<view class="button" @click="click_button('confirm')">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, provide, nextTick } from 'vue';
|
||||
import common, { getUserInfo } from '@/common/common';
|
||||
import { resetIcon, optionErrorIcon } from '@/common/imgSvg';
|
||||
import treeSelector from './treeSelector';
|
||||
const popupRef = ref(null);
|
||||
// 滚动容器的 ref
|
||||
const scrollRef = ref(null);
|
||||
// 控制滚动位置的属性(scroll-top)
|
||||
const scrollIntoId = ref('');
|
||||
const openDialog = (status) => {
|
||||
if (status) {
|
||||
popupRef.value.open('bottom');
|
||||
} else {
|
||||
popupRef.value.close();
|
||||
}
|
||||
};
|
||||
const tabsClick = (item) => {
|
||||
if (item.index === selectedPaths.value.length - 1) {
|
||||
return;
|
||||
}
|
||||
updateSelectedPaths(
|
||||
{
|
||||
orgName: item.orgName,
|
||||
orgId: item.orgId,
|
||||
parentId: item.parentId
|
||||
},
|
||||
item.index
|
||||
);
|
||||
};
|
||||
const orgTreeInit = common.getValue('orgTree');
|
||||
// 初始选中的
|
||||
const initSelect = {
|
||||
orgName: orgTreeInit[0].orgName,
|
||||
orgId: orgTreeInit[0].orgId,
|
||||
parentId: orgTreeInit[0].parentId
|
||||
};
|
||||
const orgTree = ref(orgTreeInit);
|
||||
|
||||
// 把全行信息插入
|
||||
const selectedPaths = ref([]);
|
||||
|
||||
const updateSelectedPaths = (node, level) => {
|
||||
console.log('node', node);
|
||||
const newPaths = [...selectedPaths.value];
|
||||
newPaths.splice(level);
|
||||
newPaths.push(
|
||||
reactive({
|
||||
orgName: node['orgName'],
|
||||
orgId: node['orgId'],
|
||||
parentId: node['parentId']
|
||||
})
|
||||
);
|
||||
selectedPaths.value = newPaths;
|
||||
nextTick(() => {
|
||||
scrollIntoId.value = `item-${selectedPaths.value.length}`;
|
||||
});
|
||||
};
|
||||
const click_button = (type) => {
|
||||
if (type === 'reset') {
|
||||
updateSelectedPaths({ orgName: '全部', orgId: 'A', parentId: '' }, 0);
|
||||
} else if (type === 'confirm') {
|
||||
emit('submit', JSON.parse(JSON.stringify(selectedPaths.value)));
|
||||
} else if (type === 'all') {
|
||||
emit('submit', [{ orgName: '全部', orgId: 'A', parentId: '' }]);
|
||||
}
|
||||
};
|
||||
provide('treeContextKey', {
|
||||
selectedPaths,
|
||||
updateSelectedPaths
|
||||
});
|
||||
|
||||
const emit = defineEmits(['submit']);
|
||||
const open = (status, inPaths) => {
|
||||
console.log('传入的数据', inPaths);
|
||||
if (Array.isArray(inPaths) && inPaths.length > 0) {
|
||||
selectedPaths.value = inPaths;
|
||||
} else {
|
||||
selectedPaths.value = [{ orgName: '全部', orgId: 'A', parentId: '' }];
|
||||
}
|
||||
openDialog(true);
|
||||
};
|
||||
defineExpose({
|
||||
open,
|
||||
close: () => openDialog(false)
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tree-selector-div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
.title {
|
||||
font-weight: 600;
|
||||
font-size: 34rpx;
|
||||
color: #333333;
|
||||
text-align: center;
|
||||
padding: 40rpx 0 0 0;
|
||||
border-bottom: 4rpx solid #efefef;
|
||||
.back_div {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
right: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.img {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
}
|
||||
}
|
||||
.uvTabs {
|
||||
margin-bottom: -6rpx;
|
||||
:deep(.uv-tabs__wrapper__nav__item) {
|
||||
padding: 0 44rpx 0 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.button-div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background: #ffffff;
|
||||
// box-shadow: 0px -6 15px 0px rgba(0,0,0,0.05);
|
||||
.button {
|
||||
flex: 1;
|
||||
border: none;
|
||||
display: flex;
|
||||
height: 124rpx;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
font-size: 30rpx;
|
||||
.img {
|
||||
width: 38rpx;
|
||||
height: 38rpx;
|
||||
}
|
||||
&:first-child {
|
||||
color: #333333;
|
||||
}
|
||||
&:last-child {
|
||||
color: #0066ff;
|
||||
}
|
||||
&:active {
|
||||
background: #eaeaea;
|
||||
// transform: scale(0.98); /* 按下时轻微缩小,增强反馈 */
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2); /* 内阴影模拟按下凹陷感 */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.tree-selector-scroll {
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<view class="tree-selector" :id="`item-${props.level}`">
|
||||
<view class="tree-child-selector" :class="{ fl1: props.level === 0 }">
|
||||
<view class="level" v-for="item in props.node" @click="click_node(item)">
|
||||
<view class="node" :class="classList(item.orgId)">
|
||||
{{ item.orgName }}
|
||||
</view>
|
||||
<view class="fl1"></view>
|
||||
</view>
|
||||
</view>
|
||||
<treeSelector v-if="childNode && childNode.length" :node="childNode" :level="props.level + 1"></treeSelector>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineOptions, ref, inject, reactive } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
const treeContextKey = 'treeContextKey';
|
||||
const { selectedPaths, updateSelectedPaths } = inject(treeContextKey);
|
||||
const props = defineProps({
|
||||
// 当前节点数据
|
||||
node: {
|
||||
type: Array, // 改为数组类型
|
||||
required: true,
|
||||
// 验证数组中的每个元素是否符合结构要求
|
||||
validator: (value) => {
|
||||
// 先判断是否为数组
|
||||
if (!Array.isArray(value)) return false;
|
||||
// 验证数组中的每个对象
|
||||
return value.every((item) => {
|
||||
// 每个元素必须是对象
|
||||
if (typeof item !== 'object' || item === null) return false;
|
||||
// 必须包含 orgId(字符串类型)
|
||||
if (typeof item.orgId !== 'string') return false;
|
||||
// 必须包含 orgName(字符串类型)
|
||||
if (typeof item.orgName !== 'string') return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
},
|
||||
level: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
});
|
||||
// 把选择的层级树处理成id列表
|
||||
const selectedPathsOrgCodeList = computed(() => selectedPaths.value.map((res) => res.orgId));
|
||||
// 设置子列表的数据
|
||||
const childNode = computed(() => {
|
||||
const lastItem = selectedPaths.value[selectedPaths.value.length - 1];
|
||||
const selectItem = props.node.find((res) => selectedPathsOrgCodeList.value.includes(res.orgId));
|
||||
if (selectItem) return selectItem['children'] || [];
|
||||
return [];
|
||||
});
|
||||
|
||||
// 判断需要的样式class
|
||||
const classList = (orgId) => {
|
||||
let a = [];
|
||||
if (selectedPathsOrgCodeList.value.includes(orgId) || props.level === selectedPathsOrgCodeList.value.length) {
|
||||
a.push('select-bg');
|
||||
}
|
||||
if (selectedPathsOrgCodeList.value[selectedPathsOrgCodeList.value.length - 1] === orgId) {
|
||||
a.push('select-text');
|
||||
}
|
||||
return a;
|
||||
};
|
||||
const emit = defineEmits(['update:selectedKeys']);
|
||||
|
||||
// 处理当前节点被点击
|
||||
const click_node = (node) => {
|
||||
updateSelectedPaths(node, props.level);
|
||||
};
|
||||
|
||||
defineOptions({
|
||||
name: 'treeSelector'
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tree-selector {
|
||||
border-left: 1rpx solid #eaeaea;
|
||||
border-right: 1rpx solid #eaeaea;
|
||||
display: flex;
|
||||
min-height: 600rpx;
|
||||
max-height: 800rpx;
|
||||
transition: all 0.3s ease 2s; /* 关键配置 */
|
||||
}
|
||||
|
||||
.tree-child-selector {
|
||||
display: inline-block;
|
||||
// 每一级树形容器
|
||||
min-width: 6rem;
|
||||
overflow-y: scroll;
|
||||
scrollbar-width: none; /* Firefox 特有:直接隐藏滚动条 */
|
||||
-ms-overflow-style: none; /* IE 和 Edge 特有:隐藏滚动条 */
|
||||
&::-webkit-scrollbar {
|
||||
display: none; /* 隐藏滚动条 */
|
||||
}
|
||||
.level {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.node {
|
||||
padding: 0 1rem;
|
||||
background: #f7f7f7;
|
||||
font-weight: 700;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
text-align: left;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
|
||||
white-space: nowrap;
|
||||
/* 强制不换行 */
|
||||
overflow: hidden;
|
||||
/* 超出部分隐藏 */
|
||||
text-overflow: ellipsis;
|
||||
/* 超出部分显示省略号 */
|
||||
|
||||
&.select-bg {
|
||||
background: #fff;
|
||||
}
|
||||
&.select-text {
|
||||
color: #0066ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="tabbar-shadow"> </view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "tabbar-shadow",
|
||||
data() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tabbar-shadow {
|
||||
position: fixed;
|
||||
bottom: -14px;
|
||||
z-index: 3333;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.2); // 根据需求自定义阴影颜色大小
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<view class="item">
|
||||
<view class="top">
|
||||
<view class="type">{{ subject_type_text }}</view>
|
||||
<view class="score">{{ item.tgtGrade }}分</view>
|
||||
<view class="fl1"></view>
|
||||
<view class="right" v-if="[''].includes(props.mode)">
|
||||
<view class="right_text">展开</view>
|
||||
<uv-icon class="arrow-icon" name="arrow-right"></uv-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="text">{{ item.qnsContent }}</view>
|
||||
<view class="additional_div">
|
||||
<view class="option_div">
|
||||
<radioSubjectItem v-for="(analysisVo,index) in item.itemVos" @click_option="click_option" :key="index"
|
||||
:analysisVo="analysisVo" :my_answer="props.item.anserResult" :backend_answer="[]">
|
||||
</radioSubjectItem>
|
||||
|
||||
<!-- <view class="option_item success">
|
||||
<view class="text">A.这里是选项 A 的内容</view>
|
||||
<image class="img" src="@/static/images/common/success.png" mode="heightFix"></image>
|
||||
</view>
|
||||
<view class="option_item success">
|
||||
<view class="text">B.这里是选项 B 的内容</view>
|
||||
<image class="img" src="@/static/images/common/success.png" mode="heightFix"></image>
|
||||
</view>
|
||||
<view class="option_item error">
|
||||
<view class="text">C.这里是选项 C 的内容</view>
|
||||
<image class="img" src="@/static/images/common/success.png" mode="heightFix"></image>
|
||||
</view>
|
||||
<view class="option_item info">D.这里是选项D的内容</view> -->
|
||||
</view>
|
||||
<view class="analysis_div">
|
||||
<view class="analysis_title">
|
||||
<view class="success_answer">正确答案:{{ correctResultLabel.join('') }}</view>
|
||||
|
||||
<view class="your_answer">
|
||||
你的答案:
|
||||
<text class="red_color">{{ myResultLabel.join('') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="analysis_note">
|
||||
题目解析:{{item.analyContent}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import radioSubjectItem from '@/components/examination/radio-subject-item.vue'
|
||||
import { SUBJECT_TYPE } from '@/enum.js';
|
||||
import { ref, reactive, computed, onMounted, nextTick, getCurrentInstance } from 'vue';
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: (value) => {
|
||||
// 考试: examination 练习: practice 错题本: mistake 学习: study
|
||||
return ['examination', 'practice', 'mistake', 'study'].includes(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
// 试题类型
|
||||
const qnsTyp = computed(() => props.item.qnsTyp);
|
||||
// 正确答案选项
|
||||
const correctResultLabel = computed(() =>
|
||||
props.item.itemVos.filter((res) => res.correctFlag === 'R').map((res) => res.itemNo)
|
||||
);
|
||||
// 我的答案选项
|
||||
const myResultLabel = computed(() => {
|
||||
const anserResultArray = props.item.anserResult.split(',');
|
||||
return props.item.itemVos.filter((res) => anserResultArray.includes(res.itemId)).map((res) => res.itemNo);
|
||||
});
|
||||
|
||||
console.log(myResultLabel.value, 'myResultLabel');
|
||||
|
||||
//
|
||||
|
||||
const subject_data = reactive({
|
||||
qnsId: '',
|
||||
qnsContent: '',
|
||||
qnsTyp: '',
|
||||
itemVos: props.item.itemVos.map((res) => ({
|
||||
...res,
|
||||
// 选择状态, 'info表示未选' , primary 表示蓝色, sunccess 表示绿色, error 表示红色
|
||||
select_status: 'info'
|
||||
}))
|
||||
});
|
||||
// 题目类型换成汉字
|
||||
const subject_type_text = computed(() => SUBJECT_TYPE.find((res) => qnsTyp.value === res.value)?.label || '');
|
||||
const click_option = () => {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.success {
|
||||
color: #15b859;
|
||||
background: #e7f8ed;
|
||||
}
|
||||
|
||||
.info {
|
||||
color: #333333;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #f5212d;
|
||||
background: #fce8e9;
|
||||
}
|
||||
|
||||
.option_item {
|
||||
width: 100%;
|
||||
height: 108rpx;
|
||||
|
||||
border-radius: 15rpx;
|
||||
margin-top: 32rpx;
|
||||
font-family: PingFangSC;
|
||||
font-weight: 500;
|
||||
font-size: 30rpx;
|
||||
line-height: 108rpx;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
padding-left: 52rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.text {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
/* 强制不换行 */
|
||||
overflow: hidden;
|
||||
/* 超出部分隐藏 */
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.img {
|
||||
height: 30rpx;
|
||||
padding-right: 30rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.analysis_div {
|
||||
.analysis_title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 32rpx 0 28rpx 0;
|
||||
|
||||
.success_answer {
|
||||
}
|
||||
|
||||
.your_answer {
|
||||
}
|
||||
|
||||
.red_color {
|
||||
color: #f5212d;
|
||||
}
|
||||
}
|
||||
|
||||
.analysis_note {
|
||||
font-family: PingFangSC;
|
||||
font-weight: 500;
|
||||
font-size: 30rpx;
|
||||
color: #666666;
|
||||
line-height: 48rpx;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
// height: 170rpx;
|
||||
border-radius: 16rpx;
|
||||
margin: 12rpx 0 50rpx 0;
|
||||
padding: 34rpx 30rpx 34rpx 36rpx;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.type {
|
||||
width: 108rpx;
|
||||
height: 46rpx;
|
||||
background: #267eff;
|
||||
border-radius: 8rpx;
|
||||
font-family: PingFangSC;
|
||||
font-weight: 500;
|
||||
font-size: 25rpx;
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
line-height: 46rpx;
|
||||
}
|
||||
|
||||
.score {
|
||||
margin-left: 24rpx;
|
||||
font-family: ArialMT;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.right_text {
|
||||
font-family: PingFangSC;
|
||||
font-weight: 600;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 40rpx;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
font-family: PingFangSC;
|
||||
font-weight: 500;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
line-height: 48rpx;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<KeepAlive>
|
||||
<view class="watermark-container" v-if="showWatermark">
|
||||
<view class="watermark-item" v-for="row in rowCount" :key="row" :style="{ top: `${row * itemHeight}px` }">
|
||||
<view
|
||||
class="watermark-text"
|
||||
v-for="(cols, col) in colCount"
|
||||
:key="col"
|
||||
:style="{
|
||||
left: `${col * itemWidth}px`
|
||||
}"
|
||||
>
|
||||
{{ textValue }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</KeepAlive>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { getUserInfo } from '@/common/common';
|
||||
|
||||
// 定义props
|
||||
const props = defineProps({
|
||||
// 单个水印宽度(px)
|
||||
itemWidth: {
|
||||
type: Number,
|
||||
default: 180
|
||||
},
|
||||
// 单个水印高度(px)
|
||||
itemHeight: {
|
||||
type: Number,
|
||||
default: 120
|
||||
},
|
||||
// 是否显示
|
||||
showWatermark: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
// 响应式变量(替代原data)
|
||||
const rowCount = ref(0); // 行数
|
||||
const colCount = ref(0); // 列数
|
||||
const windowWidth = ref(0); // 屏幕宽度
|
||||
const windowHeight = ref(0); // 屏幕高度
|
||||
const textValue = ref('');
|
||||
|
||||
// 计算水印网格的行列数(确保完全覆盖屏幕)
|
||||
const calcGridCount = () => {
|
||||
// 列数 = 屏幕宽度 / 单个水印宽度 + 2(冗余1列避免边缘空白)
|
||||
colCount.value = Math.ceil(windowWidth.value / props.itemWidth) + 2;
|
||||
// 行数 = 屏幕高度 / 单个水印高度 + 2(冗余1行避免边缘空白)
|
||||
rowCount.value = Math.ceil(windowHeight.value / props.itemHeight) + 2;
|
||||
};
|
||||
|
||||
// 获取屏幕尺寸(App端兼容方式)
|
||||
const getWindowInfo = () => {
|
||||
uni.getSystemInfo({
|
||||
success: (res) => {
|
||||
windowWidth.value = res.windowWidth;
|
||||
windowHeight.value = res.windowHeight;
|
||||
calcGridCount(); // 计算需要多少行列覆盖屏幕
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 组件挂载时执行(替代原mounted)
|
||||
onMounted(() => {
|
||||
getWindowInfo();
|
||||
updateWatermarkText();
|
||||
});
|
||||
// 提取更新文字的方法
|
||||
const updateWatermarkText = () => {
|
||||
const userInfo = getUserInfo();
|
||||
textValue.value = `${userInfo?.userName ?? ''} ${userInfo?.loginName ?? ''}`;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
updateWatermarkText
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.watermark-container {
|
||||
position: fixed;
|
||||
top: var(--status-bar-height); /* 适配状态栏 */
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
pointer-events: none; /* 不拦截点击事件 */
|
||||
z-index: 99999;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 行容器 */
|
||||
.watermark-item {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* 单个水印文本 */
|
||||
.watermark-text {
|
||||
position: absolute;
|
||||
white-space: nowrap; /* 避免文本换行 */
|
||||
user-select: none; /* 禁止选中 */
|
||||
/* letter-spacing: 2px; */
|
||||
color: rgba(30, 30, 70, 0.1);
|
||||
font-size: 16px;
|
||||
transform: rotate(-30deg);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,226 @@
|
||||
import {
|
||||
queryCheckIn,
|
||||
addPointsByCheckIn
|
||||
} from '@/api/pointsAndRank.js';
|
||||
import common from '@/common/common';
|
||||
import {
|
||||
getUserInfo
|
||||
} from '@/common/common';
|
||||
import {
|
||||
ref
|
||||
} from 'vue';
|
||||
export default function useCheckIn() {
|
||||
const checkInRef = ref(null)
|
||||
// ========== 核心工具方法 ==========
|
||||
/**
|
||||
* 获取当前用户的签到存储key(区分不同用户)
|
||||
*/
|
||||
const getKey = () => {
|
||||
const userInfo = getUserInfo();
|
||||
const userId = userInfo?.userId || ''; // 修复原代码userId重复赋值问题
|
||||
if (!userId) {
|
||||
console.warn('用户ID为空,无法获取签到状态');
|
||||
return '';
|
||||
}
|
||||
return `${userId}_user_sign_in_status`;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取今日日期(YYYY-MM-DD)
|
||||
*/
|
||||
const getTodayDate = () => {
|
||||
const now = new Date();
|
||||
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* 读取本地存储的签到状态
|
||||
* @returns {Object} { isSigned: boolean, signDate: string }
|
||||
*/
|
||||
const getLocalSignStatus = () => {
|
||||
const key = getKey();
|
||||
if (!key) return {
|
||||
signDate: '',
|
||||
checkInDays: 0,
|
||||
PopupDay: ''
|
||||
};
|
||||
try {
|
||||
// 读取本地存储的签到信息(兼容JSON格式)
|
||||
const rawData = common.getValue(key);
|
||||
if (!rawData) return {
|
||||
signDate: '',
|
||||
checkInDays: 0,
|
||||
popupDay: ''
|
||||
};
|
||||
|
||||
// 兼容存储的是字符串或JSON对象的情况
|
||||
const signStatus = rawData;
|
||||
return {
|
||||
signDate: signStatus.signDate || '',
|
||||
checkInDays: signStatus.checkInDays || 0,
|
||||
popupDay: signStatus.popupDay || '',
|
||||
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('读取本地签到状态失败', e);
|
||||
return {
|
||||
signDate: '',
|
||||
checkInDays: 0,
|
||||
popupDay: ''
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新本地签到状态(支持按需更新字段)
|
||||
* @param {boolean|undefined} isSigned - 是否签到(可选,传入则更新)
|
||||
* @param {number|undefined} checkInDays - 签到天数(可选,传入则更新)
|
||||
* @param {string|undefined} popupDay - 弹窗关闭日期(可选,传入则更新)
|
||||
*/
|
||||
const setLocalSignStatus = (isSigned, checkInDays, popupDay) => {
|
||||
const key = getKey();
|
||||
if (!key) return;
|
||||
|
||||
// 1. 读取本地原有数据(若无则初始化空对象)
|
||||
let oldSignStatus = {};
|
||||
try {
|
||||
oldSignStatus = common.getValue(key) || {}; // 兼容common.getValue返回null/undefined的情况
|
||||
} catch (e) {
|
||||
console.warn('读取原有签到状态失败,使用空对象初始化', e);
|
||||
oldSignStatus = {};
|
||||
}
|
||||
|
||||
// 2. 初始化新状态:先继承原有数据
|
||||
const newSignStatus = { ...oldSignStatus };
|
||||
|
||||
// 3. 按需更新字段(仅当参数传入时覆盖)
|
||||
// 处理isSigned:传入则更新,且为true时自动设置signDate
|
||||
if (isSigned !== undefined) {
|
||||
// 保持原有逻辑:isSigned为true时,signDate设为今日
|
||||
newSignStatus.signDate = isSigned ? getTodayDate() : oldSignStatus.signDate;
|
||||
}
|
||||
|
||||
// 处理checkInDays:传入则更新,未传入保留原有值
|
||||
if (checkInDays !== undefined) {
|
||||
newSignStatus.checkInDays = checkInDays;
|
||||
}
|
||||
|
||||
// 处理popupDay:传入非空则更新,未传入/空值保留原有值
|
||||
if (popupDay !== undefined) {
|
||||
newSignStatus.popupDay = popupDay? getTodayDate() : oldSignStatus.popupDay;
|
||||
}
|
||||
|
||||
// 4. 重新存入本地(覆盖原有数据)
|
||||
try {
|
||||
console.log('存入的数据(原有+更新)', newSignStatus);
|
||||
common.setValue(key, newSignStatus);
|
||||
} catch (e) {
|
||||
console.error('更新本地签到状态失败', e);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// ========== 对外暴露的核心方法 ==========
|
||||
/**
|
||||
* 初始化签到状态(页面加载时调用,同步本地与后端状态)
|
||||
* @returns {Promise<{ isSigned: boolean, checkInDays?: number }>} 签到状态 + 连续签到天数
|
||||
*/
|
||||
const initCheckInStatus = async (complete = () => {}) => {
|
||||
const today = getTodayDate();
|
||||
const localStatus = getLocalSignStatus();
|
||||
console.log('localStatus', localStatus);
|
||||
// 1. 本地已有今日签到记录 → 直接返回本地状态
|
||||
if (localStatus.signDate === today) {
|
||||
|
||||
complete(true, localStatus.checkInDays, localStatus.popupDay === today)
|
||||
return true;
|
||||
}
|
||||
// 2. 本地记录过期/无记录 → 请求接口获取最新状态
|
||||
try {
|
||||
const res = await queryCheckIn(); // 调用查询签到状态接口
|
||||
const {
|
||||
body
|
||||
} = res || {};
|
||||
console.log('bodbodyy', body);
|
||||
// // 接口返回格式适配(根据实际后端返回调整)
|
||||
const isSigned = body?.checkInToday === 'Y';
|
||||
const checkInDays = body?.days || 0;
|
||||
setLocalSignStatus(isSigned, checkInDays);
|
||||
// if (!isSigned) { // 没签到执行签到弹窗
|
||||
// console.log('没签到执行签到弹窗');
|
||||
// //如果今日点击过x那么久不弹了
|
||||
// // if(localStatus.popupDay === today) return;
|
||||
// complete(isSigned, checkInDays, localStatus.popupDay === today)
|
||||
// }
|
||||
complete(isSigned, checkInDays, localStatus.popupDay === today)
|
||||
|
||||
} catch (e) {
|
||||
console.error('请求签到状态接口失败', e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 判断今日是否已签到(简化版,供快速判断)
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const getTodayCheckInStatus = () => {
|
||||
const localStatus = getLocalSignStatus();
|
||||
const today = getTodayDate();
|
||||
// 日期匹配且已签到,才返回true
|
||||
return localStatus.signDate === today;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 执行签到操作(核心方法)
|
||||
* @param {Function} successCb - 签到成功回调
|
||||
* @param {Function} failCb - 签到失败回调
|
||||
* @returns {Promise<boolean>} 签到是否成功
|
||||
*/
|
||||
const checkInFun = async ({
|
||||
status,
|
||||
days=0,
|
||||
successCb=() =>{},
|
||||
failCb=() =>{}
|
||||
}) => {
|
||||
if(!status) { // 点击了关闭
|
||||
console.log('点击了关闭');
|
||||
|
||||
setLocalSignStatus(undefined, undefined, true);
|
||||
return;
|
||||
}
|
||||
console.log('请求接口', days);
|
||||
addPointsByCheckIn({
|
||||
days
|
||||
})
|
||||
.then((res) => {
|
||||
console.log('接口成功回调', res);
|
||||
setLocalSignStatus(true, days + 1);
|
||||
successCb?.({
|
||||
days: days + 1,
|
||||
res
|
||||
});
|
||||
}).catch((error) => {
|
||||
failCb?.(error)
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置签到状态(可选,用于特殊场景)
|
||||
*/
|
||||
const resetCheckInStatus = () => {
|
||||
const key = getKey();
|
||||
if (key) common.removeValue(key); // 需确保common有remove方法,无则补充
|
||||
};
|
||||
|
||||
// ========== 返回对外暴露的方法 ==========
|
||||
return {
|
||||
checkInRef,
|
||||
initCheckInStatus, // 初始化签到状态(页面加载时调用)
|
||||
checkInFun, // 执行签到
|
||||
getLocalSignStatus, // 获取签到缓存信息
|
||||
getTodayCheckInStatus // 获取今天签到状态
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import RecordPermission from '@/common/recordPermission.js'
|
||||
|
||||
export const initRecord = () => RecordPermission.requestPermission();
|
||||
|
||||
|
||||
export default function useIndexList() {
|
||||
return {
|
||||
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
computed,
|
||||
reactive,
|
||||
nextTick,
|
||||
ref
|
||||
} from 'vue';
|
||||
|
||||
|
||||
export default function useIndexList() {
|
||||
const tabAct = ref(0);
|
||||
const listItemRefs = ref([]);
|
||||
const tabChange = (item) => {
|
||||
tabAct.value = item.index;
|
||||
};
|
||||
|
||||
const swiperChange = (item) => {
|
||||
tabAct.value = item.detail.current;
|
||||
};
|
||||
|
||||
const search = (value) => {
|
||||
console.log('search', value);
|
||||
listItemRefs.value.forEach((item, index) => {
|
||||
item?.search(value, tabAct.value === index);
|
||||
});
|
||||
};
|
||||
return {
|
||||
tabAct,
|
||||
listItemRefs,
|
||||
tabChange,
|
||||
swiperChange,
|
||||
search,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import {
|
||||
reactive,
|
||||
ref,
|
||||
computed,
|
||||
nextTick
|
||||
} from 'vue';
|
||||
import {
|
||||
uploadFile
|
||||
} from '@/api/common';
|
||||
import {
|
||||
queryCurrentUser
|
||||
} from '@/api/login.js';
|
||||
import common from '@/common/common';
|
||||
import {
|
||||
updateDfSysUserExtandInfoImageAddr
|
||||
} from '@/api/user.js';
|
||||
|
||||
export default function useUpdateAvatar({
|
||||
success = () => {}
|
||||
}) {
|
||||
const avatarCropperUrl = ref('');
|
||||
const avatarCropperStatus = ref(true);
|
||||
// 点击修改头像
|
||||
const click_update_avatar = () => {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
sizeType: ['compressed'],
|
||||
success: (resPicture) => {
|
||||
uni.getFileInfo({
|
||||
filePath: resPicture.tempFilePaths[0],
|
||||
success: function(resSize) {
|
||||
if (resSize.size > 10 * 1024 * 1024) return common.msg(
|
||||
'图片过大,请重新选择');
|
||||
avatarCropperUrl.value = resPicture.tempFilePaths[0];
|
||||
avatarCropperStatus.value = true;
|
||||
},
|
||||
fail: function(err) {
|
||||
common.msg('获取图片失败,请选择其他图片');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
// 选择头像取消
|
||||
const avatarOnCancel = (value) => {
|
||||
avatarCropperStatus.value = false;
|
||||
};
|
||||
// 选择头像确定
|
||||
const avatarOnConfirm = (file) => {
|
||||
avatarCropperStatus.value = false;
|
||||
nextTick(async () => {
|
||||
common.loading('更新头像中');
|
||||
try {
|
||||
const data = {
|
||||
bizScen: 'S3007',
|
||||
thumbnailFlag: true,
|
||||
thumbImgSize: 100
|
||||
};
|
||||
// 上传图片
|
||||
const res = await uploadFile(file.tempFilePath, data);
|
||||
// 更新个人信息
|
||||
await updateDfSysUserExtandInfoImageAddr({
|
||||
imageAddr: res.thumbFileId
|
||||
});
|
||||
// 更新个人信息
|
||||
const userData = await queryCurrentUser();
|
||||
common.msg('修改成功');
|
||||
success(userData)
|
||||
|
||||
} catch {
|
||||
common.msg('修改失败');
|
||||
}
|
||||
common.hideLoading();
|
||||
});
|
||||
};
|
||||
return {
|
||||
avatarCropperUrl,
|
||||
avatarCropperStatus,
|
||||
click_update_avatar,
|
||||
avatarOnCancel,
|
||||
avatarOnConfirm,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
computed,
|
||||
reactive,
|
||||
nextTick,
|
||||
ref
|
||||
} from 'vue';
|
||||
|
||||
export default function useZpaging({
|
||||
fetchFunction, // 必传:接口请求函数
|
||||
searchKey = '' // 可选:搜索参数名,默认'searchText'
|
||||
}) {
|
||||
const total = ref(-1);
|
||||
const data_list = ref([]);
|
||||
const searchText = ref('');
|
||||
const pagingRef = ref(null);
|
||||
const queryList = async (pageNo, pageSize) => {
|
||||
try {
|
||||
const requestParams = {
|
||||
page: pageNo,
|
||||
limit: pageSize
|
||||
};
|
||||
// 只有当searchKey有效(非空字符串/非undefined)且searchText有值时,才添加搜索参数
|
||||
if (searchKey && searchKey.trim() && searchText.value) {
|
||||
requestParams[searchKey] = searchText.value;
|
||||
}
|
||||
const res = await fetchFunction(requestParams);
|
||||
|
||||
total.value = res.total??res.body.length
|
||||
pagingRef.value.completeByTotal(res.body, res.total);
|
||||
} catch (err) {
|
||||
console.error('分页查询失败:', err);
|
||||
pagingRef.value?.complete(false);
|
||||
}
|
||||
};
|
||||
//刷新列表要求
|
||||
const reload = () => {
|
||||
pagingRef.value?.reload()
|
||||
};
|
||||
// 清空分页数据,pageNo恢复为默认值。
|
||||
const clear = () => {
|
||||
total.value = -1
|
||||
pagingRef.value?.clear();
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const search = (value, state=true) => {
|
||||
console.log('搜索', value, state);
|
||||
searchText.value = value;
|
||||
if (state) {
|
||||
reload()
|
||||
} else {
|
||||
clear()
|
||||
}
|
||||
};
|
||||
return {
|
||||
pagingRef,
|
||||
queryList,
|
||||
reload,
|
||||
search,
|
||||
clear,
|
||||
searchText,
|
||||
total,
|
||||
data_list
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
export const MAX_UPLOAD_PICTURE_SIZE = 2048 // 最大图片上传限制
|
||||
export const Max_Upload_Document_Size = 102400 // 最大文档上传限制
|
||||
export const TextareaInputLimitCharacter = 160 // 全局多行文本输入框限制字数
|
||||
// 公共tab页的图片
|
||||
export const LineBg =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAABL0lEQVQ4T6XSv0uCURTG8e9jgpRNDS1BU1sELUFLQVM1tgQFtcR7A8m1LQqaLGhOkQKH/o5aKiiHlgiE0K1oCYLAMk9ov/RF8e313u2ew4dzzzmi2+NsA9gH3hCr6srzbAJxDkS/nbvwYNJilMkDow1FXYUHneWAlV/MqN3ZcKCzHWDb164MGa3/H/Qshdj0YbdUmORIL8HBNRsgShZjoQkznvioY/e19wCgCccSsAcM+bBnjHmyuvx5bw8mrJ93FoEkYrzFej0g5kjrpjH2BTrro8oIYhgYQ0xhTCPibfb0ggjLHKrojwvPEogDIBZgycsYKQrscqpKq3zh7BXo7YBVgRMibLWqqvnLzh6BwTZgESNHD8edoL+hOJupT9CII0oYJcQ14oy0CgHa0JTyCeBLVNeWygE5AAAAAElFTkSuQmCC";
|
||||
// 性别
|
||||
export const GENDER = [{
|
||||
value: "m",
|
||||
label: "男性",
|
||||
},
|
||||
{
|
||||
value: "f",
|
||||
label: "女性",
|
||||
}
|
||||
]
|
||||
// 试题题型
|
||||
export const SUBJECT_TYPE = [{
|
||||
value: "SN",
|
||||
label: "单选题",
|
||||
},
|
||||
{
|
||||
value: "MU",
|
||||
label: "多选题",
|
||||
},
|
||||
{
|
||||
value: "JD",
|
||||
label: "判断题",
|
||||
},
|
||||
{
|
||||
value: "ES",
|
||||
label: "问答题",
|
||||
}
|
||||
]
|
||||
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'
|
||||
}
|
||||
}
|
||||
export const pop_up_time = 500;
|
||||
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script>
|
||||
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
|
||||
CSS.supports('top: constant(a)'))
|
||||
document.write(
|
||||
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
||||
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
|
||||
</script>
|
||||
<title></title>
|
||||
<!--preload-links-->
|
||||
<!--app-context-->
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"><!--app-html--></div>
|
||||
<script type="module" src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,26 @@
|
||||
import App from './App'
|
||||
import { createSSRApp } from 'vue'
|
||||
import pinia from './store.js'
|
||||
import CommonLoading from './components/loading.vue'
|
||||
|
||||
uni.$zp = {
|
||||
config: {
|
||||
'default-page-size': 20,
|
||||
'show-loading-more-when-reload': true, // 列表刷新时自动显示加载更多view,且为加载中状态
|
||||
'show-scrollbar': false, // 控制是否出现滚动条
|
||||
'refresher-refreshing-scrollable': false, // 自定义下拉刷新刷新中状态是否允许列表滚动
|
||||
"min-delay":300,// 触发@query后最小延迟处理的时间,单位为毫秒
|
||||
'empty-view-img': '/static/images/common/default_img.png',
|
||||
'empty-view-center': false, // 空数据图片是否垂直居中,默认为是,若设置为否即为从空数据容器顶部开始显示
|
||||
//'auto-hide-empty-view-when-pull':false,// 加载中时是否自动隐藏空数据图
|
||||
}
|
||||
}
|
||||
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
app.use(pinia)
|
||||
app.component('CommonLoading',CommonLoading)
|
||||
return {
|
||||
app
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"name" : "tra-app",
|
||||
"appid" : "__UNI__EDE0E52",
|
||||
"description" : "",
|
||||
"versionName" : "0.0.3",
|
||||
"versionCode" : 3,
|
||||
"transformPx" : false,
|
||||
/* 5+App特有相关 */
|
||||
"app-plus" : {
|
||||
"usingComponents" : true,
|
||||
"nvueStyleCompiler" : "uni-app",
|
||||
"compilerVersion" : 3,
|
||||
"renderer" : "auto",
|
||||
"orientation" : [ "portrait-primary" ],
|
||||
"splashscreen" : {
|
||||
"alwaysShowBeforeRender" : true,
|
||||
"waiting" : false,
|
||||
"autoclose" : true,
|
||||
"delay" : 0
|
||||
},
|
||||
"safearea" : {
|
||||
"background" : "#CCCCCC",
|
||||
"bottom" : {
|
||||
"offset" : "none|auto"
|
||||
},
|
||||
"left" : {
|
||||
"offset" : "none|auto"
|
||||
},
|
||||
"right" : {
|
||||
"offset" : "none|auto"
|
||||
}
|
||||
},
|
||||
/* 模块配置 */
|
||||
"modules" : {
|
||||
"Camera" : {},
|
||||
"Barcode" : {},
|
||||
"Record" : {},
|
||||
"VideoPlayer" : {},
|
||||
"LivePusher" : {}
|
||||
},
|
||||
/* 应用发布信息 */
|
||||
"distribute" : {
|
||||
/* android打包配置 */
|
||||
"android" : {
|
||||
"permissions" : [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
]
|
||||
},
|
||||
/* ios打包配置 */
|
||||
"ios" : {
|
||||
"dSYMs" : false
|
||||
},
|
||||
/* SDK配置 */
|
||||
"sdkConfigs" : {},
|
||||
"splashscreen" : {
|
||||
"androidStyle" : "common",
|
||||
"android" : {
|
||||
"hdpi" : "src/static/images/login/open.9.png",
|
||||
"xhdpi" : "src/static/images/login/open.9.png",
|
||||
"xxhdpi" : "src/static/images/login/open.9.png"
|
||||
},
|
||||
"useOriginalMsgbox" : true
|
||||
}
|
||||
}
|
||||
},
|
||||
/* 快应用特有相关 */
|
||||
"quickapp" : {},
|
||||
/* 小程序特有相关 */
|
||||
"mp-weixin" : {
|
||||
"appid" : "",
|
||||
"setting" : {
|
||||
"urlCheck" : false
|
||||
},
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-alipay" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-baidu" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-toutiao" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"uniStatistics" : {
|
||||
"enable" : false
|
||||
},
|
||||
"vueVersion" : "3"
|
||||
}
|
||||
@@ -0,0 +1,599 @@
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/index/dialog",
|
||||
"style": {
|
||||
"navigationBarTitleText": "AI问答",
|
||||
"app-plus": {
|
||||
"titleNView": false,
|
||||
"animationType": "fade-in"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "首页",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/courseDetail/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "课程详情",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/courseDetail/submit",
|
||||
"style": {
|
||||
"navigationBarTitleText": "提交评价",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/setting/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "设置",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/welcome/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "欢迎",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/test/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "测试",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/study/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "学习",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/scene/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "场景",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/me/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/other/web_view",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/ysxy",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/search/search",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mascot/mascot_select_list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "吉祥物列表",
|
||||
"disableSwipeBack": true,
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/preview/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/favorites/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/messageNotification/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "消息",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/messageNotification/list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "消息列表",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/testingHall/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "考试中心",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/testingHall/details",
|
||||
"style": {
|
||||
"navigationBarTitleText": "考试详情",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/wrongQuestionRecord/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "错题本",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/wrongQuestionRecord/correct",
|
||||
"style": {
|
||||
"navigationBarTitleText": "错题本做题页",
|
||||
"disableSwipeBack": true,
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/examination/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "考试",
|
||||
"disableSwipeBack": true,
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/examination/result",
|
||||
"style": {
|
||||
"navigationBarTitleText": "考试结果",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/practice/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "练习",
|
||||
"disableSwipeBack": true,
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/learningTasks/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "学习任务",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/learningTasks/details",
|
||||
"style": {
|
||||
"navigationBarTitleText": "学习详情",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/examRanking/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "考试排行榜页",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/courseRecord/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "课程记录页",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/examinationRecord/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "考试记录页",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/courseRecord/courseStudyRecordDetail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "课程记录学习记录详情",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/courseRecord/coursePracticeRecordDetail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "课程记录练习记录详情",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/wrongQuestionRecord/result",
|
||||
"style": {
|
||||
"navigationBarTitleText": "答题本答题完成结果页",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/practice/result",
|
||||
"style": {
|
||||
"navigationBarTitleText": "练习完成结果页",
|
||||
"app-plus": {
|
||||
"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
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/competitionRecord/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "竞赛记录",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/pointsRedemption/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "积分兑换",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/test/testChat",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/course/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "课程中心",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/course/study",
|
||||
"style": {
|
||||
"navigationBarTitleText": "课程学习",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/charts/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/index/preview",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/search/result",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/example/dialog",
|
||||
"style": {
|
||||
"navigationBarTitleText": "AI问答演示",
|
||||
"app-plus": {
|
||||
"titleNView": false,
|
||||
"animationType": "fade-in"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/sparring/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "AI陪练",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/sparring/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "AI陪练详情",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/sparring/tip",
|
||||
"style": {
|
||||
"navigationBarTitleText": "AI陪练提示",
|
||||
"disableSwipeBack": true,
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/sparring/chooseRole",
|
||||
"style": {
|
||||
"navigationBarTitleText": "AI陪练选择角色",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/sparring/talk",
|
||||
"style": {
|
||||
"navigationBarTitleText": "AI陪练通话",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/sparring/result",
|
||||
"style": {
|
||||
"navigationBarTitleText": "AI陪练报告",
|
||||
"disableSwipeBack": true,
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/analytics/study",
|
||||
"style": {
|
||||
"navigationBarTitleText": "学习分析",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/traRecord/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "陪练记录页",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"tabBar": {
|
||||
"list": [
|
||||
{
|
||||
"text": "首页",
|
||||
"pagePath": "pages/index/index"
|
||||
},
|
||||
{
|
||||
"text": "学习",
|
||||
"pagePath": "pages/course/index"
|
||||
},
|
||||
{
|
||||
"text": "场景",
|
||||
"pagePath": "pages/sparring/index"
|
||||
},
|
||||
{
|
||||
"text": "我的",
|
||||
"pagePath": "pages/me/index"
|
||||
}
|
||||
],
|
||||
"color": "#666666",
|
||||
"selectedColor": "#0066FF",
|
||||
"fontSize": "16px",
|
||||
"borderStyle": "white",
|
||||
"borderColor": "#ffffff",
|
||||
"height": "70px",
|
||||
"spacing": "10px",
|
||||
"midButton": {
|
||||
"iconPath": "/static/images/icon/fawn-1.png",
|
||||
"width": "86px",
|
||||
"height": "86px",
|
||||
"iconWidth": "90px"
|
||||
}
|
||||
},
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "AI智能培训",
|
||||
"navigationBarBackgroundColor": "#F8F8F8",
|
||||
"backgroundColor": "#F8F8F8",
|
||||
"navigationStyle": "custom",
|
||||
"app-plus": {
|
||||
"titleView": false,
|
||||
"bounce": "none",
|
||||
"softinputNavBar": "none"
|
||||
}
|
||||
},
|
||||
"uniIdRouter": {}
|
||||
}
|
||||
@@ -0,0 +1,628 @@
|
||||
<template>
|
||||
<view class="study-analytics max_page">
|
||||
<view class="cont-body">
|
||||
<view class="top-box">
|
||||
<view class="top-title">
|
||||
<view class="back-icon" @click="common.navigateBack()"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="analytics-box">
|
||||
<view class="tabs-box">
|
||||
<uv-tabs :list="tabList" @click="changeTab" :scrollable="true" active-style="font-weight:bold; color:#000;"
|
||||
:current="currentTab" class="font_pf my_tabs"
|
||||
:inactive-style="{ color: '#333333', fontSize: '28rpx', fontWeight: '500' }"
|
||||
:activeStyle="{ color: '#333333', fontSize: '28rpx', fontWeight: '600' }"></uv-tabs>
|
||||
</view>
|
||||
<!-- <view class="calendar-box" @click="openCalendar">统计周期</view>
|
||||
<uv-calendar ref="calendarRef" mode="range" @confirm="calendarConfirm" minDate="2025-01-01" :maxDate="maxDate" monthNum="100" :allowSameDay="true"></uv-calendar> -->
|
||||
<view class="date-input" v-show="activeTabVal === '04'">
|
||||
<uni-datetime-picker class="custom-datepicker" ref="dateinputRef" type="daterange" v-model="daterange"
|
||||
start="2025-01-01" :end="maxDate" rangeSeparator="至" @change="changeDate"></uni-datetime-picker>
|
||||
</view>
|
||||
<view class="analytics-items">
|
||||
<view class="analytics-item">
|
||||
<view class="analytics-item-title">
|
||||
课程学习时长
|
||||
</view>
|
||||
<view class="analytics-item-value">
|
||||
<text class="analytics-item-value-v">{{statisticsObj.crsStdyTmLen }}</text>
|
||||
<text class="analytics-item-value-unit">h</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="analytics-item">
|
||||
<view class="analytics-item-title">
|
||||
完成课程数
|
||||
</view>
|
||||
<view class="analytics-item-value">
|
||||
<text class="analytics-item-value-v">{{statisticsObj.completeCrsCnt }}</text>
|
||||
<text class="analytics-item-value-unit"></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="analytics-item">
|
||||
<view class="analytics-item-title">
|
||||
课程完成率
|
||||
</view>
|
||||
<view class="analytics-item-value">
|
||||
<text class="analytics-item-value-v">{{statisticsObj.crsCompleteRate }}</text>
|
||||
<text class="analytics-item-value-unit">%</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="analytics-item">
|
||||
<view class="analytics-item-title">
|
||||
完成任务数
|
||||
</view>
|
||||
<view class="analytics-item-value">
|
||||
<text class="analytics-item-value-v">{{statisticsObj.completeTaskCnt }}</text>
|
||||
<text class="analytics-item-value-unit"></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="analytics-item">
|
||||
<view class="analytics-item-title">
|
||||
完成考试数
|
||||
</view>
|
||||
<view class="analytics-item-value">
|
||||
<text class="analytics-item-value-v">{{statisticsObj.completedExamCnt }}</text>
|
||||
<text class="analytics-item-value-unit"></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="analytics-item">
|
||||
<view class="analytics-item-title">
|
||||
考试及格率
|
||||
</view>
|
||||
<view class="analytics-item-value">
|
||||
<text class="analytics-item-value-v">{{statisticsObj.passExamRate }}</text>
|
||||
<text class="analytics-item-value-unit">%</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="analytics-item">
|
||||
<view class="analytics-item-title">
|
||||
问答次数
|
||||
</view>
|
||||
<view class="analytics-item-value">
|
||||
<text class="analytics-item-value-v">{{statisticsObj.askCnt }}</text>
|
||||
<text class="analytics-item-value-unit">次</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="analytics-item">
|
||||
<view class="analytics-item-title">
|
||||
陪练次数
|
||||
</view>
|
||||
<view class="analytics-item-value">
|
||||
<text class="analytics-item-value-v">{{statisticsObj.partnerCnt }}</text>
|
||||
<text class="analytics-item-value-unit">次</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="analytics-item">
|
||||
<view class="analytics-item-title">
|
||||
陪练通过率
|
||||
</view>
|
||||
<view class="analytics-item-value">
|
||||
<text class="analytics-item-value-v">{{statisticsObj.partnerRate }}</text>
|
||||
<text class="analytics-item-value-unit">%</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="evaluation-box dens" v-if="loading">
|
||||
<CommonLoading color="#06f" class="loading-box"/>
|
||||
</view>
|
||||
<view class="evaluation-box dens" v-if="!loading">
|
||||
<view class="dens-item" v-for="(item, index) in dimesionList">
|
||||
<view :class="['item-title', index%2===0?'study1-tip': 'study2-tip']">{{item.title}}</view>
|
||||
<view :class="['item-cont ', index%2===0?'study1-bg': 'study2-bg']">{{item.content}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="evaluation-box study" v-if="loading">
|
||||
<CommonLoading color="#06f" class="loading-box"/>
|
||||
</view>
|
||||
<view class="evaluation-box study" v-if="!loading">
|
||||
<view class="study-item" v-for="(item, index) in studyList">
|
||||
<view :class="['item-title', index%2===0?'study1-tip': 'study2-tip']">{{item.title}}</view>
|
||||
<view :class="['item-cont ', index%2===0?'study1-bg': 'study2-bg']">{{item.content}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="evaluation-box study1" v-if="loading">
|
||||
<CommonLoading color="#06f" class="loading-box"/>
|
||||
</view>
|
||||
<view class="evaluation-box study1" v-if="!loading">
|
||||
<view class="study1-item">
|
||||
<view class="item-cont study3-bg">{{suggestObj.content}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="evaluation-box course" v-if="loading">
|
||||
<CommonLoading color="#06f" class="loading-box"/>
|
||||
</view>
|
||||
<view class="evaluation-box course" v-if="!loading">
|
||||
<CourseItem v-for="item in courseList" :itemInfo="item" @clickItem="toCourseDetail(item)" />
|
||||
<view class="empty-box" v-if="courseList.length === 0">暂无数据</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import common from '@/common/common'
|
||||
import {
|
||||
queryStudyStatistics,
|
||||
queryRecmdCrsInfo,
|
||||
queryDimesionInfo,
|
||||
queryHabitInfo,
|
||||
querySuggestInfo,
|
||||
} from '@/api/analytics.js'
|
||||
import {
|
||||
onMounted,
|
||||
ref,
|
||||
computed,
|
||||
} from 'vue'
|
||||
import CourseItem from '@/components/course-item/course-item.vue'
|
||||
const tabList = ref([{
|
||||
name: '本月',
|
||||
value: '01'
|
||||
},
|
||||
{
|
||||
name: '本年',
|
||||
value: '02'
|
||||
},
|
||||
{
|
||||
name: '累计',
|
||||
value: '03'
|
||||
},
|
||||
{
|
||||
name: '自定义',
|
||||
value: '04'
|
||||
}
|
||||
])
|
||||
const courseList = ref([])
|
||||
const currentTab = ref(0)
|
||||
const activeTabVal = ref('01')
|
||||
const daterange = ref(['', ''])
|
||||
const dateinputRef = ref()
|
||||
const maxDate = computed(() => {
|
||||
let _date = new Date()
|
||||
let _year = _date.getFullYear()
|
||||
let _month = _date.getMonth() + 1
|
||||
let _day = new Date(_year, _month, 0).getDate()
|
||||
return `${_year}-${_month < 10 ? '0' + _month: _month}-${_day < 10 ? '0' + _day: _day}`
|
||||
})
|
||||
const calendarRef = ref()
|
||||
const statisticsObj = ref({
|
||||
crsStdyTmLen: 0,
|
||||
completeTaskCnt: 0,
|
||||
completedExamCnt: 0,
|
||||
passExamRate: 0,
|
||||
partnerCnt: 0,
|
||||
askCnt: 0,
|
||||
completeCrsCnt: 0,
|
||||
crsCompleteRate: 0,
|
||||
partnerRate: 0
|
||||
})
|
||||
const dimesionList = ref([])
|
||||
const studyList = ref([])
|
||||
const suggestObj = ref({
|
||||
content: ''
|
||||
})
|
||||
|
||||
// const openCalendar = () => {
|
||||
// calendarRef.value.open()
|
||||
// }
|
||||
// const calendarConfirm = (data) => {
|
||||
// console.log('data:', data)
|
||||
// currentTab.value = -1
|
||||
// }
|
||||
const changeDate = (date) => {
|
||||
if (date.length > 0) {
|
||||
if (!!date[0]) {
|
||||
getInfoNow({
|
||||
dateType: '04',
|
||||
startDate: date[0],
|
||||
endDate: date[1],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
const loading = ref(false)
|
||||
const getInfoNow = async (params) => {
|
||||
loading.value = true
|
||||
try {
|
||||
let _res1 = await queryStudyStatistics(params)
|
||||
statisticsObj.value = {..._res1.body}
|
||||
let _res2 = await queryDimesionInfo(params)
|
||||
dimesionList.value = [..._res2.body]
|
||||
let _res3 = await queryHabitInfo(params)
|
||||
studyList.value = [..._res3.body]
|
||||
let _res4 = await querySuggestInfo(params)
|
||||
if(_res4.body && _res4.body.length > 0){
|
||||
suggestObj.value = {..._res4.body[0]}
|
||||
}
|
||||
} catch {
|
||||
} finally{
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
// 跳转 课程详情
|
||||
const toCourseDetail = item => {
|
||||
common.navigateTo('/pages/courseDetail/index?crsNum=' + item.crsNum + '&crsId=' + item.crsId)
|
||||
}
|
||||
const changeTab = (info) => {
|
||||
activeTabVal.value = info.value
|
||||
if (info.value === '04') {
|
||||
dateinputRef.value&&dateinputRef.value?.clear()
|
||||
daterange.value = ['', '']
|
||||
}else{
|
||||
getInfoNow({
|
||||
dateType: activeTabVal.value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const getCrsListNow = () => {
|
||||
queryRecmdCrsInfo().then(res => {
|
||||
console.log(res)
|
||||
courseList.value = res.body || []
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
getCrsListNow()
|
||||
getInfoNow({
|
||||
dateType: activeTabVal.value
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.my_tabs {
|
||||
|
||||
// 解决这个圆角的图片,开穿透
|
||||
:deep(.uv-tabs__wrapper__nav__line) {
|
||||
background: linear-gradient(90deg, #06f 0%, rgb(0, 159, 255) 50%, rgb(255, 255, 255) 100%) !important;
|
||||
}
|
||||
|
||||
:deep(.uv-tabs__wrapper__nav__item) {
|
||||
padding: 0 30rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
.study-analytics {
|
||||
position: relative;
|
||||
background-color: #ddeafa;
|
||||
|
||||
.cont-body {
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
padding: var(--status-bar-height) 20rpx 24rpx;
|
||||
// padding: 280rpx 20rpx 24rpx;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
background-image: url(@/static/images/analytics/bg1.png);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% auto;
|
||||
background-position: top center;
|
||||
background-attachment: local;
|
||||
|
||||
|
||||
.top-box {
|
||||
height: 280rpx;
|
||||
width: 100%;
|
||||
// position: fixed;
|
||||
// float: left;
|
||||
// margin-top: var(--status-bar-height);
|
||||
|
||||
.top-title {
|
||||
height: 90rpx;
|
||||
// background-color: red;
|
||||
text-align: center;
|
||||
font-size: 33rpx;
|
||||
padding: 10rpx 0rpx 0;
|
||||
line-height: 46rpx;
|
||||
box-sizing: border-box;
|
||||
font-weight: 500;
|
||||
color: #000;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
.back-icon {
|
||||
position: absolute;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
cursor: pointer;
|
||||
left: 30rpx;
|
||||
top: 33rpx;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.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 #000;
|
||||
border-left: 4rpx solid #000;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.analytics-box {
|
||||
min-height: 490rpx;
|
||||
border-radius: 15rpx;
|
||||
// overflow: hidden;
|
||||
background: url(@/static/images/analytics/bg2.png) no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
position: relative;
|
||||
margin-bottom: 24rpx;
|
||||
backdrop-filter: blur(10rpx);
|
||||
|
||||
.calendar-box {
|
||||
position: absolute;
|
||||
right: 25rpx;
|
||||
top: 25rpx;
|
||||
color: #06f;
|
||||
padding-left: 40rpx;
|
||||
background: url(@/static/images/analytics/date-icon.png) no-repeat;
|
||||
background-size: 30rpx 30rpx;
|
||||
background-position: left center;
|
||||
font-size: 29rpx;
|
||||
}
|
||||
|
||||
.tabs-box {
|
||||
width: 600rpx;
|
||||
}
|
||||
|
||||
.analytics-items {
|
||||
padding-top: 20rpx;
|
||||
padding-bottom: 20rpx;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
.analytics-item {
|
||||
padding-bottom: 12rpx;
|
||||
box-sizing: border-box;
|
||||
width: 33.3%;
|
||||
float: left;
|
||||
text-align: center;
|
||||
height: 128rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
|
||||
&-title {
|
||||
font-size: 25rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&-value {
|
||||
font-size: 54rpx;
|
||||
font-weight: 600;
|
||||
&-v {
|
||||
font-family: DINAlternate, DINAlternate;
|
||||
}
|
||||
|
||||
&-unit {
|
||||
font-size: 28rpx;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.evaluation-box {
|
||||
background-color: rgba(255, 255, 255, .8);
|
||||
background-repeat: no-repeat;
|
||||
background-size: auto 78rpx;
|
||||
background-position: 26rpx 26rpx;
|
||||
padding: 80rpx 26rpx 24rpx;
|
||||
border-radius: 15rpx;
|
||||
margin-bottom: 24rpx;
|
||||
position: relative;
|
||||
min-height: 200rpx;
|
||||
.loading-box{
|
||||
position: absolute;
|
||||
top: 120rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
&::before{
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 208rpx;
|
||||
height: 76rpx;
|
||||
position: absolute;
|
||||
left: -7rpx;
|
||||
top: -12rpx;
|
||||
background-size: cover;
|
||||
}
|
||||
&.dens {
|
||||
&::before{
|
||||
background-image: url(@/static/images/analytics/weidu-1.png);
|
||||
}
|
||||
}
|
||||
|
||||
&.study {
|
||||
&::before{
|
||||
background-image: url(@/static/images/analytics/study1-1.png)
|
||||
}
|
||||
}
|
||||
|
||||
&.study1 {
|
||||
&::before{
|
||||
background-image: url(@/static/images/analytics/study-1.png);
|
||||
}
|
||||
}
|
||||
|
||||
&.course {
|
||||
&::before{
|
||||
background-image: url(@/static/images/analytics/course-1.png);
|
||||
}
|
||||
}
|
||||
|
||||
// .dens-item {
|
||||
// padding: 23rpx 0;
|
||||
// .item-title{
|
||||
// line-height: 40rpx;
|
||||
// position: relative;
|
||||
// padding: 8rpx 46rpx 8rpx 26rpx;
|
||||
// line-height: 42rpx;
|
||||
// font-size: 29rpx;
|
||||
// margin-bottom: 16rpx;
|
||||
// &::before{
|
||||
// content: '';
|
||||
// display: block;
|
||||
// background-color: #000;
|
||||
// width: 12rpx;
|
||||
// height: 12rpx;
|
||||
// border-radius: 50%;
|
||||
// position: absolute;
|
||||
// left: 0rpx;
|
||||
// top: 22rpx;
|
||||
// }
|
||||
// }
|
||||
|
||||
// .item-cont {
|
||||
// font-size: 28rpx;
|
||||
// line-height: 50rpx;
|
||||
// }
|
||||
// }
|
||||
|
||||
.study-item,
|
||||
.study1-item,
|
||||
.dens-item {
|
||||
padding: 12rpx 0;
|
||||
|
||||
.item-title {
|
||||
line-height: 40rpx;
|
||||
position: relative;
|
||||
padding: 8rpx 46rpx 8rpx 40rpx;
|
||||
line-height: 42rpx;
|
||||
font-size: 29rpx;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
// background-color: #000;
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
// border-radius: 50%;
|
||||
position: absolute;
|
||||
left: 0rpx;
|
||||
top: 14rpx;
|
||||
background-image: url(@/static/images/analytics/den-tips.png);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
&.study2-tip{
|
||||
&::before {
|
||||
background-image: url(@/static/images/analytics/den-tips2.png);
|
||||
}
|
||||
}
|
||||
&.bg-tm {
|
||||
padding-left: 54rpx;
|
||||
background-image: url(@/static/images/analytics/time-icon.png);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 38rpx 40rpx;
|
||||
background-position: left center;
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.bg-know {
|
||||
padding-left: 54rpx;
|
||||
background-image: url(@/static/images/analytics/knowledge.png);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 38rpx 40rpx;
|
||||
background-position: left center;
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-cont {
|
||||
font-size: 28rpx;
|
||||
line-height: 50rpx;
|
||||
|
||||
&.study1-bg {
|
||||
background-color: #e4efff;
|
||||
border-radius: 15rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
// width: calc(100% - 54rpx);
|
||||
// margin-left: 54rpx;
|
||||
}
|
||||
|
||||
&.study2-bg {
|
||||
background-color: #f6f0df;
|
||||
border-radius: 15rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
// width: calc(100% - 54rpx);
|
||||
// margin-left: 54rpx;
|
||||
}
|
||||
|
||||
&.study3-bg {
|
||||
background-color: #e4efff;
|
||||
border-radius: 15rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
// width: calc(100% - 54rpx);
|
||||
// margin-left: 54rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-box {
|
||||
text-align: center;
|
||||
font-size: 25rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.date-input {
|
||||
.custom-datepicker {
|
||||
width: 500rpx;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
width: 10rpx;
|
||||
height: 10rpx;
|
||||
border-top: 3rpx solid #999;
|
||||
border-left: 3rpx solid #999;
|
||||
transform: rotate(135deg) translateY(50%);
|
||||
}
|
||||
|
||||
::v-deep .uni-date-x {
|
||||
background-color: rgba(255, 255, 255, 0);
|
||||
width: 500rpx;
|
||||
}
|
||||
|
||||
::v-deep .uni-date-x--border {
|
||||
border: 0;
|
||||
width: 500rpx;
|
||||
}
|
||||
|
||||
::v-deep .range-separator {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,598 @@
|
||||
<template>
|
||||
<view class="exam-ranking">
|
||||
<!-- <image src="@/static/images/charts/bg-top.png" mode="aspectFit" class="bg-box"></image> -->
|
||||
<view class="bg-box"></view>
|
||||
<view class="ranking-box">
|
||||
|
||||
<view class="nav-div">
|
||||
<view class="back_div" @click="backRouter()">
|
||||
<view class="back-icon"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="title-view">
|
||||
<!-- <view class="title-info">考试排行榜</view> -->
|
||||
<image class="title-image" src="@/static/images/charts/text.png" mode="aspectFit"></image>
|
||||
<!-- <view class="title-desc">吾日三省吾身,传不习乎?</view> -->
|
||||
<image class="cup-image" src="@/static/images/charts/cup.png" mode="aspectFit"></image>
|
||||
|
||||
</view>
|
||||
<view class="ranking-body">
|
||||
<view class="tabs-view">
|
||||
<uv-tabs :list="tabList" @click="changeTab" :scrollable="false" :current="chooseTyp" active-style="font-weight:bold; color:#000;"
|
||||
class="font_pf my_tabs" :inactive-style="{ color: '#333333', fontSize: '30rpx', fontWeight: '500' }"
|
||||
:activeStyle="{ color: '#333333', fontSize: '30rpx', fontWeight: '600' }"></uv-tabs>
|
||||
<uv-tabs :list="tabSecList" @click="changeTabSec" :scrollable="false" :current="chooseTypSec"
|
||||
active-style="font-weight:bold; color:#000;" class="font_pf my_tabs2"
|
||||
:inactive-style="{ color: '#333333', fontSize: '24rpx', fontWeight: '500' }"
|
||||
:activeStyle="{ color: '#0066FF', fontSize: '24rpx', fontWeight: '500' }" :lineColor="`#06f`"></uv-tabs>
|
||||
<!-- :lineColor="`url(${LineBg}) 2% 2%`" -->
|
||||
<uv-scroll-list class="tags-view" :indicator="false">
|
||||
<uv-tags :text="item.label" v-for="item in tagsList" :key="item.value" size="medium" :bgColor="'#f6f8fa'"
|
||||
:borderColor="activeTag === item.value ? '#06f' : '#f6f8fa'"
|
||||
:color="activeTag === item.value ? '#06f' : '#333'" @click="chooseTag(item)"
|
||||
:class="activeTag === item.value ? 'tags-item active-tags-item' : 'tags-item'"></uv-tags>
|
||||
</uv-scroll-list>
|
||||
</view>
|
||||
<view class="list-header list-item">
|
||||
<view class="rank-column">排名</view>
|
||||
<view class="user-column">用户名/所属机构</view>
|
||||
<view class="score-column">{{activeNameSec}}</view>
|
||||
</view>
|
||||
<scroll-view :scroll-y="true" class="scroll-items-view">
|
||||
<view v-for="(item,index) in userList" :class="['list-item', 'item-'+(index+1)]">
|
||||
<view class="rank-column">
|
||||
{{item.ranking}}
|
||||
</view>
|
||||
<view class="user-column">
|
||||
<view class="avatar-box">
|
||||
<image-preview v-if="item.ossAddr" class="avatar-image" :src="item.ossAddr" mode="aspectFit"></image-preview>
|
||||
<image v-else class="avatar-image" :src="defaultAvatarSrc"></image>
|
||||
</view>
|
||||
<view class="user-name">{{item.userName}}</view>
|
||||
<view class="user-org">{{item.orgName}}</view>
|
||||
</view>
|
||||
<view class="score-column">{{item.grade}}</view>
|
||||
</view>
|
||||
<list-no-data v-if="userList.length == 0">暂无数据</list-no-data>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view class="ranking-self">
|
||||
<view class="list-header list-item">
|
||||
<view class="rank-column">我的排名
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-item item-me">
|
||||
<view class="rank-column">
|
||||
{{userRankObj.ranking}}
|
||||
</view>
|
||||
<view class="user-column">
|
||||
<view class="avatar-box">
|
||||
<image-preview v-if="userRankObj.ossAddr" class="avatar-image" :src="userRankObj.ossAddr" mode="aspectFit"></image-preview>
|
||||
<image class="avatar-image" v-else :src="defaultAvatarSrc"></image>
|
||||
</view>
|
||||
<view class="user-name">{{userRankObj.userName}}</view>
|
||||
<view class="user-org">{{userRankObj.orgName}}</view>
|
||||
</view>
|
||||
<view class="score-column">{{userRankObj.grade}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defaultAvatar } from "@/enum.js"
|
||||
// 我的排行榜
|
||||
import { getUserInfo } from '@/common/common';
|
||||
import { leaderBoardApi } from '@/api/charts.js'
|
||||
import {
|
||||
reactive,
|
||||
ref,
|
||||
computed,
|
||||
nextTick,
|
||||
onMounted,
|
||||
watch
|
||||
} from 'vue';
|
||||
import common from '@/common/common';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
crsId: '',
|
||||
papersId : '',
|
||||
defaultAvatarSrc: defaultAvatar,
|
||||
chooseTyp: 0,
|
||||
chooseTypSec: 0,
|
||||
tagsList:[{
|
||||
label: '本年',
|
||||
value: 'YEAR',
|
||||
},{
|
||||
label: '本月',
|
||||
value: 'MONTH',
|
||||
},{
|
||||
label: '本周',
|
||||
value: 'WEEK',
|
||||
}],
|
||||
activeTag: 'YEAR',
|
||||
rankList: []
|
||||
}
|
||||
},
|
||||
// onLoad(params){
|
||||
// this.getList()
|
||||
// },
|
||||
onShow(){
|
||||
this.getList()
|
||||
},
|
||||
computed: {
|
||||
tabList() {
|
||||
return [{
|
||||
name: '课程排行榜',
|
||||
value: 0,
|
||||
key: '01'
|
||||
},{
|
||||
name: 'AI陪练排行榜',
|
||||
value: 1,
|
||||
key: '02'
|
||||
},{
|
||||
name: '考试排行榜',
|
||||
value: 2,
|
||||
key: '03'
|
||||
}]
|
||||
},
|
||||
tabSecList() {
|
||||
let _arr = []
|
||||
if(this.chooseTyp === 0){
|
||||
_arr = [
|
||||
{
|
||||
name: '学习时长',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
name: '练习完成次数',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
name: '考试通过次数',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
name: '完成课程数',
|
||||
value: 3
|
||||
}
|
||||
]
|
||||
} else if(this.chooseTyp === 1){
|
||||
_arr = [
|
||||
{
|
||||
name: '练习通过次数',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
name: '考试通过次数',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
name: '完成AI陪练数',
|
||||
value: 2
|
||||
}
|
||||
]
|
||||
} else if(this.chooseTyp === 2){
|
||||
_arr = [
|
||||
{
|
||||
name: '考试通过次数',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
name: '完成考试数',
|
||||
value: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
return _arr
|
||||
},
|
||||
activeNameSec(){
|
||||
if(this.tabSecList.length >= this.chooseTypSec){
|
||||
return this.tabSecList[this.chooseTypSec].name|| '--'
|
||||
}else{
|
||||
return '--'
|
||||
}
|
||||
},
|
||||
userRankObj(){
|
||||
if(this.rankList.length >= this.chooseTypSec){
|
||||
return this.rankList[this.chooseTypSec] || {}
|
||||
}else{
|
||||
return {}
|
||||
}
|
||||
},
|
||||
userList(){
|
||||
return this.userRankObj.rankingListVos || []
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getList(){
|
||||
leaderBoardApi({
|
||||
leaderBoardType: this.tabList[this.chooseTyp].key,
|
||||
timeType: this.activeTag
|
||||
}).then(res=>{
|
||||
console.log(res)
|
||||
this.rankList = res.body||[]
|
||||
})
|
||||
},
|
||||
changeTab(item){
|
||||
if(this.chooseTyp === item.value) return
|
||||
this.chooseTyp = item.value
|
||||
this.chooseTypSec = 0
|
||||
this.getList()
|
||||
},
|
||||
changeTabSec(item){
|
||||
this.chooseTypSec = item.value
|
||||
},
|
||||
chooseTag (item){
|
||||
this.activeTag = item.value
|
||||
this.getList()
|
||||
},
|
||||
backRouter(){
|
||||
common.navigateBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
$top-height: var(--status-bar-height);
|
||||
|
||||
.exam-ranking {
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding-top: $top-height;
|
||||
|
||||
// background:url(@/static/images/examRanking/bg.png) no-repeat top center;
|
||||
.bg-box {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 400rpx;
|
||||
background: linear-gradient(0deg , #F8F8FA 0%, #6DA7FF 100%);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ranking-box {
|
||||
height: calc(100% - $top-height);
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: $top-height;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.nav-div {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
height: 70rpx;
|
||||
|
||||
.back_div {
|
||||
margin-right: 10rpx;
|
||||
position: absolute;
|
||||
left: 25rpx;
|
||||
top: 0;
|
||||
padding: 15rpx;
|
||||
|
||||
.back-icon {
|
||||
position: relative;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.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 #000;
|
||||
border-left: 4rpx solid #000;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title-view {
|
||||
height: 180rpx;
|
||||
position: relative;
|
||||
padding-top: 20rpx;
|
||||
|
||||
.cup-image {
|
||||
position: absolute;
|
||||
right: 46rpx;
|
||||
bottom: -33rpx;
|
||||
width: 208rpx;
|
||||
height: 208rpx;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.title-desc {
|
||||
color: #666;
|
||||
line-height: 34rpx;
|
||||
font-size: 25rpx;
|
||||
}
|
||||
|
||||
.title-image {
|
||||
width: 230rpx;
|
||||
height: 111rpx;
|
||||
position: absolute;
|
||||
bottom: 20rpx;
|
||||
left: 46rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.ranking-body {
|
||||
height: calc(100% - 170rpx - 180rpx - 70rpx);
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
border-radius: 31rpx 31rpx 0 0;
|
||||
padding-top: 20rpx;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
.scroll-items-view{
|
||||
height: calc(100% - 308rpx);
|
||||
}
|
||||
}
|
||||
|
||||
.list-item {
|
||||
height: 108rpx;
|
||||
border-bottom: 2rpx solid #efefef;
|
||||
overflow: hidden;
|
||||
|
||||
&.list-header {
|
||||
height: 54rpx;
|
||||
line-height: 54rpx;
|
||||
color: #999;
|
||||
font-size: 25rpx;
|
||||
border-bottom: 0;
|
||||
.rank-column {
|
||||
height: 54rpx;
|
||||
line-height: 54rpx;
|
||||
color: #999;
|
||||
font-weight: normal;
|
||||
font-size: 25rpx;
|
||||
font-style: initial;
|
||||
text-indent: 0;
|
||||
}
|
||||
.user-column {
|
||||
padding: 0;
|
||||
}
|
||||
.score-column {
|
||||
line-height: 54rpx;
|
||||
color: #999;
|
||||
font-weight: normal;
|
||||
font-size: 25rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&.item-1 {
|
||||
border-bottom: 0;
|
||||
margin-bottom: 8rpx;
|
||||
background: linear-gradient(90deg, #fbf9f5 0%, #fffffd 100%);
|
||||
.rank-column{
|
||||
color: #ffffff00;
|
||||
background: url(@/static/images/examRanking/no-1.png) no-repeat ;
|
||||
background-position: 40rpx center;
|
||||
background-size: 44rpx 66rpx;
|
||||
text-indent: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&.item-2 {
|
||||
border-bottom: 0;
|
||||
margin-bottom: 8rpx;
|
||||
background: linear-gradient(90deg, #f4f8f9 0%, #f7fbfe 100%);
|
||||
.rank-column{
|
||||
color: #ffffff00;
|
||||
background: url(@/static/images/examRanking/no-2.png) no-repeat ;
|
||||
background-position: 40rpx center;
|
||||
background-size: 44rpx 66rpx;
|
||||
text-indent: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&.item-3 {
|
||||
border-bottom: 0;
|
||||
margin-bottom: 8rpx;
|
||||
background: linear-gradient(90deg, #faf6f5 0%, #fcfbfc 100%);
|
||||
.rank-column{
|
||||
color: #ffffff00;
|
||||
background: url(@/static/images/examRanking/no-3.png) no-repeat ;
|
||||
background-position: 40rpx center;
|
||||
background-size: 44rpx 66rpx;
|
||||
text-indent: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&.item-me{
|
||||
border-bottom: 0;
|
||||
.rank-column {
|
||||
color: #06f;
|
||||
}
|
||||
}
|
||||
|
||||
.rank-column {
|
||||
width: 170rpx;
|
||||
padding-left: 40rpx;
|
||||
float: left;
|
||||
font-weight: 600;
|
||||
line-height: 92rpx;
|
||||
height: 108rpx;
|
||||
color: #333;
|
||||
font-size: 38rpx;
|
||||
font-style: italic;
|
||||
text-indent: 8rpx;
|
||||
}
|
||||
|
||||
.user-column {
|
||||
width: 375rpx;
|
||||
float: left;
|
||||
padding: 15rpx 0;
|
||||
|
||||
.avatar-box{
|
||||
width: 77rpx;
|
||||
height: 77rpx;
|
||||
overflow: hidden;
|
||||
float: left;
|
||||
background-color: #eee;
|
||||
border-radius: 50%;
|
||||
.avatar-image{
|
||||
width: 77rpx;
|
||||
height: 77rpx;
|
||||
}
|
||||
}
|
||||
.user-name{
|
||||
float: left;
|
||||
width: calc(100% - 77rpx);
|
||||
padding-left: 18rpx;
|
||||
padding-right: 18rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
height: 40rpx;
|
||||
line-height: 40rpx;
|
||||
font-size: 29rpx;
|
||||
color: #000;
|
||||
font-weight: 500;
|
||||
}
|
||||
.user-org{
|
||||
float: left;
|
||||
width: calc(100% - 77rpx);
|
||||
padding-left: 18rpx;
|
||||
padding-right: 18rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
height: 34rpx;
|
||||
line-height: 34rpx;
|
||||
font-size: 25rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.score-column {
|
||||
width: 150rpx;
|
||||
text-align: right;
|
||||
float: left;
|
||||
font-weight: 600;
|
||||
line-height: 108rpx;
|
||||
color: #333;
|
||||
font-size: 31rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
.ranking-self {
|
||||
height: 170rpx;
|
||||
overflow: hidden;
|
||||
background-color: #f5f9ff;
|
||||
padding-top: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.tags-view {
|
||||
height: 84rpx;
|
||||
display: block;
|
||||
padding: 15rpx 20rpx 15rpx 20rpx;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
// border-bottom: 2rpx solid #eee;
|
||||
|
||||
.tags-item {
|
||||
margin-right: 30rpx;
|
||||
display: inline-block;
|
||||
|
||||
&.active-tags-item {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 4rpx;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
position: absolute;
|
||||
right: -20rpx;
|
||||
bottom: -20rpx;
|
||||
transform: rotateZ(-45deg);
|
||||
background-color: #06f;
|
||||
overflow: hidden;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 8rpx;
|
||||
height: 4rpx;
|
||||
position: absolute;
|
||||
right: 3rpx;
|
||||
bottom: 8rpx;
|
||||
transform: rotateZ(-45deg);
|
||||
border-left: 1px solid #fff;
|
||||
border-bottom: 1px solid #fff;
|
||||
z-index: 11;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .uv-tags {
|
||||
padding: 0 20rpx;
|
||||
|
||||
.uv-tags__text {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 200rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.my_tabs {
|
||||
|
||||
// 解决这个圆角的图片,开穿透
|
||||
:deep(.uv-tabs__wrapper__nav__line) {
|
||||
// height: 30rpx !important;
|
||||
// left: 60rpx;
|
||||
// background-size: 24rpx !important;
|
||||
// background-repeat: no-repeat !important;
|
||||
background: linear-gradient(90deg, #06f 0%, rgb(0, 159, 255) 50%, rgb(255, 255, 255) 100%) !important;
|
||||
}
|
||||
|
||||
:deep(.uv-tabs__wrapper__nav__item) {
|
||||
padding: 0 30rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
.my_tabs2 {
|
||||
|
||||
// 解决这个圆角的图片,开穿透
|
||||
:deep(.uv-tabs__wrapper__nav__line) {
|
||||
background-size: 24rpx !important;
|
||||
background: linear-gradient(90deg, #06f 0%, rgb(0, 159, 255) 50%, rgb(255, 255, 255) 100%) !important;
|
||||
}
|
||||
|
||||
:deep(.uv-tabs__wrapper__nav__item) {
|
||||
padding: 0 20rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,845 @@
|
||||
<template>
|
||||
<view class="max_page detail-main">
|
||||
<nav-bar :is_seat="false"></nav-bar>
|
||||
<view class="fixed_search_div">
|
||||
<view class="back_div" @click="common.navigateBack()">
|
||||
<view class="back-icon"></view>
|
||||
</view>
|
||||
<view class="title font_pf">排行榜</view>
|
||||
</view>
|
||||
<view class="detail-body">
|
||||
<view class="body-tabs">
|
||||
<view class="tabs-sty">
|
||||
<view :class="tabAct == index ?'active-tabs-item-sty':'tabs-item-sty'"
|
||||
v-for="(item,index) in tabList" @click="tabChange(index,item)">{{item.name}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="date-body">
|
||||
<view class="date-sty">
|
||||
<view :class="itemIndex == index ?'active-date-item-sty':'date-item-sty'"
|
||||
v-for="(item,index) in dateTabList" @click="itemFun(index,item)">{{item.name}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rank-style">
|
||||
<view class="rank-top3">
|
||||
<view class="rank-content rank-content-backgound-color1" v-if="chartsList.length > 1">
|
||||
<image src="/static/images/charts/02.svg" class="crown1"></image>
|
||||
<image v-if="chartsList[1].imgAddr" :src="chartsList[1].imgAddr" class="top-avator"></image>
|
||||
<image v-else src="/static/images/charts/avator.png" class="top-avator"></image>
|
||||
<view class="content1">top·2</view>
|
||||
<view class="content2">{{chartsList[1].userName}}</view>
|
||||
<view class="content3">{{chartsList[1].score}}</view>
|
||||
<view class="content4">{{chartsList[1].dept}}</view>
|
||||
</view>
|
||||
<view class="rank-content rank-content-backgound-color2" v-if="chartsList.length > 0">
|
||||
<image src="/static/images/charts/01.svg" class="crown2"></image>
|
||||
<image v-if="chartsList[0].imgAddr" :src="chartsList[0].imgAddr" class="top-avator"></image>
|
||||
<image v-else src="/static/images/charts/avator.png" class="top-avator"></image>
|
||||
<view class="content11">top·1</view>
|
||||
<view class="content2">{{chartsList[0].userName}}</view>
|
||||
<view class="content33">{{chartsList[0].score}}</view>
|
||||
<view class="content44">{{chartsList[0].dept}}</view>
|
||||
</view>
|
||||
<view class="rank-content rank-content-backgound-color3" v-if="chartsList.length > 2">
|
||||
<image src="/static/images/charts/03.svg" class="crown3"></image>
|
||||
<image v-if="chartsList[2].imgAddr" :src="chartsList[2].imgAddr" class="top-avator"></image>
|
||||
<image v-else src="/static/images/charts/avator.png" class="top-avator"></image>
|
||||
<view class="content111">top·3</view>
|
||||
<view class="content2">{{chartsList[2].userName}}</view>
|
||||
<view class="content333">{{chartsList[2].score}}</view>
|
||||
<view class="content444">{{chartsList[2].dept}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- table形式排名 -->
|
||||
<!-- <view class="rank-other">
|
||||
<uni-table ref="table">
|
||||
<uni-tr>
|
||||
<uni-th align="center" width="100">排名</uni-th>
|
||||
<uni-th align="left" width="180">用户名/所属机构</uni-th>
|
||||
<uni-th align="center" >{{tableLastName}}</uni-th>
|
||||
</uni-tr>
|
||||
<uni-tr v-for="(item, index) in newChartsList" :key="index">
|
||||
<uni-td :class="item.name == '江月心'?'active-rank-sty':'rank-sty'" align="center">{{ item.rank }}</uni-td>
|
||||
<uni-td align="center">
|
||||
<image src="/static/images/charts/avator.png" class="avator"></image>
|
||||
<view style="float: left;margin-left: 18rpx;">
|
||||
<view :class="item.name == '江月心'?'active-name':'name'">{{ item.name }}</view>
|
||||
<view class="belong">{{ item.belong }}</view>
|
||||
</view>
|
||||
</uni-td>
|
||||
<uni-td class="score-sty" align="center">{{ item.score }}</uni-td>
|
||||
</uni-tr>
|
||||
</uni-table>
|
||||
<view style="width: 100%;height: 200rpx;"></view>
|
||||
</view>
|
||||
<view class="rank-mine">
|
||||
<view class="rank-mine-content">
|
||||
<view style="font-size: 26rpx;font-weight: 400;color: #999999;margin-left: 10rpx;">我的排名</view>
|
||||
<view style="width: 100%;">
|
||||
<view style="width: 100rpx;font-size: 40rpx;color: #0066FF;font-style: italic;;font-weight: 600;float: left;margin-top:30rpx;margin-left: 18rpx;">44</view>
|
||||
<view style="width: 200rpx;float: left;margin-left: 46rpx;margin-top: 12rpx;width: 370rpx;">
|
||||
<image src="/static/images/charts/avator.png" class="my-avator"></image>
|
||||
<view style="font-size: 30rpx;font-weight: 600;color:#000000;">张伟</view>
|
||||
<view style="font-size: 26rpx;font-weight: 400;color: #999999;margin-top:8rpx;">吉林银行大连分行</view>
|
||||
</view>
|
||||
<view style="width: 170rpx;font-size: 32rpx;text-align: center;color: #333333;font-weight: 600;float: left;margin-top: 32rpx;">91</view>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
<!-- list形式排名 -->
|
||||
<uni-list class="rank-other">
|
||||
<uni-list-item>
|
||||
<!-- 自定义 header -->
|
||||
<template v-slot:header>
|
||||
<text class="rank-other-rank-sty rank-uni-sty">排名</text>
|
||||
</template>
|
||||
<!-- 自定义 body -->
|
||||
<template v-slot:body>
|
||||
<text class="rank-other-user-sty rank-uni-sty">用户名/所属机构</text>
|
||||
</template>
|
||||
<!-- 自定义 footer-->
|
||||
<template v-slot:footer>
|
||||
<text class="rank-other-last-sty rank-uni-sty">{{tableLastName}}</text>
|
||||
</template>
|
||||
</uni-list-item>
|
||||
<uni-list-item v-if="chartsList.length < 4">
|
||||
<template v-slot:header>
|
||||
<text style="width:100%;text-align: center;color: #999999;">暂无数据</text>
|
||||
</template>
|
||||
</uni-list-item>
|
||||
<uni-list-item v-for="(item, index) in newChartsList" :key="index" v-else>
|
||||
<!-- 自定义 header -->
|
||||
<template v-slot:header>
|
||||
<text style="width: 20%;text-align: center;margin-top:15rpx"
|
||||
:class="item.userId == userId?'active-rank-sty':'rank-sty'">{{item.position}}</text>
|
||||
</template>
|
||||
<!-- 自定义 body -->
|
||||
<template v-slot:body>
|
||||
<view style="width: 55%;text-align: center;margin-left: 10rpx;">
|
||||
<image v-if="item.imgAddr" :src="item.imgAddr" class="avator"></image>
|
||||
<image v-else src="/static/images/charts/avator.png" class="avator"></image>
|
||||
<view style="float: left;margin-left: 18rpx;">
|
||||
<view :class="item.userId == userId?'active-name':'name'">{{ item.userName }}</view>
|
||||
<view style="width: 250rpx;text-align: left;margin-top: 10rpx;" class="belong">{{ item.dept }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<!-- 自定义 footer-->
|
||||
<template v-slot:footer>
|
||||
<text style="text-align: center;" class="score-sty score-sty-footer">{{ item.score }}</text>
|
||||
</template>
|
||||
</uni-list-item>
|
||||
<view style="width: 100%;height: 200rpx;"></view>
|
||||
<uni-list-item class="rank-mine" v-if="!myselfObj.userName">
|
||||
<!-- 自定义 header -->
|
||||
<template v-slot:header>
|
||||
<text class="rank-sty my-rank-title">我的排名</text>
|
||||
<view style="width: 100%;text-align: center;color: #999999;font-size: 28rpx;">暂无排名</view>
|
||||
</template>
|
||||
</uni-list-item>
|
||||
<uni-list-item class="rank-mine" v-else>
|
||||
<!-- 自定义 header -->
|
||||
<template v-slot:header>
|
||||
<text class="rank-sty my-rank-title">我的排名</text>
|
||||
<view class="my-rank-content">{{myselfObj.position}}</view>
|
||||
</template>
|
||||
<!-- 自定义 body -->
|
||||
<template v-slot:body>
|
||||
<view style="width: 65%;text-align: center;margin-top: 30rpx;">
|
||||
<image v-if="myselfObj.imgAddr" :src="myselfObj.imgAddr" class="avator" style="margin-top: 4rpx;"></image>
|
||||
<image v-else src="/static/images/charts/avator.png" class="avator" style="margin-top: 4rpx;"></image>
|
||||
<view style="float: left;margin-left: 18rpx;">
|
||||
<view class="my-rank-name">{{myselfObj.userName}}</view>
|
||||
<view style="width: 260rpx;text-align: left;margin-top: 5rpx;" class="my-rank-belong">{{myselfObj.dept}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<!-- 自定义 footer-->
|
||||
<template v-slot:footer>
|
||||
<text class="my-rank-score">{{myselfObj.score}}</text>
|
||||
</template>
|
||||
</uni-list-item>
|
||||
</uni-list>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
getLeaderBoardStdyTmApi,
|
||||
getLeaderBoardPractCntApi,
|
||||
getLeaderBoardExamCntApi,
|
||||
getLeaderBoardCrsCntApi
|
||||
} from "@/api/leaderBoard.js"
|
||||
// 我的排行榜
|
||||
import { getUserInfo } from '@/common/common';
|
||||
import {
|
||||
reactive,
|
||||
ref,
|
||||
computed,
|
||||
nextTick,
|
||||
onMounted,
|
||||
watch
|
||||
} from 'vue';
|
||||
import common from '@/common/common';
|
||||
const userId = ref("");
|
||||
const itemIndex = ref(0);
|
||||
const learnTimeList = ref({})
|
||||
const practCntList = ref({})
|
||||
const examCntList = ref({})
|
||||
const crsCntList = ref({})
|
||||
const myselfObj = ref({})
|
||||
const tableLastName = ref("学习时长(h)");
|
||||
const taskRef = ref(null);
|
||||
const tabAct = ref(0);
|
||||
const tabDateAct = ref(0);
|
||||
const allChartsList = ref({});
|
||||
const newChartsList = ref([]);
|
||||
const tabList = ref([{
|
||||
name: '学习时长'
|
||||
}, {
|
||||
name: '练习次数'
|
||||
}, {
|
||||
name: '考试次数'
|
||||
}, {
|
||||
name: '通关课程数'
|
||||
}]);
|
||||
const dateTabList = ref([{
|
||||
name: '本年',
|
||||
value: 'annual'
|
||||
}, {
|
||||
name: '本月',
|
||||
value: 'month'
|
||||
}, {
|
||||
name: '本周',
|
||||
value: 'week'
|
||||
}]);
|
||||
const chartsList = ref([]);
|
||||
const tabChange = (index, item) => {
|
||||
itemIndex.value = 0;
|
||||
tabAct.value = index;
|
||||
tableLastName.value = item.name;
|
||||
if (tableLastName.value.includes('学习时长') ) {
|
||||
tableLastName.value = '学习时长(h)'
|
||||
}
|
||||
if (tableLastName.value.includes('学习时长')) {
|
||||
allChartsList.value = learnTimeList.value;
|
||||
chartsList.value = learnTimeList.value.annual?learnTimeList.value.annual.list:[];
|
||||
myselfObj.value = allChartsList.value.annual?allChartsList.value.annual.self:{};
|
||||
myselfObj.value.score = allChartsList.value.annual?allChartsList.value.annual.self.annlStdyTmLen:"";
|
||||
chartsList.value.forEach(item => item.score = item.annlStdyTmLen)
|
||||
} else if (tableLastName.value == '练习次数') {
|
||||
allChartsList.value = practCntList.value;
|
||||
chartsList.value = practCntList.value.annual?practCntList.value.annual.list:[];
|
||||
myselfObj.value = allChartsList.value.annual?allChartsList.value.annual.self:{};
|
||||
myselfObj.value.score = allChartsList.value.annual?allChartsList.value.annual.self.annlPractCnt:"";
|
||||
chartsList.value.forEach(item => item.score = item.annlPractCnt);
|
||||
} else if (tableLastName.value == '考试次数') {
|
||||
allChartsList.value = examCntList.value;
|
||||
chartsList.value = examCntList.value.annual?examCntList.value.annual.list:[];
|
||||
myselfObj.value = allChartsList.value.annual?allChartsList.value.annual.self:{};
|
||||
myselfObj.value.score = allChartsList.value.annual?allChartsList.value.annual.self.annlExamCnt:"";
|
||||
chartsList.value.forEach(item => item.score = item.annlExamCnt);
|
||||
} else if (tableLastName.value == '通关课程数') {
|
||||
allChartsList.value = crsCntList.value;
|
||||
chartsList.value = crsCntList.value.annual?crsCntList.value.annual.list:[];
|
||||
myselfObj.value = allChartsList.value.annual?allChartsList.value.annual.self:{};
|
||||
myselfObj.value.score = allChartsList.value.annual?allChartsList.value.annual.self.annlCrsCnt:"";
|
||||
chartsList.value.forEach(item => item.score = item.annlCrsCnt);
|
||||
}
|
||||
if (chartsList.value.length > 3) {
|
||||
newChartsList.value = chartsList.value.slice(3).concat();
|
||||
} else {
|
||||
newChartsList.value = chartsList.value;
|
||||
}
|
||||
console.log(tableLastName.value, 'tablelastname')
|
||||
};
|
||||
const itemFun = (index, item) => {
|
||||
itemIndex.value = index;
|
||||
if (item.value == 'annual') {
|
||||
chartsList.value = allChartsList.value.annual ? allChartsList.value.annual.list : [];
|
||||
myselfObj.value = allChartsList.value.annual ? allChartsList.value.annual.self : {};
|
||||
if (tableLastName.value.includes('学习时长')) {
|
||||
chartsList.value.forEach(item => item.score = item.annlStdyTmLen);
|
||||
myselfObj.value.score = myselfObj.value.annlStdyTmLen;
|
||||
} else if (tableLastName.value == '练习次数') {
|
||||
chartsList.value.forEach(item => item.score = item.annlPractCnt);
|
||||
myselfObj.value.score = myselfObj.value.annlPractCnt;
|
||||
} else if (tableLastName.value == '考试次数') {
|
||||
chartsList.value.forEach(item => item.score = item.annlExamCnt);
|
||||
myselfObj.value.score = myselfObj.value.annlExamCnt;
|
||||
} else if (tableLastName.value == '通关课程数') {
|
||||
chartsList.value.forEach(item => item.score = item.annlCrsCnt);
|
||||
myselfObj.value.score = myselfObj.value.annlCrsCnt;
|
||||
}
|
||||
} else if (item.value == 'month') {
|
||||
console.log('month')
|
||||
chartsList.value = allChartsList.value.month ? allChartsList.value.month.list : [];
|
||||
myselfObj.value = allChartsList.value.month ? allChartsList.value.month.self : {};
|
||||
console.log(chartsList.value,'chartsList.value')
|
||||
console.log(myselfObj.value,'myselfObj.value')
|
||||
if (tableLastName.value.includes('学习时长')) {
|
||||
chartsList.value.forEach(item => item.score = item.thisMonthStdyTmLen);
|
||||
myselfObj.value.score = myselfObj.value.thisMonthStdyTmLen;
|
||||
console.log(chartsList.value,'chartsList.valueMonth')
|
||||
console.log(myselfObj.value,'myselfObj.valueMonth')
|
||||
} else if (tableLastName.value == '练习次数') {
|
||||
chartsList.value.forEach(item => item.score = item.thisMonthPractCnt);
|
||||
myselfObj.value.score = myselfObj.value.thisMonthPractCnt;
|
||||
} else if (tableLastName.value == '考试次数') {
|
||||
chartsList.value.forEach(item => item.score = item.thisMonthExamCnt);
|
||||
myselfObj.value.score = myselfObj.value.thisMonthExamCnt;
|
||||
} else if (tableLastName.value == '通关课程数') {
|
||||
chartsList.value.forEach(item => item.score = item.thisMonthCrsCnt);
|
||||
myselfObj.value.score = myselfObj.value.thisMonthCrsCnt;
|
||||
}
|
||||
} else if (item.value == 'week') {
|
||||
chartsList.value = allChartsList.value.week ? allChartsList.value.week.list : [];
|
||||
myselfObj.value = allChartsList.value.week ? allChartsList.value.week.self : {};
|
||||
if (tableLastName.value.includes('学习时长')) {
|
||||
chartsList.value.forEach(item => item.score = item.thisWeekStdyTmLen);
|
||||
myselfObj.value.score = myselfObj.value.thisWeekStdyTmLen;
|
||||
console.log(chartsList.value,'chartsList.valueWeek')
|
||||
console.log(myselfObj.value,'myselfObj.valueWeek')
|
||||
} else if (tableLastName.value == '练习次数') {
|
||||
chartsList.value.forEach(item => item.score = item.thisWeekPractCnt);
|
||||
myselfObj.value.score = myselfObj.value.thisWeekPractCnt;
|
||||
} else if (tableLastName.value == '考试次数') {
|
||||
chartsList.value.forEach(item => item.score = item.thisWeekExamCnt);
|
||||
myselfObj.value.score = myselfObj.value.thisWeekExamCnt;
|
||||
} else if (tableLastName.value == '通关课程数') {
|
||||
chartsList.value.forEach(item => item.score = item.thisWeekCrsCnt);
|
||||
myselfObj.value.score = myselfObj.value.thisWeekCrsCnt;
|
||||
}
|
||||
}
|
||||
if (chartsList.value.length > 3) {
|
||||
newChartsList.value = chartsList.value.slice(3).concat();
|
||||
} else {
|
||||
newChartsList.value = chartsList.value;
|
||||
}
|
||||
}
|
||||
const userInfo = computed(() => getUserInfo())
|
||||
userId.value = userInfo.value.userId;
|
||||
console.log(userId.value,'userId')
|
||||
onMounted(() => {
|
||||
getLeaderBoardStdyTmApi().then(res => {
|
||||
console.log(res, 'getLeaderBoardStdyTmApi')
|
||||
learnTimeList.value = res.body || {};
|
||||
allChartsList.value = learnTimeList.value;
|
||||
chartsList.value = allChartsList.value.annual?allChartsList.value.annual.list:[];
|
||||
myselfObj.value = allChartsList.value.annual?allChartsList.value.annual.self:{};
|
||||
console.log(chartsList.value,'chartsList.value')
|
||||
console.log(myselfObj.value,myselfObj.value,'myselfObj.value')
|
||||
chartsList.value.forEach(item => item.score = item.annlStdyTmLen);
|
||||
myselfObj.value.score = myselfObj.value.annlStdyTmLen;
|
||||
if (chartsList.value.length > 3) {
|
||||
newChartsList.value = chartsList.value.slice(3).concat();
|
||||
} else {
|
||||
newChartsList.value = chartsList.value;
|
||||
}
|
||||
console.log(newChartsList.value, 'newChartsList')
|
||||
})
|
||||
getLeaderBoardPractCntApi().then(res => {
|
||||
console.log(res, 'getLeaderBoardPractCntApi')
|
||||
practCntList.value = res.body || {};
|
||||
console.log(practCntList.value, 'practCntList')
|
||||
})
|
||||
getLeaderBoardExamCntApi().then(res => {
|
||||
console.log(res, 'getLeaderBoardExamCntApi')
|
||||
examCntList.value = res.body || {};
|
||||
console.log(examCntList.value, 'examCntList')
|
||||
})
|
||||
getLeaderBoardCrsCntApi().then(res => {
|
||||
console.log(res, 'getLeaderBoardCrsCntApi')
|
||||
crsCntList.value = res.body || {};
|
||||
console.log(crsCntList.value, 'crsCntList')
|
||||
})
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$bg-margin-left: 50rpx;
|
||||
.my_tabs {
|
||||
// 解决这个圆角的图片,开穿透
|
||||
:deep(.uv-tabs__wrapper__nav__line) {
|
||||
height: 26rpx !important;
|
||||
left: 70rpx;
|
||||
background-size: 28rpx !important;
|
||||
background-repeat: no-repeat !important;
|
||||
}
|
||||
}
|
||||
.detail-body {
|
||||
background: #ffffff;
|
||||
border-radius: 15rpx;
|
||||
z-index: 101;
|
||||
overflow: hidden;
|
||||
|
||||
.body-tab {
|
||||
height: 88rpx;
|
||||
border-bottom: 1rpx solid #BBBBBB;
|
||||
}
|
||||
|
||||
.body-tabs {
|
||||
width: 100%;
|
||||
height: 99rpx;
|
||||
line-height: 96rpx;
|
||||
border-bottom: 1rpx solid #BBBBBB;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-main {
|
||||
background-color: #F6F8FF;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #000000;
|
||||
font-size: 34rpx;
|
||||
height: 34rpx;
|
||||
font-weight: 600;
|
||||
line-height: 34rpx;
|
||||
}
|
||||
|
||||
.fixed_search_div {
|
||||
padding: calc(20rpx + var(--status-bar-height)) 15rpx 24rpx 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #fff;
|
||||
|
||||
.back_div {
|
||||
position: absolute;
|
||||
left: 20rpx;
|
||||
padding: 10rpx;
|
||||
|
||||
.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 #000000;
|
||||
border-left: 4rpx solid #000000;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.date-body {
|
||||
height: 114rpx;
|
||||
line-height: 114rpx;
|
||||
}
|
||||
|
||||
.date-sty {
|
||||
margin-left: 32rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
display: inline-block;
|
||||
|
||||
.date-item-sty {
|
||||
display: inline-block;
|
||||
width: 56rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
color: #040404;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
margin-left: 42rpx;
|
||||
margin-right: 42rpx;
|
||||
}
|
||||
|
||||
.active-date-item-sty {
|
||||
display: inline-block;
|
||||
width: 140rpx;
|
||||
height: 60rpx;
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
border-radius: 38rpx;
|
||||
background-color: #4080FF;
|
||||
}
|
||||
}
|
||||
|
||||
// 墨刀样式
|
||||
// .tabs-sty{
|
||||
// font-size: 28rpx;
|
||||
// font-weight: 600;
|
||||
// display: flex;
|
||||
// flex-direction: row;
|
||||
// align-items: center;
|
||||
// justify-content: center;
|
||||
// position: relative;
|
||||
// }
|
||||
// .tabs-item-sty{
|
||||
// width: 25%;
|
||||
// text-align: center;
|
||||
// height: 100rpx;
|
||||
// justify-content: center;
|
||||
// }
|
||||
// .active-tabs-item-sty{
|
||||
// color: #2572F0;
|
||||
// width: 25%;
|
||||
// text-align: center;
|
||||
// background-color: #E6F7FF;
|
||||
// border-bottom: 2px solid #2572F0;
|
||||
// }
|
||||
// 蓝湖样式
|
||||
.tabs-sty {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tabs-item-sty {
|
||||
color: #999999;
|
||||
font-weight: 500;
|
||||
width: 25%;
|
||||
text-align: center;
|
||||
height: 100rpx;
|
||||
}
|
||||
|
||||
.active-tabs-item-sty {
|
||||
position: relative;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
font-weight: 600;
|
||||
width: 25%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.active-tabs-item-sty::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 90rpx;
|
||||
height: 1rpx;
|
||||
border-bottom: 4rpx solid #4080FF;
|
||||
top: 91rpx;
|
||||
left: 25%;
|
||||
}
|
||||
|
||||
.uni-table th {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.uni-table td {
|
||||
// border: none;
|
||||
}
|
||||
|
||||
.rank-style {
|
||||
width: 100%;
|
||||
|
||||
.rank-top3 {
|
||||
background-color: #FFFFFF;
|
||||
padding: 0 32rpx 36rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.rank-content {
|
||||
text-align: center;
|
||||
border-radius: 32rpx;
|
||||
padding-top: 24rpx;
|
||||
position: relative;
|
||||
|
||||
.crown1 {
|
||||
width: 32rpx;
|
||||
height: 24rpx;
|
||||
position: absolute;
|
||||
top: 10rpx;
|
||||
left: 130rpx;
|
||||
}
|
||||
|
||||
.crown2 {
|
||||
width: 32rpx;
|
||||
height: 24rpx;
|
||||
position: absolute;
|
||||
top: 10rpx;
|
||||
left: 140rpx;
|
||||
}
|
||||
|
||||
.crown3 {
|
||||
width: 32rpx;
|
||||
height: 24rpx;
|
||||
position: absolute;
|
||||
top: 16rpx;
|
||||
left: 130rpx;
|
||||
}
|
||||
|
||||
.top-avator {
|
||||
width: 158rpx;
|
||||
height: 158rpx;
|
||||
}
|
||||
|
||||
.content1 {
|
||||
font-size: 28rpx;
|
||||
color: #737FA6;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.content11 {
|
||||
font-size: 28rpx;
|
||||
color: #AFA455;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.content111 {
|
||||
font-size: 28rpx;
|
||||
color: #B07653;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.content2 {
|
||||
font-size: 32rpx;
|
||||
color: #000000;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.content3 {
|
||||
font-size: 28rpx;
|
||||
color: #436197;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.content33 {
|
||||
font-size: 28rpx;
|
||||
color: #986C1A;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.content333 {
|
||||
font-size: 28rpx;
|
||||
color: #8A4815;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.content4 {
|
||||
font-size: 24rpx;
|
||||
color: #6682B7;
|
||||
font-weight: 400;
|
||||
width: 158rpx;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.content44 {
|
||||
font-size: 24rpx;
|
||||
color: #986C1A;
|
||||
font-weight: 400;
|
||||
width: 158rpx;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.content444 {
|
||||
font-size: 24rpx;
|
||||
color: #986C1A;
|
||||
font-weight: 400;
|
||||
width: 158rpx;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.rank-content-backgound-color1 {
|
||||
margin-top: 32rpx;
|
||||
width: 206rpx;
|
||||
// height: 418rpx;
|
||||
padding-bottom: 20rpx;
|
||||
background-color: #E8F3FF;
|
||||
padding-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.rank-content-backgound-color2 {
|
||||
width: 220rpx;
|
||||
// height: 432rpx;
|
||||
padding-bottom: 20rpx;
|
||||
background: linear-gradient(180deg, #FEEFB7 0%, #FADB66 100%);
|
||||
}
|
||||
|
||||
.rank-content-backgound-color3 {
|
||||
margin-top: 42rpx;
|
||||
width: 206rpx;
|
||||
// height: 418rpx;
|
||||
padding-bottom: 20rpx;
|
||||
background: linear-gradient(180deg, #FCDAB7 0%, #F9B16C 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rank-other {
|
||||
width: 100%;
|
||||
|
||||
:deep(.uni-table) {
|
||||
min-width: 0 !important;
|
||||
}
|
||||
:deep(.uni-list--border-top) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rank-uni-sty {
|
||||
font-weight: 400;
|
||||
font-size: 26rpx;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.rank-other-rank-sty {
|
||||
width: 20%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.rank-other-user-sty {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.rank-other-last-sty {
|
||||
width: 30%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.rank-sty {
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.active-rank-sty {
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
color: #0066FF;
|
||||
}
|
||||
|
||||
.score-sty {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.score-sty-footer {
|
||||
width: 30%;
|
||||
text-align: center;
|
||||
margin-top: 15rpx
|
||||
}
|
||||
|
||||
.avator {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.active-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
color: #0066FF;
|
||||
}
|
||||
|
||||
.belong {
|
||||
font-size: 26rpx;
|
||||
text-align: left;
|
||||
font-weight: 400;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
:deep(.uni-table-th-content) {
|
||||
font-weight: 400;
|
||||
font-size: 26rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
.rank-mine {
|
||||
width: 100%;
|
||||
height: 176rpx;
|
||||
background-color: #f5f9ff !important;
|
||||
box-shadow: 0rpx 1rpx 0rpx 0rpx #EFEFEF;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
|
||||
.rank-mine-content {
|
||||
width: 100%;
|
||||
margin-left: 46rpx;
|
||||
margin-top: 14rpx;
|
||||
|
||||
.my-avator {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
float: left;
|
||||
margin-right: 18rpx;
|
||||
}
|
||||
}
|
||||
:deep(.uni-list-item__container) {
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
.my-rank-title {
|
||||
position: absolute;
|
||||
width: 20%;
|
||||
top: 16rpx;
|
||||
left: 32rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
font-weight: 400;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.my-rank-content {
|
||||
width: 20%;
|
||||
text-align: center;
|
||||
font-size: 40rpx;
|
||||
color: #0066FF;
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
margin-top: 20rpx
|
||||
}
|
||||
|
||||
.my-rank-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #000000;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.my-rank-belong {
|
||||
font-size: 26rpx;
|
||||
font-weight: 400;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.my-rank-score {
|
||||
width: 30%;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
text-align: center;
|
||||
color: #333333;
|
||||
font-weight: 600;
|
||||
margin-top: 15rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user