Merge branch 'develop' into 'sit'

Develop

See merge request K17_AITS/tra-app!161
This commit is contained in:
田岩
2025-09-19 17:14:56 +08:00
37 changed files with 2764 additions and 1952 deletions
+2 -28
View File
@@ -1,6 +1,7 @@
# 开发环境
ENV = 'development'
# 'development'
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
# UAT
# VITE_APP_BASE_API_Url = 'http://25.18.122.78:9786'
@@ -16,35 +17,8 @@ VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
# h5专用地址x2
# VITE_APP_BASE_H5_API_Url = 'http://25.16.122.65:7001'
# VITE_APP_BASE_H5_API_Url = 'http://25.18.122.65:7001'
# VITE_APP_BASE_H5_API_Url = 'http://25.18.122.21:9786'
# VITE_APP_BASE_H5_API_Url = 'https://aitstest.jlbank.com.cn:7001'
# dev
# VITE_APP_BASE_H5_API_Url = 'http://25.18.122.65:7001'
# sit
# VITE_APP_BASE_H5_API_Url = 'http://25.18.122.92:9786'
# UAT
# VITE_APP_BASE_H5_API_Url = 'http://25.18.122.78:9786'
# 任东
# VITE_APP_BASE_H5_API_Url_TRAAPP = 'http://25.64.16.130:9601'
# VITE_APP_BASE_H5_API_Url_TRASTUDY = 'http://25.64.16.130:9602'
# VITE_APP_BASE_H5_API_Url_TRAEXAM = 'http://25.64.16.140:9604'
# 张建成
# VITE_APP_BASE_H5_API_Url_TRAASK = 'http://25.64.16.144:9605'
# VITE_APP_BASE_H5_API_Url_TRASTUDY = 'http://25.64.16.144:9602'
# 王洋洋
# VITE_APP_BASE_H5_API_Url_TRAEXAM = 'http://25.64.16.140:9604'
# 于嘉文
# VITE_APP_BASE_H5_API_Url_TRAEXAM = 'http://25.64.16.143:9604'
# 孙宇
# VITE_APP_BASE_H5_API_Url_TRAEXAM = 'http://25.64.16.139:9604'
# 付丙鑫
# VITE_APP_BASE_H5_API_Url_TRASTUDY = 'http://25.64.16.145:9602'
Binary file not shown.
-1
View File
@@ -1,5 +1,4 @@
<script>
export default {
onLaunch: function () {
uni.onTabBarMidButtonTap(() => {
+18 -1
View File
@@ -12,7 +12,6 @@ export const getCourseStudyInfoApi = (data) => {
data
});
};
// 获取课程学习 知识点 接口
export const queryTraStdyBatchWaitParagraphInfoApi = (data) => {
return request({
@@ -147,6 +146,24 @@ export const getAskAboutRawApi = (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) => {
+93
View File
@@ -0,0 +1,93 @@
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');
console.log('permResultx', permResult);
if(permResult === 1) {
}
} else {
reject('need_request');
}
// #endif
}
});
},
};
export default RecordPermission;
+1 -1
View File
@@ -1,7 +1,7 @@
<template>
<view class="course-item-box" @click="clickItem">
<view class="img-box-view">
<ImagePreview class="img-box" :imgId="itemInfo.imgId" :isCache="true" width="225" height="170" mode="aspectFill"></ImagePreview>
<ImagePreview class="img-box" :imgId="itemInfo.imgId" :isCache="true" width="225" height="170" mode="scaleToFill"></ImagePreview>
<view class="item-grade">{{itemInfo.evalGrade}}</view>
<!-- <image class="item-grade" mode="aspectFill"></view> -->
</view>
+4 -3
View File
@@ -73,11 +73,11 @@
</template>
<script setup>
import { toRefs, ref, computed, unref, onMounted, onUnmounted, nextTick, watch, reactive } from 'vue';
import {ref, computed, onMounted, nextTick, watch, reactive } from 'vue';
import common from '@/common/common';
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: {
@@ -132,7 +132,8 @@
// const innerAudioContext = uni.createInnerAudioContext();
const startTimer = ref(null); // 延迟启动的定时器ID
const startFlag = ref(1);
const startRecord = (obj) => {
const startRecord = async (obj) => {
try {await initRecord()}catch(err) {return;}
if (props.isDisabled) return;
// 开始录音
console.log('开始录音', recordingStatus.value);
@@ -1,6 +1,6 @@
<template>
<view v-bind="$attrs">
<image :src="imgUrlShow" fit="cover" :mode="mode" @click="showDetail" style="width: 100%;height: 100%;"></image>
<image :src="imgUrlShow" :mode="mode" @click="showDetail" style="width: 100%;height: 100%;"></image>
<!-- <uv-overlay :show="showModel" opacity=".6" @click="showModel = false"> -->
<uni-popup ref="PopupRef" class="popup" mask-background-color="rgba(0,0,0,.2)">
<view class="overlay-box" @click="closePopup">
+17 -2
View File
@@ -1,6 +1,7 @@
<template>
<view class="video-view-box" @click="showModelNow">
<view v-bind="$attrs" class="video-view-box" @click="showModelNow">
<imagePreviewVue :imgId="picId" class="preview-pic" v-if="picId"/>
<imagePreviewVue :src="picSrc" class="preview-pic" v-if="picSrc"/>
<view class="img-box">
<image
class="icon"
@@ -29,13 +30,23 @@
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
@@ -154,6 +165,10 @@
height: 100rpx;
transform: translate(-50%, -50%);
}
.preview-pic{
width: 100%;
height: 100%;
}
.overlay-box .video-cont-center{
width: 100vw;
max-height: 100%;
@@ -1,44 +0,0 @@
<template>
<view>
<view class="copyRightBox">
<view class="font" ref="sy" v-for="(item, index) in 30" :key="index">
{{ sytext }}
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {};
},
props: {
//载入的标签数据
sytext: {
type: String,
default: '吉AI学'
}
},
mounted() {}
};
</script>
<style>
.copyRightBox {
overflow: hidden;
width: 100%;
height: 700px;
pointer-events: none;
position: fixed;
top: 20px;
z-index: 99999;
}
.font {
float: left;
transform: rotate(-30deg);
margin-top: 220upx;
margin-left: 100upx;
font-size: 34upx;
color: rgba(100, 0, 0, 0.2);
letter-spacing: 4px;
}
</style>
+127
View File
@@ -0,0 +1,127 @@
<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`,
transform: `rotate(${rotate}deg)`,
fontSize: `${fontSize}px`,
color: color
}"
>
{{ textValue }}
</view>
</view>
</view>
</KeepAlive>
</template>
<script>
import { getUserInfo } from '@/common/common';
export default {
name: 'Watermark',
props: {
text: {
type: String,
default: '李想 0613',
validator: (val) => typeof val === 'string' && val.trim() !== ''
},
// 单个水印宽度(px
itemWidth: {
type: Number,
default: 180
},
// 单个水印高度(px
itemHeight: {
type: Number,
default: 120
},
// 旋转角度(度)
rotate: {
type: Number,
default: -30
},
// 字体大小(px
fontSize: {
type: Number,
default: 16
},
// 字体颜色
color: {
type: String,
default: 'rgba(30, 30, 70, 0.1)'
},
// 是否显示
showWatermark: {
type: Boolean,
default: true
}
},
data() {
return {
rowCount: 0, // 行数
colCount: 0, // 列数
windowWidth: 0, // 屏幕宽度
windowHeight: 0, // 屏幕高度
textValue: ''
};
},
mounted() {
// 获取屏幕尺寸(App端兼容方式)
this.getWindowInfo();
const userInfo = getUserInfo();
this.textValue = (userInfo?.userName??'') + ' ' + (userInfo?.loginName??'');
},
methods: {
getWindowInfo() {
uni.getSystemInfo({
success: (res) => {
this.windowWidth = res.windowWidth;
this.windowHeight = res.windowHeight;
this.calcGridCount(); // 计算需要多少行列覆盖屏幕
}
});
},
// 计算水印网格的行列数(确保完全覆盖屏幕)
calcGridCount() {
// 列数 = 屏幕宽度 / 单个水印宽度 + 2(冗余1列避免边缘空白)
this.colCount = Math.ceil(this.windowWidth / this.itemWidth) + 2;
// 行数 = 屏幕高度 / 单个水印高度 + 2(冗余1行避免边缘空白)
this.rowCount = Math.ceil(this.windowHeight / this.itemHeight) + 2;
}
}
};
</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; */
}
</style>
+10
View File
@@ -0,0 +1,10 @@
import RecordPermission from '@/common/recordPermission.js'
export const initRecord = () => RecordPermission.requestPermission();
export default function useIndexList() {
return {
};
}
+157 -161
View File
@@ -25,10 +25,8 @@
<!-- 对话信息 -->
<view :class="['talking-info', [4].indexOf(type) >= 0 ? 'talking-info-less' : '']">
<!-- 语音加载状态 -->
<view
:class="['talking-gif', !voiceLoading && activeVoiceId === dataInfo.id ? 'is-play' : '']"
v-if="!([2, 5, 8, 9].indexOf(type) >= 0)"
>
<view :class="['talking-gif', !voiceLoading && activeVoiceId === dataInfo.id ? 'is-play' : '']"
v-if="!([2, 5, 8, 9].indexOf(type) >= 0)">
<CommonLoading color="#06f" v-show="voiceLoading" />
</view>
<view class="talking-cont">
@@ -40,21 +38,12 @@
</view>
<!-- 维度信息预览 -->
<view v-if="type === 5 && !dataInfo.isLoading" style="padding-top: 16rpx">
<ChartFour
v-if="dataInfo.evalDimens.length === 4"
:dataList="dataInfo.evalDimens"
:score="dataInfo.asrGrade"
/>
<ChartThree
v-if="dataInfo.evalDimens.length === 3"
:dataList="dataInfo.evalDimens"
:score="dataInfo.asrGrade"
/>
<ChartFive
v-if="dataInfo.evalDimens.length === 5"
:dataList="dataInfo.evalDimens"
:score="dataInfo.asrGrade"
/>
<ChartFour v-if="dataInfo.evalDimens.length === 4" :dataList="dataInfo.evalDimens"
:score="dataInfo.asrGrade" />
<ChartThree v-if="dataInfo.evalDimens.length === 3" :dataList="dataInfo.evalDimens"
:score="dataInfo.asrGrade" />
<ChartFive v-if="dataInfo.evalDimens.length === 5" :dataList="dataInfo.evalDimens"
:score="dataInfo.asrGrade" />
</view>
<!-- 题干/段落内容 -->
<text class="talking-text" v-if="[3, 5, 6, 9].indexOf(type) >= 0 && !dataInfo.isLoading">
@@ -66,29 +55,18 @@
{{ showContent }}
</text>
<!-- 试题问题 -->
<Subject
v-if="[4, 8].indexOf(type) >= 0 && dataInfo.qnsTyp !== 'ES'"
:isPreview="!isLast"
:item="subjectInfo"
:mode="mode"
:clearResult="type === 4"
@subject_click="subject_click"
/>
<Subject v-if="[4, 8].indexOf(type) >= 0 && dataInfo.qnsTyp !== 'ES'" :isPreview="!isLast" :item="subjectInfo"
:mode="mode" :clearResult="type === 4" @subject_click="subject_click" />
<!-- 试题问题反馈 -->
<!-- <RadioSubjectRequest v-if="[8].indexOf(type) >= 0 && dataInfo.qnsTyp !== 'ES'" :item="dataInfo" :mode="mode" @subject_click="subject_click" /> -->
<!-- 段落图片视频预览 -->
<view v-if="type === 3" style="overflow: hidden">
<ImagePreview
class="cont-img-box"
v-if="(dataInfo.imageAddrs || []).length > 0"
:imgId="dataInfo.imageAddrs[0]"
:previewDetail="true"
></ImagePreview>
<VideoPreview
class="cont-img-box"
v-if="(dataInfo.vidoAddrs || []).length > 0"
:fileId="dataInfo.vidoAddrs[0]"
></VideoPreview>
<ImagePreview class="cont-img-box" v-if="(dataInfo.imageAddrs || []).length > 0"
:imgId="dataInfo.imageAddrs[0]" :previewDetail="true"></ImagePreview>
<VideoPreview class="cont-img-box" v-if="(dataInfo.vidoAddrs || []).length > 0"
:fileId="dataInfo.vidoAddrs[0].vidAddr"
:picId="dataInfo.vidoAddrs[0].vidImgAddr">
</VideoPreview>
</view>
<!-- 举例预览 -->
<view class="talking-text" v-if="type === 3 && dataInfo.exampie" style="overflow: hidden">
@@ -102,34 +80,18 @@
</view>
<!-- 播放/暂停 -->
<view class="play-btn" v-if="!([9].indexOf(type) >= 0)">
<image
class="icon"
src="@/static/images/course/play.png"
style="width: 100%; height: 100%"
@click.stop="playVoice()"
v-show="!isPlaying"
></image>
<image
class="icon"
v-show="isPlaying"
src="@/static/images/course/pause.png"
style="width: 100%; height: 100%"
@click.stop="pauseVoice"
></image>
<image class="icon" src="@/static/images/course/play.png" style="width: 100%; height: 100%"
@click.stop="playVoice()" v-show="!isPlaying"></image>
<image class="icon" v-show="isPlaying" src="@/static/images/course/pause.png"
style="width: 100%; height: 100%" @click.stop="pauseVoice"></image>
</view>
<view
class="next-learn-btn"
v-if="showNextBtn && [5, 8, 9].indexOf(type) >= 0 && isLast"
@click.stop="goOnRestartPrgh"
>
<view class="next-learn-btn" v-if="showNextBtn && [5, 8, 9].indexOf(type) >= 0 && isLast"
@click.stop="goOnRestartPrgh">
重新学
<image class="icon icon-iamge" src="@/static/images/course/refresh-white.png"></image>
</view>
<view
class="next-learn-btn restart"
v-if="showNextBtn && [5, 8].indexOf(type) >= 0 && isLast"
@click.stop="goOnRestart"
>
<view class="next-learn-btn restart" v-if="showNextBtn && [5, 8].indexOf(type) >= 0 && isLast"
@click.stop="goOnRestart">
重新答
<image class="icon icon-iamge" src="@/static/images/course/restart.png"></image>
</view>
@@ -173,24 +135,12 @@
<view :class="['user-answer', props.class]" :id="props.id" v-else-if="[1, 2].indexOf(type) >= 0">
<view class="user-info">
<view class="avatar-box">
<image
v-if="userInfo.imagePath"
class="img-box"
:src="userInfo.imagePath"
style="width: 100%; height: 100%"
></image>
<ImagePreview
v-else-if="userInfo.imageAddr"
class="img-box"
:src="userInfo.imageAddr"
style="width: 100%; height: 100%"
></ImagePreview>
<image
v-else
class="img-box"
src="/static/images/me/default_user_avatar.png"
style="width: 100%; height: 100%"
></image>
<image v-if="userInfo.imagePath" class="img-box" :src="userInfo.imagePath" style="width: 100%; height: 100%">
</image>
<ImagePreview v-else-if="userInfo.imageAddr" class="img-box" :src="userInfo.imageAddr"
style="width: 100%; height: 100%"></ImagePreview>
<image v-else class="img-box" src="/static/images/me/default_user_avatar.png" style="width: 100%; height: 100%">
</image>
</view>
<view class="ai-name">
{{ userInfo.userName }}
@@ -198,10 +148,8 @@
</view>
<view class="talking-info">
<view
:class="['talking-gif', !voiceLoading && activeVoiceId === dataInfo.id ? 'is-play' : '']"
v-if="[1].indexOf(type) >= 0"
>
<view :class="['talking-gif', !voiceLoading && activeVoiceId === dataInfo.id ? 'is-play' : '']"
v-if="[1].indexOf(type) >= 0">
<CommonLoading color="#fff" v-show="voiceLoading || dataInfo.isLoading" />
</view>
<view class="right-time">{{ voiceTime }}</view>
@@ -218,20 +166,10 @@
<image class="icon" src="@/static/images/course/replay.png" style="width: 100%;height: 100%;"></image>
</view> -->
<view class="play-btn" v-if="[1].indexOf(type) >= 0">
<image
class="icon"
@click.stop="playQnsVoice('qns', dataInfo)"
v-show="!isPlaying"
src="@/static/images/course/play-blue.png"
style="width: 100%; height: 100%"
></image>
<image
class="icon"
v-show="isPlaying"
@click.stop="pauseVoice"
src="@/static/images/course/stop-blue.png"
style="width: 100%; height: 100%"
></image>
<image class="icon" @click.stop="playQnsVoice('qns', dataInfo)" v-show="!isPlaying"
src="@/static/images/course/play-blue.png" style="width: 100%; height: 100%"></image>
<image class="icon" v-show="isPlaying" @click.stop="pauseVoice" src="@/static/images/course/stop-blue.png"
style="width: 100%; height: 100%"></image>
</view>
<view class="icon text-btn" v-if="type === 1" @click.stop="translateVoice(dataInfo)"></view>
<view class="icon text-right-btn" @click.stop="finishQuestion(dataInfo)" v-show="isLast">完成</view>
@@ -245,24 +183,12 @@
<view :class="['user-answer', props.class]" :id="props.id" v-else-if="[0, 6].indexOf(type) >= 0">
<view class="user-info">
<view class="avatar-box">
<image
v-if="userInfo.imagePath"
class="img-box"
:src="userInfo.imagePath"
style="width: 100%; height: 100%"
></image>
<ImagePreview
v-else-if="userInfo.imageAddr"
class="img-box"
:src="userInfo.imageAddr"
style="width: 100%; height: 100%"
></ImagePreview>
<image
v-else
class="img-box"
src="/static/images/me/default_user_avatar.png"
style="width: 100%; height: 100%"
></image>
<image v-if="userInfo.imagePath" class="img-box" :src="userInfo.imagePath" style="width: 100%; height: 100%">
</image>
<ImagePreview v-else-if="userInfo.imageAddr" class="img-box" :src="userInfo.imageAddr"
style="width: 100%; height: 100%"></ImagePreview>
<image v-else class="img-box" src="/static/images/me/default_user_avatar.png" style="width: 100%; height: 100%">
</image>
</view>
<view class="ai-name">
{{ userInfo.userName }}
@@ -281,18 +207,39 @@
</template>
<script setup>
import { watch, unref, ref, toRefs, computed, nextTick, onMounted } from 'vue';
import {
watch,
unref,
ref,
toRefs,
computed,
nextTick,
onMounted
} from 'vue';
import ImagePreview from '@/components/image-preview/image-preview.vue';
import VideoPreview from '@/components/image-preview/video-preview.vue';
import { downloadVoiceFile, uploadVoiceFile, getAudioByText } from '@/api/common.js';
import { studyParagraphOverApi, getAskAboutRawApi } from '@/api/study.js';
import {
downloadVoiceFile,
uploadVoiceFile,
getAudioByText
} from '@/api/common.js';
import {
studyParagraphOverApi,
getAskAboutRawApi
} from '@/api/study.js';
import ChartFour from './chartFour.vue';
import ChartThree from './chartThree.vue';
import ChartFive from './chartFive.vue';
import common from '@/common/common';
import { getUserInfo } from '@/common/common';
import { useStorageStore } from '@/store/storage.js';
import { onUnload } from '@dcloudio/uni-app';
import {
getUserInfo
} from '@/common/common';
import {
useStorageStore
} from '@/store/storage.js';
import {
onUnload
} from '@dcloudio/uni-app';
import Subject from '@/components/examination/subject.vue';
// import RadioSubjectRequest from '@/pages/wrong_question_record/components/radio-subject.vue'
@@ -346,7 +293,15 @@
}
});
const { type, dataInfo, activeVoiceId, isPlaying, isLast, lastQuestionInfo, teacherInfo } = toRefs(props);
const {
type,
dataInfo,
activeVoiceId,
isPlaying,
isLast,
lastQuestionInfo,
teacherInfo
} = toRefs(props);
const emit = defineEmits(['changePlay', 'nextQuestion', 'withdraw', 'finishQuestion', 'answerQuestion']);
// 声音播放初始化
const talkVoiceObj = ref(uni.createInnerAudioContext());
@@ -374,8 +329,7 @@
(val) => {
itemVoice.value = '';
talkVoiceObj.value.src = '';
},
{
}, {
deep: true,
immediate: true
}
@@ -386,8 +340,7 @@
if (!val) {
talkVoiceObj.value?.pause();
}
},
{
}, {
deep: true,
immediate: true
}
@@ -396,24 +349,21 @@
() => props.voiceRate,
(val) => {
talkVoiceObj.value.playbackRate = Number(val);
},
{
}, {
deep: true,
immediate: true
}
);
watch(
() => props.lastTalkingInfo,
() => {},
{
() => {}, {
deep: true,
immediate: true
}
);
watch(
() => props.dataInfo,
() => {},
{
() => {}, {
deep: true,
immediate: true
}
@@ -503,7 +453,7 @@
});
talkVoiceObj.value.onError(() => {
console.log('onError');
if(!talkVoiceObj.value.src) return
talkVoiceObj.value.src = '';
emit('changePlay', '');
});
@@ -548,29 +498,29 @@
} else {
voiceLoading.value = true;
let _type =
props.type == 2
? 'ans'
: props.type == 3
? 'pgrh'
: props.type == 4
? 'qns'
: props.type == 5
? 'analysis'
: props.type == 8
? 'stemAnalysis'
: '';
props.type == 2 ?
'ans' :
props.type == 3 ?
'pgrh' :
props.type == 4 ?
'qns' :
props.type == 5 ?
'analysis' :
props.type == 8 ?
'stemAnalysis' :
'';
let _readId =
props.type == 2
? unref(dataInfo)?.intrctId
: props.type == 3
? unref(dataInfo)?.pgrphId
: props.type == 4
? unref(dataInfo)?.qnsId
: props.type == 5
? unref(dataInfo)?.qnsId
: props.type == 8
? unref(dataInfo)?.qnsId
: '';
props.type == 2 ?
unref(dataInfo)?.intrctId :
props.type == 3 ?
unref(dataInfo)?.pgrphId :
props.type == 4 ?
unref(dataInfo)?.qnsId :
props.type == 5 ?
unref(dataInfo)?.qnsId :
props.type == 8 ?
unref(dataInfo)?.qnsId :
'';
let _params = {
crsId: unref(dataInfo)?.crsId || '',
pgrphId: unref(dataInfo)?.pgrphId || '',
@@ -584,10 +534,10 @@
let _url =
'/trastudy/traStdyInfo/readContent?' +
Object.keys(_params)
.map((t) => {
return encodeURIComponent(t) + '=' + encodeURIComponent(_params[t]);
})
.join('&');
.map((t) => {
return encodeURIComponent(t) + '=' + encodeURIComponent(_params[t]);
})
.join('&');
storageStore.getStorageById('audio', _url + _params.teachNo, (url) => {
if (url) {
@@ -747,8 +697,10 @@
width: 100%;
overflow: hidden;
margin-bottom: 20rpx;
.user-info {
overflow: hidden;
.avatar-box {
float: left;
width: 76rpx;
@@ -759,16 +711,19 @@
border-radius: 10rpx;
margin-right: 16rpx;
margin-bottom: 16rpx;
.img-box {
width: 100%;
height: 100%;
}
}
.ai-name {
float: left;
color: #666;
font-size: 25rpx;
line-height: 35rpx;
.ai-name-desc {
color: #999;
margin-left: 16rpx;
@@ -776,6 +731,7 @@
}
}
}
.talking-info {
padding: 20rpx;
border-radius: 15rpx;
@@ -783,32 +739,39 @@
overflow: hidden;
padding-bottom: 30rpx;
width: 100%;
&.talking-info-less {
float: left;
min-width: 400rpx;
padding-bottom: 20rpx;
}
.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-gif.gif) no-repeat;
background-size: contain;
}
}
.talking-cont {
margin: 16rpx 0;
overflow: hidden;
min-height: 48rpx;
&.last-cont {
margin-bottom: 0;
.talking-text {
margin-bottom: 0;
}
}
.type-tag {
width: 108rpx;
height: 46rpx;
@@ -822,6 +785,7 @@
line-height: 46rpx;
margin-bottom: 10rpx;
}
.talking-text {
font-size: 28rpx;
line-height: 48rpx;
@@ -829,10 +793,12 @@
margin-bottom: 20rpx;
word-break: break-all;
white-space: pre-line;
&.loading {
// min-height: 36px;
}
}
.cont-img-box {
width: 433rpx;
height: 269rpx;
@@ -842,9 +808,11 @@
margin-bottom: 20rpx;
}
}
.btns-cont {
overflow: hidden;
border-top: 3rpx solid #eee;
.replay-btn {
float: left;
width: 46rpx;
@@ -853,6 +821,7 @@
margin-right: 16rpx;
margin-top: 25rpx;
}
.play-btn {
float: left;
width: 46rpx;
@@ -861,6 +830,7 @@
margin-right: 16rpx;
margin-top: 25rpx;
}
.next-learn-btn {
float: left;
padding: 0 60rpx 0 15rpx;
@@ -873,14 +843,17 @@
margin-top: 16rpx;
margin-right: 16rpx;
position: relative;
&.replay-study-btn {
float: right;
margin-right: 0;
}
&.restart {
background-color: #eaf2ff;
color: #06f;
}
.icon-iamge {
width: 25rpx;
height: 25rpx;
@@ -890,6 +863,7 @@
margin-top: -12rpx;
}
}
.replay-learn-btn {
float: left;
padding: 0 60rpx 0 20rpx;
@@ -902,6 +876,7 @@
margin-top: 16rpx;
margin-right: 16rpx;
position: relative;
.icon-iamge {
width: 25rpx;
height: 25rpx;
@@ -911,6 +886,7 @@
margin-top: -12rpx;
}
}
.next-btn {
float: right;
padding: 0 60rpx 0 20rpx;
@@ -922,6 +898,7 @@
border-radius: 8rpx;
margin-top: 16rpx;
position: relative;
.icon-iamge {
width: 25rpx;
height: 25rpx;
@@ -934,12 +911,15 @@
}
}
}
.user-answer {
overflow: hidden;
width: 100%;
margin-bottom: 20rpx;
.user-info {
overflow: hidden;
.avatar-box {
float: right;
width: 76rpx;
@@ -950,11 +930,13 @@
border-radius: 10rpx;
margin-left: 16rpx;
margin-bottom: 16rpx;
.img-box {
width: 100%;
height: 100%;
}
}
.ai-name {
float: right;
color: #666;
@@ -970,6 +952,7 @@
background-color: #06f;
color: #fff;
}
.talking-info {
padding: 20rpx;
border-radius: 15rpx;
@@ -977,17 +960,20 @@
padding-bottom: 30rpx;
position: relative;
color: #fff;
.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;
@@ -996,9 +982,11 @@
height: 38rpx;
line-height: 38rpx;
}
.talking-cont {
margin-top: 16rpx;
overflow: hidden;
.talking-text {
font-size: 28rpx;
line-height: 48rpx;
@@ -1006,14 +994,17 @@
word-break: break-all;
white-space: pre-line;
overflow: hidden;
&.translate-text {
min-height: 48rpx;
}
}
}
.btns-cont {
overflow: hidden;
border-top: 3rpx solid #eee;
.replay-btn {
float: left;
width: 46rpx;
@@ -1022,6 +1013,7 @@
margin-right: 16rpx;
margin-top: 25rpx;
}
.play-btn {
float: left;
width: 46rpx;
@@ -1030,6 +1022,7 @@
margin-right: 16rpx;
margin-top: 25rpx;
}
.text-btn {
float: left;
width: 46rpx;
@@ -1045,6 +1038,7 @@
font-weight: bold;
border-radius: 20rpx;
}
.text-right-btn {
float: right;
padding: 0 10rpx;
@@ -1055,6 +1049,7 @@
margin-left: 16rpx;
margin-top: 25rpx;
}
.next-btn {
float: right;
padding: 0 60rpx 0 20rpx;
@@ -1066,6 +1061,7 @@
border-radius: 8rpx;
margin-top: 16rpx;
position: relative;
.icon-iamge {
width: 25rpx;
height: 25rpx;
@@ -1078,4 +1074,4 @@
}
}
}
</style>
</style>
+225 -197
View File
@@ -1,22 +1,54 @@
<template>
<view class="talk-btns" ref="talkBtnRef">
<view class="touch-btn" @touchstart="getRecordPermission" @touchend="hideOverlayTalking" @touchmove="handleMove"
v-if="!keyboardIsShow">按住说话
<view
class="touch-btn"
@touchstart="getRecordPermission"
@touchend="hideOverlayTalking"
@touchmove="handleMove"
v-if="!keyboardIsShow"
>
按住说话
</view>
<view class="touch-btn" v-if="keyboardIsShow">
<uv-input class="input-box" ref="inputRef" v-model="answerValue" :maxlength="props.textValueLength"
:adjustPosition="adjustPosition" placeholderStyle='color:#999999;font-size:26rpx' placeholder="请输入文字">
</uv-input>
<uv-input
class="input-box"
ref="inputRef"
v-model="answerValue"
:maxlength="props.textValueLength"
:adjustPosition="adjustPosition"
placeholderStyle="color:#999999;font-size:26rpx"
placeholder="请输入文字"
></uv-input>
</view>
<view class="talk-btn-box" v-show="!onlyVoice">
<image class="talk-btn-icon" v-show="!keyboardIsShow" src="/src/static/images/course/jianpan.png" mode=""
@click="changeBtn"></image>
<image class="talk-btn-icon" v-show="keyboardIsShow && !answerValue" src="/src/static/images/course/record.png"
mode="" @click="changeBtn"></image>
<image class="talk-btn-icon" v-show="keyboardIsShow && !!answerValue" src="/src/static/images/course/send.png"
mode="" @click.stop="showKeyboard"></image>
<image
class="talk-btn-icon"
v-show="!keyboardIsShow"
src="/src/static/images/course/jianpan.png"
mode=""
@click="changeBtn"
></image>
<image
class="talk-btn-icon"
v-show="keyboardIsShow && !answerValue"
src="/src/static/images/course/record.png"
mode=""
@click="changeBtn"
></image>
<image
class="talk-btn-icon"
v-show="keyboardIsShow && !!answerValue"
src="/src/static/images/course/send.png"
mode=""
@click.stop="showKeyboard"
></image>
</view>
<uni-popup ref="talkModelRef" type="bottom" @click="showTalkingModel = false" mask-background-color="rgba(0,0,0,0.9)">
<uni-popup
ref="talkModelRef"
type="bottom"
@click="showTalkingModel = false"
mask-background-color="rgba(0,0,0,0.9)"
>
<view class="overlay-box">
<view class="circle-bg-box">
<view class="icon-box">
@@ -25,42 +57,30 @@
</view>
<view class="tips-box">松开发送</view>
<view class="btns-box">
<view :class="['close-btn',isOut?'is-out':'']">取消
<view :class="['close-btn', isOut ? 'is-out' : '']">
取消
<image class="btn-box" v-if="!isOut" src="/src/static/images/course/close.png" mode=""></image>
<image class="btn-box" v-else src="/src/static/images/course/close-active.png" mode=""></image>
</view>
</view>
<view :class="['talk-dialog',isOut?'is-out':'']">
<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 {
toRefs,
ref,
computed,
unref,
onMounted,
onUnmounted,
nextTick,
watch,
getCurrentInstance
} from 'vue';
import { toRefs, ref, computed, unref, onMounted, onUnmounted, nextTick, watch, getCurrentInstance } from 'vue';
import common from '../../../common/common';
import permission from '@/common/permission.js'
import {
getPhoneEnvBool
} from '@/common/common.js'
import { useStorageStore } from "@/store/storage.js"
const storageStore = useStorageStore()
const proxy = getCurrentInstance()
const emit = defineEmits(['uploadVoice', 'submitAnswer', 'startRecord'])
import permission from '@/common/permission.js';
import { getPhoneEnvBool } from '@/common/common.js';
import { useStorageStore } from '@/store/storage.js';
const storageStore = useStorageStore();
const proxy = getCurrentInstance();
const emit = defineEmits(['uploadVoice', 'submitAnswer', 'startRecord']);
const props = defineProps({
isDisabled: {
default: true,
@@ -93,59 +113,73 @@
adjustPosition: {
default: false,
type: Boolean
},
})
const {
isDisabled,
onlyVoice,
adjustPosition
} = toRefs(props)
const isLoadingState = computed(() => props.isLoading)
const showTalkingModel = ref(false)
const keyboardIsShow = ref(false)
const talkModelRef= ref()
storageStore.setTouchBtnZIndex(9)
const btnZindex = computed(()=> storageStore.getTouchBtnZIndex)
watch(() => showTalkingModel.value, (val) => val ? talkModelRef.value.open() : talkModelRef.value.close())
watch(() => props.isLoading, () => {}, {
deep: true,
immediate: true
})
watch(() => props.canAnswer, () => {}, {
deep: true,
immediate: true
})
watch(() => props.onlyVoice, () => {}, {
deep: true,
immediate: true
})
watch(() => props.isDisabled, (val) => {
if (val) {
keyboardIsShow.value = false
}
}, {
deep: true,
immediate: true
})
});
const { isDisabled, onlyVoice, adjustPosition } = toRefs(props);
const isLoadingState = computed(() => props.isLoading);
const showTalkingModel = ref(false);
const keyboardIsShow = ref(false);
const talkModelRef = ref();
storageStore.setTouchBtnZIndex(9);
const btnZindex = computed(() => storageStore.getTouchBtnZIndex);
const answerValue = ref('')
const talkBtnRef = ref()
watch(
() => showTalkingModel.value,
(val) => (val ? talkModelRef.value.open() : talkModelRef.value.close())
);
watch(
() => props.isLoading,
() => {},
{
deep: true,
immediate: true
}
);
watch(
() => props.canAnswer,
() => {},
{
deep: true,
immediate: true
}
);
watch(
() => props.onlyVoice,
() => {},
{
deep: true,
immediate: true
}
);
watch(
() => props.isDisabled,
(val) => {
if (val) {
keyboardIsShow.value = false;
}
},
{
deep: true,
immediate: true
}
);
const answerValue = ref('');
const talkBtnRef = ref();
// 录音
const audioPath = ref('')
const audioObj = uni.createInnerAudioContext()
const audioPath = ref('');
const audioObj = uni.createInnerAudioContext();
//#ifdef APP-PLUS
// app端
const recordObjCom = ref({})
const IsAndEnv = getPhoneEnvBool('AND')
const isStartNum = ref(0)
const isStopNum = ref(0)
const recordObjCom = ref({});
const IsAndEnv = getPhoneEnvBool('AND');
const isStartNum = ref(0);
const isStopNum = ref(0);
const initRecord = () => {
recordObjCom.value = uni.getRecorderManager()
recordObjCom.value = uni.getRecorderManager();
unref(recordObjCom)?.onStop((res, duration) => {
audioPath.value = res.tempFilePath
audioPath.value = res.tempFilePath;
if (!isOut.value) {
if (startFlag.value !== 2) {
// setTimeout(() => {
@@ -158,243 +192,237 @@
closeModel(() => {
nextTick(() => {
// showTalkingModel.value = false
common.msg('说话时间太短,没有听清!')
})
})
common.msg('说话时间太短,没有听清!');
});
});
} else {
closeModel()
emit('uploadVoice', audioPath.value)
closeModel();
emit('uploadVoice', audioPath.value);
}
} else {
closeModel()
closeModel();
}
})
});
unref(recordObjCom).onStart(() => {
startFlag.value = 1
startFlag.value = 1;
setTimeout(() => {
startFlag.value = 2
}, 1000)
let _recordPerm = uni.getAppAuthorizeSetting()
let _state = _recordPerm.microphoneAuthorized
console.log(isStopNum.value === isStartNum.value, _state)
startFlag.value = 2;
}, 1000);
let _recordPerm = uni.getAppAuthorizeSetting();
let _state = _recordPerm.microphoneAuthorized;
console.log(isStopNum.value === isStartNum.value, _state);
if (_state === 'authorized') {
if (isStopNum.value === isStartNum.value) {
stopRecord()
stopRecord();
} else {
showTalkingModel.value = true
showTalkingModel.value = true;
}
}
})
}
const startFlag = ref(1)
});
};
const startFlag = ref(1);
// const canStart = ref(true)
const startRecord = () => {
isStartNum.value += 1
isStartNum.value += 1;
unref(recordObjCom).start({
format: 'mp3'
})
}
});
};
const stopRecord = () => {
isStopNum.value = isStartNum.value
isStopNum.value = isStartNum.value;
try {
if (unref(recordObjCom).stop) {
unref(recordObjCom).stop()
unref(recordObjCom).stop();
} else {
closeModel()
closeModel();
}
} catch {
closeModel()
closeModel();
}
// setTimeout(() => {
// canStart.value = true
// }, 1000)
}
};
const closeModel = (cb = () => {}) => {
isStopNum.value = isStartNum.value
isStopNum.value = isStartNum.value;
setTimeout(() => {
showTalkingModel.value = false
cb && cb()
}, 350)
showTalkingModel.value = false;
cb && cb();
}, 350);
setTimeout(() => {
isTalking.value = false
}, 800)
}
isTalking.value = false;
}, 800);
};
// #endif
//#ifndef APP-PLUS
const recordObjCom = ref({})
const initRecord = () => {}
const recordObjCom = ref({});
const initRecord = () => {};
// #endif
onMounted(async () => {
initRecord?.()
initRecord?.();
//#ifdef APP-PLUS
// 安卓端
if (IsAndEnv) {
let _recordPerm = await permission.requestAndroidPermission('android.permission.RECORD_AUDIO')
let _recordPerm = await permission.requestAndroidPermission('android.permission.RECORD_AUDIO');
if (_recordPerm !== 1) {
common.show('提示信息', '当前页面需要使用的录音权限,是否前往开启?', true, '取消', '确定').then((res) => {
if (res) {
// 去开启权限
permission.gotoAppPermissionSetting()
permission.gotoAppPermissionSetting();
} else {
// 取消
common.navigateBack()
common.navigateBack();
}
})
});
}
} else {
let _recordPerm = uni.getAppAuthorizeSetting()
let _state = _recordPerm.microphoneAuthorized
let _recordPerm = uni.getAppAuthorizeSetting();
let _state = _recordPerm.microphoneAuthorized;
if (_state === 'denied') {
common.show('提示信息', '当前页面需要使用的录音权限,是否前往开启?', true, '取消', '确定').then((res) => {
if (res) {
// 去开启权限
uni.openAppAuthorizeSetting()
uni.openAppAuthorizeSetting();
} else {
// 取消
common.navigateBack()
common.navigateBack();
}
})
});
} else if (_state === 'not determined') {
// startRecord()
}
}
// #endif
// setTimeout(()=> {talkModelRef.value.open()}, 2000)
})
});
// 录音结束
// 记录 按下移动位置
const touchX = ref(0)
const touchY = ref(0)
const baseY = ref(0)
const windowInfo = uni.getWindowInfo()
const touchX = ref(0);
const touchY = ref(0);
const baseY = ref(0);
const windowInfo = uni.getWindowInfo();
// 计算是否超出按下盒子位置
const isOut = computed(() => {
let _btnHeight = windowInfo.screenWidth / 750 * 234
let moveVal = (windowInfo.screenHeight - touchY.value) > _btnHeight
return moveVal
})
let _btnHeight = (windowInfo.screenWidth / 750) * 234;
let moveVal = windowInfo.screenHeight - touchY.value > _btnHeight;
return moveVal;
});
const outScreen = computed(() => {
(windowInfo.screenHeight - touchY.value) <= 0
})
const recNum = ref(1)
const activeNum = ref(1)
windowInfo.screenHeight - touchY.value <= 0;
});
const recNum = ref(1);
const activeNum = ref(1);
const getRecordPermission = (obj) => {
if(isDisabled.value) return
if (isDisabled.value) return;
if (isTalking.value) {
common.msg('操作频繁,请稍后重试!')
common.msg('操作频繁,请稍后重试!');
setTimeout(() => {
isTalking.value = false
}, 500)
return
isTalking.value = false;
}, 500);
return;
}
showOverlayTalking(obj)
}
const isMoving = ref(false)
const isTalking = ref(false)
showOverlayTalking(obj);
};
const isMoving = ref(false);
const isTalking = ref(false);
const showOverlayTalking = (obj) => {
if (showTalkingModel.value) return
if (showTalkingModel.value) return;
if (isLoadingState.value) {
uni.showToast({
title: props.loadingText,
icon: "none",
icon: 'none',
duration: 1000
})
return
});
return;
}
if (!props.canAnswer) {
uni.showToast({
title: props.canAnswerText,
icon: "none",
icon: 'none',
duration: 1000
})
return
});
return;
}
// if(!canStart.value){
// return
// }
isMoving.value = true
isTalking.value = true
emit('startRecord')
recNum.value += 1
isMoving.value = true;
isTalking.value = true;
emit('startRecord');
recNum.value += 1;
if (!isDisabled.value) {
touchX.value = obj.changedTouches[0].pageX
touchY.value = obj.changedTouches[0].pageY
baseY.value = obj.currentTarget.offsetTop
touchX.value = obj.changedTouches[0].pageX;
touchY.value = obj.changedTouches[0].pageY;
baseY.value = obj.currentTarget.offsetTop;
//#ifdef APP-PLUS
// canStart.value = false
startRecord()
startRecord();
// #endif
setTimeout(() => {
hideOverlayTalking()
}, 59 * 1010)
hideOverlayTalking();
}, 59 * 1010);
} else {
showTalkingModel.value = false
showTalkingModel.value = false;
uni.showToast({
title: '暂无问题需要回答!',
icon: "none",
icon: 'none',
duration: 1000
})
});
}
}
};
const hideOverlayTalking = (obj) => {
isMoving.value = false
if (!showTalkingModel.value) return
isMoving.value = false;
if (!showTalkingModel.value) return;
//#ifdef APP-PLUS
stopRecord()
stopRecord();
// #endif
}
};
const handleMove = (obj) => {
if (isMoving.value) {
touchX.value = obj.changedTouches[0].pageX
touchY.value = obj.changedTouches[0].pageY
touchX.value = obj.changedTouches[0].pageX;
touchY.value = obj.changedTouches[0].pageY;
if (outScreen.value) {
hideOverlayTalking(obj)
hideOverlayTalking(obj);
}
}
}
};
// 发送文本
const showKeyboard = () => {
if (isLoadingState.value) {
uni.showToast({
title: props.loadingText,
icon: "none",
icon: 'none',
duration: 1000
})
return
});
return;
}
if (!isDisabled.value) {
if (keyboardIsShow.value) {
if (answerValue.value.trim()) {
emit(
'submitAnswer',
answerValue.value,
changeBtn
)
emit('submitAnswer', answerValue.value, changeBtn);
} else {
// keyboardIsShow.value = !keyboardIsShow.value
common.msg("不能发送空白消息!")
common.msg('不能发送空白消息!');
}
} else {
keyboardIsShow.value = !keyboardIsShow.value
keyboardIsShow.value = !keyboardIsShow.value;
}
} else {
uni.showToast({
title: '暂无问题需要回答!',
icon: "none",
icon: 'none',
duration: 1000
})
});
}
}
};
const changeBtn = () => {
if (props.isDisabled) return
keyboardIsShow.value = !keyboardIsShow.value
answerValue.value = ''
}
if (props.isDisabled) return;
keyboardIsShow.value = !keyboardIsShow.value;
answerValue.value = '';
};
</script>
<style lang="scss" scoped>
@@ -406,7 +434,7 @@
z-index: v-bind(btnZindex);
.touch-btn {
height: 92rpx;
box-shadow: 0 8rpx 20rpx 0 rgba(0, 0, 0, .06);
box-shadow: 0 8rpx 20rpx 0 rgba(0, 0, 0, 0.06);
text-align: center;
line-height: 92rpx;
color: #000;
@@ -584,4 +612,4 @@
::v-deep .uv-input__content__field-wrapper__field {
padding-right: 60rpx;
}
</style>
</style>
+2 -2
View File
@@ -436,12 +436,12 @@ onShow(() => {
.top-bg {
background: linear-gradient(16deg, rgba(234, 246, 255, 0) 0%, #c3e6ff 100%);
height: 480rpx;
width: 750rpx;
width: 100%;
}
.bot-bg {
// background-color: #F6F8FF;
background: linear-gradient(136deg, rgba(234, 246, 255, 0) 0%, #c3e6ff 100%);
width: 750rpx;
width: 100%;
flex: 1;
}
.course-center-body {
+55 -6
View File
@@ -126,7 +126,17 @@ import { onLoad, onShow, onUnload, onHide } from '@dcloudio/uni-app'
import { previewFile, downloadFile, commonUploadVoiceFile, downloadVoiceFile, uploadVoiceFile } from '@/api/common.js'
import { getToken, getPhoneEnvBool } from '@/common/common.js'
import { get_base_url, goto_login_fun } from '@/api/request'
import { getCourseStudyInfoApi, afreshStudyCourseApi, queryQuestionEvalateApi, textChartApi, setStudyTeacherApi, textChartPreviewApi, previewAfreshStudyCourse } from '@/api/study.js'
import {
getCourseStudyInfoApi,
afreshStudyCourseApi,
queryQuestionEvalateApi,
textChartApi,
setStudyTeacherApi,
textChartPreviewApi,
previewAfreshStudyCourse,
studyEndApi,
studyStartApi
} from '@/api/study.js'
import common from '@/common/common'
import TalkingItem from '@/pages/course/components/talkingItem.vue'
import ImagePreview from '@/components/image-preview/image-preview.vue'
@@ -231,6 +241,17 @@ const getData = async flag => {
watchFlag.value += 1
}
}
const startStudy = (data) => {
studyStartApi(data).then(res=>{
const { body } = res
stdyId.value = body.stdyId || ''
stdyBatch.value = body.stdyBatch || ''
oldStdyId.value = body.oldStdyId || ''
logId.value = body.logId || ''
getGraphInfoUnStudy(1)
step.value = 2
}).catch(err=>{})
}
// 重新学习
const resStartStudyNow = ($list = []) => {
let _ajax = isPreview.value ? previewAfreshStudyCourse : afreshStudyCourseApi
@@ -680,7 +701,8 @@ const reconnect = () => {
initsocketTask()
}
const sendMessage = data => {
unref(SocketObj).send(data)
// unref(SocketObj).send(data)
startStudy(data.body)
}
// ws 结束
const startPgrphId = ref('')
@@ -697,7 +719,7 @@ onLoad(params => {
const showTalkingModel = ref(false)
// 录音
onMounted(() => {
initsocketTask()
// initsocketTask()
getData('1')
})
const keyboardHeight = ref('0px')
@@ -721,9 +743,17 @@ const changeAppHeightBottom = height => {
bottomHeight.value = systemInfo.screenHeight - systemInfo.safeArea.bottom + 'px'
// #endif
}
const closeStudy = () => {
return studyEndApi({
stdyId: stdyId.value
})
}
onHide(() => {
changePlayItemId('')
socketStore.closeSocket()
if(isPreview.value === 'S'){
closeStudy()
}
// socketStore.closeSocket()
//#ifdef APP-PLUS
uni.offKeyboardHeightChange(setHeight)
// #endif
@@ -738,12 +768,31 @@ onShow(() => {
// #endif
if (watchFlag.value === 1) return
// getData('2');
initsocketTask()
// initsocketTask()
if(isPreview.value === 'S'){
studyStartApi({
crsId: crsId.value,
teacherNo: choiceTeacher.value,
studyType: isPreview.value ? 'P' : 'S',
isContinue: isContinue.value,
stdyId: stdyId.value
}).then(res=>{
const { body } = res
stdyId.value = body.stdyId || ''
stdyBatch.value = body.stdyBatch || ''
oldStdyId.value = body.oldStdyId || ''
logId.value = body.logId || ''
step.value = 2
}).catch(err=>{})
}
})
const watchFlag = ref(1)
onUnload(() => {
console.log('onUnload')
socketStore.closeSocket()
if(isPreview.value === 'S'){
closeStudy()
}
// socketStore.closeSocket()
changePlayItemId('')
console.log(typeof teacherVoiceObj)
try {
+4 -4
View File
@@ -157,14 +157,14 @@
};
const toStudyPrgh = (info) => {
common.navigateTo(
'/pages/course/study?crsNum=' +
form.value.crsNum +
'&crsId=' +
'/pages/course/study?crsId=' +
crsId.value +
'&pgrphId=' +
info.pgrphId +
'&imgId=' +
form.imgId
form.imgId +
'&isPreview=' +
isPreview.value
);
};
@@ -49,7 +49,10 @@
<view style="overflow: hidden;">
<ImagePreview class="cont-img-box" v-if="dataInfo.imgAddr" :src="dataInfo.imgAddr" :previewDetail="true">
</ImagePreview>
<VideoPreview class="cont-img-box" v-if="dataInfo.vidAddr" :src="dataInfo.vidAddr"></VideoPreview>
<VideoPreview class="cont-img-box" v-if="dataInfo.vidAddr"
:src="dataInfo.vidAddr"
:picSrc="dataInfo.vidImgAddr"
></VideoPreview>
</view>
<!-- 维度信息预览 -->
<view v-if="[4, 8].indexOf(type) >= 0 && dataInfo.qnsTyp === 'ES'" style="padding-top: 16rpx;">
+2 -1
View File
@@ -41,7 +41,8 @@
</view>
<view class="ranking-self">
<view class="list-header list-item">
<view class="rank-column">我的排名</view>
<view class="rank-column">我的排名
</view>
</view>
<view class="list-item item-me">
<view class="rank-column">
+1066 -1010
View File
File diff suppressed because it is too large Load Diff
+6 -4
View File
@@ -508,19 +508,21 @@
}
.body-title {
width: 100%;
position: relative;
text-align: center;
height: 160rpx;
height: 180rpx;
padding-top: var(--status-bar-height);
margin-bottom: -20rpx;
line-height: calc(160rpx - var(--status-bar-height) - 20rpx);
margin-bottom: -10rpx;
line-height: calc(180rpx - var(--status-bar-height) - 10rpx);
font-family: PingFangSC;
font-weight: 600;
font-size: 32rpx;
color: #000000;
font-style: normal;
background-image: url('@/static/images/learningTasks/learningTasksBg.png');
background-repeat: no-repeat;
background-size: 100% calc(420rpx + var(--status-bar-height)); /* 宽度充满,高度自适应比例 */
+2 -5
View File
@@ -1,6 +1,5 @@
<template>
<view class="main_div max_page">
<nav-bar :is_seat="false"></nav-bar>
<view class="bg_div">
<image src="/static/images/me/bg.png" class="bg_img"></image>
@@ -14,7 +13,7 @@
class="img_100"
:src="user_info.imageAddr"
></image-preview>
<image v-else class="img_100" :src="defaultAvatarSrc"></image>
<image class="img_100" :src="defaultAvatar"></image>
</view>
<view class="personal_information_data">
<view class="name_sex_div">
@@ -145,7 +144,6 @@
</template>
<script setup>
import { ref, computed, reactive, nextTick } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import common from '@/common/common';
@@ -158,8 +156,7 @@
import { uploadFile } from '@/api/common';
import { setMascot } from '@/common/mascot.js';
import { queryCurrentUser } from '@/api/login.js';
import { defaultAvatar } from "@/enum.js"
const defaultAvatarSrc = computed(()=> defaultAvatar)
import { defaultAvatar } from '@/enum.js';
const badgeList = [
{
name: '黑铁',
-355
View File
@@ -1,355 +0,0 @@
<template>
<view class="main_div max_page">
<scroll-view
ref="scrollRef"
:scroll-y="true"
class="scroll-Y"
:refresher-enabled="false"
:show-scrollbar="false"
@scrolltolower="scrolltolower"
@refresherrefresh="onRefresh"
@refresherrestore="triggered = 'restore'"
:refresher-triggered="triggered"
refresher-default-style="none"
>
<view class="content">
<view
style="
position: absolute;
top: -90rpx;
width: 100%;
height: 90rpx;
display: flex;
align-items: center;
justify-content: center;
"
></view>
<view class="item" v-for="item in data_list" @click="read">
<view class="icon">
<image
v-if="item.noticeTyp === '01'"
class="img_100"
src="@/static/images/messageNotification/type_01.png"
mode="widthFix"
></image>
<image
v-if="item.noticeTyp === '02'"
class="img_100"
src="@/static/images/messageNotification/type_02.png"
mode="widthFix"
></image>
<image
v-if="item.noticeTyp === '03'"
class="img_100"
src="@/static/images/messageNotification/type_03.png"
mode="widthFix"
></image>
</view>
<view class="right">
<view class="top">
<view class="title">
{{ item.noticeTitle }}
</view>
<view class="time">
{{ item.ctTime?.substr(0, 16) }}
</view>
</view>
<view class="bottom">
<view class="note ellipsis-text">
{{ item.noticeContent }}
</view>
<!-- <view class="num_div"></view> -->
<view class="read_div" v-if="item.isRead"></view>
</view>
</view>
</view>
<uv-load-more v-if="total !== 0" :status="status" loadmore-text="轻轻上拉加载" :height="30"></uv-load-more>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, reactive, nextTick, onMounted } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { queryTraPersonalMessageNoticePaging } from '@/api/messageNotification.js';
const customStyle = {
backgroundColor: '#ffffff',
paddingLeft: '40rpx',
height: '78rpx',
paddingTop: '5px',
paddingBottom: '5px',
border: 'none'
};
const suffixIconStyle = {
color: '#2c8ef2',
fontSize: '50rpx'
};
const options = [
{
text: '删除',
style: {
backgroundColor: '#f56c6c'
}
}
];
const status = ref('loadmore'); // loadmore - loading - nomore -
const limit = 20;
const total = ref(-1);
const page = ref(0);
const data_list = reactive([]);
const triggered = ref(false);
const _freshing = ref(false);
const offset = ref(0);
const scrollRef = ref(null);
//
const onPulling = (e) => {
page.value = 1;
total.value = -1;
page.data_list.length = 0;
getData();
};
//
const onRefresh = () => {
if (_freshing.value) return;
_freshing.value = true;
setTimeout(() => {
triggered.value = false;
_freshing.value = false;
}, 1000);
};
const getData = (from = '') => {
if (from == 'first') {
if (total.value !== -1) {
return;
}
status.value = 'loading';
total.value = -1;
page.value = 1;
data_list.length = 0;
} else {
if (status.value !== 'loadmore' && status.value !== '') return;
status.value = 'loading';
page.value++;
}
console.log(status.value, '阿斯顿撒');
setTimeout(() => {
queryTraPersonalMessageNoticePaging({
page: page.value,
limit: limit
})
.then((res) => {
total.value = res.total;
data_list.push(...res.body);
})
.finally(() => {
if (data_list.length >= total.value) {
status.value = 'nomore';
} else {
status.value = 'loadmore';
}
});
}, 200);
};
const scrolltolower = (item) => {
getData();
};
onMounted(() => {
// triggered.value = true;
getData();
// setTimeout(() => {
// triggered.value = true
// message_list.value.push(...[1, 2, 3, 4, 5])
// }, 1000)
console.log(scrollRef.value);
});
</script>
<style scoped lang="scss">
.pulldown-loading-box {
position: fixed;
left: 0;
right: 0;
z-index: 3;
transform: translateY(-100%);
transition: transform 0.3s;
z-index: 1;
}
.scroll-Y {
height: calc(100vh - 70rpx - var(--status-bar-height) - 78rpx - 40rpx);
position: relative;
overflow: hidden;
border-radius: 20rpx 20rpx 0 0;
.content {
width: 100%;
background-color: #fff;
box-shadow: 0 0 6rpx #b3d6ff;
position: relative;
.item {
height: 154rpx;
padding: 38rpx 0 38rpx 0;
margin: 0 38rpx;
display: flex;
justify-content: space-between;
align-items: center;
border-top: 2rpx solid #efefef;
.icon {
width: 78rpx;
height: 78rpx;
}
.right {
margin-left: 16rpx;
flex: 1;
.top {
display: flex;
justify-content: space-between;
.title {
font-family: PingFangSC;
font-weight: 600;
font-size: 30rpx;
color: #000000;
line-height: 46rpx;
text-align: left;
font-style: normal;
}
.time {
font-size: 23rpx;
color: #999999;
line-height: 30rpx;
text-align: right;
font-style: normal;
}
}
.bottom {
display: flex;
justify-content: space-between;
align-items: center;
overflow: hidden;
width: 100%;
.note {
font-family: PingFangSC;
font-weight: 400;
font-size: 26rpx;
color: #666666;
line-height: 35rpx;
text-align: left;
font-style: normal;
flex: 1;
}
.num_div {
padding: 0 8rpx;
height: 31rpx;
background: #ff6c75;
border-radius: 15rpx;
display: flex;
align-items: center;
justify-content: center;
font-family: ArialMT;
font-size: 22rpx;
color: #ffffff;
line-height: 26px;
min-width: 40rpx;
}
.read_div {
width: 16rpx;
height: 16rpx;
background-color: #d70c18;
border-radius: 100%;
}
}
}
}
}
}
.main_div {
display: flex;
flex-direction: column;
align-items: center;
background: linear-gradient(0deg, rgba(234, 246, 255, 0) 0%, #c3e6ff 100%);
// background-size: 100% 300px;
// background-repeat: no-repeat; /* */
padding-top: var(--status-bar-height);
overflow: hidden;
}
.body-title {
width: 100%;
height: 70rpx;
font-size: 33rpx;
padding-top: 10rpx;
font-weight: 500;
color: #000;
position: relative;
text-align: center;
.delete_div {
position: absolute;
right: 20rpx;
top: 17rpx;
width: 50rpx;
height: 50rpx;
.img {
width: 34rpx;
height: 34rpx;
}
}
.back_div {
position: absolute;
left: 26rpx;
height: 34rpx;
top: 17rpx;
.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 #2b2c2e;
border-left: 4rpx solid #2b2c2e;
transform: rotate(-45deg);
}
}
}
.input-view {
width: 90%;
margin: 20rpx auto;
height: 78rpx;
}
</style>
+1 -1
View File
@@ -81,7 +81,7 @@
<script setup>
import { ref, reactive, computed, onMounted, nextTick, onUnmounted } from 'vue';
import { onLoad, onHide, onShow, onUnload, onBackPress } from '@dcloudio/uni-app';
import { onLoad, onHide, onShow, onUnload, onBackPress, onReady } from '@dcloudio/uni-app';
import Feedback from '@/pages/examination/components/feedback.vue';
import Dialog from '@/pages/examination/components/dialog.vue';
+21 -7
View File
@@ -33,7 +33,8 @@
<uv-tabs :list="tabList" @click="changeTab" :scrollable="false" active-style="font-weight:bold; color:#000;" line-color="#06f"></uv-tabs>
</view>
<scroll-view class="lists-view" :scroll-y="true" @scrolltolower="scrolltolower">
<view class="ability-view model-view" v-show="showModelList('ability')">
<list-no-data v-if="showEmptyBox">暂无数据</list-no-data>
<view class="ability-view model-view" v-show="showModelList('ability', abilityListShow)">
<view class="view-title">
<image src="@/static/images/search/result/ability.png" class="view-title-img"></image>
功能
@@ -54,7 +55,7 @@
<view class="view-result" v-show="abilityListShow.length === 0">暂无数据</view>
</view>
<view class="course-view model-view" v-show="showModelList('course')">
<view class="course-view model-view" v-show="showModelList('course',courseList )">
<view class="view-title">
<image src="@/static/images/search/result/course.png" class="view-title-img"></image>
课程
@@ -69,7 +70,7 @@
</view>
<view class="view-result" v-show="courseList.length === 0">暂无数据</view>
</view>
<view class="task-view model-view" v-show="showModelList('task')">
<view class="task-view model-view" v-show="showModelList('task', taskList)">
<view class="view-title">
<image src="@/static/images/search/result/task.png" class="view-title-img"></image>
任务
@@ -84,7 +85,7 @@
</view>
<view class="view-result" v-show="taskList.length === 0">暂无数据</view>
</view>
<view class="test-view model-view" v-show="showModelList('test')">
<view class="test-view model-view" v-show="showModelList('test', testList)">
<view class="view-title">
<image src="@/static/images/search/result/test.png" class="view-title-img"></image>
AI陪练
@@ -92,7 +93,7 @@
<view class="view-list"></view>
<view class="view-result" v-show="testList.length === 0">暂无数据</view>
</view>
<view class="exam-view model-view" v-show="showModelList('exam')">
<view class="exam-view model-view" v-show="showModelList('exam', examList)">
<view class="view-title">
<image src="@/static/images/search/result/exam.png" class="view-title-img"></image>
考试
@@ -231,9 +232,22 @@ const testList = ref([])
const taskList = ref([])
const examList = ref([])
const courseList = ref([])
const showModelList = typeName => {
return ['', 'all', typeName].indexOf(activeTab.value) >= 0
const showModelList = (typeName, list=[]) => {
console.log(typeName, list.length > 0)
if ( ['', 'all'].indexOf(activeTab.value) >= 0){
return list.length > 0
}else{
return [typeName].indexOf(activeTab.value) >= 0
}
}
const showEmptyBox = computed(() => {
if ( ['', 'all'].indexOf(activeTab.value) >= 0){
let _num = testList.value.length + taskList.value.length + courseList.value.length + examList.value.length + abilityListShow.value.length
return _num === 0
}else{
return false
}
})
const changeTab = item => {
if (!item) return
activeTab.value = item.type
+108 -115
View File
@@ -14,25 +14,33 @@
<view class="font_pf">头像</view>
</template>
<template #value>
<view style="width: 42rpx; height: 42rpx;border-radius: 100%; overflow: hidden">
<view style="width: 42rpx; height: 42rpx; border-radius: 100%; overflow: hidden">
<image v-if="userInfo.imagePath" class="img_100" :src="userInfo.imagePath"></image>
<image-preview v-else class="img_100" :src="userInfo.imageAddr"></image-preview>
</view>
</template>
</uv-cell>
<uv-cell name="sex" isLink title="性别">
<!-- <uv-cell name="sex" isLink title="性别">
<template #title>
<text class="font_pf">性别</text>
</template>
<template #value>
<picker mode="selector" :value="index" range-key="label" style="width: 100%" :range="sexOptions"
@change="sexChange">
<view style="text-align: right">{{showGenderText}}</view>
<picker
mode="selector"
:value="index"
range-key="label"
style="width: 100%"
:range="sexOptions"
@change="sexChange"
>
<view style="text-align: right">{{ showGenderText }}</view>
</picker>
</template>
</uv-cell>
<uv-cell title="百禄形象" @click="gotoMascot()" :isLink="true" :border="false"></uv-cell>
<!-- <uv-cell :isLink="true" :border="false">
</uv-cell> -->
<uv-cell title="性别" @click="sexPicker.open()" :isLink="true" :value="showGenderText"></uv-cell>
<uv-cell title="百禄形象" @click="gotoMascot()" :isLink="true" :border="false" :value="mascotName"></uv-cell>
<!-- <uv-cell :isLink="true" :border="false">
<template #title>
<text class="font_pf">竞赛是否匿名</text>
</template>
@@ -45,113 +53,79 @@
</uv-cell> -->
</uv-cell-group>
</view>
<avatar-cropper v-if="avatarCropperStatus" mode="ratio" :imageUrl="avatarCropperUrl" :width="500" :height="500"
:delay="150" @cancel="onCancel" @confirm="onConfirm"></avatar-cropper>
<avatar-cropper
v-if="avatarCropperStatus"
mode="ratio"
:imageUrl="avatarCropperUrl"
:width="500"
:height="500"
:delay="150"
@cancel="onCancel"
@confirm="onConfirm"
></avatar-cropper>
<uv-picker ref="sexPicker" :columns="[sexOptions]" keyName="label" @confirm="sexConfirm"></uv-picker>
</view>
</template>
<script setup>
import {
reactive,
ref,
computed,
nextTick
} from 'vue';
import {
onLoad
} from '@dcloudio/uni-app';
import {
uploadFile
} from '@/api/common'
import {
queryCurrentUser
} from '@/api/login.js';
import { reactive, ref, computed, nextTick } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { uploadFile } from '@/api/common';
import { queryCurrentUser } from '@/api/login.js';
import common from '@/common/common';
import {
getUserInfo
} from '@/common/common';
import {
GENDER
} from '@/enum.js';
import {
updateDfSysUserExtandInfoGender,
updateDfSysUserExtandInfoImageAddr
} from "@/api/user.js"
import {
queryValidTraMascotInfo
} from "@/api/mascot.js"
import {
get_base_url
} from '@/api/request.js'
import { getUserInfo } from '@/common/common';
import { GENDER } from '@/enum.js';
import { updateDfSysUserExtandInfoGender, updateDfSysUserExtandInfoImageAddr } from '@/api/user.js';
import { queryValidTraMascotInfo } from '@/api/mascot.js';
import { get_base_url } from '@/api/request.js';
const index = ref(0);
const sexPicker = ref(null);
const avatarCropperUrl = ref('');
const aaa = ref('');
const userInfo = ref({
gender: '',
imagePath: '',
imageAddr: ''
imageAddr: '',
mascotInfo: null
});
const avatarCropperStatus = ref(true)
const sexOptions = GENDER
const anonymousOptions = [{
value: "y",
label: "是",
const avatarCropperStatus = ref(true);
const mascotName = computed(() => userInfo.value.mascotInfo?.mascotName || '');
const sexOptions = GENDER;
const anonymousOptions = [
{
value: 'y',
label: '是'
},
{
value: "n",
label: "否",
value: 'n',
label: '否'
}
] //
]; //
//
const gotoMascot = () => {
common.navigateTo('/pages/mascot/mascot_select_list?from=setting')
// common.loading()
// queryValidTraMascotInfo().then(({body}) => {
// console.log(body);
// common.setPageCache('mascotList', body)
// body.map(({imgAddr}) => {
// const url = imgAddr
// if (imgAddr){
// const img = new Image();
// img.onload = () => {
// console.log(`: ${url}`);
// // resolve(img);
// };
// img.onerror = () => {
// console.error(`: ${url}`);
// // reject(new Error(`Failed to load image: ${url}`));
// };
// img.src = base_url + url;
// }
// })
// common.hideLoading()
// common.navigateTo('/pages/mascot/mascot_select_list')
// })
}
common.navigateTo('/pages/mascot/mascot_select_list?from=setting');
};
//
const showGenderText = computed(() => {
const item = sexOptions.find((item) => item.value === userInfo.value.gender)
const item = sexOptions.find((item) => item.value === userInfo.value.gender);
if (item) {
return item.label
return item.label;
} else {
return '未填写'
return '未填写';
}
})
});
//
const showAnonymousText = computed(() => {
const item = anonymousOptions.find((item) => item.value === userInfo.value?.anonymous)
const item = anonymousOptions.find((item) => item.value === userInfo.value?.anonymous);
if (item) {
return item.label
return item.label;
} else {
return '否'
return '否';
}
})
});
//
const click_update_avatar = () => {
uni.chooseImage({
@@ -174,57 +148,76 @@
};
//
const onCancel = (value) => {
avatarCropperStatus.value = false
}
avatarCropperStatus.value = false;
};
//
const onConfirm = (file) => {
avatarCropperStatus.value = false
avatarCropperStatus.value = false;
nextTick(async () => {
common.loading('更新头像中')
common.loading('更新头像中');
try {
const data = {
bizScen: 'S3007',
thumbnailFlag: true,
thumbImgSize: 100
}
};
//
const res = await uploadFile(file.tempFilePath, data)
const res = await uploadFile(file.tempFilePath, data);
//
await updateDfSysUserExtandInfoImageAddr({
imageAddr: res.thumbFileId
})
});
//
const userData = await queryCurrentUser()
common.msg('修改成功')
userInfo.value.imageAddr = userData.imageAddr
userInfo.value.imagePath = ''
const userData = await queryCurrentUser();
common.msg('修改成功');
userInfo.value.imageAddr = userData.imageAddr;
userInfo.value.imagePath = '';
} catch {
common.msg('修改失败')
common.msg('修改失败');
}
common.hideLoading()
})
}
common.hideLoading();
});
};
//
const sexChange = (e) => {
userInfo.value.gender = sexOptions[e.detail.value].value;
common.loading("修改中")
const sexConfirm = (e) => {
userInfo.value.gender = e.value[0].value;
common.loading('修改中');
updateDfSysUserExtandInfoGender({
gender: userInfo.value.gender
}).then(async () => {
await queryCurrentUser()
common.msg('修改成功')
}).finally(() => {
common.hideLoading()
})
.then(async () => {
await queryCurrentUser();
common.msg('修改成功');
})
.finally(() => {
common.hideLoading();
});
};
// const sexChange = (e) => {
// userInfo.value.gender = sexOptions[e.detail.value].value;
// console.log('userInfo.value.gender', userInfo.value.gender);
// common.loading('');
// updateDfSysUserExtandInfoGender({
// gender: userInfo.value.gender
// })
// .then(async () => {
// await queryCurrentUser();
// common.msg('');
// })
// .finally(() => {
// common.hideLoading();
// });
// };
const anonymousChange = (e) => {
userInfo.value.anonymous = anonymousOptions[e.detail.value].value;
// common.loading("")
common.msg('修改成功')
common.msg('修改成功');
};
onLoad((params) => {
userInfo.value = getUserInfo()
userInfo.value = getUserInfo();
console.log('userInfo.value', userInfo.value);
});
</script>
@@ -255,7 +248,7 @@
}
.detail-main {
background-color: #F6F8FF;
background-color: #f6f8ff;
}
.title {
@@ -303,4 +296,4 @@
}
}
}
</style>
</style>
-2
View File
@@ -22,7 +22,6 @@
</template>
<script setup>
import watermark from '@/components/jm-watermark/jm-watermark.vue'
import common from '@/common/common';
import {
get_base_url
@@ -30,7 +29,6 @@
const url = get_base_url();
const ENV = import.meta.env
const appBaseInfo = uni.getAppBaseInfo()
console.log('appBaseInfo', appBaseInfo);
import showDialog from '@/common/dialogMessage'
const bbbb = () => {
+33
View File
@@ -0,0 +1,33 @@
## 1.0.142023-12-29
1. 修复上个版本引出的BUG
## 1.0.132023-12-26
1. 修复抖音小程序滚到底不触发change的BUG
## 1.0.122023-11-20
1. 修复issues反馈的问题uv-picker在组合式API的自定义组件中,columns动态赋值无法显示选项:https://gitee.com/climblee/uv-ui/issues/I8H0GQ
## 1.0.112023-10-11
1. 将immediate-change默认值改为true,该值在于change回调的及时性,微信小程序生效
## 1.0.102023-08-25
1. 增加round属性设置弹窗圆角,默认为0
## 1.0.92023-08-24
1. 修复cli项目不返回值的问题
## 1.0.82023-08-04
1. 优化
## 1.0.72023-08-02
1. 改组件中删除uv-toolbar组件,请单独下载uv-toolbar组件
## 1.0.62023-07-02
uv-picker 由于弹出层uv-popup的修改,打开和关闭方法更改,详情参考文档:https://www.uvui.cn/components/picker.html
## 1.0.52023-06-26
1. 增加color参数
2. 增加activeColor参数
## 1.0.42023-06-15
1. 修改支付宝报错的BUG
## 1.0.32023-06-12
1. setColumnValues的使用统一化,避免某些平台报错
2. 取消change回调回传的组件实例,直接统一通过ref的方式调取setColumnValues方法
## 1.0.22023-05-23
1. uv-toolbar组件新增下边框属性
## 1.0.12023-05-16
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
2. 优化部分功能
## 1.0.02023-05-10
uv-picker 选择器
@@ -0,0 +1,95 @@
export default {
props: {
// 是否展示顶部的操作栏
showToolbar: {
type: Boolean,
default: true
},
// 顶部标题
title: {
type: String,
default: ''
},
// 弹窗圆角
round: {
type: [String, Number],
default: 0
},
// 对象数组,设置每一列的数据
columns: {
type: Array,
default: () => []
},
// 是否显示加载中状态
loading: {
type: Boolean,
default: false
},
// 各列中,单个选项的高度
itemHeight: {
type: [String, Number],
default: 44
},
// 取消按钮的文字
cancelText: {
type: String,
default: '取消'
},
// 确认按钮的文字
confirmText: {
type: String,
default: '确定'
},
// 取消按钮的颜色
cancelColor: {
type: String,
default: '#909193'
},
// 确认按钮的颜色
confirmColor: {
type: String,
default: '#3c9cff'
},
// 文字颜色
color: {
type: String,
default: ''
},
// 选中文字的颜色
activeColor: {
type: String,
default: ''
},
// 每列中可见选项的数量
visibleItemCount: {
type: [String, Number],
default: 5
},
// 选项对象中,需要展示的属性键名
keyName: {
type: String,
default: 'text'
},
// 是否允许点击遮罩关闭选择器
closeOnClickOverlay: {
type: Boolean,
default: true
},
// 是否允许点击确认关闭选择器
closeOnClickConfirm: {
type: Boolean,
default: true
},
// 各列的默认索引
defaultIndex: {
type: Array,
default: () => [],
},
// 是否在手指松开时立即触发 change 事件。若不开启则会在滚动动画结束后触发 change 事件,只在微信2.21.1及以上有效
immediateChange: {
type: Boolean,
default: true
},
...uni.$uv?.props?.picker
}
}
@@ -0,0 +1,330 @@
<template>
<uv-popup
ref="pickerPopup"
mode="bottom"
:round="round"
:close-on-click-overlay="closeOnClickOverlay"
@change="popupChange"
>
<view class="uv-picker">
<uv-toolbar
v-if="showToolbar"
:cancelColor="cancelColor"
:confirmColor="confirmColor"
:cancelText="cancelText"
:confirmText="confirmText"
:title="title"
@cancel="cancel"
@confirm="confirm"
></uv-toolbar>
<!-- #ifdef MP-TOUTIAO -->
<picker-view
class="uv-picker__view"
:indicatorStyle="`height: ${$uv.addUnit(itemHeight)}`"
:value="innerIndex"
:immediateChange="immediateChange"
:style="{
height: `${$uv.addUnit(visibleItemCount * itemHeight)}`
}"
@pickend="changeHandler"
>
<!-- #endif -->
<!-- #ifndef MP-TOUTIAO -->
<picker-view
class="uv-picker__view"
:indicatorStyle="`height: ${$uv.addUnit(itemHeight)}`"
:value="innerIndex"
:immediateChange="immediateChange"
:style="{
height: `${$uv.addUnit(visibleItemCount * itemHeight)}`
}"
@change="changeHandler"
>
<!-- #endif -->
<!-- @pickend在这里为了解决抖音等滚到底不触发change兼容性问题 -->
<picker-view-column
v-for="(item, index) in innerColumns"
:key="index"
class="uv-picker__view__column"
>
<text
v-if="$uv.test.array(item)"
class="uv-picker__view__column__item uv-line-1"
v-for="(item1, index1) in item"
:key="index1"
:style="[{
height: $uv.addUnit(itemHeight),
lineHeight: $uv.addUnit(itemHeight),
fontWeight: index1 === innerIndex[index] ? 'bold' : 'normal'
},textStyle(index,index1)]"
>{{ getItemText(item1) }}</text>
</picker-view-column>
</picker-view>
<view
v-if="loading"
class="uv-picker--loading"
>
<uv-loading-icon mode="circle"></uv-loading-icon>
</view>
</view>
</uv-popup>
</template>
<script>
/**
* uv-picker
* @description 选择器
* @property {Boolean} showToolbar 是否显示顶部的操作栏默认 true
* @property {String} title 顶部标题
* @property {Array} columns 对象数组设置每一列的数据
* @property {Boolean} loading 是否显示加载中状态默认 false
* @property {String | Number} itemHeight 各列中单个选项的高度默认 44
* @property {String} cancelText 取消按钮的文字默认 '取消'
* @property {String} confirmText 确认按钮的文字默认 '确定'
* @property {String} cancelColor 取消按钮的颜色默认 '#909193'
* @property {String} confirmColor 确认按钮的颜色默认 '#3c9cff'
* @property {String} color 文字颜色默认 ''
* @property {String} activeColor 选中文字的颜色默认 ''
* @property {String | Number} visibleItemCount 每列中可见选项的数量默认 5
* @property {String} keyName 选项对象中需要展示的属性键名默认 'text'
* @property {Boolean} closeOnClickOverlay 是否允许点击遮罩关闭选择器默认 false
* @property {Array} defaultIndex 各列的默认索引
* @property {Boolean} immediateChange 是否在手指松开时立即触发change事件默认 false
* @event {Function} close 关闭选择器时触发
* @event {Function} cancel 点击取消按钮触发
* @event {Function} change 当选择值变化时触发
* @event {Function} confirm 点击确定按钮返回当前选择的值
*/
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
import props from './props.js';
export default {
name: 'uv-picker',
emits: ['confirm','cancel','close','change'],
mixins: [mpMixin, mixin, props],
computed: {
//
textStyle(){
return (index,index1) => {
const style = {};
// #ifndef APP-NVUE
style.display = 'block';
// #endif
if(this.color) {
style.color = this.color;
}
if(this.activeColor && index1 === this.innerIndex[index]) {
style.color = this.activeColor;
}
return style;
}
}
},
data() {
return {
//
lastIndex: [],
// picker-viewvalue
innerIndex: [],
//
innerColumns: [],
//
columnIndex: 0,
}
},
watch: {
//
defaultIndex: {
immediate: true,
handler(n) {
this.setIndexs(n, true)
}
},
// columns
columns: {
deep: true,
immediate: true,
handler(n) {
this.setColumns(n)
}
},
},
methods: {
open() {
this.$refs.pickerPopup.open();
},
close() {
this.$refs.pickerPopup.close();
},
popupChange(e) {
if(!e.show) this.$emit('close');
},
// item
getItemText(item) {
if (this.$uv.test.object(item)) {
return item[this.keyName]
} else {
return item
}
},
//
cancel() {
this.$emit('cancel');
this.close();
},
//
confirm() {
// 使deepClonevue3cli
this.$emit('confirm', this.$uv.deepClone({
indexs: this.innerIndex,
value: this.innerColumns.map((item, index) => item[this.innerIndex[index]]),
values: this.innerColumns
}));
if(this.closeOnClickConfirm) {
this.close();
}
},
//
changeHandler(e) {
const {
value
} = e.detail
let index = 0,
columnIndex = 0
//
for (let i = 0; i < value.length; i++) {
let item = value[i]
if (item !== (this.lastIndex[i] || 0)) { // undefined0
// columnIndex
columnIndex = i
// index
index = item
break // 使
}
}
this.columnIndex = columnIndex
const values = this.innerColumns
// ""
this.setLastIndex(value)
this.setIndexs(value)
this.$emit('change', {
value: this.innerColumns.map((item, index) => item[value[index]]),
index,
indexs: value,
// values
values,
columnIndex
})
},
// index
setIndexs(index, setLastIndex) {
this.innerIndex = this.$uv.deepClone(index)
if (setLastIndex) {
this.setLastIndex(index)
}
},
//
setLastIndex(index) {
// changeHandler
//
this.lastIndex = this.$uv.deepClone(index)
},
//
setColumnValues(columnIndex, values) {
// innerColumnscolumnIndexvalues使splice
this.innerColumns.splice(columnIndex, 1, values)
// innerIndex0
let tmpIndex = this.$uv.deepClone(this.innerIndex)
for (let i = 0; i < this.innerColumns.length; i++) {
if (i > this.columnIndex) {
tmpIndex[i] = 0
}
}
//
this.setIndexs(tmpIndex)
},
//
getColumnValues(columnIndex) {
// changesetColumnValues
// changegetColumnValues
(async () => {
await this.$uv.sleep()
})()
return this.innerColumns[columnIndex]
},
// columns
setColumns(columns) {
this.innerColumns = this.$uv.deepClone(columns)
// defaultIndex0
if (this.innerIndex.length === 0) {
this.innerIndex = new Array(columns.length).fill(0)
}
},
//
getIndexs() {
return this.innerIndex
},
//
getValues() {
// changesetColumnValues
// changegetValues
(async () => {
await this.$uv.sleep()
})()
return this.innerColumns.map((item, index) => item[this.innerIndex[index]])
}
},
}
</script>
<style lang="scss" scoped>
$show-lines: 1;
@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
.uv-picker {
position: relative;
&__view {
&__column {
@include flex;
flex: 1;
justify-content: center;
&__item {
@include flex;
justify-content: center;
align-items: center;
font-size: 16px;
text-align: center;
/* #ifndef APP-NVUE */
display: block;
/* #endif */
color: $uv-main-color;
&--disabled {
/* #ifndef APP-NVUE */
cursor: not-allowed;
/* #endif */
opacity: 0.35;
}
}
}
}
&--loading {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
@include flex;
justify-content: center;
align-items: center;
background-color: rgba(255, 255, 255, 0.87);
z-index: 1000;
}
}
</style>
+90
View File
@@ -0,0 +1,90 @@
{
"id": "uv-picker",
"displayName": "uv-picker 选择器 全面兼容vue3+2、app、h5、小程序等多端",
"version": "1.0.14",
"description": "uv-picker 此选择器用于单列,多列,多列联动的选择场景...",
"keywords": [
"uv-picker",
"uvui",
"uv-ui",
"picker",
"联动选择"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [
"uv-ui-tools",
"uv-popup",
"uv-loading-icon",
"uv-toolbar"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}
+21
View File
@@ -0,0 +1,21 @@
## Picker 选择器
> **组件名:uv-picker**
此选择器用于单列,多列,多列联动的选择场景。
`uv-datetime-picker`等组件也用到了该组件,功能完善,需要特别注意的是`columns`参数的形式是数组嵌套。
# <a href="https://www.uvui.cn/components/picker.html" target="_blank">查看文档</a>
## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP</small>
### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)
</a>
#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
+2
View File
@@ -0,0 +1,2 @@
## 1.0.02023-08-02
1. 新增工具条组件
@@ -0,0 +1,40 @@
export default {
props: {
// 是否展示工具条
show: {
type: Boolean,
default: true
},
// 是否显示下边框
showBorder: {
type: Boolean,
default: false
},
// 取消按钮的文字
cancelText: {
type: String,
default: '取消'
},
// 确认按钮的文字
confirmText: {
type: String,
default: '确认'
},
// 取消按钮的颜色
cancelColor: {
type: String,
default: '#909193'
},
// 确认按钮的颜色
confirmColor: {
type: String,
default: '#3c9cff'
},
// 标题文字
title: {
type: String,
default: ''
},
...uni.$uv?.props?.toolbar
}
}
@@ -0,0 +1,109 @@
<template>
<view
:class="['uv-toolbar',{'uv-border-bottom':showBorder}]"
@touchmove.stop.prevent="noop"
v-if="show"
>
<view
class="uv-toolbar__cancel__wrapper"
hover-class="uv-hover-class"
>
<text
class="uv-toolbar__wrapper__cancel"
@tap="cancel"
:style="{
color: cancelColor
}"
>{{ cancelText }}</text>
</view>
<text
class="uv-toolbar__title uv-line-1"
v-if="title"
>{{ title }}</text>
<view
class="uv-toolbar__confirm__wrapper"
hover-class="uv-hover-class"
>
<text
class="uv-toolbar__wrapper__confirm"
@tap="confirm"
:style="{
color: confirmColor
}"
>{{ confirmText }}</text>
</view>
</view>
</template>
<script>
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
import props from './props.js';
/**
* Toolbar 工具条
* @description
* @tutorial https://www.uvui.cn/components/toolbar.html
* @property {Boolean} show 是否展示工具条默认 true
* @property {Boolean} showBorder 是否展示工具条下方边框默认 false
* @property {String} cancelText 取消按钮的文字默认 '取消'
* @property {String} confirmText 确认按钮的文字默认 '确认'
* @property {String} cancelColor 取消按钮的颜色默认 '#909193'
* @property {String} confirmColor 确认按钮的颜色默认 '#3c9cff'
* @property {String} title 标题文字
* @event {Function}
* @example
*/
export default {
name: 'uv-toolbar',
emits: ['confirm', 'cancel'],
mixins: [mpMixin, mixin, props],
methods: {
//
cancel() {
this.$emit('cancel')
},
//
confirm() {
this.$emit('confirm')
}
}
}
</script>
<style lang="scss" scoped>
$show-lines: 1;
$show-hover: 1;
$show-border: 1;
$show-border-bottom: 1;
@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
$uv-tips-color: #909193 !default;
$uv-main-color: #303133 !default;
$uv-primary: #3c9cff !default;
.uv-toolbar {
height: 42px;
@include flex;
justify-content: space-between;
align-items: center;
&__wrapper {
&__cancel {
color: $uv-tips-color;
font-size: 15px;
padding: 0 15px;
}
}
&__title {
color: $uv-main-color;
padding: 0 60rpx;
font-size: 16px;
flex: 1;
text-align: center;
}
&__wrapper {
&__confirm {
color: $uv-primary;
font-size: 15px;
padding: 0 15px;
}
}
}
</style>
+87
View File
@@ -0,0 +1,87 @@
{
"id": "uv-toolbar",
"displayName": "uv-toolbar 工具条",
"version": "1.0.0",
"description": "该组价是仅用于uv-ui中一个公共小工具,提供一个取消和确定的样式,可以设置标题,主要用于弹窗顶部的选择确定工具条",
"keywords": [
"uv-toolbar",
"uvui",
"uv-ui",
"工具条",
"工具"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [
"uv-ui-tools"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}
+31
View File
@@ -0,0 +1,31 @@
## Toolbar 工具条
> **组件名:uv-toolbar**
该组价是仅用于uv-ui中一个公共小工具,提供一个取消和确定的样式,可以设置标题,主要用于弹窗顶部的选择确定工具条。
### 基本使用
```vue
<uv-toolbar title="标题文字"></uv-toolbar>
```
### Toolbar Props
| 属性名 | 类型 | 默认值 | 说明 |
|:-|:-|:-|:-|
| show | Boolean | true | 是否展示工具条 |
| showBorder | Boolean | false | 是否显示下边框 |
| cancelText | String | '取消' | 取消按钮的文字 |
| confirmText | String | '确定' | 确定按钮的文字 |
| cancelColor | String | '#909193' | 取消按钮的颜色 |
| confirmColor | String | '#3c9cff' | 确认按钮的颜色 |
| title | String | - | 标题文字 |
## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)
### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)
#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>