Merge branch 'master' of http://119.29.194.155:8894/zwq/zhxy-jsd
This commit is contained in:
commit
d0859759c1
@ -94,6 +94,14 @@ export const jsdXkListApi = async (params: any) => {
|
||||
return await get("/mobile/js/xk/list", params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取当前学期教师上课课程列表
|
||||
*/
|
||||
export const getCurrentSemesterTeacherCoursesApi = async (jsId?: string) => {
|
||||
const params = jsId ? { jsId } : {};
|
||||
return await get("/api/xkkc/getCurrentSemesterTeacherCourses", params);
|
||||
};
|
||||
|
||||
// 选课列表
|
||||
export const jsdXkkcSaveApi = async (params: any) => {
|
||||
return await post("/api/xkkc/save", params);
|
||||
@ -357,5 +365,12 @@ export const getByUserIdAndInspectItemIdApi = async (params: any) => {
|
||||
|
||||
// 清空用户open_id
|
||||
export const clearUserOpenIdApi = async (params: { userId: string }) => {
|
||||
return await post(`/api/user/clearOpenId?userId=${params.userId}`);
|
||||
return await post("/api/user/clearUserOpenId", params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 课堂随拍保存API
|
||||
*/
|
||||
export const jsdXkkcPhotoSaveApi = async (params: any) => {
|
||||
return await post("/mobile/js/xk/photo/save", params);
|
||||
};
|
||||
|
||||
@ -30,14 +30,14 @@
|
||||
:src="item.lastRank ? lastIcon : item.showChild ? currentIcon : defaultIcon"></image>
|
||||
{{ item.name }}
|
||||
</view>
|
||||
<!-- 多选时所有节点都显示多选框 -->
|
||||
<view class="tki-tree-check" v-if="multiple" @tap.stop="_treeItemSelect(item, index)">
|
||||
<!-- 只有叶子节点显示可选择的选择框 -->
|
||||
<view class="tki-tree-check" @tap.stop="_treeItemSelect(item, index)" :class="{'disabled': !item.lastRank}">
|
||||
<view class="tki-tree-check-yes" v-if="item.checked" :class="{'radio':!multiple}"
|
||||
:style="{'border-color':confirmColor}">
|
||||
<view class="tki-tree-check-yes-b" :style="{'background-color':confirmColor}"></view>
|
||||
</view>
|
||||
<view class="tki-tree-check-no" v-else :class="{'radio':!multiple}"
|
||||
:style="{'border-color':confirmColor}"></view>
|
||||
:style="{'border-color': !item.lastRank ? '#ccc' : confirmColor}"></view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
@ -277,17 +277,16 @@ export default {
|
||||
// console.log(this.treeList)
|
||||
},
|
||||
_treeItemSelect(item, index) {
|
||||
if (this.multiple) {
|
||||
// 只有叶子节点才能被选择
|
||||
if (!item.lastRank) {
|
||||
// 父节点多选框:全选/全不选所有子节点
|
||||
const allChecked = this._isAllChildrenChecked(item);
|
||||
this._setAllChildrenChecked(item, !allChecked);
|
||||
item.checked = !allChecked;
|
||||
} else {
|
||||
// 如果不是叶子节点,不允许选择
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.multiple) {
|
||||
// 子节点单独切换
|
||||
this.treeList[index].checked = !this.treeList[index].checked;
|
||||
this._updateParentChecked(item);
|
||||
}
|
||||
this._fixMultiple(index);
|
||||
} else {
|
||||
// 单选逻辑
|
||||
@ -496,4 +495,14 @@ export default {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.tki-tree-check.disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.tki-tree-check.disabled .tki-tree-check-no {
|
||||
border-color: #ccc !important;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@ -505,6 +505,25 @@
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/base/groupTeaching/dmXkkcRecord",
|
||||
"style": {
|
||||
"navigationBarTitleText": "点名记录",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/base/groupTeaching/photoXkkcDetail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "课堂随拍"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/routine/kefuxuncha/xcRecord",
|
||||
"style": {
|
||||
"navigationBarTitleText": "巡查记录"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/base/xs/qj/sp",
|
||||
"style": {
|
||||
|
||||
@ -3,12 +3,13 @@
|
||||
<!-- 选课信息头部 - 固定部分 -->
|
||||
<view class="selection-header">
|
||||
<view class="header-content">
|
||||
<view class="title-section" @click="clickShowXkSelector">
|
||||
<view class="title-section">
|
||||
<view class="title">
|
||||
<text v-if="xkData && xkData.xkmc">{{ xkData.xkmc }}</text>
|
||||
<text v-else>选课信息</text>
|
||||
<text v-if="xkkcList && xkkcList.length > 0">
|
||||
{{ getCurrentSemesterName() }} - 我的课程
|
||||
</text>
|
||||
<text v-else>暂无课程</text>
|
||||
</view>
|
||||
<view class="switch-btn" v-if="xkList.length > 1">切换</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -19,21 +20,13 @@
|
||||
<view class="course-list" v-if="xkkcList && xkkcList.length > 0">
|
||||
<view v-for="(xkkc, index) in xkkcList" :key="xkkc.id || index" class="course-item">
|
||||
<view class="course-name">{{ xkkc.kcmc }}</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">上课周期类型:</view>
|
||||
<view class="info-data">{{ xkkc.skzqlx }}</view>
|
||||
</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">上课周期:</view>
|
||||
<view class="info-data">{{ xkkc.skzqmc }}</view>
|
||||
</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">上课开始时间:</view>
|
||||
<view class="info-data">{{ xkkc.skkstime }}</view>
|
||||
</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">上课结束时间:</view>
|
||||
<view class="info-data">{{ xkkc.skjstime }}</view>
|
||||
<view class="info-label">上课时间:</view>
|
||||
<view class="info-data">{{ formatClassTime(xkkc.skkstime, xkkc.skjstime) }}</view>
|
||||
</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">开课地点:</view>
|
||||
@ -43,8 +36,11 @@
|
||||
<view class="info-label">上课人数:</view>
|
||||
<view class="info-data">{{ xkkc.hasNum || 0 }} | {{ xkkc.maxNum || 0 }}</view>
|
||||
</view>
|
||||
<view class="separator-line"></view>
|
||||
<view class="course-btn-group">
|
||||
<view class="dm-btn" @click.stop="goDm(xkkc)">点名</view>
|
||||
<view class="record-btn" @click.stop="goRecord(xkkc)">点名记录</view>
|
||||
<!-- <view class="photo-btn" @click.stop="goPhoto(xkkc)">课堂随拍</view>-->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -58,29 +54,6 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view>
|
||||
<!-- 俱乐部选择弹窗 -->
|
||||
<u-popup :show="showXkFlag" @close="showXkFlag = false" mode="bottom" round="10">
|
||||
<view class="xk-selector">
|
||||
<view class="selector-header">
|
||||
<text class="selector-title">选择俱乐部</text>
|
||||
<u-icon name="close" size="20" @click="showXkFlag = false"></u-icon>
|
||||
</view>
|
||||
<view class="xk-list">
|
||||
<view v-for="(xk, index) in xkList" :key="index" class="xk-item" :class="{
|
||||
'xk-item-active': xkData.id === xk.id
|
||||
}" @click="switchXk(xk)">
|
||||
<view class="xk-info">
|
||||
<text class="xk-name">{{ xk.xkmc }}</text>
|
||||
<text class="xk-type">{{ xk.xkmc }}</text>
|
||||
</view>
|
||||
<u-icon v-if="xkData.id === xk.id" name="checkmark" color="#409EFF" size="20"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@ -92,7 +65,7 @@ import {
|
||||
} from "vue";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { useDataStore } from "@/store/modules/data";
|
||||
import { jsdXkListApi } from "@/api/base/server";
|
||||
import { getCurrentSemesterTeacherCoursesApi } from "@/api/base/server";
|
||||
import { dmBeforeMinuteApi } from "@/api/system/config/index";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
@ -101,15 +74,6 @@ const { getData, setData } = useDataStore();
|
||||
|
||||
const wdNameList = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
|
||||
|
||||
// 控制选择器显示状态
|
||||
const showXkFlag = ref(false);
|
||||
|
||||
const xkList = ref<any>([]);
|
||||
|
||||
const xkData = ref();
|
||||
|
||||
const courseInfo = ref<any>({});
|
||||
|
||||
// 课程列表数据
|
||||
const xkkcList = ref<any[]>([]);
|
||||
|
||||
@ -119,51 +83,53 @@ onMounted(async () => {
|
||||
uni.showLoading({
|
||||
title: "加载中...",
|
||||
});
|
||||
try {
|
||||
await loadCourseList();
|
||||
await loadDmBeforeMinute();
|
||||
} catch (error) {
|
||||
console.error('页面初始化失败:', error);
|
||||
uni.showToast({
|
||||
title: '页面加载失败',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
});
|
||||
|
||||
// 加载课程列表
|
||||
const loadCourseList = async () => {
|
||||
const res = await jsdXkListApi({
|
||||
jsId: getJs.id
|
||||
});
|
||||
try {
|
||||
const res = await getCurrentSemesterTeacherCoursesApi(getJs.id);
|
||||
if (res.resultCode == 1) {
|
||||
if (res.result && res.result.length) {
|
||||
xkList.value = res.result;
|
||||
switchXk(res.result[0]);
|
||||
xkkcList.value = res.result;
|
||||
// 处理课程周期显示
|
||||
processCoursePeriods();
|
||||
} else {
|
||||
xkList.value = [];
|
||||
xkData.value = {};
|
||||
xkkcList.value = [];
|
||||
}
|
||||
} else {
|
||||
xkkcList.value = [];
|
||||
uni.showToast({
|
||||
title: res.message || '获取课程列表失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载课程列表失败:', error);
|
||||
xkkcList.value = [];
|
||||
uni.showToast({
|
||||
title: '加载课程列表失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 获取点名时间提前分钟数
|
||||
const loadDmBeforeMinute = async () => {
|
||||
const res = await dmBeforeMinuteApi();
|
||||
if (res.resultCode == 1) {
|
||||
// 将res.result从字符串转换成int的number赋给dmBeforeMinute
|
||||
dmBeforeMinute.value = parseInt(res.result);
|
||||
}
|
||||
}
|
||||
|
||||
// 显示选课选择器
|
||||
function clickShowXkSelector() {
|
||||
if (xkList.value.length > 1) {
|
||||
showXkFlag.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 切换选课
|
||||
function switchXk(xk: any) {
|
||||
xkData.value = xk;
|
||||
xkkcList.value = xk.xkkcs;
|
||||
showXkFlag.value = false;
|
||||
for (let i = 0; i < xk.xkkcs.length; i++) {
|
||||
let xkkc = xk.xkkcs[i];
|
||||
// 处理课程周期显示
|
||||
const processCoursePeriods = () => {
|
||||
for (let i = 0; i < xkkcList.value.length; i++) {
|
||||
let xkkc = xkkcList.value[i];
|
||||
// 判断周期
|
||||
switch (xkkc.skzqlx) {
|
||||
case '每天':
|
||||
@ -181,11 +147,60 @@ function switchXk(xk: any) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 显示切换成功提示
|
||||
uni.showToast({
|
||||
title: `已切换到${xk.xkmc}`,
|
||||
icon: "none",
|
||||
});
|
||||
};
|
||||
|
||||
// 获取当前学期名称
|
||||
const getCurrentSemesterName = () => {
|
||||
if (xkkcList.value && xkkcList.value.length > 0) {
|
||||
// 从第一个课程获取学期名称
|
||||
return xkkcList.value[0].xqmc || '当前学期';
|
||||
}
|
||||
return '当前学期';
|
||||
};
|
||||
|
||||
// 格式化上课时间
|
||||
const formatClassTime = (startTime: string, endTime: string) => {
|
||||
if (!startTime || !endTime) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
// 尝试解析时间,支持多种格式
|
||||
let start, end;
|
||||
|
||||
// 如果是时间格式(HH:mm:ss 或 HH:mm)
|
||||
if (startTime.includes(':') && !startTime.includes('-') && !startTime.includes('/')) {
|
||||
start = startTime;
|
||||
end = endTime;
|
||||
} else {
|
||||
// 尝试用 dayjs 解析
|
||||
const startDate = dayjs(startTime);
|
||||
const endDate = dayjs(endTime);
|
||||
|
||||
if (startDate.isValid() && endDate.isValid()) {
|
||||
start = startDate.format('HH:mm:ss');
|
||||
end = endDate.format('HH:mm:ss');
|
||||
} else {
|
||||
// 如果解析失败,直接返回原始值
|
||||
return `${startTime}~${endTime}`;
|
||||
}
|
||||
}
|
||||
|
||||
return `${start}~${end}`;
|
||||
} catch (error) {
|
||||
console.error('时间格式化错误:', error);
|
||||
// 如果出错,返回原始值
|
||||
return `${startTime}~${endTime}`;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取点名时间提前分钟数
|
||||
const loadDmBeforeMinute = async () => {
|
||||
const res = await dmBeforeMinuteApi();
|
||||
if (res.resultCode == 1) {
|
||||
// 将res.result从字符串转换成int的number赋给dmBeforeMinute
|
||||
dmBeforeMinute.value = parseInt(res.result);
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转到点名
|
||||
@ -224,7 +239,7 @@ const goDm = (xkkc: any) => {
|
||||
const endTime = dayjs(strDate + xkkc.skjstime, 'YYYY-MM-DD HH:mm:ss');
|
||||
dmFlag = now.isBefore(endTime) && now.isAfter(startTime)
|
||||
} else {
|
||||
msg = "没到点名日期";
|
||||
msg = "上课时间未到,无法点名";
|
||||
}
|
||||
if (dmFlag) {
|
||||
setData(xkkc);
|
||||
@ -233,7 +248,117 @@ const goDm = (xkkc: any) => {
|
||||
});
|
||||
} else {
|
||||
if (msg === "") {
|
||||
msg = "没到点名时间";
|
||||
msg = "上课时间未到,无法点名";
|
||||
}
|
||||
uni.showToast({
|
||||
title: msg,
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 跳转到点名记录
|
||||
const goRecord = (xkkc: any) => {
|
||||
const now = dayjs();
|
||||
let wDay = now.day();
|
||||
if (wDay === 0) {
|
||||
wDay = 7;
|
||||
}
|
||||
let mDay = now.date();
|
||||
const strDate = now.format('YYYY-MM-DD') + ' ';
|
||||
let recordFlag = false;
|
||||
let msg = "";
|
||||
// 判断周期
|
||||
switch (xkkc.skzqlx) {
|
||||
case '每天':
|
||||
recordFlag = true;
|
||||
break;
|
||||
case '每周':
|
||||
const daysOfWeek = xkkc.skzq.split(',').map(Number);
|
||||
recordFlag = daysOfWeek.includes(wDay);
|
||||
// 从wdNameList读取daysOfWeek对应的周几
|
||||
xkkc.skzqmc = daysOfWeek.map((day: number) => wdNameList[day - 1]).join(',');
|
||||
break;
|
||||
case '每月':
|
||||
const daysOfMonth = xkkc.skzq.split(',').map(Number);
|
||||
recordFlag = daysOfMonth.includes(mDay);
|
||||
// 从根据编号加
|
||||
xkkc.skzqmc = daysOfMonth.map((day: number) => day + "号").join(',');
|
||||
break;
|
||||
}
|
||||
// 判断日期是否合格
|
||||
if (recordFlag) {
|
||||
// xkkc.skkstime开始时间向前dmBeforeMinute分钟
|
||||
const startTime = dayjs(strDate + xkkc.skkstime).subtract(dmBeforeMinute.value, 'minute').format('YYYY-MM-DD HH:mm:ss');
|
||||
const endTime = dayjs(strDate + xkkc.skjstime, 'YYYY-MM-DD HH:mm:ss');
|
||||
recordFlag = now.isBefore(endTime) && now.isAfter(startTime)
|
||||
} else {
|
||||
msg = "上课时间未到,无法查看点名记录";
|
||||
}
|
||||
if (recordFlag) {
|
||||
setData(xkkc);
|
||||
uni.navigateTo({
|
||||
url: `/pages/base/groupTeaching/dmXkkcRecord`,
|
||||
});
|
||||
} else {
|
||||
if (msg === "") {
|
||||
msg = "上课时间未到,无法查看点名记录";
|
||||
}
|
||||
uni.showToast({
|
||||
title: msg,
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 跳转到课堂随拍
|
||||
const goPhoto = (xkkc: any) => {
|
||||
const now = dayjs();
|
||||
let wDay = now.day();
|
||||
if (wDay === 0) {
|
||||
wDay = 7;
|
||||
}
|
||||
let mDay = now.date();
|
||||
const strDate = now.format('YYYY-MM-DD') + ' ';
|
||||
let photoFlag = false;
|
||||
let msg = "";
|
||||
// 判断周期
|
||||
switch (xkkc.skzqlx) {
|
||||
case '每天':
|
||||
photoFlag = true;
|
||||
break;
|
||||
case '每周':
|
||||
const daysOfWeek = xkkc.skzq.split(',').map(Number);
|
||||
photoFlag = daysOfWeek.includes(wDay);
|
||||
// 从wdNameList读取daysOfWeek对应的周几
|
||||
xkkc.skzqmc = daysOfWeek.map((day: number) => wdNameList[day - 1]).join(',');
|
||||
break;
|
||||
case '每月':
|
||||
const daysOfMonth = xkkc.skzq.split(',').map(Number);
|
||||
photoFlag = daysOfMonth.includes(mDay);
|
||||
// 从根据编号加
|
||||
xkkc.skzqmc = daysOfMonth.map((day: number) => day + "号").join(',');
|
||||
break;
|
||||
}
|
||||
// 判断日期是否合格
|
||||
if (photoFlag) {
|
||||
// xkkc.skkstime开始时间向前dmBeforeMinute分钟
|
||||
const startTime = dayjs(strDate + xkkc.skkstime).subtract(dmBeforeMinute.value, 'minute').format('YYYY-MM-DD HH:mm:ss');
|
||||
const endTime = dayjs(strDate + xkkc.skjstime, 'YYYY-MM-DD HH:mm:ss');
|
||||
photoFlag = now.isBefore(endTime) && now.isAfter(startTime)
|
||||
} else {
|
||||
msg = "上课时间未到,无法随拍";
|
||||
}
|
||||
if (photoFlag) {
|
||||
setData(xkkc);
|
||||
uni.navigateTo({
|
||||
url: `/pages/base/groupTeaching/photoXkkcDetail`,
|
||||
});
|
||||
} else {
|
||||
if (msg === "") {
|
||||
msg = "上课时间未到,无法随拍";
|
||||
}
|
||||
uni.showToast({
|
||||
title: msg,
|
||||
@ -251,7 +376,7 @@ onBeforeUnmount(() => {
|
||||
<style lang="scss" scoped>
|
||||
.interest-course {
|
||||
min-height: 100%;
|
||||
background-color: #f5f7fa;
|
||||
background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
@ -287,46 +412,50 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.selection-header {
|
||||
background: linear-gradient(135deg, #4a90e2, #2879ff);
|
||||
padding: 20px 15px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 25px 20px;
|
||||
color: #fff;
|
||||
border-radius: 0 0 15px 15px;
|
||||
box-shadow: 0 4px 12px rgba(40, 121, 255, 0.2);
|
||||
border-radius: 0 0 20px 20px;
|
||||
box-shadow: 0 8px 32px rgba(102, 126, 234, 0.3);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(45deg, rgba(255, 255, 255, 0.1) 0%, transparent 50%, rgba(255, 255, 255, 0.05) 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
.title-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.title {
|
||||
flex: 1 0 1px;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 14px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.switch-btn {
|
||||
padding: 5px 15px;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
color: #fff;
|
||||
border-radius: 15px;
|
||||
font-size: 15px;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -343,65 +472,256 @@ onBeforeUnmount(() => {
|
||||
.course-item {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid #f0f0f0;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
animation: fadeInUp 0.6s ease-out;
|
||||
|
||||
// &:nth-child(odd) {
|
||||
// margin-right: 10px;
|
||||
// }
|
||||
|
||||
// &:nth-child(even) {
|
||||
// margin-left: 10px;
|
||||
// }
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.12);
|
||||
border-color: #e8e8e8;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border: 1px solid #3fbf72;
|
||||
background-color: rgba(63, 191, 114, 0.05);
|
||||
box-shadow: 0 2px 8px rgba(63, 191, 114, 0.15);
|
||||
border: 2px solid #3fbf72;
|
||||
background-color: rgba(63, 191, 114, 0.02);
|
||||
box-shadow: 0 4px 20px rgba(63, 191, 114, 0.15);
|
||||
}
|
||||
|
||||
.course-name {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.4;
|
||||
animation: fadeInLeft 0.5s ease-out 0.1s both;
|
||||
}
|
||||
|
||||
.course-btn-group {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px; // 按钮之间的间距
|
||||
|
||||
.dm-btn {
|
||||
flex: 1 0 1px;
|
||||
display: inline-block;
|
||||
color: #2879ff;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding: 8px 18px;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(135deg, rgba(40, 121, 255, 0.1), rgba(40, 121, 255, 0.05));
|
||||
border: 1px solid #2879ff;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 2px 8px rgba(40, 121, 255, 0.15);
|
||||
animation: fadeInUp 0.5s ease-out 0.3s both;
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(135deg, rgba(40, 121, 255, 0.15), rgba(40, 121, 255, 0.1));
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 15px rgba(40, 121, 255, 0.25);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: linear-gradient(135deg, rgba(40, 121, 255, 0.2), rgba(40, 121, 255, 0.15));
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 8px rgba(40, 121, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.record-btn {
|
||||
display: inline-block;
|
||||
color: #4CAF50; /* A green color for record */
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding: 8px 18px;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(135deg, rgba(76, 175, 80, 0.1), rgba(76, 175, 80, 0.05));
|
||||
border: 1px solid #4CAF50;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 2px 8px rgba(76, 175, 80, 0.15);
|
||||
animation: fadeInUp 0.5s ease-out 0.4s both;
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(135deg, rgba(76, 175, 80, 0.15), rgba(76, 175, 80, 0.1));
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 15px rgba(76, 175, 80, 0.25);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: linear-gradient(135deg, rgba(76, 175, 80, 0.2), rgba(76, 175, 80, 0.15));
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 8px rgba(76, 175, 80, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.photo-btn {
|
||||
display: inline-block;
|
||||
color: #ff6b35;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding: 8px 18px;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(135deg, rgba(255, 107, 53, 0.1), rgba(255, 107, 53, 0.05));
|
||||
border: 1px solid #ff6b35;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 2px 8px rgba(255, 107, 53, 0.15);
|
||||
animation: fadeInUp 0.5s ease-out 0.4s both;
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(135deg, rgba(255, 107, 53, 0.15), rgba(255, 107, 53, 0.1));
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 15px rgba(255, 107, 53, 0.25);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: linear-gradient(135deg, rgba(255, 107, 53, 0.2), rgba(255, 107, 53, 0.15));
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 8px rgba(255, 107, 53, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.course-info-item {
|
||||
display: flex;
|
||||
margin-bottom: 12px;
|
||||
font-size: 12px;
|
||||
margin-bottom: 14px;
|
||||
font-size: 13px;
|
||||
align-items: center;
|
||||
animation: fadeInUp 0.5s ease-out 0.15s both;
|
||||
|
||||
.info-label {
|
||||
color: #949AA4;
|
||||
color: #666;
|
||||
flex: 0 0 100px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.info-data {
|
||||
flex: 1 0 1px;
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.separator-line {
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, #e8e8e8, transparent);
|
||||
margin: 18px 0;
|
||||
opacity: 0.8;
|
||||
animation: fadeIn 0.5s ease-out 0.25s both;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加动画关键帧
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 学生选择器弹窗样式 */
|
||||
@keyframes fadeInLeft {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 全局图片样式 */
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// 暂无数据样式
|
||||
.empty-course-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80px 20px;
|
||||
text-align: center;
|
||||
|
||||
.empty-icon {
|
||||
margin-bottom: 25px;
|
||||
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
border: 2px solid rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #475569;
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
font-size: 14px;
|
||||
color: #64748b;
|
||||
max-width: 80%;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
|
||||
/* 选课已结束样式 */
|
||||
.enrollment-ended {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
padding: 12px 15px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 俱乐部选择弹窗样式 */
|
||||
.xk-selector {
|
||||
background-color: #ffffff;
|
||||
border-top-left-radius: 12px;
|
||||
@ -430,6 +750,7 @@ onBeforeUnmount(() => {
|
||||
align-items: center;
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
@ -439,6 +760,10 @@ onBeforeUnmount(() => {
|
||||
background-color: rgba(64, 158, 255, 0.05);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(64, 158, 255, 0.02);
|
||||
}
|
||||
|
||||
.xk-info {
|
||||
flex: 1;
|
||||
margin-left: 12px;
|
||||
@ -464,62 +789,4 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 全局图片样式 */
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// 暂无数据样式
|
||||
.empty-course-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
text-align: center;
|
||||
|
||||
.empty-icon {
|
||||
margin-bottom: 20px;
|
||||
background-color: #f5f6f7;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
max-width: 80%;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
/* 选课已结束样式 */
|
||||
.enrollment-ended {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
padding: 12px 15px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
<view class="avatar-container mr-8">
|
||||
<image
|
||||
class="student-avatar"
|
||||
:src="xs.xmtx || '/static/images/default-avatar.png'"
|
||||
:src="getImageUrl(xs.xstx)"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
</view>
|
||||
@ -60,7 +60,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<text class="font-12 cor-666">{{ xs.bjmc }}</text>
|
||||
<view class="contact-parent mt-8 flex-center">
|
||||
<view class="contact-parent mt-8 flex-center" @click="contactParent(xs)">
|
||||
<text class="font-12 cor-primary">联系家长</text>
|
||||
<u-icon
|
||||
name="phone"
|
||||
@ -86,25 +86,41 @@
|
||||
|
||||
<template #bottom>
|
||||
<view class="submit-btn-wrap py-10 px-20 bg-white">
|
||||
<button class="submit-btn" @click="submit">提交</button>
|
||||
<button
|
||||
class="submit-btn"
|
||||
:class="{ 'submit-btn-disabled': isSubmitting }"
|
||||
:disabled="isSubmitting"
|
||||
@click="submit"
|
||||
>
|
||||
{{ isSubmitting ? '提交中...' : '提交' }}
|
||||
</button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 提交遮罩层 -->
|
||||
<view v-if="isSubmitting" class="submit-overlay">
|
||||
<view class="submit-loading">
|
||||
<u-loading-icon mode="spinner" size="28"></u-loading-icon>
|
||||
<text class="loading-text">正在提交...</text>
|
||||
</view>
|
||||
</view>
|
||||
</BasicLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {onMounted, ref} from "vue";
|
||||
import {onMounted, ref, computed} from "vue";
|
||||
import BasicLayout from "@/components/BasicLayout/Layout.vue";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { useDataStore } from "@/store/modules/data";
|
||||
import { jsdXkXsListApi, jsdXkdmListApi } from "@/api/base/server";
|
||||
import { useDicStore } from "@/store/modules/dic";
|
||||
import { BASE_IMAGE_URL } from "@/config";
|
||||
const { findByPid } = useDicStore();
|
||||
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const { getJs } = useUserStore();
|
||||
const { getData } = useDataStore();
|
||||
const { getData, setData } = useDataStore();
|
||||
|
||||
const js = computed(() => getJs)
|
||||
const xkkc = computed(() => getData)
|
||||
@ -138,6 +154,16 @@ const statusOptions = ref<Array<{ text: string, value: string }>>([]);
|
||||
const curXs = ref<any>(null);
|
||||
const defSel = ref<any>([]);
|
||||
|
||||
// 提交状态管理
|
||||
const isSubmitting = ref(false);
|
||||
|
||||
// 获取图片完整URL
|
||||
const getImageUrl = (imagePath: string) => {
|
||||
if (!imagePath) return '/static/images/default-avatar.png';
|
||||
if (imagePath.startsWith('http')) return imagePath;
|
||||
return BASE_IMAGE_URL + imagePath;
|
||||
};
|
||||
|
||||
// 获取状态对应的样式类
|
||||
const getStatusClass = (status: string) => {
|
||||
switch (status) {
|
||||
@ -257,21 +283,74 @@ const toRollCallRecord = () => {
|
||||
|
||||
// 联系家长
|
||||
const contactParent = (student: any) => {
|
||||
console.log("联系家长", student.name);
|
||||
// 构建完整的学生信息,确保包含所有必要字段
|
||||
const completeStudent = {
|
||||
...student,
|
||||
// 确保字段名的一致性
|
||||
id: student.xsId || student.id,
|
||||
xsxm: student.xsxm || student.xm,
|
||||
xstx: student.xstx || student.avatar,
|
||||
xb: student.xb || student.gender,
|
||||
sfzh: student.sfzh,
|
||||
cstime: student.cstime,
|
||||
njmc: student.njmcName || student.njmc,
|
||||
bjmc: student.bjmc,
|
||||
// 如果后端返回的是njId和bjId,也保留
|
||||
njId: student.njId,
|
||||
bjId: student.bjId
|
||||
};
|
||||
|
||||
// 设置完整的学生信息到store中,供详情页面使用
|
||||
setData(completeStudent);
|
||||
|
||||
// 跳转到家长通讯录详情页面
|
||||
uni.navigateTo({
|
||||
url: "/pages/view/homeSchool/parentAddressBook/detail"
|
||||
});
|
||||
};
|
||||
|
||||
// 提交数据
|
||||
const submit = async () => {
|
||||
if (isSubmitting.value) {
|
||||
return;
|
||||
}
|
||||
isSubmitting.value = true;
|
||||
try {
|
||||
const res = await jsdXkdmListApi({
|
||||
jsId: js.value.id,
|
||||
xkkcId: xkkc.value.id,
|
||||
dmtime: now,
|
||||
xkdmList: xsList.value
|
||||
});
|
||||
|
||||
if (res && res.resultCode === 1) {
|
||||
uni.showToast({
|
||||
title: "提交成功",
|
||||
icon: "success",
|
||||
});
|
||||
|
||||
// 延迟一下再返回,让用户看到成功提示
|
||||
setTimeout(() => {
|
||||
// 返回到课程列表页面
|
||||
uni.navigateBack({
|
||||
delta: 1
|
||||
});
|
||||
}, 1500);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res?.resultMessage || "提交失败",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("提交失败:", error);
|
||||
uni.showToast({
|
||||
title: "提交失败,请重试",
|
||||
icon: "none",
|
||||
});
|
||||
} finally {
|
||||
isSubmitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 页面加载时获取状态选项
|
||||
@ -384,6 +463,13 @@ onMounted(async () => {
|
||||
border-radius: 4px;
|
||||
border: 1px solid #4080ff;
|
||||
display: inline-flex;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:active {
|
||||
background-color: rgba(64, 128, 255, 0.1);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
@ -397,6 +483,65 @@ onMounted(async () => {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.submit-btn-disabled {
|
||||
background-color: #ccc;
|
||||
color: #666;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.submit-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
.submit-loading {
|
||||
background-color: #fff;
|
||||
padding: 30px 40px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
||||
animation: slideInUp 0.3s ease-out;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: 15px;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.cor-primary {
|
||||
color: #4080ff;
|
||||
}
|
||||
|
||||
443
src/pages/base/groupTeaching/dmXkkcRecord.vue
Normal file
443
src/pages/base/groupTeaching/dmXkkcRecord.vue
Normal file
@ -0,0 +1,443 @@
|
||||
<template>
|
||||
<BasicLayout>
|
||||
<!-- 课程信息卡片 -->
|
||||
<view class="course-card mx-15 my-15 bg-white white-bg-color r-md p-15">
|
||||
<view class="flex-row items-center mb-15">
|
||||
<view class="course-icon flex-center mr-10">
|
||||
<u-icon name="calendar" color="#4080ff" size="20"></u-icon>
|
||||
</view>
|
||||
<text class="font-16 font-bold">{{ xkkc.kcmc }}</text>
|
||||
<text class="font-14 cor-999 ml-10">{{ todayInfo.date }} ({{ todayInfo.weekName }})</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 点名记录统计 -->
|
||||
<view class="record-stats mx-15 mb-15">
|
||||
<view class="stats-grid">
|
||||
<view class="stat-item total" @click="showStudentList('total')">
|
||||
<view class="stat-number">{{ totalStudents }}</view>
|
||||
<view class="stat-label">总人数</view>
|
||||
</view>
|
||||
<view class="stat-item present" @click="showStudentList('present')">
|
||||
<view class="stat-number">{{ presentStudents }}</view>
|
||||
<view class="stat-label">实到</view>
|
||||
</view>
|
||||
<view class="stat-item leave" @click="showStudentList('leave')">
|
||||
<view class="stat-number">{{ leaveStudents }}</view>
|
||||
<view class="stat-label">请假</view>
|
||||
</view>
|
||||
<view class="stat-item absent" @click="showStudentList('absent')">
|
||||
<view class="stat-number">{{ absentStudents }}</view>
|
||||
<view class="stat-label">缺勤</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 学生列表弹窗 -->
|
||||
<uni-popup ref="studentPopup" type="bottom" :mask-click="false">
|
||||
<view class="student-popup">
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">{{ currentStatTitle }}</text>
|
||||
<view class="close-btn" @click="closeStudentList">
|
||||
<u-icon name="close" color="#666" size="20"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="student-list">
|
||||
<view
|
||||
v-for="(student, index) in currentStudentList"
|
||||
:key="student.xsId"
|
||||
class="student-item"
|
||||
>
|
||||
<view class="student-avatar">
|
||||
<image
|
||||
v-if="student.xstx"
|
||||
:src="getImageUrl(student.xstx)"
|
||||
mode="aspectFill"
|
||||
class="avatar-img"
|
||||
/>
|
||||
<view v-else class="avatar-text">{{ student.xsxm?.charAt(0) || '学' }}</view>
|
||||
</view>
|
||||
<view class="student-info">
|
||||
<view class="student-name">{{ student.xsxm }}</view>
|
||||
<view class="student-class">{{ student.njmcName }} {{ student.bjmc }}</view>
|
||||
</view>
|
||||
<view class="student-status">
|
||||
<text :class="getStatusClass(student.xszt)">{{ getStatusText(student.xszt) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
|
||||
<!-- 返回按钮 -->
|
||||
<view class="bottom-actions mx-15 mb-30">
|
||||
<button class="back-btn" @click="goBack">返回</button>
|
||||
</view>
|
||||
</BasicLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { useDataStore } from "@/store/modules/data";
|
||||
import BasicLayout from "@/components/BasicLayout/Layout.vue";
|
||||
import { jsdXkXsListApi } from "@/api/base/server";
|
||||
import { BASE_IMAGE_URL } from "@/config";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const { getJs } = useUserStore();
|
||||
const { getData } = useDataStore();
|
||||
|
||||
const js = computed(() => getJs);
|
||||
const xkkc = computed(() => getData);
|
||||
|
||||
// 今日信息
|
||||
const now = dayjs();
|
||||
let wDay = now.day();
|
||||
if (wDay === 0) {
|
||||
wDay = 7;
|
||||
}
|
||||
const wdNameList = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
|
||||
const todayInfo = ref({
|
||||
date: now.format("YYYY-MM-DD"),
|
||||
weekName: wdNameList[wDay - 1],
|
||||
});
|
||||
|
||||
// 学生列表数据
|
||||
const studentList = ref<any[]>([]);
|
||||
const currentStatType = ref<string>('');
|
||||
const currentStatTitle = ref<string>('');
|
||||
const currentStudentList = ref<any[]>([]);
|
||||
|
||||
// 统计数量
|
||||
const totalStudents = computed(() => studentList.value.length);
|
||||
const presentStudents = computed(() => studentList.value.filter(s => s.xszt === '正常').length);
|
||||
const leaveStudents = computed(() => studentList.value.filter(s => s.xszt === '请假').length);
|
||||
const absentStudents = computed(() => studentList.value.filter(s => s.xszt === '缺勤').length);
|
||||
|
||||
// 弹窗引用
|
||||
const studentPopup = ref<any>(null);
|
||||
|
||||
// 获取图片完整URL
|
||||
const getImageUrl = (path: string) => {
|
||||
if (!path) return '';
|
||||
if (path.startsWith('http')) return path;
|
||||
return BASE_IMAGE_URL + path;
|
||||
};
|
||||
|
||||
// 获取状态样式类
|
||||
const getStatusClass = (status: string) => {
|
||||
switch (status) {
|
||||
case '正常': return 'status-normal';
|
||||
case '请假': return 'status-leave';
|
||||
case '缺勤': return 'status-absent';
|
||||
default: return 'status-normal';
|
||||
}
|
||||
};
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status: string) => {
|
||||
switch (status) {
|
||||
case '正常': return '正常';
|
||||
case '请假': return '请假';
|
||||
case '缺勤': return '缺勤';
|
||||
default: return '正常';
|
||||
}
|
||||
};
|
||||
|
||||
// 显示学生列表
|
||||
const showStudentList = (type: string) => {
|
||||
currentStatType.value = type;
|
||||
|
||||
switch (type) {
|
||||
case 'total':
|
||||
currentStatTitle.value = '总人数学生列表';
|
||||
currentStudentList.value = studentList.value;
|
||||
break;
|
||||
case 'present':
|
||||
currentStatTitle.value = '实到学生列表';
|
||||
currentStudentList.value = studentList.value.filter(s => s.xszt === '正常');
|
||||
break;
|
||||
case 'leave':
|
||||
currentStatTitle.value = '请假学生列表';
|
||||
currentStudentList.value = studentList.value.filter(s => s.xszt === '请假');
|
||||
break;
|
||||
case 'absent':
|
||||
currentStatTitle.value = '缺勤学生列表';
|
||||
currentStudentList.value = studentList.value.filter(s => s.xszt === '缺勤');
|
||||
break;
|
||||
}
|
||||
|
||||
studentPopup.value.open();
|
||||
};
|
||||
|
||||
// 关闭学生列表
|
||||
const closeStudentList = () => {
|
||||
studentPopup.value.close();
|
||||
};
|
||||
|
||||
// 返回上一页
|
||||
const goBack = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
// 加载学生列表
|
||||
const loadStudentList = async () => {
|
||||
try {
|
||||
uni.showLoading({ title: '加载中...' });
|
||||
|
||||
const res = await jsdXkXsListApi({
|
||||
xkkcId: xkkc.value.id,
|
||||
date: todayInfo.value.date
|
||||
});
|
||||
|
||||
if (res && res.resultCode === 1) {
|
||||
studentList.value = res.result || [];
|
||||
} else {
|
||||
studentList.value = [];
|
||||
uni.showToast({
|
||||
title: res?.message || '获取学生列表失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载学生列表失败:', error);
|
||||
studentList.value = [];
|
||||
uni.showToast({
|
||||
title: '加载学生列表失败',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadStudentList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.course-card {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.course-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 4px;
|
||||
background-color: rgba(64, 128, 255, 0.1);
|
||||
}
|
||||
|
||||
.record-stats {
|
||||
.stats-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
padding-left: 10px;
|
||||
border-left: 4px solid #4080ff;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px 15px;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
&.total {
|
||||
border: 2px solid #666;
|
||||
.stat-number { color: #333; }
|
||||
}
|
||||
|
||||
&.present {
|
||||
border: 2px solid #2879ff;
|
||||
.stat-number { color: #2879ff; }
|
||||
}
|
||||
|
||||
&.leave {
|
||||
border: 2px solid #ff9900;
|
||||
.stat-number { color: #ff9900; }
|
||||
}
|
||||
|
||||
&.absent {
|
||||
border: 2px solid #ff4d4f;
|
||||
.stat-number { color: #ff4d4f; }
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.student-popup {
|
||||
background: white;
|
||||
border-radius: 20px 20px 0 0;
|
||||
max-height: 70vh;
|
||||
overflow: hidden;
|
||||
|
||||
.popup-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
.popup-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: #f5f5f5;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.student-list {
|
||||
padding: 0 20px 20px;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
|
||||
.student-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.student-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
margin-right: 15px;
|
||||
|
||||
.avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.avatar-text {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #4080ff;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.student-info {
|
||||
flex: 1;
|
||||
|
||||
.student-name {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.student-class {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.student-status {
|
||||
.status-normal {
|
||||
color: #2879ff;
|
||||
background: rgba(40, 121, 255, 0.1);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.status-leave {
|
||||
color: #ff9900;
|
||||
background: rgba(255, 153, 0, 0.1);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.status-absent {
|
||||
color: #ff4d4f;
|
||||
background: rgba(255, 77, 79, 0.1);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-actions {
|
||||
.back-btn {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
background: #4080ff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 22px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
// 工具类
|
||||
.mx-15 { margin-left: 15px; margin-right: 15px; }
|
||||
.my-15 { margin-top: 15px; margin-bottom: 15px; }
|
||||
.mb-15 { margin-bottom: 15px; }
|
||||
.mb-30 { margin-bottom: 30px; }
|
||||
.mr-10 { margin-right: 10px; }
|
||||
.ml-10 { margin-left: 10px; }
|
||||
.bg-white { background-color: white; }
|
||||
.white-bg-color { background-color: white; }
|
||||
.r-md { border-radius: 8px; }
|
||||
.p-15 { padding: 15px; }
|
||||
.flex-row { display: flex; flex-direction: row; }
|
||||
.items-center { align-items: center; }
|
||||
.font-16 { font-size: 16px; }
|
||||
.font-14 { font-size: 14px; }
|
||||
.font-bold { font-weight: bold; }
|
||||
.cor-999 { color: #999; }
|
||||
</style>
|
||||
419
src/pages/base/groupTeaching/photoXkkcDetail.vue
Normal file
419
src/pages/base/groupTeaching/photoXkkcDetail.vue
Normal file
@ -0,0 +1,419 @@
|
||||
<template>
|
||||
<BasicLayout>
|
||||
<!-- 课程信息卡片 -->
|
||||
<view class="mb-5">
|
||||
<view class="flex-row items-center white-bg-color p-15 r-md">
|
||||
<!-- 左侧课程图片 -->
|
||||
<view class="course-image-section" v-if="xkkc.lxtp">
|
||||
<image
|
||||
:src="getImageUrl(xkkc.lxtp)"
|
||||
class="course-image"
|
||||
mode="aspectFill"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
</view>
|
||||
<view class="flex-col ml-10 flex-1 course-info">
|
||||
<view class="course-name">{{ xkkc.kcmc }}</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">上课周期:</view>
|
||||
<view class="info-data">{{ xkkc.skzqmc }}</view>
|
||||
</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">上课时间:</view>
|
||||
<view class="info-data">{{ formatClassTime(xkkc.skkstime, xkkc.skjstime) }}</view>
|
||||
</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">上课地点:</view>
|
||||
<view class="info-data">{{ xkkc.kcdd }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 拍照和视频功能区域 -->
|
||||
<view class="p-15">
|
||||
<!-- 现场拍照 -->
|
||||
<view class="section-card mb-15">
|
||||
<view class="section-title">现场拍照</view>
|
||||
<view class="photo-section">
|
||||
<view class="photo-preview" v-if="photoList.length > 0">
|
||||
<view
|
||||
v-for="(photo, index) in photoList"
|
||||
:key="index"
|
||||
class="photo-item"
|
||||
>
|
||||
<image
|
||||
:src="photo.url"
|
||||
class="photo-image"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="photo-actions">
|
||||
<view class="action-btn delete-btn" @click="deletePhoto(index)">
|
||||
<u-icon name="trash" size="16" color="#fff"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="photo-upload" @click="takePhoto">
|
||||
<u-icon name="camera" size="32" color="#999"></u-icon>
|
||||
<text class="upload-text">点击拍照</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 现场视频 -->
|
||||
<view class="section-card mb-15">
|
||||
<view class="section-title">现场视频</view>
|
||||
<view class="video-section">
|
||||
<view class="video-preview" v-if="videoList.length > 0">
|
||||
<view
|
||||
v-for="(video, index) in videoList"
|
||||
:key="index"
|
||||
class="video-item"
|
||||
>
|
||||
<video
|
||||
:src="video.url"
|
||||
class="video-player"
|
||||
controls
|
||||
show-center-play-btn
|
||||
></video>
|
||||
<view class="video-actions">
|
||||
<view class="action-btn delete-btn" @click="deleteVideo(index)">
|
||||
<u-icon name="trash" size="16" color="#fff"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="video-upload" @click="recordVideo">
|
||||
<u-icon name="videocam" size="32" color="#999"></u-icon>
|
||||
<text class="upload-text">点击录制</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<template #bottom>
|
||||
<view class="white-bg-color py-5">
|
||||
<view class="flex-row items-center pb-10 pt-5">
|
||||
<u-button text="返回" class="ml-15 mr-7" :plain="true" @click="navigateBack" />
|
||||
<u-button text="提交" class="mr-15 mr-7" type="primary" @click="submit" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</BasicLayout>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { navigateBack } from "@/utils/uniapp";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { useDataStore } from "@/store/modules/data";
|
||||
import { jsdXkkcPhotoSaveApi } from "@/api/base/server";
|
||||
import dayjs from "dayjs";
|
||||
import { BASE_IMAGE_URL } from "@/config";
|
||||
|
||||
const { getJs } = useUserStore();
|
||||
const { getData } = useDataStore();
|
||||
|
||||
const js = computed(() => getJs)
|
||||
const xkkc = ref<any>({})
|
||||
|
||||
// 照片和视频列表
|
||||
const photoList = ref<Array<{url: string, path: string}>>([])
|
||||
const videoList = ref<Array<{url: string, path: string}>>([])
|
||||
|
||||
// 格式化上课时间
|
||||
const formatClassTime = (startTime: string, endTime: string) => {
|
||||
if (!startTime || !endTime) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
let start, end;
|
||||
|
||||
if (startTime.includes(':') && !startTime.includes('-') && !startTime.includes('/')) {
|
||||
start = startTime;
|
||||
end = endTime;
|
||||
} else {
|
||||
const startDate = dayjs(startTime);
|
||||
const endDate = dayjs(endTime);
|
||||
|
||||
if (startDate.isValid() && endDate.isValid()) {
|
||||
start = startDate.format('HH:mm:ss');
|
||||
end = endDate.format('HH:mm:ss');
|
||||
} else {
|
||||
return `${startTime}~${endTime}`;
|
||||
}
|
||||
}
|
||||
|
||||
return `${start}~${end}`;
|
||||
} catch (error) {
|
||||
console.error('时间格式化错误:', error);
|
||||
return `${startTime}~${endTime}`;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取完整的图片URL
|
||||
const getImageUrl = (imagePath: string) => {
|
||||
if (!imagePath) {
|
||||
return '';
|
||||
}
|
||||
if (imagePath.startsWith('http://') || imagePath.startsWith('https://')) {
|
||||
return imagePath;
|
||||
}
|
||||
return `${BASE_IMAGE_URL}${imagePath}`;
|
||||
};
|
||||
|
||||
// 处理图片加载错误
|
||||
const handleImageError = () => {
|
||||
console.log('课程图片加载失败');
|
||||
};
|
||||
|
||||
// 拍照功能
|
||||
const takePhoto = () => {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['camera'],
|
||||
success: (res) => {
|
||||
const tempFilePath = res.tempFilePaths[0];
|
||||
photoList.value.push({
|
||||
url: tempFilePath,
|
||||
path: tempFilePath
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('拍照失败:', err);
|
||||
uni.showToast({
|
||||
title: '拍照失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 录制视频功能
|
||||
const recordVideo = () => {
|
||||
uni.chooseVideo({
|
||||
sourceType: ['camera'],
|
||||
maxDuration: 60, // 最大录制时长60秒
|
||||
camera: 'back',
|
||||
success: (res) => {
|
||||
const tempFilePath = res.tempFilePath;
|
||||
videoList.value.push({
|
||||
url: tempFilePath,
|
||||
path: tempFilePath
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('录制视频失败:', err);
|
||||
uni.showToast({
|
||||
title: '录制视频失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 删除照片
|
||||
const deletePhoto = (index: number) => {
|
||||
photoList.value.splice(index, 1);
|
||||
};
|
||||
|
||||
// 删除视频
|
||||
const deleteVideo = (index: number) => {
|
||||
videoList.value.splice(index, 1);
|
||||
};
|
||||
|
||||
// 提交数据
|
||||
const submit = async () => {
|
||||
if (photoList.value.length === 0 && videoList.value.length === 0) {
|
||||
uni.showToast({
|
||||
title: '请至少拍摄一张照片或录制一段视频',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 这里需要根据实际API调整
|
||||
const submitData = {
|
||||
xkkcId: xkkc.value.id,
|
||||
jsId: js.value.id,
|
||||
photoList: photoList.value,
|
||||
videoList: videoList.value,
|
||||
submitTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||
};
|
||||
|
||||
// 调用保存API
|
||||
const res = await jsdXkkcPhotoSaveApi(submitData);
|
||||
|
||||
if (res && res.resultCode === 1) {
|
||||
uni.showToast({
|
||||
title: '提交成功',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
|
||||
// 提交成功后延迟返回列表页
|
||||
setTimeout(() => {
|
||||
uni.navigateBack({
|
||||
delta: 1
|
||||
});
|
||||
}, 1500);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res?.resultMessage || '提交失败',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交失败:', error);
|
||||
uni.showToast({
|
||||
title: '提交失败,请重试',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
xkkc.value = getData;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.course-info {
|
||||
flex: 1;
|
||||
|
||||
.course-name {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.course-info-item {
|
||||
display: flex;
|
||||
margin-bottom: 12px;
|
||||
font-size: 12px;
|
||||
align-items: center;
|
||||
|
||||
.info-label {
|
||||
color: #949AA4;
|
||||
flex: 0 0 auto;
|
||||
margin-right: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.info-data {
|
||||
flex: 0 0 auto;
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.course-image-section {
|
||||
flex: 0 0 100px;
|
||||
margin-right: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.course-image {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
}
|
||||
|
||||
.section-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
padding-left: 8px;
|
||||
border-left: 3px solid #007aff;
|
||||
}
|
||||
|
||||
.photo-section, .video-section {
|
||||
.photo-preview, .video-preview {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.photo-item, .video-item {
|
||||
position: relative;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.photo-image, .video-player {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.video-player {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.photo-actions, .video-actions {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
|
||||
.action-btn {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
|
||||
&.delete-btn {
|
||||
background-color: rgba(255, 0, 0, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.photo-upload, .video-upload {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 2px dashed #ddd;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: #007aff;
|
||||
background-color: rgba(0, 122, 255, 0.05);
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -3,12 +3,13 @@
|
||||
<!-- 选课信息头部 - 固定部分 -->
|
||||
<view class="selection-header">
|
||||
<view class="header-content">
|
||||
<view class="title-section" @click="clickShowXkSelector">
|
||||
<view class="title-section">
|
||||
<view class="title">
|
||||
<text v-if="xkData && xkData.xkmc">{{ xkData.xkmc }}</text>
|
||||
<text v-else>选课信息</text>
|
||||
<text v-if="xkkcList && xkkcList.length > 0">
|
||||
{{ getCurrentSemesterName() }} - 我的课程
|
||||
</text>
|
||||
<text v-else>暂无课程</text>
|
||||
</view>
|
||||
<view class="switch-btn" v-if="xkList.length > 1">切换</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -18,22 +19,17 @@
|
||||
<!-- 课程网格列表 -->
|
||||
<view class="course-list" v-if="xkkcList && xkkcList.length > 0">
|
||||
<view v-for="(xkkc, index) in xkkcList" :key="xkkc.id || index" class="course-item">
|
||||
<view class="course-name">{{ xkkc.kcmc }}</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">上课周期类型:</view>
|
||||
<view class="info-data">{{ xkkc.skzqlx }}</view>
|
||||
<view class="status-badge" :class="getStatusClass(xkkc)">
|
||||
{{ getStatusText(xkkc) }}
|
||||
</view>
|
||||
<view class="course-name">{{ xkkc.kcmc }}</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">上课周期:</view>
|
||||
<view class="info-data">{{ xkkc.skzqmc }}</view>
|
||||
</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">上课开始时间:</view>
|
||||
<view class="info-data">{{ xkkc.skkstime }}</view>
|
||||
</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">上课结束时间:</view>
|
||||
<view class="info-data">{{ xkkc.skjstime }}</view>
|
||||
<view class="info-label">上课时间:</view>
|
||||
<view class="info-data">{{ formatClassTime(xkkc.skkstime, xkkc.skjstime) }}</view>
|
||||
</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">开课地点:</view>
|
||||
@ -43,8 +39,9 @@
|
||||
<view class="info-label">上课人数:</view>
|
||||
<view class="info-data">{{ xkkc.hasNum || 0 }} | {{ xkkc.maxNum || 0 }}</view>
|
||||
</view>
|
||||
<view class="separator-line"></view>
|
||||
<view class="course-btn-group">
|
||||
<view class="detail-btn" @click.stop="goDetail(xkkc)">详情</view>
|
||||
<view class="detail-btn" @click.stop="goDetail(xkkc)">填报</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -58,29 +55,6 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view>
|
||||
<!-- 俱乐部选择弹窗 -->
|
||||
<u-popup :show="showXkFlag" @close="showXkFlag = false" mode="bottom" round="10">
|
||||
<view class="xk-selector">
|
||||
<view class="selector-header">
|
||||
<text class="selector-title">选择俱乐部</text>
|
||||
<u-icon name="close" size="20" @click="showXkFlag = false"></u-icon>
|
||||
</view>
|
||||
<view class="xk-list">
|
||||
<view v-for="(xk, index) in xkList" :key="index" class="xk-item" :class="{
|
||||
'xk-item-active': xkData.id === xk.id
|
||||
}" @click="switchXk(xk)">
|
||||
<view class="xk-info">
|
||||
<text class="xk-name">{{ xk.xkmc }}</text>
|
||||
<text class="xk-type">{{ xk.xkmc }}</text>
|
||||
</view>
|
||||
<u-icon v-if="xkData.id === xk.id" name="checkmark" color="#409EFF" size="20"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@ -92,7 +66,7 @@ import {
|
||||
} from "vue";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { useDataStore } from "@/store/modules/data";
|
||||
import { jsdXkListApi } from "@/api/base/server";
|
||||
import { getCurrentSemesterTeacherCoursesApi } from "@/api/base/server";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const { getJs } = useUserStore();
|
||||
@ -100,15 +74,6 @@ const { getData, setData } = useDataStore();
|
||||
|
||||
const wdNameList = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
|
||||
|
||||
// 控制选择器显示状态
|
||||
const showXkFlag = ref(false);
|
||||
|
||||
const xkList = ref<any>([]);
|
||||
|
||||
const xkData = ref();
|
||||
|
||||
const courseInfo = ref<any>({});
|
||||
|
||||
// 课程列表数据
|
||||
const xkkcList = ref<any[]>([]);
|
||||
|
||||
@ -121,36 +86,39 @@ const loadCourseList = async () => {
|
||||
uni.showLoading({
|
||||
title: "加载中...",
|
||||
});
|
||||
const res = await jsdXkListApi({
|
||||
jsId: getJs.id
|
||||
});
|
||||
try {
|
||||
const res = await getCurrentSemesterTeacherCoursesApi(getJs.id);
|
||||
if (res.resultCode == 1) {
|
||||
if (res.result && res.result.length) {
|
||||
xkList.value = res.result;
|
||||
switchXk(res.result[0]);
|
||||
xkkcList.value = res.result;
|
||||
// 处理课程周期显示
|
||||
processCoursePeriods();
|
||||
} else {
|
||||
xkList.value = [];
|
||||
xkData.value = {};
|
||||
xkkcList.value = [];
|
||||
}
|
||||
} else {
|
||||
xkkcList.value = [];
|
||||
uni.showToast({
|
||||
title: res.resultMessage || '获取课程列表失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载课程列表失败:', error);
|
||||
xkkcList.value = [];
|
||||
uni.showToast({
|
||||
title: '加载课程列表失败',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
};
|
||||
|
||||
// 显示选课选择器
|
||||
function clickShowXkSelector() {
|
||||
if (xkList.value.length > 1) {
|
||||
showXkFlag.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 切换选课
|
||||
function switchXk(xk: any) {
|
||||
xkData.value = xk;
|
||||
xkkcList.value = xk.xkkcs;
|
||||
showXkFlag.value = false;
|
||||
for (let i = 0; i < xk.xkkcs.length; i++) {
|
||||
let xkkc = xk.xkkcs[i];
|
||||
// 处理课程周期显示
|
||||
const processCoursePeriods = () => {
|
||||
for (let i = 0; i < xkkcList.value.length; i++) {
|
||||
let xkkc = xkkcList.value[i];
|
||||
// 判断周期
|
||||
switch (xkkc.skzqlx) {
|
||||
case '每天':
|
||||
@ -168,12 +136,68 @@ function switchXk(xk: any) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 显示切换成功提示
|
||||
uni.showToast({
|
||||
title: `已切换到${xk.xkmc}`,
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 获取当前学期名称
|
||||
const getCurrentSemesterName = () => {
|
||||
if (xkkcList.value && xkkcList.value.length > 0) {
|
||||
// 从第一个课程获取学期名称
|
||||
return xkkcList.value[0].xqmc || '当前学期';
|
||||
}
|
||||
return '当前学期';
|
||||
};
|
||||
|
||||
// 格式化上课时间
|
||||
const formatClassTime = (startTime: string, endTime: string) => {
|
||||
if (!startTime || !endTime) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
// 尝试解析时间,支持多种格式
|
||||
let start, end;
|
||||
|
||||
// 如果是时间格式(HH:mm:ss 或 HH:mm)
|
||||
if (startTime.includes(':') && !startTime.includes('-') && !startTime.includes('/')) {
|
||||
start = startTime;
|
||||
end = endTime;
|
||||
} else {
|
||||
// 尝试用 dayjs 解析
|
||||
const startDate = dayjs(startTime);
|
||||
const endDate = dayjs(endTime);
|
||||
|
||||
if (startDate.isValid() && endDate.isValid()) {
|
||||
start = startDate.format('HH:mm:ss');
|
||||
end = endDate.format('HH:mm:ss');
|
||||
} else {
|
||||
// 如果解析失败,直接返回原始值
|
||||
return `${startTime}~${endTime}`;
|
||||
}
|
||||
}
|
||||
|
||||
return `${start}~${end}`;
|
||||
} catch (error) {
|
||||
console.error('时间格式化错误:', error);
|
||||
// 如果出错,返回原始值
|
||||
return `${startTime}~${endTime}`;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (xkkc: any) => {
|
||||
if (!xkkc.kcjsms && !xkkc.jxll) {
|
||||
return '待填报';
|
||||
}
|
||||
return '已填报';
|
||||
};
|
||||
|
||||
// 获取状态样式类
|
||||
const getStatusClass = (xkkc: any) => {
|
||||
if (!xkkc.kcjsms && !xkkc.jxll) {
|
||||
return 'pending';
|
||||
}
|
||||
return 'completed';
|
||||
};
|
||||
|
||||
// 查看课程详情
|
||||
const goDetail = (xkkc: any) => {
|
||||
@ -191,7 +215,7 @@ onBeforeUnmount(() => {
|
||||
<style lang="scss" scoped>
|
||||
.interest-course {
|
||||
min-height: 100%;
|
||||
background-color: #f5f7fa;
|
||||
background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
@ -227,46 +251,50 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.selection-header {
|
||||
background: linear-gradient(135deg, #4a90e2, #2879ff);
|
||||
padding: 20px 15px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 25px 20px;
|
||||
color: #fff;
|
||||
border-radius: 0 0 15px 15px;
|
||||
box-shadow: 0 4px 12px rgba(40, 121, 255, 0.2);
|
||||
border-radius: 0 0 20px 20px;
|
||||
box-shadow: 0 8px 32px rgba(102, 126, 234, 0.3);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(45deg, rgba(255, 255, 255, 0.1) 0%, transparent 50%, rgba(255, 255, 255, 0.05) 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
.title-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.title {
|
||||
flex: 1 0 1px;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 14px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.switch-btn {
|
||||
padding: 5px 15px;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
color: #fff;
|
||||
border-radius: 15px;
|
||||
font-size: 15px;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -283,13 +311,21 @@ onBeforeUnmount(() => {
|
||||
.course-item {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid #f0f0f0;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
animation: fadeInUp 0.6s ease-out;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.12);
|
||||
border-color: #e8e8e8;
|
||||
}
|
||||
|
||||
// &:nth-child(odd) {
|
||||
// margin-right: 10px;
|
||||
@ -300,111 +336,153 @@ onBeforeUnmount(() => {
|
||||
// }
|
||||
|
||||
&.selected {
|
||||
border: 1px solid #3fbf72;
|
||||
background-color: rgba(63, 191, 114, 0.05);
|
||||
box-shadow: 0 2px 8px rgba(63, 191, 114, 0.15);
|
||||
border: 2px solid #3fbf72;
|
||||
background-color: rgba(63, 191, 114, 0.02);
|
||||
box-shadow: 0 4px 20px rgba(63, 191, 114, 0.15);
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
background-color: #fff;
|
||||
border-radius: 20px;
|
||||
padding: 6px 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
border: 1px solid #e0e0e0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
letter-spacing: 0.5px;
|
||||
animation: fadeInRight 0.5s ease-out 0.2s both;
|
||||
}
|
||||
|
||||
.status-badge.pending {
|
||||
color: #f56c6c;
|
||||
border-color: #fde2e2;
|
||||
background-color: #fef0f0;
|
||||
}
|
||||
|
||||
.status-badge.completed {
|
||||
color: #67c23a;
|
||||
border-color: #e1f3d8;
|
||||
background-color: #f0f9ff;
|
||||
}
|
||||
|
||||
.course-name {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 15px;
|
||||
padding-right: 90px;
|
||||
line-height: 1.4;
|
||||
animation: fadeInLeft 0.5s ease-out 0.1s both;
|
||||
}
|
||||
|
||||
.course-btn-group {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
.detail-btn {
|
||||
flex: 1 0 1px;
|
||||
display: inline-block;
|
||||
color: #2879ff;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding: 8px 18px;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(135deg, rgba(40, 121, 255, 0.1), rgba(40, 121, 255, 0.05));
|
||||
border: 1px solid #2879ff;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 2px 8px rgba(40, 121, 255, 0.15);
|
||||
animation: fadeInUp 0.5s ease-out 0.3s both;
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(135deg, rgba(40, 121, 255, 0.15), rgba(40, 121, 255, 0.1));
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 15px rgba(40, 121, 255, 0.25);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: linear-gradient(135deg, rgba(40, 121, 255, 0.2), rgba(40, 121, 255, 0.15));
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 8px rgba(40, 121, 255, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.course-info-item {
|
||||
display: flex;
|
||||
margin-bottom: 12px;
|
||||
font-size: 12px;
|
||||
margin-bottom: 14px;
|
||||
font-size: 13px;
|
||||
align-items: center;
|
||||
animation: fadeInUp 0.5s ease-out 0.15s both;
|
||||
|
||||
.info-label {
|
||||
color: #949AA4;
|
||||
color: #666;
|
||||
flex: 0 0 100px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.info-data {
|
||||
flex: 1 0 1px;
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.separator-line {
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, #e8e8e8, transparent);
|
||||
margin: 18px 0;
|
||||
opacity: 0.8;
|
||||
animation: fadeIn 0.5s ease-out 0.25s both;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 学生选择器弹窗样式 */
|
||||
.xk-selector {
|
||||
background-color: #ffffff;
|
||||
border-top-left-radius: 12px;
|
||||
border-top-right-radius: 12px;
|
||||
padding-bottom: 20px;
|
||||
|
||||
.selector-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
|
||||
.selector-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
.xk-list {
|
||||
padding: 0 15px;
|
||||
|
||||
.xk-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&-active {
|
||||
background-color: rgba(64, 158, 255, 0.05);
|
||||
}
|
||||
|
||||
.xk-info {
|
||||
flex: 1;
|
||||
margin-left: 12px;
|
||||
|
||||
.xk-name {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.xk-type {
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.xk-class {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
// 添加动画关键帧
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInLeft {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 全局图片样式 */
|
||||
.w-full {
|
||||
width: 100%;
|
||||
@ -420,32 +498,35 @@ onBeforeUnmount(() => {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
padding: 80px 20px;
|
||||
text-align: center;
|
||||
|
||||
.empty-icon {
|
||||
margin-bottom: 20px;
|
||||
background-color: #f5f6f7;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin-bottom: 25px;
|
||||
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
border: 2px solid rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
color: #475569;
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
color: #64748b;
|
||||
max-width: 80%;
|
||||
line-height: 1.5;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,52 +4,87 @@
|
||||
</template> -->
|
||||
<view class="mb-5">
|
||||
<view class="flex-row items-center white-bg-color p-15 r-md">
|
||||
<!-- 左侧课程图片 -->
|
||||
<view class="course-image-section" v-if="xkkc.lxtp">
|
||||
<image
|
||||
:src="getImageUrl(xkkc.lxtp)"
|
||||
class="course-image"
|
||||
mode="aspectFill"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
</view>
|
||||
<view class="flex-col ml-10 flex-1 course-info">
|
||||
<view class="course-name">{{ xkkc.kcmc }}</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">上课周期类型:</view>
|
||||
<view class="info-data">{{ xkkc.skzqlx }}</view>
|
||||
</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">上课周期:</view>
|
||||
<view class="info-data">{{ xkkc.skzqmc }}</view>
|
||||
</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">上课开始时间:</view>
|
||||
<view class="info-data">{{ xkkc.skkstime }}</view>
|
||||
</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">上课结束时间:</view>
|
||||
<view class="info-data">{{ xkkc.skjstime }}</view>
|
||||
</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">开课老师:</view>
|
||||
<view class="info-data">{{ js.jsxm }}</view>
|
||||
</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">开课地点:</view>
|
||||
<view class="info-data">{{ xkkc.kcdd }}</view>
|
||||
</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">上课人数:</view>
|
||||
<view class="info-data">{{ xkkc.hasNum || 0 }} | {{ xkkc.maxNum || 0 }}</view>
|
||||
</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">金额:</view>
|
||||
<view class="info-data cor-FF8D02">¥{{ xkkc.kcje }}</view>
|
||||
<view class="info-label">上课时间:</view>
|
||||
<view class="info-data">{{ formatClassTime(xkkc.skkstime, xkkc.skjstime) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 四个主要部分 -->
|
||||
<view class="p-15">
|
||||
<BasicForm @register="register">
|
||||
<template #jxjh>
|
||||
<view class="back-f8f8f8">
|
||||
<view class="flex-row items-center justify-between py-15 global-bg-color">
|
||||
<view>
|
||||
<BasicTitle line title="教学计划" :isBorder="false" />
|
||||
<!-- 第一部分:教师信息 -->
|
||||
<view class="section-card mb-15">
|
||||
<view class="section-title">教师信息 <text class="required">*</text></view>
|
||||
<view class="teacher-info-header">
|
||||
<!-- 教师头像 -->
|
||||
<view class="teacher-avatar-section" v-if="teacherHeadPic">
|
||||
<image
|
||||
:src="getImageUrl(teacherHeadPic)"
|
||||
class="teacher-avatar"
|
||||
mode="aspectFill"
|
||||
@error="handleTeacherAvatarError"
|
||||
/>
|
||||
</view>
|
||||
<view @click="addEducation">
|
||||
<!-- 右侧信息 -->
|
||||
<view class="teacher-details">
|
||||
<view class="detail-row">
|
||||
<view class="detail-item">
|
||||
<text class="detail-label">开课老师:</text>
|
||||
<text class="detail-value">{{ js.jsxm }}</text>
|
||||
</view>
|
||||
<view class="detail-item">
|
||||
<text class="detail-label">上课地点:</text>
|
||||
<text class="detail-value">{{ xkkc.kcdd }}</text>
|
||||
</view>
|
||||
<view class="detail-item">
|
||||
<text class="detail-label">上课人数:</text>
|
||||
<text class="detail-value">{{ xkkc.hasNum || 0 }} | {{ xkkc.maxNum || 0 }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 老师信息输入框单独一行 -->
|
||||
<view class="teacher-input-row">
|
||||
<textarea
|
||||
v-model="xkkc.kcjsms"
|
||||
placeholder="请输入教师信息(必填)"
|
||||
class="form-textarea"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 第二部分:教学理念 -->
|
||||
<view class="section-card mb-15">
|
||||
<view class="section-title">教学理念 <text class="required">*</text></view>
|
||||
<textarea
|
||||
v-model="xkkc.jxll"
|
||||
placeholder="请输入教学理念(必填)"
|
||||
class="form-textarea"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 第三部分:教学计划 -->
|
||||
<view class="section-card mb-15">
|
||||
<view class="section-header">
|
||||
<view class="section-title">教学计划 <text class="required">*</text></view>
|
||||
<view @click="addEducation" class="add-icon">
|
||||
<BasicIcon type="icon-tianjia" size="25" />
|
||||
</view>
|
||||
</view>
|
||||
@ -65,8 +100,6 @@
|
||||
</view>
|
||||
<view v-else class="p-15 flex-row-center color-9 font-13 white-bg-color">教学计划暂无数据</view>
|
||||
</view>
|
||||
</template>
|
||||
</BasicForm>
|
||||
</view>
|
||||
<template #bottom>
|
||||
<view class="white-bg-color py-5">
|
||||
@ -87,36 +120,76 @@ import { cloneDeep, get } from "lodash";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { useDataStore } from "@/store/modules/data";
|
||||
import { jsdXkkcSaveApi } from "@/api/base/server";
|
||||
import dayjs from "dayjs";
|
||||
import { BASE_IMAGE_URL } from "@/config";
|
||||
|
||||
const { getJs } = useUserStore();
|
||||
const { getData } = useDataStore();
|
||||
|
||||
const js = computed(() => getJs)
|
||||
const xkkc = ref<any>({})
|
||||
|
||||
// 获取教师头像
|
||||
const teacherHeadPic = ref<string>('')
|
||||
|
||||
// 从localStorage获取教师头像
|
||||
const getTeacherHeadPic = () => {
|
||||
try {
|
||||
console.log('开始获取教师头像...');
|
||||
const appUserStr = uni.getStorageSync('app-user')
|
||||
console.log('app-user原始数据:', appUserStr);
|
||||
|
||||
// 解析JSON字符串为对象
|
||||
let appUser;
|
||||
try {
|
||||
appUser = typeof appUserStr === 'string' ? JSON.parse(appUserStr) : appUserStr;
|
||||
} catch (parseError) {
|
||||
console.error('JSON解析失败:', parseError);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('解析后的appUser数据:', appUser);
|
||||
|
||||
// 详细检查数据结构
|
||||
console.log('appUser存在:', !!appUser);
|
||||
console.log('appUser类型:', typeof appUser);
|
||||
console.log('appUser的所有键:', Object.keys(appUser));
|
||||
|
||||
if (appUser) {
|
||||
console.log('appUser.userdata存在:', !!appUser.userdata);
|
||||
console.log('appUser.userdata类型:', typeof appUser.userdata);
|
||||
if (appUser.userdata) {
|
||||
console.log('appUser.userdata的所有键:', Object.keys(appUser.userdata));
|
||||
console.log('appUser.userdata.js存在:', !!appUser.userdata.js);
|
||||
if (appUser.userdata.js) {
|
||||
console.log('appUser.userdata.js的所有键:', Object.keys(appUser.userdata.js));
|
||||
console.log('appUser.userdata.js.headPic存在:', !!appUser.userdata.js.headPic);
|
||||
console.log('headPic值:', appUser.userdata.js.headPic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 直接从app-user获取userdata
|
||||
if (appUser && appUser.userdata && appUser.userdata.js) {
|
||||
console.log('userdata.js数据:', appUser.userdata.js);
|
||||
if (appUser.userdata.js.headPic) {
|
||||
console.log('找到headPic:', appUser.userdata.js.headPic);
|
||||
teacherHeadPic.value = appUser.userdata.js.headPic
|
||||
console.log('设置teacherHeadPic为:', teacherHeadPic.value);
|
||||
} else {
|
||||
console.log('userdata.js中没有headPic字段');
|
||||
}
|
||||
} else {
|
||||
console.log('没有找到userdata或js');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取教师头像失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const [register, { getValue, setValue }] = useForm({
|
||||
schema: [
|
||||
{
|
||||
field: "kcjsms",
|
||||
label: "老师信息",
|
||||
component: "BasicInput",
|
||||
itemProps: {
|
||||
labelPosition: "top",
|
||||
},
|
||||
componentProps: {
|
||||
type: "textarea",
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "jxll",
|
||||
label: "教学理念",
|
||||
component: "BasicInput",
|
||||
itemProps: {
|
||||
labelPosition: "top",
|
||||
},
|
||||
componentProps: {
|
||||
type: "textarea",
|
||||
},
|
||||
},
|
||||
// 不再需要kcjsms和jxll字段,因为已经用原生textarea处理
|
||||
{ colSlot: 'jxjh' },
|
||||
],
|
||||
});
|
||||
@ -174,17 +247,135 @@ function deleteMemberFamily(index: number, item: any) {
|
||||
education.xl = list
|
||||
}
|
||||
|
||||
// 格式化上课时间
|
||||
const formatClassTime = (startTime: string, endTime: string) => {
|
||||
if (!startTime || !endTime) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
// 尝试解析时间,支持多种格式
|
||||
let start, end;
|
||||
|
||||
// 如果是时间格式(HH:mm:ss 或 HH:mm)
|
||||
if (startTime.includes(':') && !startTime.includes('-') && !startTime.includes('/')) {
|
||||
start = startTime;
|
||||
end = endTime;
|
||||
} else {
|
||||
// 尝试用 dayjs 解析
|
||||
const startDate = dayjs(startTime);
|
||||
const endDate = dayjs(endTime);
|
||||
|
||||
if (startDate.isValid() && endDate.isValid()) {
|
||||
start = startDate.format('HH:mm:ss');
|
||||
end = endDate.format('HH:mm:ss');
|
||||
} else {
|
||||
// 如果解析失败,直接返回原始值
|
||||
return `${startTime}~${endTime}`;
|
||||
}
|
||||
}
|
||||
|
||||
return `${start}~${end}`;
|
||||
} catch (error) {
|
||||
console.error('时间格式化错误:', error);
|
||||
// 如果出错,返回原始值
|
||||
return `${startTime}~${endTime}`;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取完整的图片URL
|
||||
const getImageUrl = (imagePath: string) => {
|
||||
console.log('getImageUrl被调用,参数:', imagePath);
|
||||
console.log('BASE_IMAGE_URL:', BASE_IMAGE_URL);
|
||||
|
||||
if (!imagePath) {
|
||||
console.log('图片路径为空,返回空字符串');
|
||||
return '';
|
||||
}
|
||||
// 如果已经是完整URL,直接返回
|
||||
if (imagePath.startsWith('http://') || imagePath.startsWith('https://')) {
|
||||
console.log('已经是完整URL,直接返回:', imagePath);
|
||||
return imagePath;
|
||||
}
|
||||
// 否则拼接BASE_IMAGE_URL
|
||||
const fullUrl = `${BASE_IMAGE_URL}${imagePath}`;
|
||||
console.log('拼接后的完整URL:', fullUrl);
|
||||
return fullUrl;
|
||||
};
|
||||
|
||||
// 处理图片加载错误
|
||||
const handleImageError = () => {
|
||||
console.log('课程图片加载失败');
|
||||
};
|
||||
|
||||
// 处理教师头像加载错误
|
||||
const handleTeacherAvatarError = () => {
|
||||
console.log('教师头像加载失败');
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
const data = await getValue()
|
||||
xkkc.value.kcjsms = data.kcjsms;
|
||||
xkkc.value.jxll = data.jxll;
|
||||
// 必填验证
|
||||
if (!xkkc.value.kcjsms || xkkc.value.kcjsms.trim() === '') {
|
||||
uni.showToast({
|
||||
title: '请输入教师信息',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!xkkc.value.jxll || xkkc.value.jxll.trim() === '') {
|
||||
uni.showToast({
|
||||
title: '请输入教学理念',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!education.xl || education.xl.length === 0) {
|
||||
uni.showToast({
|
||||
title: '请添加教学计划',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证教学计划是否完整
|
||||
for (let i = 0; i < education.xl.length; i++) {
|
||||
const item = education.xl[i];
|
||||
if (!item.value.jhjd || item.value.jhjd.trim() === '' ||
|
||||
!item.value.jhsj || item.value.jhsj.trim() === '' ||
|
||||
!item.value.jhdd || item.value.jhdd.trim() === '' ||
|
||||
!item.value.jhnr || item.value.jhnr.trim() === '') {
|
||||
uni.showToast({
|
||||
title: `教学计划第${i + 1}项信息不完整`,
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 直接使用v-model绑定的值,不需要调用getValue()
|
||||
// xkkc.value.kcjsms 和 xkkc.value.jxll 已经通过v-model绑定
|
||||
xkkc.value.jxjh = JSON.stringify(education.xl)
|
||||
|
||||
await jsdXkkcSaveApi(xkkc.value).then(res => {
|
||||
uni.showToast({
|
||||
title: '提交成功',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
|
||||
// 提交成功后延迟返回列表页
|
||||
setTimeout(() => {
|
||||
uni.navigateBack({
|
||||
delta: 1
|
||||
});
|
||||
}, 1500); // 延迟1.5秒,让用户看到成功提示
|
||||
|
||||
}).catch(err => {
|
||||
uni.showToast({
|
||||
title: '提交失败',
|
||||
@ -195,14 +386,59 @@ const submit = async () => {
|
||||
}
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
console.log('页面初始化开始...');
|
||||
xkkc.value = getData;
|
||||
setValue(getData);
|
||||
// 将xkkc中的jxjh从json字符串转换为数组
|
||||
if (xkkc.value.jxjh && xkkc.value.jxjh.length > 0) {
|
||||
education.xl = JSON.parse(xkkc.value.jxjh)
|
||||
|
||||
// 处理教学计划数据回显
|
||||
if (xkkc.value.jxjh) {
|
||||
try {
|
||||
// 如果jxjh是字符串,先解析
|
||||
let jxjhData = xkkc.value.jxjh;
|
||||
if (typeof jxjhData === 'string') {
|
||||
jxjhData = JSON.parse(jxjhData);
|
||||
}
|
||||
|
||||
// 检查数据结构并转换为正确的格式
|
||||
if (Array.isArray(jxjhData)) {
|
||||
education.xl = jxjhData.map(item => {
|
||||
// 确保每个item都有value属性
|
||||
if (item.value) {
|
||||
return {
|
||||
value: {
|
||||
jhjd: item.value.jhjd || '',
|
||||
jhsj: item.value.jhsj || '',
|
||||
jhdd: item.value.jhdd || '',
|
||||
jhnr: item.value.jhnr || ''
|
||||
}
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
console.log('教学计划数据回显成功:', education.xl);
|
||||
} else {
|
||||
education.xl = [];
|
||||
console.log('教学计划数据格式不正确,初始化为空数组');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析教学计划数据失败:', error);
|
||||
education.xl = [];
|
||||
}
|
||||
} else {
|
||||
education.xl = [];
|
||||
console.log('没有教学计划数据,初始化为空数组');
|
||||
}
|
||||
|
||||
console.log('调用getTeacherHeadPic前,teacherHeadPic值:', teacherHeadPic.value);
|
||||
getTeacherHeadPic(); // 调用获取教师头像的函数
|
||||
console.log('调用getTeacherHeadPic后,teacherHeadPic值:', teacherHeadPic.value);
|
||||
|
||||
// 添加调试信息
|
||||
setTimeout(() => {
|
||||
console.log('延迟检查teacherHeadPic值:', teacherHeadPic.value);
|
||||
console.log('teacherHeadPic是否存在:', !!teacherHeadPic.value);
|
||||
console.log('当前教学计划数据:', education.xl);
|
||||
}, 1000);
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@ -212,7 +448,9 @@ onMounted(() => {
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.course-info {
|
||||
flex: 1;
|
||||
|
||||
.course-name {
|
||||
font-size: 16px;
|
||||
@ -224,13 +462,157 @@ onMounted(() => {
|
||||
display: flex;
|
||||
margin-bottom: 12px;
|
||||
font-size: 12px;
|
||||
align-items: center;
|
||||
|
||||
.info-label {
|
||||
color: #949AA4;
|
||||
flex: 0 0 100px;
|
||||
flex: 0 0 auto;
|
||||
margin-right: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.info-data {
|
||||
flex: 1 0 1px;
|
||||
flex: 0 0 auto;
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.course-image-section {
|
||||
flex: 0 0 100px;
|
||||
margin-right: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.course-image {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
}
|
||||
|
||||
.teacher-name {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.teacher-info-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.teacher-details {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.teacher-input-row {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
cursor: pointer;
|
||||
color: #007aff;
|
||||
}
|
||||
|
||||
.teacher-avatar-section {
|
||||
flex: 0 0 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.teacher-avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #e0e0e0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.teacher-input-section {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.section-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
padding-left: 8px;
|
||||
border-left: 3px solid #007aff;
|
||||
|
||||
.required {
|
||||
color: #f56c6c;
|
||||
font-weight: 700;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
width: 100%; /* 恢复为100%宽度 */
|
||||
min-height: 80px;
|
||||
padding: 12px 15px; /* 左右padding保持15px */
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
resize: vertical;
|
||||
box-sizing: border-box; /* 确保padding和border包含在宽度内 */
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #007aff;
|
||||
box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.1);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -234,7 +234,7 @@ const sections = reactive<Section[]>([
|
||||
{
|
||||
id: "r7",
|
||||
icon: "file-text-fill-2",
|
||||
text: "课程介绍",
|
||||
text: "课程填报",
|
||||
show: true,
|
||||
permissionKey: "routine-kcjs", // 课程介绍权限编码
|
||||
path: "/pages/base/groupTeaching/xkList",
|
||||
|
||||
@ -6,17 +6,17 @@
|
||||
<view class="header-content">
|
||||
<image
|
||||
class="avatar-large"
|
||||
:src="xsInfo?.avatar"
|
||||
:src="imagUrl(xsInfo?.avatar)"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
<text class="student-name">{{ xsInfo?.xm }}</text>
|
||||
<view class="student-meta">
|
||||
<uni-icons
|
||||
:type="xsInfo?.gender === '女' ? 'female' : 'male'"
|
||||
:type="xsInfo?.gender === '2' ? 'female' : 'male'"
|
||||
size="16"
|
||||
:color="xsInfo?.gender === '女' ? '#ff8d8f' : '#fff'"
|
||||
:color="xsInfo?.gender === '2' ? '#ff8d8f' : '#fff'"
|
||||
></uni-icons>
|
||||
<text class="meta-text">{{ xsInfo?.gender }}</text>
|
||||
<text class="meta-text">{{ getGenderText(xsInfo?.gender) }}</text>
|
||||
<view class="separator"></view>
|
||||
<text class="meta-text">{{ xsInfo?.age }}岁</text>
|
||||
</view>
|
||||
@ -35,7 +35,11 @@
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-label">出生日期:</text>
|
||||
<text class="info-value">{{ xsInfo?.birthDate }}</text>
|
||||
<text class="info-value">{{ xsInfo?.birthDate || '未知' }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-label">年龄:</text>
|
||||
<text class="info-value">{{ xsInfo?.age || '未知' }}岁</text>
|
||||
</view>
|
||||
<!-- <view class="info-item">
|
||||
<text class="info-label">现居住地:</text>
|
||||
@ -54,11 +58,16 @@
|
||||
class="parent-card-item"
|
||||
>
|
||||
<view class="parent-avatar-container">
|
||||
<!-- 如果有头像则显示图片,否则显示文字头像 -->
|
||||
<image
|
||||
v-if="jz.avatar"
|
||||
class="parent-avatar"
|
||||
:src="imagUrl(jz.avatar)"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
<view v-else class="parent-avatar-text">
|
||||
{{ jz.jzxm }}
|
||||
</view>
|
||||
<view class="call-icon-overlay" @click="callParent(jz.jzsj)">
|
||||
<uni-icons
|
||||
type="phone-filled"
|
||||
@ -67,7 +76,8 @@
|
||||
></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<text class="parent-name">{{ jz.jzxm }}</text>
|
||||
<!-- 去掉家长姓名显示 -->
|
||||
<!-- <text class="parent-name">{{ jz.jzxm }}</text> -->
|
||||
<text class="parent-relation">({{ jz.jzxsgxId }})</text>
|
||||
</view>
|
||||
</view>
|
||||
@ -78,6 +88,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from "vue";
|
||||
import dayjs from "dayjs";
|
||||
import { imagUrl } from "@/utils";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
@ -87,22 +98,87 @@ const { getData } = useDataStore();
|
||||
|
||||
const xsInfo = computed(() => {
|
||||
const xs = getData || {};
|
||||
// 根据使用dayjs身份证计算出生日期和年龄
|
||||
const birthDate = dayjs(xs.sfzh.substring(6, 10) + xs.sfzh.substring(10, 12) + xs.sfzh.substring(12, 14));
|
||||
xs.birthDate = birthDate.format("YYYY-MM-DD");
|
||||
xs.age = dayjs().diff(birthDate, "year");
|
||||
return xs;
|
||||
// 适配从点名页面传递过来的学生信息字段
|
||||
const studentInfo = {
|
||||
id: xs.xsId || xs.id, // 支持两种字段名
|
||||
xm: xs.xsxm || xs.xm, // 学生姓名
|
||||
avatar: xs.xstx || xs.avatar, // 学生头像
|
||||
gender: xs.xb || xs.gender, // 性别
|
||||
njmc: xs.njmcName || xs.njmc || '', // 年级名称,优先使用njmcName
|
||||
bjmc: xs.bjmc || '', // 班级名称
|
||||
sfzh: xs.sfzh || '', // 身份证号
|
||||
cstime: xs.cstime || '', // 出生日期
|
||||
birthDate: '', // 计算后的出生日期
|
||||
age: 0 // 年龄
|
||||
};
|
||||
|
||||
// 优先使用cstime字段,如果没有再尝试从身份证号计算
|
||||
if (studentInfo.cstime) {
|
||||
try {
|
||||
const birthDate = dayjs(studentInfo.cstime);
|
||||
if (birthDate.isValid()) {
|
||||
studentInfo.birthDate = birthDate.format("YYYY-MM-DD");
|
||||
studentInfo.age = dayjs().diff(birthDate, "year");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("出生日期解析失败:", error);
|
||||
}
|
||||
} else if (studentInfo.sfzh && studentInfo.sfzh.length >= 14) {
|
||||
// 如果没有cstime,尝试从身份证号计算
|
||||
try {
|
||||
const birthDate = dayjs(studentInfo.sfzh.substring(6, 10) + studentInfo.sfzh.substring(10, 12) + studentInfo.sfzh.substring(12, 14));
|
||||
studentInfo.birthDate = birthDate.format("YYYY-MM-DD");
|
||||
studentInfo.age = dayjs().diff(birthDate, "year");
|
||||
} catch (error) {
|
||||
console.error("身份证号解析失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有性别信息,尝试从身份证号计算
|
||||
if (!studentInfo.gender && studentInfo.sfzh && studentInfo.sfzh.length >= 17) {
|
||||
try {
|
||||
const genderCode = parseInt(studentInfo.sfzh.substring(16, 17));
|
||||
studentInfo.gender = genderCode % 2 === 0 ? '2' : '1'; // 将数字转换为 '2' 或 '1'
|
||||
} catch (error) {
|
||||
console.error("性别计算失败:", error);
|
||||
studentInfo.gender = '未知';
|
||||
}
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if (!studentInfo.gender) studentInfo.gender = '未知';
|
||||
if (!studentInfo.njmc) studentInfo.njmc = '未知年级';
|
||||
if (!studentInfo.bjmc) studentInfo.bjmc = '未知班级';
|
||||
|
||||
return studentInfo;
|
||||
});
|
||||
|
||||
const jzList = ref<any[]>([]);
|
||||
|
||||
onLoad(async (options) => {
|
||||
if (xsInfo && xsInfo.value && xsInfo.value.id) {
|
||||
if (xsInfo.value && xsInfo.value.id) {
|
||||
try {
|
||||
uni.showLoading({ title: "加载中..." });
|
||||
const res = await xsJzListByXsIdApi({ xsId: xsInfo.value.id });
|
||||
if (res && res.resultCode === 1) {
|
||||
jzList.value = res.result || [];
|
||||
} else {
|
||||
jzList.value = [];
|
||||
console.warn("获取家长信息失败:", res?.resultMessage);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取家长信息出错:", error);
|
||||
jzList.value = [];
|
||||
uni.showToast({
|
||||
title: "获取家长信息失败",
|
||||
icon: "none"
|
||||
});
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
} else {
|
||||
console.warn("学生信息不完整,无法获取家长信息");
|
||||
}
|
||||
});
|
||||
|
||||
// 拨打电话
|
||||
@ -122,6 +198,25 @@ const callParent = (phoneNumber: string) => {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 性别文本转换
|
||||
const getGenderText = (gender: string) => {
|
||||
if (gender === '1') {
|
||||
return '男';
|
||||
} else if (gender === '2') {
|
||||
return '女';
|
||||
} else {
|
||||
return '未知';
|
||||
}
|
||||
};
|
||||
|
||||
// 获取家长头像,添加默认头像
|
||||
const getParentAvatar = (avatarUrl: string) => {
|
||||
if (!avatarUrl) {
|
||||
return '/static/images/default_avatar.png'; // 默认头像路径
|
||||
}
|
||||
return avatarUrl;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@ -252,9 +347,16 @@ const callParent = (phoneNumber: string) => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
// flex: 0 0 auto; // 移除 flex 属性
|
||||
text-align: center;
|
||||
margin-bottom: 10px; // 保留垂直间距
|
||||
margin-bottom: 15px; // 增加底部间距
|
||||
padding: 8px; // 添加内边距
|
||||
border-radius: 8px; // 添加圆角
|
||||
transition: all 0.2s ease; // 添加过渡效果
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(102, 126, 234, 0.05); // 悬停效果
|
||||
transform: translateY(-2px); // 轻微上移
|
||||
}
|
||||
}
|
||||
|
||||
.parent-avatar-container {
|
||||
@ -265,7 +367,29 @@ const callParent = (phoneNumber: string) => {
|
||||
width: 50px; // 头像尺寸
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
background-color: #eee;
|
||||
background-color: #f0f0f0; // 默认背景色
|
||||
border: 2px solid #e0e0e0; // 默认边框
|
||||
object-fit: cover; // 确保图片填充整个容器
|
||||
}
|
||||
|
||||
.parent-avatar-text {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); // 渐变背景
|
||||
border: 2px solid #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px; // 调整字体大小,适应显示全部姓名
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
text-align: center; // 确保文字居中
|
||||
line-height: 1.2; // 调整行高
|
||||
padding: 2px; // 添加内边距
|
||||
word-break: break-all; // 允许长名字换行
|
||||
}
|
||||
|
||||
.call-icon-overlay {
|
||||
@ -281,9 +405,11 @@ const callParent = (phoneNumber: string) => {
|
||||
justify-content: center;
|
||||
border: 1px solid #fff; // 白色描边
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); // 添加阴影效果
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -300,7 +426,12 @@ const callParent = (phoneNumber: string) => {
|
||||
|
||||
.parent-relation {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
color: #666;
|
||||
background-color: #f5f5f5;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
margin-top: 5px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// 清理不再需要的旧样式
|
||||
|
||||
@ -1,15 +1,5 @@
|
||||
<template>
|
||||
<view class="add-resource-page">
|
||||
<!-- 页面头部 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<uni-icons type="left" size="20" color="#333"></uni-icons>
|
||||
<text class="back-text">返回</text>
|
||||
</view>
|
||||
<view class="header-title">上传资源</view>
|
||||
<view class="header-right"></view>
|
||||
</view>
|
||||
|
||||
<!-- 表单内容 -->
|
||||
<scroll-view scroll-y class="form-scroll-view">
|
||||
<view class="form-container">
|
||||
@ -17,19 +7,12 @@
|
||||
<view class="info-card">
|
||||
<view class="form-item">
|
||||
<text class="form-label">资源目录 <text class="required">*</text></text>
|
||||
<picker
|
||||
mode="selector"
|
||||
:range="treeData"
|
||||
range-key="title"
|
||||
@change="handleResourceTypeChange"
|
||||
>
|
||||
<view class="picker-row">
|
||||
<view class="picker-row" @click="showResourceTypeTree">
|
||||
<text :class="{ placeholder: !formData.resourType }">
|
||||
{{ getResourceTypeText() || '请选择资源目录' }}
|
||||
</text>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -136,13 +119,23 @@
|
||||
|
||||
<!-- 底部提交按钮 -->
|
||||
<view class="bottom-actions">
|
||||
<button class="action-btn cancel-btn" @click="goBack">
|
||||
取消
|
||||
</button>
|
||||
<button class="action-btn confirm-btn" @click="handleSubmitForm" :disabled="isSubmitting">
|
||||
{{ isSubmitting ? '提交中...' : '提交' }}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 资源目录选择树 -->
|
||||
<BasicTree
|
||||
ref="treeRef"
|
||||
:range="treeData"
|
||||
idKey="key"
|
||||
rangeKey="title"
|
||||
title="选择资源目录"
|
||||
:multiple="false"
|
||||
:selectParent="false"
|
||||
@confirm="onTreeConfirm"
|
||||
@cancel="onTreeCancel"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@ -152,6 +145,7 @@ import { typesFindTreeApi } from "@/api/base/server";
|
||||
import { attachmentUpload } from "@/api/system/upload";
|
||||
import { imagUrl } from "@/utils";
|
||||
import { useDicStore } from "@/store/modules/dic";
|
||||
import BasicTree from "@/components/BasicTree/Tree.vue";
|
||||
|
||||
const { findByPid } = useDicStore();
|
||||
|
||||
@ -182,6 +176,9 @@ const formData = reactive({
|
||||
// 树形数据
|
||||
const treeData = ref([]);
|
||||
|
||||
// 树组件引用
|
||||
const treeRef = ref();
|
||||
|
||||
// 提交状态
|
||||
const isSubmitting = ref(false);
|
||||
|
||||
@ -191,15 +188,29 @@ const categoryOptions = ref([]);
|
||||
// 加载资源类别数据
|
||||
const loadCategoryOptions = async () => {
|
||||
try {
|
||||
// 使用正确的字典ID
|
||||
const result = await findByPid({ pid: 1391443399 });
|
||||
if (result && Array.isArray(result)) {
|
||||
categoryOptions.value = result.map(item => ({
|
||||
|
||||
// 检查返回的数据结构
|
||||
if (result && result.resultCode === 1 && result.result && Array.isArray(result.result) && result.result.length > 0) {
|
||||
categoryOptions.value = result.result.map(item => ({
|
||||
value: item.dictionaryCode,
|
||||
label: item.dictionaryValue
|
||||
}));
|
||||
} else {
|
||||
// 如果没有数据,使用默认选项
|
||||
categoryOptions.value = [
|
||||
{ value: '1', label: '课件' },
|
||||
{ value: '2', label: '教案' },
|
||||
{ value: '3', label: '学案' },
|
||||
{ value: '4', label: '作业' },
|
||||
{ value: '5', label: '试卷' },
|
||||
{ value: '6', label: '教材' },
|
||||
{ value: '7', label: '示范课' },
|
||||
{ value: '8', label: '音视频合集' },
|
||||
];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载资源类别失败:', error);
|
||||
// 如果加载失败,使用默认选项
|
||||
categoryOptions.value = [
|
||||
{ value: '1', label: '课件' },
|
||||
@ -214,16 +225,26 @@ const loadCategoryOptions = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 返回上一页
|
||||
const goBack = () => {
|
||||
uni.navigateBack();
|
||||
|
||||
|
||||
// 显示资源目录选择树
|
||||
const showResourceTypeTree = () => {
|
||||
if (treeRef.value) {
|
||||
treeRef.value._show();
|
||||
}
|
||||
};
|
||||
|
||||
// 资源目录选择
|
||||
const handleResourceTypeChange = (e: any) => {
|
||||
const index = e.detail.value;
|
||||
const selectedItem = treeData.value[index];
|
||||
// 树形选择确认
|
||||
const onTreeConfirm = (selectedItems: any[]) => {
|
||||
if (selectedItems.length > 0) {
|
||||
const selectedItem = selectedItems[0]; // 单选模式
|
||||
formData.resourType = selectedItem.key;
|
||||
}
|
||||
};
|
||||
|
||||
// 树形选择取消
|
||||
const onTreeCancel = () => {
|
||||
// 取消选择资源目录
|
||||
};
|
||||
|
||||
// 资源类别选择
|
||||
@ -415,7 +436,7 @@ const handleSubmitForm = async () => {
|
||||
id: formData.id
|
||||
};
|
||||
|
||||
console.log('提交参数:', params);
|
||||
|
||||
|
||||
const result = await resourcesSaveApi(params);
|
||||
|
||||
@ -457,16 +478,45 @@ const resetFormData = () => {
|
||||
const loadTreeData = async () => {
|
||||
try {
|
||||
const res = await typesFindTreeApi();
|
||||
// 处理返回的数据结构,确保与BasicTree组件兼容
|
||||
if (res && Array.isArray(res)) {
|
||||
treeData.value = res.map(item => ({
|
||||
key: item.key || item.id,
|
||||
title: item.title || item.name,
|
||||
children: item.children ? item.children.map(child => ({
|
||||
key: child.key || child.id,
|
||||
title: child.title || child.name,
|
||||
children: child.children || []
|
||||
})) : []
|
||||
}));
|
||||
} else {
|
||||
treeData.value = res.result || [];
|
||||
console.log('树形数据加载完成:', treeData.value);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载树形数据失败:', error);
|
||||
|
||||
// 如果加载失败,使用空数组
|
||||
treeData.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 获取资源目录文本
|
||||
const getResourceTypeText = () => {
|
||||
const selectedItem = treeData.value.find(item => item.key === formData.resourType);
|
||||
// 递归查找选中的项目
|
||||
const findSelectedItem = (items: any[], targetKey: string): any => {
|
||||
for (const item of items) {
|
||||
if (item.key === targetKey) {
|
||||
return item;
|
||||
}
|
||||
if (item.children && item.children.length > 0) {
|
||||
const found = findSelectedItem(item.children, targetKey);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const selectedItem = findSelectedItem(treeData.value, formData.resourType);
|
||||
return selectedItem ? selectedItem.title : '';
|
||||
};
|
||||
|
||||
@ -490,39 +540,7 @@ onMounted(async () => {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.back-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 80rpx;
|
||||
}
|
||||
|
||||
.form-scroll-view {
|
||||
flex: 1;
|
||||
@ -671,19 +689,8 @@ onMounted(async () => {
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
flex: 1;
|
||||
padding: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
background: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
|
||||
289
src/pages/view/routine/kefuxuncha/xcRecord.vue
Normal file
289
src/pages/view/routine/kefuxuncha/xcRecord.vue
Normal file
@ -0,0 +1,289 @@
|
||||
<template>
|
||||
<BasicLayout>
|
||||
<!-- 巡查记录列表 -->
|
||||
<view class="inspection-list">
|
||||
<BasicListLayout
|
||||
@register="registerInspection"
|
||||
style="position: absolute"
|
||||
>
|
||||
<template v-slot="{ data, index }">
|
||||
<view class="inspection-record bg-white r-md p-15 mb-15">
|
||||
<view class="record-header">
|
||||
<view class="record-time">
|
||||
<u-icon name="clock" color="#666" size="14"></u-icon>
|
||||
<text class="time-text">{{ formatTime(data.xctime) }}</text>
|
||||
</view>
|
||||
<view class="record-status">
|
||||
<text class="status-text">已巡查</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="record-content">
|
||||
<view class="content-item">
|
||||
<text class="item-label">巡查教师:</text>
|
||||
<text class="item-value">{{ data.jsxm }}</text>
|
||||
</view>
|
||||
<view class="content-item flex-col">
|
||||
<text class="item-label" style="flex: 0 0 25px"
|
||||
>巡查项目:</text
|
||||
>
|
||||
<view class="item-value" style="width: 100%">
|
||||
<template
|
||||
v-if="data.xkXcXmList && data.xkXcXmList.length > 0"
|
||||
>
|
||||
<view
|
||||
v-for="(xm, idx) in data.xkXcXmList"
|
||||
:key="xm.xcXmId"
|
||||
style="margin-bottom: 4px"
|
||||
>
|
||||
<view>
|
||||
<text>{{ idx + 1 }}、{{ xm.xcMc }}</text>
|
||||
<view
|
||||
style="
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 4px 0;
|
||||
"
|
||||
>
|
||||
<text>分值:{{ xm.xmFz }}分</text>
|
||||
<text
|
||||
>巡查结果:{{
|
||||
xm.xcJg === "A" ? "有" : "无"
|
||||
}}</text
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else> 无巡查项目 </template>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="content-item"
|
||||
v-if="data.zp && data.zp.length > 0"
|
||||
>
|
||||
<text class="item-label">巡查图片:</text>
|
||||
<view
|
||||
class="item-value"
|
||||
style="display: flex; flex-wrap: wrap; gap: 8px"
|
||||
>
|
||||
<image
|
||||
v-for="(img, imgIdx) in getImageArray(data.zp)"
|
||||
:key="imgIdx"
|
||||
:src="imagUrl(img)"
|
||||
mode="aspectFill"
|
||||
style="
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #eee;
|
||||
cursor: pointer;
|
||||
"
|
||||
@click="handlePreviewImage(img, getImageArray(data.zp))"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="content-item"
|
||||
v-if="data.sp && data.sp.length > 0"
|
||||
>
|
||||
<text class="item-label">巡查视频:</text>
|
||||
<view
|
||||
class="item-value"
|
||||
style="display: flex; flex-wrap: wrap; gap: 8px"
|
||||
>
|
||||
<view
|
||||
v-for="(video, vIdx) in getVideoArray(data.sp)"
|
||||
:key="vIdx"
|
||||
style="
|
||||
width: 80px;
|
||||
height: 60px;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #eee;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #000;
|
||||
"
|
||||
@click="handlePreviewVideo(getVideoArray(data.sp), vIdx)"
|
||||
>
|
||||
<video
|
||||
:src="video"
|
||||
style="width: 100%; height: 100%; object-fit: cover"
|
||||
:controls="false"
|
||||
:show-center-play-btn="false"
|
||||
:show-play-btn="false"
|
||||
:show-fullscreen-btn="false"
|
||||
:show-progress="false"
|
||||
:show-mute-btn="false"
|
||||
:enable-progress-gesture="false"
|
||||
:enable-play-gesture="false"
|
||||
:loop="false"
|
||||
:muted="true"
|
||||
:poster="''"
|
||||
></video>
|
||||
<view
|
||||
style="
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
"
|
||||
>
|
||||
<u-icon
|
||||
name="play-right-fill"
|
||||
color="#fff"
|
||||
size="28"
|
||||
></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</BasicListLayout>
|
||||
</view>
|
||||
</BasicLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { xkXcFindPageApi } from "@/api/base/xkXcApi";
|
||||
import BasicLayout from "@/components/BasicLayout/Layout.vue";
|
||||
import { useLayout } from "@/components/BasicListLayout/hooks/useLayout";
|
||||
import { useDataStore } from "@/store/modules/data";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import dayjs from "dayjs";
|
||||
import { imagUrl } from "@/utils";
|
||||
|
||||
const { getJs } = useUserStore();
|
||||
const { getData } = useDataStore();
|
||||
|
||||
const js = computed(() => getJs);
|
||||
const xkkc = computed(() => getData);
|
||||
|
||||
// 巡查记录列表参数
|
||||
let inspectionParams = ref({
|
||||
rows: 10,
|
||||
xkkcId: xkkc.value.id,
|
||||
jsId: js.value.id,
|
||||
});
|
||||
|
||||
// 巡查记录列表
|
||||
const [registerInspection, { reload }] = useLayout({
|
||||
api: xkXcFindPageApi,
|
||||
componentProps: {},
|
||||
param: inspectionParams.value,
|
||||
});
|
||||
|
||||
// 图片预览
|
||||
const handlePreviewImage = (img: string, images: string[]) => {
|
||||
// 兼容uni-app的图片预览API
|
||||
uni.previewImage({
|
||||
current: img,
|
||||
urls: images,
|
||||
});
|
||||
};
|
||||
|
||||
// 视频预览
|
||||
const handlePreviewVideo = (videos: string[], index: number) => {
|
||||
// 兼容uni-app的视频预览API
|
||||
// uni.previewMedia 仅在H5/小程序/APP支持
|
||||
uni.previewMedia({
|
||||
current: index,
|
||||
sources: videos.map((url) => ({
|
||||
url,
|
||||
type: "video",
|
||||
})),
|
||||
});
|
||||
};
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (timestamp: string) => {
|
||||
return dayjs(timestamp).format("YYYY-MM-DD HH:mm");
|
||||
};
|
||||
|
||||
// 将逗号分隔的字符串转换为数组
|
||||
const getImageArray = (str: string) => {
|
||||
if (!str) return [];
|
||||
return str.split(",").map((item) => item.trim());
|
||||
};
|
||||
|
||||
// 将逗号分隔的字符串转换为数组
|
||||
const getVideoArray = (str: string) => {
|
||||
if (!str) return [];
|
||||
return str.split(",").map((item) => item.trim());
|
||||
};
|
||||
|
||||
// 页面加载时重新加载巡查记录
|
||||
onMounted(() => {
|
||||
reload();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.inspection-list {
|
||||
position: relative;
|
||||
height: calc(100vh - 50px);
|
||||
|
||||
.inspection-record {
|
||||
.record-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
.record-time {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
|
||||
.time-text {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.record-status {
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #4080ff;
|
||||
display: inline-flex;
|
||||
font-size: 12px;
|
||||
color: #4080ff;
|
||||
}
|
||||
}
|
||||
|
||||
.record-content {
|
||||
.content-item {
|
||||
display: flex;
|
||||
margin-bottom: 5px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
|
||||
.item-label {
|
||||
font-weight: bold;
|
||||
flex: 0 0 80px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .zp-loading-fixed {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
::v-deep .d-load-main {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -23,21 +23,13 @@
|
||||
class="course-item"
|
||||
>
|
||||
<view class="course-name">{{ xkkc.kcmc }}</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">上课周期类型:</view>
|
||||
<view class="info-data">{{ xkkc.skzqlx }}</view>
|
||||
</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">上课周期:</view>
|
||||
<view class="info-data">{{ xkkc.skzqmc }}</view>
|
||||
</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">上课开始时间:</view>
|
||||
<view class="info-data">{{ xkkc.skkstime }}</view>
|
||||
</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">上课结束时间:</view>
|
||||
<view class="info-data">{{ xkkc.skjstime }}</view>
|
||||
<view class="info-label">上课时间:</view>
|
||||
<view class="info-data">{{ formatClassTime(xkkc.skkstime, xkkc.skjstime) }}</view>
|
||||
</view>
|
||||
<view class="course-info-item">
|
||||
<view class="info-label">开课地点:</view>
|
||||
@ -49,8 +41,10 @@
|
||||
>{{ xkkc.hasNum || 0 }} | {{ xkkc.maxNum || 0 }}</view
|
||||
>
|
||||
</view>
|
||||
<view class="separator-line"></view>
|
||||
<view class="course-btn-group">
|
||||
<view class="xc-btn" @click.stop="goXc(xkkc)">巡查</view>
|
||||
<view class="record-btn" @click.stop="goRecord(xkkc)">巡查记录</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -110,6 +104,7 @@ import { jsdXkListApi } from "@/api/base/server";
|
||||
import { useDataStore } from "@/store/modules/data";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { onBeforeUnmount, onMounted, ref } from "vue";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const { getJs } = useUserStore();
|
||||
const { getData, setData } = useDataStore();
|
||||
@ -198,6 +193,50 @@ const goXc = (xkkc: any) => {
|
||||
});
|
||||
};
|
||||
|
||||
// 跳转到巡查记录
|
||||
const goRecord = (xkkc: any) => {
|
||||
setData(xkkc);
|
||||
uni.navigateTo({
|
||||
url: `/pages/view/routine/kefuxuncha/xcRecord`,
|
||||
});
|
||||
};
|
||||
|
||||
// 格式化课程时间
|
||||
const formatClassTime = (startTime: string, endTime: string) => {
|
||||
if (!startTime || !endTime) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
// 尝试解析时间,支持多种格式
|
||||
let start, end;
|
||||
|
||||
// 如果是时间格式(HH:mm:ss 或 HH:mm)
|
||||
if (startTime.includes(':') && !startTime.includes('-') && !startTime.includes('/')) {
|
||||
start = startTime;
|
||||
end = endTime;
|
||||
} else {
|
||||
// 尝试用 dayjs 解析
|
||||
const startDate = dayjs(startTime);
|
||||
const endDate = dayjs(endTime);
|
||||
|
||||
if (startDate.isValid() && endDate.isValid()) {
|
||||
start = startDate.format('HH:mm:ss');
|
||||
end = endDate.format('HH:mm:ss');
|
||||
} else {
|
||||
// 如果解析失败,直接返回原始值
|
||||
return `${startTime}~${endTime}`;
|
||||
}
|
||||
}
|
||||
|
||||
return `${start}~${end}`;
|
||||
} catch (error) {
|
||||
console.error('时间格式化错误:', error);
|
||||
// 如果出错,返回原始值
|
||||
return `${startTime}~${endTime}`;
|
||||
}
|
||||
};
|
||||
|
||||
// 页面卸载前清除定时器
|
||||
onBeforeUnmount(() => {});
|
||||
</script>
|
||||
@ -205,7 +244,7 @@ onBeforeUnmount(() => {});
|
||||
<style lang="scss" scoped>
|
||||
.interest-course {
|
||||
min-height: 100%;
|
||||
background-color: #f5f7fa;
|
||||
background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
@ -213,43 +252,63 @@ onBeforeUnmount(() => {});
|
||||
}
|
||||
|
||||
.selection-header {
|
||||
background: linear-gradient(135deg, #4a90e2, #2879ff);
|
||||
padding: 20px 15px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 25px 20px;
|
||||
color: #fff;
|
||||
border-radius: 0 0 15px 15px;
|
||||
box-shadow: 0 4px 12px rgba(40, 121, 255, 0.2);
|
||||
border-radius: 0 0 20px 20px;
|
||||
box-shadow: 0 8px 32px rgba(102, 126, 234, 0.3);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(45deg, rgba(255, 255, 255, 0.1) 0%, transparent 50%, rgba(255, 255, 255, 0.05) 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
.title-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
|
||||
.title {
|
||||
flex: 1 0 1px;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 14px;
|
||||
opacity: 0.8;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.switch-btn {
|
||||
padding: 5px 15px;
|
||||
padding: 8px 16px;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
color: #fff;
|
||||
border-radius: 15px;
|
||||
font-size: 15px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -259,13 +318,7 @@ onBeforeUnmount(() => {});
|
||||
.scrollable-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.inspection-tabs {
|
||||
padding: 15px 15px 0 15px;
|
||||
background-color: #fff;
|
||||
margin-bottom: 10px;
|
||||
-webkit-overflow-scrolling: touch; // 增强iOS滚动体验
|
||||
}
|
||||
|
||||
.course-list {
|
||||
@ -274,56 +327,216 @@ onBeforeUnmount(() => {});
|
||||
.course-item {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid #f0f0f0;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
animation: fadeInUp 0.6s ease-out;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.12);
|
||||
border-color: #e8e8e8;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border: 1px solid #3fbf72;
|
||||
background-color: rgba(63, 191, 114, 0.05);
|
||||
box-shadow: 0 2px 8px rgba(63, 191, 114, 0.15);
|
||||
border: 2px solid #3fbf72;
|
||||
background-color: rgba(63, 191, 114, 0.02);
|
||||
box-shadow: 0 4px 20px rgba(63, 191, 114, 0.15);
|
||||
}
|
||||
|
||||
.course-name {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.4;
|
||||
animation: fadeInLeft 0.5s ease-out 0.1s both;
|
||||
}
|
||||
|
||||
.course-btn-group {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px; // Added gap for spacing between buttons
|
||||
|
||||
.xc-btn {
|
||||
flex: 1 0 1px;
|
||||
display: inline-block;
|
||||
color: #2879ff;
|
||||
color: #ff6b35;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding: 8px 18px;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(135deg, rgba(255, 107, 53, 0.1), rgba(255, 107, 53, 0.05));
|
||||
border: 1px solid #ff6b35;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 2px 8px rgba(255, 107, 53, 0.15);
|
||||
animation: fadeInUp 0.5s ease-out 0.3s both;
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(135deg, rgba(255, 107, 53, 0.15), rgba(255, 107, 53, 0.1));
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 15px rgba(255, 107, 53, 0.25);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: linear-gradient(135deg, rgba(255, 107, 53, 0.2), rgba(255, 107, 53, 0.15));
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 8px rgba(255, 107, 53, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.record-btn {
|
||||
display: inline-block;
|
||||
color: #409EFF;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding: 8px 18px;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(135deg, rgba(64, 158, 255, 0.1), rgba(64, 158, 255, 0.05));
|
||||
border: 1px solid #409EFF;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15);
|
||||
animation: fadeInUp 0.5s ease-out 0.3s both;
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(135deg, rgba(64, 158, 255, 0.15), rgba(64, 158, 255, 0.1));
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 15px rgba(64, 158, 255, 0.25);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: linear-gradient(135deg, rgba(64, 158, 255, 0.2), rgba(64, 158, 255, 0.15));
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.course-info-item {
|
||||
display: flex;
|
||||
margin-bottom: 12px;
|
||||
font-size: 12px;
|
||||
margin-bottom: 14px;
|
||||
font-size: 13px;
|
||||
align-items: center;
|
||||
animation: fadeInUp 0.5s ease-out 0.15s both;
|
||||
|
||||
.info-label {
|
||||
color: #949aa4;
|
||||
color: #666;
|
||||
flex: 0 0 100px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.info-data {
|
||||
flex: 1 0 1px;
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.separator-line {
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, #e8e8e8, transparent);
|
||||
margin: 18px 0;
|
||||
opacity: 0.8;
|
||||
animation: fadeIn 0.5s ease-out 0.25s both;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 学生选择器弹窗样式 */
|
||||
// 添加动画关键帧
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInLeft {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 全局图片样式 */
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// 暂无数据样式
|
||||
.empty-course-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80px 20px;
|
||||
text-align: center;
|
||||
|
||||
.empty-icon {
|
||||
margin-bottom: 25px;
|
||||
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
border: 2px solid rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #475569;
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
font-size: 14px;
|
||||
color: #64748b;
|
||||
max-width: 80%;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
|
||||
/* 选课选择弹窗样式 */
|
||||
.xk-selector {
|
||||
background-color: #ffffff;
|
||||
border-top-left-radius: 12px;
|
||||
@ -352,6 +565,7 @@ onBeforeUnmount(() => {});
|
||||
align-items: center;
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
@ -361,6 +575,10 @@ onBeforeUnmount(() => {});
|
||||
background-color: rgba(64, 158, 255, 0.05);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(64, 158, 255, 0.02);
|
||||
}
|
||||
|
||||
.xk-info {
|
||||
flex: 1;
|
||||
margin-left: 12px;
|
||||
@ -386,62 +604,4 @@ onBeforeUnmount(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 全局图片样式 */
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// 暂无数据样式
|
||||
.empty-course-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
text-align: center;
|
||||
|
||||
.empty-icon {
|
||||
margin-bottom: 20px;
|
||||
background-color: #f5f6f7;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
max-width: 80%;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
/* 选课已结束样式 */
|
||||
.enrollment-ended {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
padding: 12px 15px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -230,7 +230,7 @@ export const previewFile = (fileUrl: string, fileName: string, fileType: string)
|
||||
const encodedUrl = btoa(finalFileUrl);
|
||||
console.log('使用kkFileView预览,Base64编码后的URL:', encodedUrl);
|
||||
|
||||
const previewUrl = `http://yufangzc.com:8891/onlinePreview?url=${encodeURIComponent(encodedUrl)}`;
|
||||
const previewUrl = `${KK_FILE_VIEW_URL}/onlinePreview?url=${encodeURIComponent(encodedUrl)}`;
|
||||
console.log('最终预览URL (普通文件):', previewUrl);
|
||||
|
||||
const needLandscape = ['ppt', 'pptx'].includes(type);
|
||||
|
||||
@ -7,20 +7,34 @@ import {URL_REG} from "@/utils/RegExp";
|
||||
* 参数传入图片路径
|
||||
* */
|
||||
export function imagUrl(path: string): string {
|
||||
if (!URL_REG.test(path)) {
|
||||
if (BASE_IMAGE_URL === '/') {
|
||||
// 兜底:空值直接返回空字符串,避免拼接出 ...undefined
|
||||
if (!path) {
|
||||
return ''
|
||||
}
|
||||
|
||||
// 已是完整 URL 则原样返回
|
||||
if (URL_REG.test(path)) {
|
||||
return path
|
||||
}
|
||||
return BASE_IMAGE_URL + path;
|
||||
|
||||
// BASE_IMAGE_URL 为根路径时,直接返回规范化后的相对路径
|
||||
if (BASE_IMAGE_URL === '/') {
|
||||
return path.startsWith('/') ? path : `/${path}`
|
||||
}
|
||||
return path;
|
||||
|
||||
// 规范化:确保只存在一个斜杠分隔
|
||||
const base = BASE_IMAGE_URL.endsWith('/') ? BASE_IMAGE_URL.slice(0, -1) : BASE_IMAGE_URL
|
||||
const tail = path.startsWith('/') ? path : `/${path}`
|
||||
return `${base}${tail}`
|
||||
}
|
||||
|
||||
export function replaceImageUrl(imageUrl: string) {
|
||||
if (BASE_IMAGE_URL !== '/') {
|
||||
return imageUrl.replace(BASE_IMAGE_URL, '')
|
||||
}
|
||||
return BASE_IMAGE_URL
|
||||
if (!imageUrl) return ''
|
||||
if (BASE_IMAGE_URL === '/') return BASE_IMAGE_URL
|
||||
// 仅当以 BASE_IMAGE_URL 开头时才裁剪
|
||||
return imageUrl.startsWith(BASE_IMAGE_URL)
|
||||
? imageUrl.slice(BASE_IMAGE_URL.length)
|
||||
: imageUrl
|
||||
}
|
||||
|
||||
// 点击选项处理逻辑
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user