修改获取录音权限,修改登录页完成直接进首页,首页下方课程列表组件化

This commit is contained in:
田岩
2025-09-20 16:40:34 +08:00
parent d3729efc4f
commit 6c5ed19ed9
23 changed files with 821 additions and 346 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
# app入口
# VITE_APP_BASE_API_Url = 'http://25.16.122.65:7001'
#VITE_APP_BASE_API_Url = 'http://25.16.122.65:7001'
# VITE_APP_BASE_API_Url = 'http://25.16.122.92:9786'
# VITE_APP_BASE_API_Url = 'http://25.18.122.66:9786'
+2 -1
View File
@@ -95,6 +95,8 @@ export const commonUploadVoiceFile = (file, url, data = {}, fileKey = 'voice') =
} 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) {
@@ -102,7 +104,6 @@ export const commonUploadVoiceFile = (file, url, data = {}, fileKey = 'voice') =
}
},
fail(error) {
console.log('err', error);
return reject('Other');
}
});
+2 -1
View File
@@ -50,7 +50,8 @@ export const goto_login_fun = (from = 'http') => {
if (from === 'socket' && page_route !== '') {
common.redirectTo('/pages/login/login?path=' + page_route)
} else {
common.navigateTo('/pages/login/login')
common.redirectTo('/pages/login/login')
// common.navigateTo('/pages/login/login')
}
}).finally(() => {
+4 -3
View File
@@ -75,12 +75,13 @@ const RecordPermission = {
// 安卓需要再次请求权限
const permResult = await permission.requestAndroidPermission(
'android.permission.RECORD_AUDIO');
console.log('permResultx', permResult);
if(permResult === 1) {
resolve('need_request');
} else {
reject('permResultx')
}
} else {
reject('need_request');
resolve('need_request');
}
// #endif
}
+13 -13
View File
@@ -189,15 +189,15 @@
item_answer.value = typeof newVal === 'string' ? newVal.split(',') : newVal;
}
);
watch(
item_answer,
(newVal) => {
console.log('item_answer', newVal);
emit('update:modelValue', 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);
};
@@ -297,7 +297,7 @@
// 无论什么类型如果表里已经有了,再次点击就是弹出
if (item_answer.value.includes(analysisVo.itemId)) {
item_answer.value = item_answer.value.filter((id) => id !== analysisVo.itemId);
// modelValue.value = item_answer.value;
modelValue.value = item_answer.value;
if (!['study'].includes(props.mode)) {
muCheckbox('choose');
}
@@ -305,13 +305,13 @@
// 单选
// 单选插入,并且移除其他
item_answer.value[0] = analysisVo.itemId;
// modelValue.value = item_answer.value;
modelValue.value = item_answer.value;
muCheckbox('choose');
} else if (props.item.qnsTyp === 'MU') {
// 多选题
// 多选直接插入
item_answer.value.push(analysisVo.itemId);
// modelValue.value = item_answer.value;
modelValue.value = item_answer.value;
if (!['study'].includes(props.mode)) {
muCheckbox('choose');
}
@@ -319,7 +319,7 @@
// 判断题
// 判断插入,并且移除其他
item_answer.value = [analysisVo.itemId];
// modelValue.value = item_answer.value;
modelValue.value = item_answer.value;
muCheckbox('choose');
}
};
-5
View File
@@ -163,10 +163,6 @@
const endTime = Date.now();
const times = Math.max(500 - endTime + startTime, 0);
setTimeout(() => {
console.log('延迟数据', times);
// nextTick(() => {
// });
popupRef.value.close();
console.log('停止录音');
recorderManager.stop();
@@ -177,7 +173,6 @@
onMounted(async () => {
// #ifdef APP-PLUS
recorderManager.onStop(function (res) {
console.log('recorder stop' + JSON.stringify(res));
console.log('录音文件临时位置', res.tempFilePath, isOut.value);
if (isOut.value) return; // 被划掉的语音
const endTime = Date.now();
+89 -90
View File
@@ -1,52 +1,51 @@
<template>
<view v-bind="$attrs">
<image :src="imgUrlShow" :mode="mode" @click="showDetail" style="width: 100%;height: 100%;"></image>
<!-- <uv-overlay :show="showModel" opacity=".6" @click="showModel = false"> -->
<view>
<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)">
<view class="overlay-box" @click="closePopup">
<image class="detail-img" :src="imgUrlShow" :mode="mode"></image>
</view>
</uni-popup>
<!-- </uv-overlay> -->
</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 { previewFile, previewCacheThumbImage } from '@/api/common.js';
import { get_base_url } from '@/api/request.js';
import { useStorageStore } from '@/store/storage.js';
// const storageStore = useStorageStore()
export default {
name:"image-preview",
props:{
imgId:{
default:'',
type:String
name: 'image-preview',
props: {
imgId: {
default: '',
type: String
},
src:{
default:'',
type:String
src: {
default: '',
type: String
},
width:{
default:50,
type:[String, Number]
width: {
default: 50,
type: [String, Number]
},
height:{
default:50,
type:[String, Number]
height: {
default: 50,
type: [String, Number]
},
isCache:{ // 获取缩略图
isCache: {
// 获取缩略图
type: Boolean,
default: false
},
mode:{
mode: {
type: String,
default: 'scaleToFill'
default: 'aspectFill'
},
previewDetail: {
default: '',
type:[String, Boolean]
type: [String, Boolean]
}
},
data() {
@@ -57,122 +56,122 @@
showModel: false
};
},
computed:{
storageStore:() => useStorageStore(),
computed: {
storageStore: () => useStorageStore(),
imageObj() {
return this.storageStore?.imgObj||{}
}
return this.storageStore?.imgObj || {};
}
},
watch: {
imgId: {
handler(newval, oldval) {
if(newval.indexOf('/') === 0) {
this.imgUrlShow = this.base_url + newval
return
if (newval.indexOf('/') === 0) {
this.imgUrlShow = this.base_url + newval;
return;
}
if(newval.indexOf('http') === 0){
this.imgUrlShow = newval
return
if (newval.indexOf('http') === 0) {
this.imgUrlShow = newval;
return;
}
if (newval && this.watching) {
this.getPreviewUrlBlob(newval);
}
},
immediate: true,
deep: true,
deep: true
},
src: {
handler(newval, oldval) {
if (newval && this.watching) {
this.imgUrlShow = this.base_url + this.src
this.imgUrlShow = this.base_url + this.src;
}
},
immediate: true,
deep: true,
deep: true
}
},
methods: {
closePopup(){
this.showModel = false
this.storageStore.setTouchBtnZIndex(9)
this.$refs.PopupRef.close()
closePopup() {
this.showModel = false;
this.storageStore.setTouchBtnZIndex(9);
this.$refs.PopupRef.close();
},
showDetail(){
if(!!this.previewDetail){
this.showModel = true
this.storageStore.setTouchBtnZIndex(-1)
this.$refs.PopupRef.open()
showDetail() {
if (!!this.previewDetail) {
this.showModel = true;
this.storageStore.setTouchBtnZIndex(-1);
this.$refs.PopupRef.open();
}
},
getPreviewUrlBlob(id) {
if (!id) {
this.imgUrlShow = ''
return
this.imgUrlShow = '';
return;
}
if(this.isCache){
let _cache = this.imageObj[`/${id}/${this.width}/${this.height}`] || ''
if(_cache){
if (this.isCache) {
let _cache = this.imageObj[`/${id}/${this.width}/${this.height}`] || '';
if (_cache) {
this.imgUrlShow = _cache;
return
return;
}
previewCacheThumbImage(id, this.width,this.height).then((res) => {
previewCacheThumbImage(id, this.width, this.height).then((res) => {
this.imgUrlShow = res;
this.storageStore.setStorage('image', `/${id}/${this.width}/${this.height}`, res)
this.storageStore.setStorage('image', `/${id}/${this.width}/${this.height}`, res);
});
}else{
let _cache = this.imageObj[`${id}`] || ''
if(_cache){
} else {
let _cache = this.imageObj[`${id}`] || '';
if (_cache) {
this.imgUrlShow = _cache;
return
return;
}
previewFile(id).then((result) => {
//#ifdef APP-PLUS
let _header = result.header['Content-Type'].split(';')[0]
let _header = result.header['Content-Type'].split(';')[0];
// #endif
//#ifndef APP-PLUS
let _header = result.header['content-type'].split(';')[0]
let _header = result.header['content-type'].split(';')[0];
// #endif
let base64 = `data:${_header};base64,` + uni.arrayBufferToBase64(result.data)
let base64 = `data:${_header};base64,` + uni.arrayBufferToBase64(result.data);
this.imgUrlShow = base64;
this.storageStore.setStorage('image', id, base64)
this.storageStore.setStorage('image', id, base64);
});
}
},
}
},
mounted() {
if (this.imgId) {
if(this.imgId.indexOf('/') === 0) {
this.imgUrlShow = this.base_url + this.imgId
}else if(this.imgId.indexOf('http') === 0){
this.imgUrlShow = this.imgId
}else{
if (this.imgId.indexOf('/') === 0) {
this.imgUrlShow = this.base_url + this.imgId;
} else if (this.imgId.indexOf('http') === 0) {
this.imgUrlShow = this.imgId;
} else {
this.getPreviewUrlBlob(this.imgId);
}
}
this.watching = true
if(this.src) {
this.imgUrlShow = this.base_url + this.src
this.watching = true;
if (this.src) {
this.imgUrlShow = this.base_url + this.src;
}
}
}
};
</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, .65);
.detail-img{
position: absolute;
left: 50%;
top: 50%;
.overlay-box {
position: fixed;
left: 0;
top: 0;
width: 100vw;
max-height: 100%;
transform: translate(-50%, -50%);
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>
</style>
@@ -2,7 +2,7 @@
<view class="item">
<view class="details_div">
<view class="img_div">
<image-preview class="img" :src="item.ossAddr" mode="scaleToFill"></image-preview>
<image-preview class="img" :src="item.ossAddr"></image-preview>
</view>
<view class="right_div">
<view class="title ellipsis-text">
@@ -128,13 +128,13 @@
.img_div {
position: relative;
width: 226rpx;
width: 224rpx;
height: 168rpx;
overflow: hidden;
border-radius: 22rpx;
.img {
width: 226rpx;
width: 224rpx;
height: 168rpx;
}
@@ -2,7 +2,7 @@
<view class="item">
<view class="details_div">
<view class="img_div">
<image-preview class="img" :src="item.ossAddr" mode="scaleToFill"></image-preview>
<image-preview class="img" :src="item.ossAddr"></image-preview>
</view>
<view class="right_div">
<view class="title ellipsis-text">
@@ -127,13 +127,13 @@
.img_div {
position: relative;
width: 226rpx;
width: 224rpx;
height: 168rpx;
overflow: hidden;
border-radius: 22rpx;
.img {
width: 226rpx;
width: 224rpx;
height: 168rpx;
}
@@ -2,7 +2,7 @@
<view class="item">
<view class="details_div">
<view class="img_div">
<image-preview class="img" :src="item.ossAddr" mode="scaleToFill"></image-preview>
<image-preview class="img" :src="item.ossAddr"></image-preview>
</view>
<view class="right_div">
<view class="title ellipsis-text">
@@ -128,13 +128,13 @@
.img_div {
position: relative;
width: 226rpx;
width: 224rpx;
height: 168rpx;
overflow: hidden;
border-radius: 22rpx;
.img {
width: 226rpx;
width: 224rpx;
height: 168rpx;
}
+4 -1
View File
@@ -303,12 +303,13 @@
const topic = topicList[questionNumber.value];
touchBtnRef.value?.clearinputText(); // 清除发送框数据
if (type === 'voice') {
if(topic.es_status == '00') return;
if (topic.es_status == '00') return;
topic.es_status = '00'; // 00转圈
const voicePath = submitObj;
commonUploadVoiceFile(voicePath, '/traask/traAskchat/voiceTrans', {})
.then((res) => {
let _res = JSON.parse(res.data);
console.log('voicePath_res_res_res', _res);
if (_res.rtnCode === '0000') {
const _content = _res.body.transContent.trim();
if (_content === '') {
@@ -351,6 +352,7 @@
// 添加试题答案缓存
const addTopicAnswerCache = () => {
const my_answer = topic.value.my_answer;
console.log('my_answer', my_answer);
const qnsId = topic.value.qnsId;
console.log('asdsadas', answerCacheList.value);
const newCache = { ...answerCacheList.value };
@@ -611,6 +613,7 @@
addTopicAnswerCache();
const answerDtoList = getAnswerDtoList();
console.log('answerDtoList', answerDtoList);
commitPaperCache({
examTyp: paperData.examId ? 'PAPERS' : 'CRS',
papersId: paperData.papersId,
@@ -19,7 +19,7 @@
>
<view class="item" @click="click_item(item)">
<view class="img_div">
<image-preview class="img" :src="item?.ossAddr" mode="scaleToFill"></image-preview>
<image-preview class="img" :src="item?.ossAddr"></image-preview>
<view class="subscript">{{ item.evalGrade ?? 0 }}</view>
</view>
<view class="right_div">
@@ -19,7 +19,7 @@
>
<view class="item" :class="getStatusClass(item.isFinish)" @click="click_item(item)">
<view class="img_div">
<image-preview class="img" :src="item?.ossAddr" mode="scaleToFill"></image-preview>
<image-preview class="img" :src="item?.ossAddr"></image-preview>
<view class="subscript" v-if="item.isFinish === '02'">已完成</view>
<view class="subscript" v-else>未完成</view>
</view>
@@ -204,7 +204,7 @@
border-radius: 22rpx;
.img {
width: 226rpx;
width: 224rpx;
height: 168rpx;
}
+280
View File
@@ -0,0 +1,280 @@
<template>
<!-- 热门课程分模块标题 -->
<view class="module_title_div">
<view class="icon icon_1">
<image class="img" :src="icon"></image>
</view>
<view class="text font_pf">{{ title }}</view>
</view>
<!-- 热门课程 -->
<view class="hot_class_div">
<scroll-view class="scroll-view" :scroll-x="true" :show-scrollbar="false">
<view class="scroll-view-item" v-for="(item, index) in class_list" :key="index">
<view class="item_div">
<!-- <uv-skeletons :loading="!item.crsNum" :rows="3" avatar avatar-shape="square" :title="false">
<view class="item" @click="goto_course_detail(item.crsId)">
<view class="left">
<image-preview
class="img-box img"
:imgId="item.imgId"
:isCache="true"
:width="240"
:height="240"
></image-preview>
</view>
<view class="right">
<view class="title ellipsis-text">{{ item.crsName }}</view>
<view class="note ellipsis-text">更新{{ (item.mtTime || '').substr(0, 10) }}</view>
<view class="button_div">
<view class="note ellipsis-text">已学{{ item.accmStdyCnt }}人次</view>
<view class="button">去学习</view>
</view>
</view>
</view>
</uv-skeletons> -->
<uv-skeletons :loading="!item.crsNum" avatar avatar-shape="square" :title="false" :skeleton="skeleton">
<view class="item" @click="goto_course_detail(item.crsId)">
<view class="left">
<image-preview
class="img-box img"
:imgId="item.imgId"
:isCache="true"
:width="240"
:height="160"
></image-preview>
</view>
<view class="right">
<view class="title ellipsis-text">
{{ item.crsName }}
</view>
<view class="note ellipsis-text">更新{{ (item.mtTime || '').substr(0, 10) }}</view>
<view class="button_div">
<view class="note ellipsis-text">已学{{ item.accmStdyCnt }}人次</view>
<view class="button">去学习</view>
</view>
</view>
</view>
</uv-skeletons>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { computed, nextTick, onBeforeMount, onMounted, reactive, ref } from 'vue';
import { queryCrsInfoByLastnew, queryCrsInfoByPopular, queryCrsInfoByRecmd } from '@/api/index.js';
import common from '@/common/common';
import hostIcon from '@/static/images/index/hot.png';
import recommendIcon from '@/static/images/index/recommend.png';
import newIcon from '@/static/images/index/new.png';
const props = defineProps({
name: {
type: String
}
});
// 1. 先定义命名函数,明确函数用途
const getConfigByProps = () => {
if (props.name === 'hot_class') {
return {
fetchFunction: queryCrsInfoByPopular,
title: '热门课程',
icon: hostIcon
};
} else if (props.name === 'recommend_class') {
return {
fetchFunction: queryCrsInfoByRecmd,
title: '推荐课程',
icon: recommendIcon
};
} else if (props.name === 'new_class') {
return {
fetchFunction: queryCrsInfoByLastnew,
title: '最新课程',
icon: newIcon
};
}
};
// 2. 调用命名函数,解构赋值
const { fetchFunction, title, icon } = getConfigByProps(); // 调用函数,获取返回对象后解构
const init_class_data = [
{
crsNum: '',
img: '',
crsName: '',
issuTm: '',
accmStdyCnt: ''
},
{
crsNum: '',
img: '',
crsName: '',
issuTm: '',
accmStdyCnt: ''
}
];
const class_list = ref([...init_class_data]);
const skeleton = [
{
type: 'flex',
num: 1,
children: [
{
type: 'avatar',
num: 1,
style: 'marginRight: 22rpx;width: 200rpx;height:150rpx;border-radius:16rpx;'
},
{
type: 'line',
num: 3,
style: ['width: 240rpx;', null, null]
}
]
}
];
const get_data = () => {
fetchFunction().then((res) => {
if (props.name === 'hot_class') {
} else {
// class_list.value = res.body;
}
class_list.value = res.body;
});
};
defineExpose({ get_data });
// 进入课程详情页
const goto_course_detail = (crsId) => {
common.navigateTo('/pages/courseDetail/index?crsId=' + crsId);
};
</script>
<style lang="scss" scoped>
$bg-margin-left: 30rpx;
// 设置骨架屏左侧图片框的大小,设置成和图片大小一致,否则横向滑动会串行(对不齐)
// 热门课程等三个横向滑动模块的样式
.hot_class_div {
margin-left: $bg-margin-left;
.scroll-view {
white-space: nowrap;
width: 100%;
}
.scroll-view-item {
display: inline-block;
margin-right: 30rpx;
background-color: #ffffff;
border-radius: 22rpx;
box-shadow: #eaeaea 0 0 6rpx;
padding: 32rpx 22rpx 36rpx 26rpx;
}
.item_div {
width: 562rpx;
// display: block;
}
.item {
display: flex;
width: 562rpx;
.left {
border-radius: 16rpx;
overflow: hidden;
width: 200rpx;
height: 150rpx;
.img {
width: 200rpx;
height: 150rpx;
}
}
.right {
overflow: hidden;
flex: 1;
padding-left: 22rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
.title {
font-weight: 500;
font-size: 30rpx;
color: #333333;
line-height: 40rpx;
}
.note {
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 23rpx;
color: #666666;
line-height: 33rpx;
text-align: left;
font-style: normal;
}
.button_div {
display: flex;
justify-content: space-between;
.button {
text-align: center;
line-height: 62rpx;
width: 138rpx;
height: 62rpx;
background: #ebf1ff;
border-radius: 30rpx;
font-weight: 500;
font-size: 30rpx;
color: #0066ff;
font-style: normal;
margin-right: 10rpx;
}
}
}
}
}
.module_title_div {
display: flex;
margin: 30rpx $bg-margin-left;
align-items: center;
.icon {
margin-right: 6rpx;
}
.icon_1 {
.img {
width: 31rpx;
height: 36rpx;
}
}
.icon_2 {
margin-top: 10rpx;
.img {
width: 38rpx;
height: 38rpx;
}
}
.icon_3 {
// margin-top: 10rpx;
.img {
width: 38rpx;
height: 31rpx;
}
}
.title {
font-size: 34rpx;
color: #323e57;
font-weight: 700;
line-height: 46rpx;
text-align: left;
}
}
</style>
+32 -201
View File
@@ -152,15 +152,12 @@
</view>
</view>
</view>
<!-- 热门课程分模块标题 -->
<view class="module_title_div">
<view class="icon icon_1">
<image class="img" src="@/static/images/index/hot.png"></image>
</view>
<view class="text font_pf">热门课程</view>
</view>
<!-- 热门课程 -->
<view class="hot_class_div">
<classScroll :ref="(el) => (classScrollRef[0] = el)" name="hot_class"></classScroll>
<classScroll :ref="(el) => (classScrollRef[1] = el)" name="recommend_class"></classScroll>
<classScroll :ref="(el) => (classScrollRef[2] = el)" name="new_class"></classScroll>
<!-- <view class="hot_class_div">
<scroll-view class="scroll-view" :scroll-x="true" :show-scrollbar="false">
<view class="scroll-view-item" v-for="(item, index) in hot_class_list" :key="index">
<view class="item_div">
@@ -170,7 +167,6 @@
<image-preview
class="img-box img"
:imgId="item.imgId"
mode="scaleToFill"
:isCache="true"
:width="240"
:height="240"
@@ -189,84 +185,8 @@
</view>
</view>
</scroll-view>
</view>
<!-- 推荐课程分模块标题 -->
<view class="module_title_div">
<view class="icon icon_2">
<image class="img" src="@/static/images/index/recommend.png"></image>
</view>
<view class="text font_pf">推荐课程</view>
</view>
<!-- 推荐课程 -->
<view class="hot_class_div">
<scroll-view class="scroll-view" :scroll-x="true" :show-scrollbar="false">
<view class="scroll-view-item" v-for="(item, index) in recommend_class_list" :key="index">
<view class="item_div">
<uv-skeleton :loading="!item.crsNum" :rows="3" avatar avatar-shape="square" :title="false">
<view class="item" @click="goto_course_detail(item.crsId)">
<view class="left">
<image-preview
class="img-box img"
:imgId="item.imgId"
mode="scaleToFill"
:isCache="true"
:width="240"
:height="240"
></image-preview>
</view>
<view class="right">
<view class="title ellipsis-text">{{ item.crsName }}</view>
<view class="note ellipsis-text">更新{{ (item.mtTime || '').substr(0, 10) }}</view>
<view class="button_div">
<view class="note ellipsis-text">已学{{ item.accmStdyCnt }}人次</view>
<view class="button">去学习</view>
</view>
</view>
</view>
</uv-skeleton>
</view>
</view>
</scroll-view>
</view>
<!-- 最新课程分模块标题 -->
<view class="module_title_div">
<view class="icon icon_3">
<image class="img" src="@/static/images/index/new.png"></image>
</view>
<view class="text font_pf">最新课程</view>
</view>
<!-- 最新课程 -->
<view class="hot_class_div">
<scroll-view class="scroll-view" :scroll-x="true" :show-scrollbar="false">
<view class="scroll-view-item" v-for="(item, index) in new_class_list" :key="index">
<view class="item_div">
<uv-skeleton :loading="!item.crsNum" :rows="3" avatar avatar-shape="square" :title="false">
<view class="item" @click="goto_course_detail(item.crsId)">
<view class="left">
<image-preview
class="img-box img"
:imgId="item.imgId"
mode="aspectFill"
:isCache="true"
:width="240"
:height="240"
></image-preview>
</view>
<view class="right">
<view class="title ellipsis-text">{{ item.crsName }}</view>
<view class="note ellipsis-text">更新{{ (item.mtTime || '').substr(0, 10) }}</view>
<view class="button_div">
<view class="note ellipsis-text">已学{{ item.accmStdyCnt }}人次</view>
<view class="button">去学习</view>
</view>
</view>
</view>
</uv-skeleton>
</view>
</view>
</scroll-view>
</view>
</view> -->
<view class="bottom_line">
<uv-divider text="我是有底线的" lineColor="#9cB0DC" :hairline="true"></uv-divider>
</view>
@@ -282,11 +202,13 @@
import { getUserInfo } from '@/common/common';
import common from '@/common/common';
import { setMascot } from '@/common/mascot.js';
import { getStatisticsData, queryCrsInfoByLastnew, queryCrsInfoByPopular, queryCrsInfoByRecmd } from '@/api/index.js';
import { getStatisticsData } from '@/api/index.js';
import { noticeMessageCount } from '@/api/messageNotification.js';
import classScroll from './components/class_scroll.vue';
const scrollTop = ref(0);
const no_read_message_num = ref(0);
const classScrollRef = ref([]);
// 处理显示的消息数量文本
const messageDisplayText = computed(() => {
return no_read_message_num.value > 99 ? '99+' : no_read_message_num.value;
@@ -301,14 +223,13 @@
const stdtTmLenRef = ref(null);
const accmPracticeCntRef = ref(null);
const accmExamCntRef = ref(null);
const three_list_request_time = ref(0); // 三个列表更新时间
// 计算透明度
const opacity = computed(() => {
if (scrollTop.value <= 130) return 0;
return Math.min(3, (scrollTop.value - 130) / 30);
});
const bgOpacity = computed(() => {
return `rgba(246,248,255,${opacity.value})`;
});
@@ -319,27 +240,9 @@
});
// 滚动消息
const notice_text = ref(['绿色金融政策解析与商业银行绿色水水水水水水水水水水水水水', '绿色金融政策解析与商业银行红色']);
const init_class_data = [
{
crsNum: '',
img: '',
crsName: '',
issuTm: '',
accmStdyCnt: ''
},
{
crsNum: '',
img: '',
crsName: '',
issuTm: '',
accmStdyCnt: ''
}
];
// 热门课程
const hot_class_list = ref([...init_class_data]);
const new_class_list = ref([...init_class_data]);
const recommend_class_list = ref([...init_class_data]);
const mascotInfo = ref({});
const statistics_data = reactive({
stdtTmLen: 0, // 学习
accmPracticeCnt: 0, // 训练
@@ -403,43 +306,19 @@
}
}
});
const index = ref(0);
const ai_test = () => {
index.value++;
if (index.value == 4) {
index.value = 1;
}
uni.setTabBarStyle({
midButton: {
iconPath: '/static/images/icon/fawn-' + index.value + '.png',
width: '86px',
height: '86px',
iconWidth: '90px'
}
});
};
// 进入搜索页
const goto_search_page = () => {
common.navigateTo('/pages/search/search?from=index');
};
// 进入课程详情页
const goto_course_detail = (crsId) => {
common.navigateTo('/pages/courseDetail/index?crsId=' + crsId);
};
const scrolltolower = () => {
common.navigateTo('/pages/index/dialog', {
animationType: 'slide-in-bottom'
});
};
onLoad(() => {
statistics_data['init'](); // 初始化数据
// 检测如果没设置吉祥物,跳转到吉祥物界面
// 判断下如果没有设置吉祥物就跳转到吉祥物页面
const userInfo = getUserInfo();
if (userInfo?.mascotInfo.mascotId === null) {
//说明这人在上次选吉祥物的时候退出了,没有选到吉祥物
//说明这人在上次选吉祥物的时候退出了,没有选到吉祥物(概率很低)
common.redirectTo('/pages/mascot/mascot_select_list');
return;
}
@@ -456,18 +335,12 @@
}
setMascot();
statistics_data['get_statistics_data'](); // 获取统计数据
queryCrsInfoByLastnew().then((res) => {
new_class_list.value = res.body;
});
queryCrsInfoByPopular().then((res) => {
hot_class_list.value = res.body;
});
queryCrsInfoByRecmd().then((res) => {
recommend_class_list.value = res.body;
nextTick(() => {
classScrollRef.value.forEach((item) => item?.get_data());
});
noticeMessageCount().then((res) => {
no_read_message_num.value = res.body;
// no_read_message_num.value = 109;
});
});
onHide(() => {});
@@ -476,11 +349,11 @@
<style scoped lang="scss">
$bg-margin-left: 30rpx;
// 设置骨架屏左侧图片框的大小,设置成和图片大小一致,否则横向滑动会串行(对不齐)
:deep(.uv-skeleton__wrapper__avatar) {
height: 146rpx !important;
width: 146rpx !important;
border-radius: 16rpx;
}
// :deep(.uv-skeleton__wrapper__avatar) {
// height: 146rpx !important;
// width: 146rpx !important;
// border-radius: 16rpx;
// }
// 底线样式
.bottom_line {
@@ -517,18 +390,18 @@
.left {
border-radius: 16rpx;
overflow: hidden;
width: 180rpx;
height: 146rpx;
.img {
width: 100%;
height: 100%;
width: 200rpx;
height: 150rpx;
}
// margin-right: 22rpx;
}
.right {
width: calc(100% - 180rpx);
margin-left: 22rpx;
flex: 1;
// width: calc(100% - 180rpx);
padding-left: 22rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
@@ -850,48 +723,6 @@
}
}
}
.module_title_div {
display: flex;
margin: 30rpx $bg-margin-left;
align-items: center;
.icon {
margin-right: 6rpx;
}
.icon_1 {
.img {
width: 31rpx;
height: 36rpx;
}
}
.icon_2 {
margin-top: 10rpx;
.img {
width: 38rpx;
height: 38rpx;
}
}
.icon_3 {
// margin-top: 10rpx;
.img {
width: 38rpx;
height: 31rpx;
}
}
.title {
font-size: 34rpx;
color: #323e57;
font-weight: 700;
line-height: 46rpx;
text-align: left;
}
}
}
.main_div {
@@ -1,7 +1,7 @@
<template>
<view class="item" :class="getStatusClass(item.isFinish)" @click="click_item(item)">
<view class="img_div">
<image-preview class="img" :src="item.ossAddr" mode="scaleToFill"></image-preview>
<image-preview class="img" :src="item.ossAddr"></image-preview>
<view class="subscript" v-if="item.isFinish === '02'">已完成</view>
<view class="subscript" v-else>未完成</view>
</view>
@@ -109,7 +109,7 @@
border-radius: 22rpx;
.img {
width: 226rpx;
width: 224rpx;
height: 168rpx;
}
+5 -7
View File
@@ -18,7 +18,7 @@
</template>
<view class="detail_div">
<view class="left">
<image-preview class="img" :src="detailData.ossAddr" mode="scaleToFill"></image-preview>
<image-preview class="img" :src="detailData.ossAddr"></image-preview>
</view>
<view class="right">
<view class="title_div">
@@ -508,8 +508,6 @@
}
.body-title {
width: 100%;
position: relative;
text-align: center;
@@ -522,14 +520,14 @@
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)); /* 宽度充满,高度自适应比例 */
background-position: top;
background-color: #fff;
}
.detail_div {
height: 250rpx;
z-index: 2;
@@ -558,8 +556,8 @@
border-radius: 0 0 0 28rpx;
}
.left > .img {
width: 225rpx;
height: 169rpx;
width: 224rpx;
height: 168rpx;
border-radius: 16rpx;
overflow: hidden;
}
@@ -9,7 +9,7 @@
>
<view class="item" v-for="item in data_list" @click="goto_course_detail(item)">
<view class="img_div">
<image-preview class="img" :imgId="item.imgId" mode="scaleToFill"></image-preview>
<image-preview class="img" :imgId="item.imgId"></image-preview>
</view>
<view class="right_div">
<view class="title ellipsis-text">{{ item.crsName }}</view>
@@ -60,7 +60,7 @@
height: 168rpx;
overflow: hidden;
border-radius: 22rpx;
.img {
width: 224rpx;
height: 168rpx;
+11 -7
View File
@@ -112,7 +112,9 @@
:custom-style="customStyle"
:throttleTime="300"
:hairline="false"
:loading="fixed_button_loading"
:disabled="isTimeInvalid"
loadingText="正在加载"
>
开始考试
</uv-button>
@@ -127,9 +129,8 @@
import { startTraExamPapersInfo, queryTraExamPapersByPapersId } from '@/api/examination.js';
import common from '@/common/common';
import { setPageCache, getPageCache } from '@/common/common';
import useZpaging from '@/composables/useZpaging.js';
const pagingRef = ref(null);
const fixed_button_loading = ref(false);
const detailData = reactive({
papersId: '',
papersName: '',
@@ -171,8 +172,6 @@
const query = () => {
setTimeout(() => {
queryTraExamPapersByPapersId({ papersId: detailData.papersId }).then((res) => {
console.log('水水水水', res.body);
const compareKeys = [
'examId',
'papersName',
@@ -215,7 +214,8 @@
return common.msg('考试时间已结束');
}
}
common.loading();
// common.loading();
fixed_button_loading.value = true;
startTraExamPapersInfo({
examId: detailData.examId
})
@@ -223,8 +223,12 @@
setPageCache('examination', { ...res.body, examId: detailData.examId });
common.navigateTo('/pages/examination/index');
})
.catch(() => {
pagingRef.value.reload();
})
.finally(() => {
common.hideLoading();
fixed_button_loading.value = false;
// common.hideLoading();
});
};
onLoad((e) => {
@@ -236,7 +240,7 @@
// 考试完成,考试中心这里任务要完成
setTimeout(() => {
pagingRef.value.reload();
}, 100);
}, 1000);
});
});
@@ -0,0 +1,4 @@
## 1.0.12023-11-15
1. 修复支付宝小程序报错的BUG
## 1.0.02023-10-30
1. 新版骨架屏上线
@@ -0,0 +1,247 @@
<template>
<view class="uv-skeleton">
<view v-if="loading">
<view v-for="(item, index) in elements" :key="index">
<!-- 横向并列布局 -->
<view class="uv-skeleton__group" :style="[style(item)]" v-if="item.type=='flex'">
<view v-for="(item2,index2) in item.children" :class="[item2.clas]" :style="[style(item2)]" :key="index2">
<view v-for="(item3,index3) in item2.elements" :key="index3">
<!-- 垂直高度站位 -->
<view :style="{height: $uv.addUnit(item3.height,'rpx')}" v-if="item3.type=='gap'"></view>
<view :class="[item3.clas, roundClass, animateClass]" :style="[style(item3)]" v-else ref="skeleton"></view>
</view>
</view>
</view>
<!-- 自定义骨架屏内容 -->
<!-- ps 自定义扩展说明 -->
<!-- 如果你需要自定义模板可以参照这个custom的写法增加一个skeleton配置类型编写布局和样式就可以了 -->
<!-- 注意事项为了保证骨架效果和动态效果的一致扩展时在你希望实际展示在页面中的元素上加上 :class="[style, animateClass]" :style="[style(item)]" ref="skeleton" -->
<view :class="[item.clas, animateClass]" :style="[style(item)]" ref="skeleton" v-else-if="item.type == 'custom'"></view>
<!-- 垂直高度站位 -->
<view :style="{height: $uv.addUnit(item.height,'rpx')}" v-else-if="item.type=='gap'"></view>
<!-- 其他基本单位 line avatar -->
<view :class="[item.clas, roundClass, animateClass]" :style="[style(item)]" v-else ref="skeleton"></view>
</view>
</view>
<view v-else>
<slot />
</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'
// #ifdef APP-NVUE
const animation = weex.requireModule('animation');
// #endif
export default {
name: 'uv-skeletons',
mixins: [mpMixin, mixin],
props: {
// 是否显示骨架
loading: {
type: Boolean,
default: true
},
// 骨架内容 具体说明参考文档:
skeleton: {
type: Array,
default: () => []
},
// 是否开启动画效果
animate: {
type: Boolean,
default: true
},
// 是否圆角骨架风格
round: {
type: Boolean,
default: false
},
...uni.$uv?.props?.skeleton
},
data() {
return {
elements: [],
opacity: 0.5,
duration: 600
}
},
computed: {
animateClass() {
return this.animate ? 'uv-skeleton--animation' : 'uv-skeleton--static';
},
roundClass() {
return this.round ? 'uv-skeleton--round' : 'uv-skeleton--radius';
},
style(item) {
return item => {
const style = {};
return this.$uv.deepMerge(style, this.$uv.addStyle(item.style));
}
}
},
created() {
this.init();
// #ifdef APP-NVUE
if (this.loading && this.animate) {
this.$uv.sleep(50).then(res => {
this.createAnimation(this.opacity);
})
}
// #endif
},
methods: {
/**
* 初始化数据
*/
init() {
let elements = [];
if (!this.$uv.test.array(this.skeleton)) return this.$uv.error('skeleton参数必须为数组形式,参考文档示例:');
this.skeleton.forEach(el => {
const elClass = this.getElCounts(el);
elements = elements.concat(elClass);
})
this.elements = [...elements];
},
/**
* 处理骨架屏参数内容
* @param {Object} el 每项数据
*/
getElCounts(el) {
let elements = [];
let children = [];
if (this.$uv.test.number(el)) {
elements.push({
type: 'gap',
height: el
});
return elements;
} else {
const type = el.type ? el.type : 'line';
const num = el.num ? el.num : 1;
const style = el.style ? el.style : {};
const styleIsArray = this.$uv.test.array(style);
const gap = el.gap ? el.gap : '20rpx';
const child = el.children ? el.children : [];
for (let i = 0; i < child.length; i++) {
children[i] = {
clas: child[i].type.indexOf('avatar') > -1 || child[i].type.indexOf('custom') > -1 ? '' : 'uv-skeleton__group__sub',
elements: this.getElCounts(child[i])
};
}
for (let i = 0; i < num; i++) {
if (gap && i < num && i > 0) {
elements.push({
type: 'gap',
height: gap
});
}
elements.push({
clas: `uv-skeleton__${type}`,
type,
style: styleIsArray ? style[i] : style,
gap,
children
});
}
return elements;
}
},
/**
* 创建动画
*/
createAnimation(opacity = 1) {
// loading结束之后或未开启动画不执行
if (!this.loading || !this.animate) return;
let count = 0;
const skeletons = this.$refs.skeleton;
skeletons.forEach(item => {
animation.transition(item, {
styles: {
opacity: opacity,
},
duration: this.duration
}, () => {
count++;
if (count >= skeletons.length) {
setTimeout(() => {
this.createAnimation(opacity < 1 ? 1 : this.opacity);
}, 200)
}
});
})
}
}
}
</script>
<style scoped lang="scss">
@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
@mixin background {
/* #ifdef APP-NVUE */
background-color: #e6e6e6;
/* #endif */
/* #ifndef APP-NVUE */
background: linear-gradient(90deg, #F1F2F4 25%, #e6e6e6 37%, #F1F2F4 50%);
background-size: 400% 100%;
/* #endif */
}
.uv-skeleton__line {
height: 32rpx;
// margin-bottom: 30rpx;
}
.uv-skeleton__line--sm {
height: 24rpx;
margin-bottom: 30rpx;
}
.uv-skeleton__line--lg {
height: 48rpx;
margin-bottom: 30rpx;
}
.uv-skeleton--static {
@include background;
}
.uv-skeleton--radius {
border-radius: 8rpx;
}
.uv-skeleton--round {
border-radius: 30rpx;
}
.uv-skeleton__avatar {
width: 100rpx;
height: 100rpx;
border-radius: 50rpx;
}
.uv-skeleton__avatar--sm {
width: 50rpx;
height: 50rpx;
border-radius: 25rpx;
}
.uv-skeleton__avatar--lg {
width: 200rpx;
height: 200rpx;
border-radius: 100rpx;
}
.uv-skeleton__group {
@include flex;
&__sub {
flex: 1;
}
}
.uv-skeleton--animation {
@include background;
/* #ifndef APP-NVUE */
animation: skeleton 1.8s ease infinite
/* #endif */
}
/* #ifndef APP-NVUE */
@keyframes skeleton {
0% {
background-position: 100% 50%
}
100% {
background-position: 0 50%
}
}
/* #endif */
</style>
+88
View File
@@ -0,0 +1,88 @@
{
"id": "uv-skeletons",
"displayName": "uv-skeletons 骨架屏 全面兼容小程序、nvue、vue2、vue3等多端",
"version": "1.0.1",
"description": "全新升级骨架屏,更加灵活,体验更加,强烈推荐使用新版骨架屏。一般用于页面在请求远程数据尚未完成时,在内容加载出来前展示与内容布局结构一致的灰白块,提升用户视觉体验。",
"keywords": [
"uv-skeletons",
"uvui",
"uv-ui",
"skeleton",
"骨架屏"
],
"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-text"
],
"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"
}
}
}
}
}
+23
View File
@@ -0,0 +1,23 @@
# Skeletons 新版骨架屏
> **组件名:uv-skeletons**
骨架屏一般用于页面在请求远程数据尚未完成时,在内容加载出来前展示与内容布局结构一致的灰白块,提升用户视觉体验。
新版骨架屏与老版本使用参数有很大的区别,所以才有了新版本,无法替换老版本。
新版骨架屏比老版本骨架屏更加灵活,理论上简单的左右上下布局结构均可以轻松实现,样式自由度较高,只要你会写css,推荐使用新版。
# <a href="https://www.uvui.cn/components/skeletons.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>