Compare commits
2 Commits
79ea4d33ce
...
8bdde206b0
| Author | SHA1 | Date | |
|---|---|---|---|
| 8bdde206b0 | |||
| dda9a21de3 |
@ -68,6 +68,12 @@ export const xsKsccApi = async (params: any) => {
|
|||||||
return await get("/mobile/jz/kscc", params);
|
return await get("/mobile/jz/kscc", params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学期-考试场次树;可选 bjId:后端按 yfzc_kscc.bj_id 过滤(家长端传当前学生班级)
|
||||||
|
*/
|
||||||
|
export const ksccFindAllXqKsccTreeApi = (params?: { bjId?: string }) =>
|
||||||
|
get("/api/kscc/findAllXqKsccTree", params ?? {});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询学生考试成绩
|
* 查询学生考试成绩
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -46,6 +46,20 @@ export const questionnaireAnswerFindByUserApi = async (params: { questionnaireId
|
|||||||
return await get("/api/questionnaireAnswer/findByUser", params);
|
return await get("/api/questionnaireAnswer/findByUser", params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 家长端:本人或同学生其他家长是否已填写(任一则视为已填)
|
||||||
|
*/
|
||||||
|
export const questionnaireAnswerFindByUserOrFamilyApi = async (params: {
|
||||||
|
questionnaireId: string;
|
||||||
|
xxtsId?: string;
|
||||||
|
xsId?: string;
|
||||||
|
}) => {
|
||||||
|
const q: Record<string, string> = { questionnaireId: params.questionnaireId };
|
||||||
|
if (params.xxtsId) q.xxtsId = params.xxtsId;
|
||||||
|
if (params.xsId) q.xsId = params.xsId;
|
||||||
|
return await get("/api/questionnaireAnswer/findByUserOrFamily", q);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取问卷填写人列表(分页)
|
* 获取问卷填写人列表(分页)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -175,6 +175,13 @@
|
|||||||
"enablePullDownRefresh": false
|
"enablePullDownRefresh": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/base/xszp/resource-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "资源详情",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/base/jl/detailwb",
|
"path": "pages/base/jl/detailwb",
|
||||||
"style": {
|
"style": {
|
||||||
@ -220,8 +227,8 @@
|
|||||||
{
|
{
|
||||||
"path": "pages/base/grades/list",
|
"path": "pages/base/grades/list",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "考试列表",
|
"navigationBarTitleText": "成绩查询",
|
||||||
"enablePullDownRefresh": false
|
"enablePullDownRefresh": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
<view class="score-info-section">
|
<view class="score-info-section">
|
||||||
<view class="score-left">
|
<view class="score-left">
|
||||||
<text class="total-score">共{{ ksccKmList.length }}科</text>
|
<text class="total-score">共{{ ksccKmList.length }}科</text>
|
||||||
<text class="full-score">满分{{ totalKmFs }}分</text>
|
<text v-if="showXsFs" class="full-score">满分{{ totalKmFs }}分</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="grade">{{ curKsdj.dj || '-' }}</text>
|
<text class="grade">{{ curKsdj.dj || '-' }}</text>
|
||||||
</view>
|
</view>
|
||||||
@ -59,12 +59,19 @@
|
|||||||
>
|
>
|
||||||
<view class="subject-header">
|
<view class="subject-header">
|
||||||
<text class="subject-name">{{ kscj.km?.kmmc || kscj.kmmc || '未知科目' }}</text>
|
<text class="subject-name">{{ kscj.km?.kmmc || kscj.kmmc || '未知科目' }}</text>
|
||||||
<text class="detail-btn" :style="{ color: kscj.djclr || '#909399' }" v-if="kscj.dj">{{ kscj.djBx }}</text>
|
<!-- sf_xs_fs=0:隐藏右侧等级表现文案(如优秀);左侧字母等级 dj 仍展示 -->
|
||||||
|
<text
|
||||||
|
class="detail-btn"
|
||||||
|
:style="{ color: kscj.djclr || '#909399' }"
|
||||||
|
v-if="showXsFs && kscj.djBx"
|
||||||
|
>
|
||||||
|
{{ kscj.djBx }}
|
||||||
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="subject-body">
|
<view class="subject-body">
|
||||||
<text class="subject-grade" :style="{ color: kscj.djclr || '#303133' }">{{ kscj.dj || '-' }}</text>
|
<text class="subject-grade" :style="{ color: kscj.djclr || '#303133' }">{{ kscj.dj || '-' }}</text>
|
||||||
<view class="grade-info-wrapper">
|
<view class="grade-info-wrapper" v-if="showXsFs">
|
||||||
<text v-if="kscc.sfXsFs && kscj.ksfs" class="subject-score">{{ kscj.ksfs }}</text>
|
<text v-if="showSubjectNumericScore(kscj)" class="subject-score">{{ kscj.ksfs }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -103,6 +110,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, watch, nextTick } from "vue";
|
import { ref, onMounted, watch, nextTick } from "vue";
|
||||||
|
import { onLoad } from "@dcloudio/uni-app";
|
||||||
import uCharts from "@/components/charts/u-charts.js";
|
import uCharts from "@/components/charts/u-charts.js";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { xsKscjApi, ksccPjFindByKsccAndXsApi } from "@/api/base/server";
|
import { xsKscjApi, ksccPjFindByKsccAndXsApi } from "@/api/base/server";
|
||||||
@ -119,6 +127,42 @@ const fsScale = 100;
|
|||||||
const curXs = ref<any>({})
|
const curXs = ref<any>({})
|
||||||
const kscc = ref<any>({})
|
const kscc = ref<any>({})
|
||||||
|
|
||||||
|
/** 从列表 URL 带入场次,避免依赖全局 store */
|
||||||
|
const ksccFromRoute = ref(false)
|
||||||
|
|
||||||
|
/** 与 yfzc_kscc.sf_xs_fs 一致:接口 showFs=false 时不展示卷面分及科目右侧等级表现文案(djBx);各科字母等级 dj 与顶部总等级仍展示 */
|
||||||
|
const showXsFs = ref(true);
|
||||||
|
|
||||||
|
function showSubjectNumericScore(kscj: any): boolean {
|
||||||
|
if (!showXsFs.value) return false;
|
||||||
|
if (kscj == null || kscj.ksfs === undefined || kscj.ksfs === null || kscj.ksfs === "") return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad((options?: Record<string, string>) => {
|
||||||
|
const q = options || {};
|
||||||
|
const kid = q.ksccId != null ? String(q.ksccId).trim() : "";
|
||||||
|
if (!kid) return;
|
||||||
|
ksccFromRoute.value = true;
|
||||||
|
let name = "";
|
||||||
|
try {
|
||||||
|
name = q.ksmc != null ? decodeURIComponent(String(q.ksmc)) : "";
|
||||||
|
} catch {
|
||||||
|
name = q.ksmc != null ? String(q.ksmc) : "";
|
||||||
|
}
|
||||||
|
kscc.value = {
|
||||||
|
id: kid,
|
||||||
|
ksmc: name || "考试场次",
|
||||||
|
};
|
||||||
|
if (q.njmcId != null && String(q.njmcId).trim()) {
|
||||||
|
try {
|
||||||
|
kscc.value.njmcId = decodeURIComponent(String(q.njmcId));
|
||||||
|
} catch {
|
||||||
|
kscc.value.njmcId = String(q.njmcId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const kscjList = ref<any>([])
|
const kscjList = ref<any>([])
|
||||||
const curKsdj = ref<any>({})
|
const curKsdj = ref<any>({})
|
||||||
|
|
||||||
@ -234,76 +278,168 @@ const floatToInt = (floatNum: number, scale: number) => {
|
|||||||
return Math.round(floatNum * scale);
|
return Math.round(floatNum * scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
let singleFlag = false;
|
/** 与后端 KsccKmCjServiceImpl.matchRule 一致(initKsdj 后边界已为 scaled int) */
|
||||||
|
function matchDjRule(score: number, boundary: unknown, rule: string | undefined): boolean {
|
||||||
|
if (boundary === null || boundary === undefined || boundary === "") return true;
|
||||||
|
const b = typeof boundary === "number" ? boundary : parseFloat(String(boundary));
|
||||||
|
if (Number.isNaN(b)) return true;
|
||||||
|
const r = rule != null ? String(rule).trim() : "";
|
||||||
|
if (!r) return true;
|
||||||
|
switch (r) {
|
||||||
|
case ">=":
|
||||||
|
return score >= b;
|
||||||
|
case ">":
|
||||||
|
return score > b;
|
||||||
|
case "<=":
|
||||||
|
return score <= b;
|
||||||
|
case "<":
|
||||||
|
return score < b;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化考试等级列表
|
/** initKsdj 已将 kmDjList 的 zdf/zgf 转为与分数相同的 scale,此处用 scaled 分数匹配 */
|
||||||
|
function resolveKmDjScaled(fs: number | null | undefined, kmId: string): any | null {
|
||||||
|
if (fs === null || fs === undefined || Number.isNaN(Number(fs))) return null;
|
||||||
|
const scoreScaled = floatToInt(Number(fs), fsScale);
|
||||||
|
const listDj = kmDjList.value.filter((d: any) => String(d.kmId || "") === String(kmId));
|
||||||
|
const hit = listDj.find((item: any) => {
|
||||||
|
const zdfRule =
|
||||||
|
item.zdfRule != null && String(item.zdfRule).trim() !== ""
|
||||||
|
? String(item.zdfRule).trim()
|
||||||
|
: ">=";
|
||||||
|
const zgfRule =
|
||||||
|
item.zgfRule != null && String(item.zgfRule).trim() !== ""
|
||||||
|
? String(item.zgfRule).trim()
|
||||||
|
: "<=";
|
||||||
|
return (
|
||||||
|
matchDjRule(scoreScaled, item.zdf, zdfRule) && matchDjRule(scoreScaled, item.zgf, zgfRule)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return hit || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 多场等级中取 sort 最优(数值越小越靠前)的一条 */
|
||||||
|
function pickBestDjMatch(rows: any[], scoreScaled: number): any | null {
|
||||||
|
const matches = rows.filter((item: any) => {
|
||||||
|
const zdfRule =
|
||||||
|
item.zdfRule != null && String(item.zdfRule).trim() !== ""
|
||||||
|
? String(item.zdfRule).trim()
|
||||||
|
: ">=";
|
||||||
|
const zgfRule =
|
||||||
|
item.zgfRule != null && String(item.zgfRule).trim() !== ""
|
||||||
|
? String(item.zgfRule).trim()
|
||||||
|
: "<=";
|
||||||
|
return (
|
||||||
|
matchDjRule(scoreScaled, item.zdf, zdfRule) && matchDjRule(scoreScaled, item.zgf, zgfRule)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (!matches.length) return null;
|
||||||
|
return matches.reduce((best, cur) =>
|
||||||
|
Number(cur.sort ?? 9999) < Number(best.sort ?? 9999) ? cur : best
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场次总等级(lx≠2):先将「本场得分之和」线性映射到教务 djList 的刻度上界(各档 zgf 最大值),
|
||||||
|
* 再按 zdfRule/zgfRule 匹配。当场次等级按模板(如满分 400)配置而本场科目满分之和为 200 时,
|
||||||
|
* 避免误判;刻度一致时不缩放。(与 zhxy-jsd kspj/detail.vue 一致)
|
||||||
|
*/
|
||||||
|
function resolveOverallKsDj(sumScaled: number, examFullMarksScaled: number): any {
|
||||||
|
const ksDjRows = djList.value.filter((d: any) => String(d.lx || "") !== "2");
|
||||||
|
if (!ksDjRows.length) return {};
|
||||||
|
|
||||||
|
let scoreForMatch = sumScaled;
|
||||||
|
if (examFullMarksScaled > 0) {
|
||||||
|
const maxZgfScaled = Math.max(0, ...ksDjRows.map((d: any) => Number(d.zgf) || 0));
|
||||||
|
if (maxZgfScaled > 0 && maxZgfScaled !== examFullMarksScaled) {
|
||||||
|
scoreForMatch = Math.round((sumScaled * maxZgfScaled) / examFullMarksScaled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pickBestDjMatch(ksDjRows, scoreForMatch) || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化考试等级列表(与教师端 kspj/detail 一致)
|
||||||
const initKsdj = () => {
|
const initKsdj = () => {
|
||||||
let maxDj = 0.0;
|
|
||||||
let maxKmDj = 0.0;
|
|
||||||
djList.value.forEach((ksdj: any) => {
|
djList.value.forEach((ksdj: any) => {
|
||||||
ksdj.zdf = floatToInt(ksdj.zdf, fsScale); // 等级最低分
|
ksdj.zdf = floatToInt(ksdj.zdf, fsScale);
|
||||||
ksdj.zgf = floatToInt(ksdj.zgf, fsScale); // 等级最高分
|
ksdj.zgf = floatToInt(ksdj.zgf, fsScale);
|
||||||
ksdj.djclr = colorMap[ksdj.dj] || ''; // 等级颜色
|
ksdj.djclr = colorMap[ksdj.dj] || "";
|
||||||
maxDj = Math.max(maxDj, ksdj.zgf);
|
|
||||||
});
|
});
|
||||||
kmDjList.value.forEach((kmdj: any) => {
|
kmDjList.value.forEach((kmdj: any) => {
|
||||||
kmdj.zdf = floatToInt(kmdj.zdf, fsScale); // 等级最低分
|
kmdj.zdf = floatToInt(kmdj.zdf, fsScale);
|
||||||
kmdj.zgf = floatToInt(kmdj.zgf, fsScale); // 等级最高分
|
kmdj.zgf = floatToInt(kmdj.zgf, fsScale);
|
||||||
kmdj.djclr = colorMap[kmdj.dj] || ''; // 等级颜色
|
kmdj.djclr = colorMap[kmdj.dj] || "";
|
||||||
maxKmDj = Math.max(maxKmDj, kmdj.zgf);
|
|
||||||
});
|
});
|
||||||
singleFlag = maxDj < maxKmDj + 100;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const rebuildData = () => {
|
const rebuildData = () => {
|
||||||
let ksfsList: any[] = [];
|
let ksfsList: any[] = [];
|
||||||
let totalFs = 0.00;
|
let totalFs = 0.0;
|
||||||
|
totalKmFs.value = 0;
|
||||||
|
|
||||||
ksccKscjList.value = ksccKscjList.value.map((cj: any) => {
|
ksccKscjList.value = ksccKscjList.value.map((cj: any) => {
|
||||||
ksfsList.push(cj.ksfs);
|
ksfsList.push(cj.ksfs);
|
||||||
totalFs += cj.ksfs;
|
totalFs += Number(cj.ksfs) || 0;
|
||||||
let fs = floatToInt(cj.ksfs, fsScale);
|
const djMissing = !cj.dj || String(cj.dj).trim() === "";
|
||||||
cj.djclr = colorMap[cj.dj] || '';
|
if (djMissing && kmDjList.value.length > 0 && cj.kmId) {
|
||||||
// 查询考试等级
|
const resolved = resolveKmDjScaled(cj.ksfs, cj.kmId);
|
||||||
|
if (resolved) {
|
||||||
|
cj.dj = resolved.dj;
|
||||||
|
cj.djBx = resolved.djBx;
|
||||||
|
cj.djLx = resolved.djLx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cj.djclr = colorMap[cj.dj] || "";
|
||||||
cj.km = ksccKmList.value.find((item: any) => item.kmId === cj.kmId) || {};
|
cj.km = ksccKmList.value.find((item: any) => item.kmId === cj.kmId) || {};
|
||||||
return cj;
|
return cj;
|
||||||
});
|
});
|
||||||
// 雷达图数据
|
|
||||||
radarData.categories = ksccKmList.value.map((item: any) => {
|
radarData.categories = ksccKmList.value.map((item: any) => {
|
||||||
totalKmFs.value += item.kmfs;
|
totalKmFs.value += Number(item.kmfs) || 0;
|
||||||
return item.kmmc;
|
return item.kmmc;
|
||||||
});
|
});
|
||||||
radarData.series = [{ name: "分数", data: ksfsList }];
|
radarData.series = [{ name: "分数", data: ksfsList }];
|
||||||
// 当前成绩列表
|
|
||||||
totalFs = floatToInt(totalFs, fsScale);
|
const sumScaled = floatToInt(totalFs, fsScale);
|
||||||
if (singleFlag) {
|
const examFullMarksScaled = floatToInt(totalKmFs.value, fsScale);
|
||||||
totalFs = totalFs / ksccKmList.value.length;
|
curKsdj.value = resolveOverallKsDj(sumScaled, examFullMarksScaled);
|
||||||
}
|
};
|
||||||
curKsdj.value = djList.value.find((item: any) => item.zdf <= totalFs && item.zgf >= totalFs) || {};
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
pageLoading.value = true;
|
pageLoading.value = true;
|
||||||
try {
|
try {
|
||||||
curXs.value = getCurXs;
|
curXs.value = getCurXs;
|
||||||
kscc.value = getData;
|
if (!ksccFromRoute.value) {
|
||||||
|
kscc.value = getData || {};
|
||||||
|
}
|
||||||
|
const ksccId = kscc.value?.id;
|
||||||
|
if (!ksccId) {
|
||||||
|
uni.showToast({ title: "请先选择考试场次", icon: "none" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
const res = await xsKscjApi({
|
const res = await xsKscjApi({
|
||||||
xsId: getCurXs.id,
|
xsId: getCurXs.id,
|
||||||
ksccId: getData.id,
|
ksccId,
|
||||||
njmcId: getData.njmcId
|
njmcId: kscc.value?.njmcId ?? "",
|
||||||
});
|
});
|
||||||
if (res.resultCode == 1) {
|
if (res.resultCode == 1) {
|
||||||
sysKmList.value = res.result.kmList || [];
|
const r = res.result || {};
|
||||||
ksccKmList.value = res.result.ksccKmList || [];
|
showXsFs.value = r.showFs !== false;
|
||||||
kmDjList.value = res.result.kmDjList || [];
|
sysKmList.value = r.kmList || [];
|
||||||
djList.value = res.result.djList || [];
|
ksccKmList.value = r.ksccKmList || [];
|
||||||
ksccKscjList.value = res.result.ksccKscjList || [];
|
kmDjList.value = r.kmDjList || [];
|
||||||
|
djList.value = r.djList || [];
|
||||||
|
ksccKscjList.value = r.ksccKscjList || [];
|
||||||
initKsdj();
|
initKsdj();
|
||||||
rebuildData();
|
rebuildData();
|
||||||
}
|
}
|
||||||
// 加载班主任寄语
|
// 加载班主任寄语
|
||||||
try {
|
try {
|
||||||
const pjRes = await ksccPjFindByKsccAndXsApi({
|
const pjRes = await ksccPjFindByKsccAndXsApi({
|
||||||
ksccId: getData.id,
|
ksccId,
|
||||||
xsId: getCurXs.id,
|
xsId: getCurXs.id,
|
||||||
});
|
});
|
||||||
if (pjRes && pjRes.resultCode === 1 && pjRes.result && pjRes.result.pjBzr) {
|
if (pjRes && pjRes.resultCode === 1 && pjRes.result && pjRes.result.pjBzr) {
|
||||||
|
|||||||
@ -1,132 +1,157 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="ks-application-page">
|
<view class="page">
|
||||||
<BasicListLayout @register="register">
|
<view class="head-tip">请选择考试场次</view>
|
||||||
<template v-slot="{ data }">
|
<scroll-view scroll-y class="scroll">
|
||||||
<view class="ks-card" @click="viewDetails(data)">
|
<view v-if="loading" class="state">加载中...</view>
|
||||||
<view class="card-header">
|
<view v-else-if="!treeData.length" class="state">暂无考试场次数据</view>
|
||||||
<text class="applicant-name"
|
<view v-else class="sections">
|
||||||
>{{ data.ksmc }}</text
|
<view v-for="xq in treeData" :key="xqKey(xq)" class="section">
|
||||||
>
|
<text class="section-title">{{ xq.title || xq.label || "学期" }}</text>
|
||||||
</view>
|
<view
|
||||||
<view class="card-body">
|
v-for="c in xq.children || []"
|
||||||
<view class="info-row">
|
:key="ksccKey(c)"
|
||||||
<text class="label">考试科目:</text>
|
class="kscc-row"
|
||||||
<text class="value">{{ data.kmmc }}</text>
|
@click="goDetail(xq, c)"
|
||||||
</view>
|
>
|
||||||
<view class="info-row">
|
<text class="kscc-name">{{ c.title || c.ksmc || "—" }}</text>
|
||||||
<text class="label">开始时间:</text>
|
<text class="arrow">›</text>
|
||||||
<text class="value">{{ data.kskstime }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="info-row">
|
|
||||||
<text class="label">结束时间:</text>
|
|
||||||
<text class="value">{{ data.ksjstime }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="card-footer">
|
|
||||||
<text>查看详情</text>
|
|
||||||
<text class="arrow">
|
|
||||||
<uni-icons type="arrowright" size="16" color="#ccc"></uni-icons>
|
|
||||||
</text>
|
|
||||||
</view>
|
</view>
|
||||||
|
<view v-if="!(xq.children || []).length" class="kscc-empty">该学期下暂无场次</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</view>
|
||||||
</BasicListLayout>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref, onMounted } from "vue";
|
||||||
import { useLayout } from "@/components/BasicListLayout/hooks/useLayout";
|
import { onPullDownRefresh } from "@dcloudio/uni-app";
|
||||||
import { xsKsccApi } from "@/api/base/server";
|
import { ksccFindAllXqKsccTreeApi } from "@/api/base/server";
|
||||||
import { useUserStore } from "@/store/modules/user";
|
import { useUserStore } from "@/store/modules/user";
|
||||||
import { useDataStore } from "@/store/modules/data";
|
|
||||||
const { getCurXs } = useUserStore();
|
const { getCurXs } = useUserStore();
|
||||||
const { setData } = useDataStore();
|
|
||||||
|
|
||||||
let pageParams = ref({
|
const loading = ref(true);
|
||||||
rows: 10,
|
const treeData = ref<any[]>([]);
|
||||||
xsId: getCurXs.id
|
|
||||||
})
|
|
||||||
|
|
||||||
const [register, { reload }] = useLayout({
|
function xqKey(xq: any) {
|
||||||
api: xsKsccApi,
|
return String(xq.key ?? xq.id ?? xq.title ?? "");
|
||||||
componentProps: {},
|
}
|
||||||
param: pageParams.value
|
|
||||||
|
function ksccKey(c: any) {
|
||||||
|
return String(c.key ?? c.id ?? c.title ?? "");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTree() {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const xs = getCurXs as any;
|
||||||
|
const bjId = xs?.bjId ?? xs?.bj_id;
|
||||||
|
const res: any = await ksccFindAllXqKsccTreeApi(
|
||||||
|
bjId ? { bjId: String(bjId).trim() } : {}
|
||||||
|
);
|
||||||
|
const raw = res?.result ?? res?.rows ?? res?.data ?? res;
|
||||||
|
treeData.value = Array.isArray(raw) ? raw : [];
|
||||||
|
} catch {
|
||||||
|
treeData.value = [];
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goDetail(xq: any, c: any) {
|
||||||
|
const ksccId = c?.key ?? c?.id;
|
||||||
|
if (!ksccId) {
|
||||||
|
uni.showToast({ title: "场次ID无效", icon: "none" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const xqTitle = String(xq?.title ?? xq?.label ?? "").trim();
|
||||||
|
const ksTitle = String(c?.title ?? c?.ksmc ?? "").trim();
|
||||||
|
const idEnc = encodeURIComponent(String(ksccId));
|
||||||
|
const ksEnc = encodeURIComponent(ksTitle || "考试场次");
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/base/grades/detail?ksccId=${idEnc}&ksmc=${ksEnc}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
void loadTree();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 查看详情或新增处理函数
|
onPullDownRefresh(() => {
|
||||||
const viewDetails = (item: any | null) => {
|
void loadTree().finally(() => uni.stopPullDownRefresh());
|
||||||
setData(item);
|
});
|
||||||
uni.navigateTo({ url: '/pages/base/grades/detail' });
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.ks-card {
|
.page {
|
||||||
background-color: #ffffff;
|
min-height: 100vh;
|
||||||
border-radius: 8px;
|
height: 100vh;
|
||||||
margin-bottom: 15px;
|
|
||||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
padding: 12px 15px;
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
.applicant-name {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-body {
|
|
||||||
padding: 15px;
|
|
||||||
.info-row {
|
|
||||||
display: flex;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
.label {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #666;
|
|
||||||
width: 70px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
.value {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-footer {
|
|
||||||
padding: 12px 15px;
|
|
||||||
border-top: 1px solid #f0f0f0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
flex-direction: column;
|
||||||
align-items: center;
|
background: #f1f5f9;
|
||||||
font-size: 14px;
|
box-sizing: border-box;
|
||||||
color: #888;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.arrow {
|
|
||||||
font-size: 16px;
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.head-tip {
|
||||||
// 新增按钮样式示例 (可根据 BasicListLayout 的 bottom 插槽调整)
|
padding: 20rpx 24rpx 12rpx;
|
||||||
.button {
|
font-size: 26rpx;
|
||||||
padding: 10px 15px;
|
color: #64748b;
|
||||||
background-color: #447ade;
|
background: #f8fafc;
|
||||||
color: white;
|
border-bottom: 1rpx solid #e2e8f0;
|
||||||
text-align: center;
|
flex-shrink: 0;
|
||||||
border-radius: 5px;
|
}
|
||||||
margin: 10px 15px; // 示例外边距
|
.scroll {
|
||||||
font-size: 16px;
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
height: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.sections {
|
||||||
|
padding: 16rpx 24rpx 40rpx;
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
margin-bottom: 28rpx;
|
||||||
|
}
|
||||||
|
.section-title {
|
||||||
|
display: block;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #334155;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
padding-left: 4rpx;
|
||||||
|
}
|
||||||
|
.kscc-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 28rpx 24rpx;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
border: 1rpx solid #e2e8f0;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(15, 23, 42, 0.04);
|
||||||
|
}
|
||||||
|
.kscc-name {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #0f172a;
|
||||||
|
padding-right: 16rpx;
|
||||||
|
}
|
||||||
|
.arrow {
|
||||||
|
font-size: 36rpx;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
.kscc-empty {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #94a3b8;
|
||||||
|
padding: 16rpx 8rpx;
|
||||||
|
}
|
||||||
|
.state {
|
||||||
|
text-align: center;
|
||||||
|
color: #64748b;
|
||||||
|
padding: 80rpx 24rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -118,7 +118,15 @@ import { ref, computed, watch, nextTick } from "vue";
|
|||||||
import { onLoad } from "@dcloudio/uni-app";
|
import { onLoad } from "@dcloudio/uni-app";
|
||||||
import { getByJlIdApi, jlzxFindByJlParamsApi, relayFinishApi } from "@/api/base/server";
|
import { getByJlIdApi, jlzxFindByJlParamsApi, relayFinishApi } from "@/api/base/server";
|
||||||
import { imagUrl } from "@/utils";
|
import { imagUrl } from "@/utils";
|
||||||
import { BASE_IMAGE_URL } from "@/config";
|
import {
|
||||||
|
canPreview,
|
||||||
|
isImage,
|
||||||
|
isVideo,
|
||||||
|
previewFile,
|
||||||
|
previewImage,
|
||||||
|
previewVideo,
|
||||||
|
downloadFile,
|
||||||
|
} from "@/utils/filePreview";
|
||||||
import { useUserStore } from "@/store/modules/user";
|
import { useUserStore } from "@/store/modules/user";
|
||||||
import { showLoading, hideLoading } from "@/utils/uniapp";
|
import { showLoading, hideLoading } from "@/utils/uniapp";
|
||||||
|
|
||||||
@ -395,59 +403,33 @@ onLoad(async (options) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 附件预览函数
|
// 附件:与 zhxy-jsd 教学资源 indexList 一致 — 图片 / 视频 / kkFileView 文档预览
|
||||||
const previewAttachment = (filePath: string) => {
|
const previewAttachment = (filePath: string) => {
|
||||||
if (!filePath) {
|
if (!filePath) {
|
||||||
uni.showToast({ title: "附件链接无效", icon: "none" });
|
uni.showToast({ title: "附件链接无效", icon: "none" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const fileName = getFileName(filePath);
|
||||||
|
const ext = fileName.includes(".") ? (fileName.split(".").pop() || "").toLowerCase() : "";
|
||||||
|
const fullUrl = imagUrl(filePath);
|
||||||
|
|
||||||
// 构建完整的文件URL
|
if (isVideo(ext)) {
|
||||||
const baseUrl = BASE_IMAGE_URL; // 使用配置文件中的URL
|
const title = fileName.replace(/\.[^.]+$/i, "") || fileName;
|
||||||
const fullUrl = filePath.startsWith('http') ? filePath : baseUrl + filePath;
|
previewVideo(fullUrl, title);
|
||||||
|
return;
|
||||||
// 判断文件类型
|
}
|
||||||
const fileExtension = filePath.split('.').pop()?.toLowerCase();
|
if (isImage(ext)) {
|
||||||
const isImage = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(fileExtension || '');
|
previewImage(fullUrl);
|
||||||
|
return;
|
||||||
// 如果是图片类型,直接预览
|
}
|
||||||
if (isImage) {
|
if (canPreview(fileName)) {
|
||||||
uni.previewImage({
|
previewFile(fullUrl, fileName, ext).catch(() => {
|
||||||
urls: [fullUrl],
|
downloadFile(fullUrl, fileName);
|
||||||
current: fullUrl
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
uni.showToast({ title: "该文件格式暂不支持预览", icon: "none" });
|
||||||
uni.showLoading({ title: "正在准备附件..." });
|
downloadFile(fullUrl, fileName);
|
||||||
|
|
||||||
uni.downloadFile({
|
|
||||||
url: fullUrl,
|
|
||||||
success: (res) => {
|
|
||||||
if (res.statusCode === 200) {
|
|
||||||
const tempFilePath = res.tempFilePath;
|
|
||||||
uni.hideLoading();
|
|
||||||
// Attempt to open the downloaded file
|
|
||||||
uni.openDocument({
|
|
||||||
filePath: tempFilePath,
|
|
||||||
success: function (res) {
|
|
||||||
// 打开文档成功
|
|
||||||
},
|
|
||||||
fail: function (err) {
|
|
||||||
uni.showToast({ title: "无法打开该文件类型", icon: "none" });
|
|
||||||
uni.hideLoading(); // Ensure loading is hidden on failure too
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({ title: "下载附件失败", icon: "none" });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({ title: "下载附件失败", icon: "none" });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(noticeDetail, (val) => {
|
watch(noticeDetail, (val) => {
|
||||||
|
|||||||
@ -138,7 +138,15 @@ import { ref, computed, watch, nextTick, onUnmounted } from "vue";
|
|||||||
import { onLoad } from "@dcloudio/uni-app";
|
import { onLoad } from "@dcloudio/uni-app";
|
||||||
import { getByJlIdApi, jlzxFindByJlParamsApi, relayFinishApi } from "@/api/base/server";
|
import { getByJlIdApi, jlzxFindByJlParamsApi, relayFinishApi } from "@/api/base/server";
|
||||||
import { imagUrl } from "@/utils";
|
import { imagUrl } from "@/utils";
|
||||||
import { BASE_IMAGE_URL } from "@/config";
|
import {
|
||||||
|
canPreview,
|
||||||
|
isImage,
|
||||||
|
isVideo,
|
||||||
|
previewFile,
|
||||||
|
previewImage,
|
||||||
|
previewVideo,
|
||||||
|
downloadFile,
|
||||||
|
} from "@/utils/filePreview";
|
||||||
import { useUserStore } from "@/store/modules/user";
|
import { useUserStore } from "@/store/modules/user";
|
||||||
import { showLoading, hideLoading } from "@/utils/uniapp";
|
import { showLoading, hideLoading } from "@/utils/uniapp";
|
||||||
|
|
||||||
@ -503,59 +511,33 @@ onLoad(async (options) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 附件预览函数
|
// 附件:与 zhxy-jsd 教学资源 indexList 一致 — 图片 / 视频 / kkFileView 文档预览
|
||||||
const previewAttachment = (filePath: string) => {
|
const previewAttachment = (filePath: string) => {
|
||||||
if (!filePath) {
|
if (!filePath) {
|
||||||
uni.showToast({ title: "附件链接无效", icon: "none" });
|
uni.showToast({ title: "附件链接无效", icon: "none" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const fileName = getFileName(filePath);
|
||||||
|
const ext = fileName.includes(".") ? (fileName.split(".").pop() || "").toLowerCase() : "";
|
||||||
|
const fullUrl = imagUrl(filePath);
|
||||||
|
|
||||||
// 构建完整的文件URL
|
if (isVideo(ext)) {
|
||||||
const baseUrl = BASE_IMAGE_URL; // 使用配置文件中的URL
|
const title = fileName.replace(/\.[^.]+$/i, "") || fileName;
|
||||||
const fullUrl = filePath.startsWith('http') ? filePath : baseUrl + filePath;
|
previewVideo(fullUrl, title);
|
||||||
|
return;
|
||||||
// 判断文件类型
|
}
|
||||||
const fileExtension = filePath.split('.').pop()?.toLowerCase();
|
if (isImage(ext)) {
|
||||||
const isImage = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(fileExtension || '');
|
previewImage(fullUrl);
|
||||||
|
return;
|
||||||
// 如果是图片类型,直接预览
|
}
|
||||||
if (isImage) {
|
if (canPreview(fileName)) {
|
||||||
uni.previewImage({
|
previewFile(fullUrl, fileName, ext).catch(() => {
|
||||||
urls: [fullUrl],
|
downloadFile(fullUrl, fileName);
|
||||||
current: fullUrl
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
uni.showToast({ title: "该文件格式暂不支持预览", icon: "none" });
|
||||||
uni.showLoading({ title: "正在准备附件..." });
|
downloadFile(fullUrl, fileName);
|
||||||
|
|
||||||
uni.downloadFile({
|
|
||||||
url: fullUrl,
|
|
||||||
success: (res) => {
|
|
||||||
if (res.statusCode === 200) {
|
|
||||||
const tempFilePath = res.tempFilePath;
|
|
||||||
uni.hideLoading();
|
|
||||||
// Attempt to open the downloaded file
|
|
||||||
uni.openDocument({
|
|
||||||
filePath: tempFilePath,
|
|
||||||
success: function (res) {
|
|
||||||
// 打开文档成功
|
|
||||||
},
|
|
||||||
fail: function (err) {
|
|
||||||
uni.showToast({ title: "无法打开该文件类型", icon: "none" });
|
|
||||||
uni.hideLoading(); // Ensure loading is hidden on failure too
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({ title: "下载附件失败", icon: "none" });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({ title: "下载附件失败", icon: "none" });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(noticeDetail, (val) => {
|
watch(noticeDetail, (val) => {
|
||||||
|
|||||||
@ -353,7 +353,7 @@ import {
|
|||||||
questionnaireCheckPermissionApi,
|
questionnaireCheckPermissionApi,
|
||||||
questionnaireQuestionFindPageApi,
|
questionnaireQuestionFindPageApi,
|
||||||
questionnaireAnswerSaveApi,
|
questionnaireAnswerSaveApi,
|
||||||
questionnaireAnswerFindByUserApi,
|
questionnaireAnswerFindByUserOrFamilyApi,
|
||||||
questionnaireAnswerFindDetailApi
|
questionnaireAnswerFindDetailApi
|
||||||
} from '@/api/base/wjApi';
|
} from '@/api/base/wjApi';
|
||||||
import { useUserStore } from '@/store/modules/user';
|
import { useUserStore } from '@/store/modules/user';
|
||||||
@ -368,6 +368,7 @@ const options = ref<any>({});
|
|||||||
const questionnaireId = ref('');
|
const questionnaireId = ref('');
|
||||||
const openId = ref<string>(""); // 微信 openId
|
const openId = ref<string>(""); // 微信 openId
|
||||||
const xxtsId = ref<string>(""); // 消息推送ID(从URL参数id获取)
|
const xxtsId = ref<string>(""); // 消息推送ID(从URL参数id获取)
|
||||||
|
const xsId = ref<string>(""); // 学生ID(推送链接中的 xsId,用于「同一生多家长」去重)
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const submitting = ref(false); // 提交中状态
|
const submitting = ref(false); // 提交中状态
|
||||||
const currentStep = ref<'fill' | 'success' | 'noPermission' | 'alreadyFilled' | 'timeInvalid'>('fill');
|
const currentStep = ref<'fill' | 'success' | 'noPermission' | 'alreadyFilled' | 'timeInvalid'>('fill');
|
||||||
@ -415,6 +416,7 @@ onLoad(async (params) => {
|
|||||||
options.value = params;
|
options.value = params;
|
||||||
openId.value = params?.openId || '';
|
openId.value = params?.openId || '';
|
||||||
xxtsId.value = params?.id || ''; // 从URL参数id获取xxtsId
|
xxtsId.value = params?.id || ''; // 从URL参数id获取xxtsId
|
||||||
|
xsId.value = (params?.xsId as string) || '';
|
||||||
|
|
||||||
// 如果有 openId,先进行认证
|
// 如果有 openId,先进行认证
|
||||||
if (openId.value) {
|
if (openId.value) {
|
||||||
@ -500,8 +502,10 @@ const initializePage = async () => {
|
|||||||
|
|
||||||
// 4. 检查是否已填写(如果不允许重复提交)
|
// 4. 检查是否已填写(如果不允许重复提交)
|
||||||
if (!allowResubmit.value) {
|
if (!allowResubmit.value) {
|
||||||
const answerResult = await questionnaireAnswerFindByUserApi({
|
const answerResult = await questionnaireAnswerFindByUserOrFamilyApi({
|
||||||
questionnaireId: questionnaireId.value
|
questionnaireId: questionnaireId.value,
|
||||||
|
xxtsId: xxtsId.value || undefined,
|
||||||
|
xsId: xsId.value || undefined,
|
||||||
});
|
});
|
||||||
if (answerResult && answerResult.resultCode === 1) {
|
if (answerResult && answerResult.resultCode === 1) {
|
||||||
const hasAnswered = answerResult.result || answerResult.data;
|
const hasAnswered = answerResult.result || answerResult.data;
|
||||||
@ -1091,6 +1095,9 @@ const doSubmitQuestionnaire = async () => {
|
|||||||
if (xxtsId.value) {
|
if (xxtsId.value) {
|
||||||
submitParams.xxtsId = xxtsId.value;
|
submitParams.xxtsId = xxtsId.value;
|
||||||
}
|
}
|
||||||
|
if (xsId.value) {
|
||||||
|
submitParams.xsId = xsId.value;
|
||||||
|
}
|
||||||
|
|
||||||
// 提交答案
|
// 提交答案
|
||||||
const result = await questionnaireAnswerSaveApi(submitParams);
|
const result = await questionnaireAnswerSaveApi(submitParams);
|
||||||
|
|||||||
@ -14,24 +14,27 @@
|
|||||||
<view class="page-banner">
|
<view class="page-banner">
|
||||||
<view class="banner-content">
|
<view class="banner-content">
|
||||||
<view class="banner-title-wrapper">
|
<view class="banner-title-wrapper">
|
||||||
<text class="banner-icon">📝</text>
|
<text class="banner-icon">{{ activeTab === 0 ? '📝' : '📚' }}</text>
|
||||||
<view class="banner-text">
|
<view class="banner-text">
|
||||||
<text class="banner-title">作品任务</text>
|
<text class="banner-title">{{ activeTab === 0 ? '作品任务' : '作品资源' }}</text>
|
||||||
<text class="banner-subtitle">独立项目选择 · 学习成长</text>
|
<text class="banner-subtitle">{{
|
||||||
|
activeTab === 0 ? '独立项目选择 · 学习成长' : '课程资料 · 在线查看'
|
||||||
|
}}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="task-count">
|
<view class="task-count">
|
||||||
<text class="count-number">{{ taskList.length }}</text>
|
<text class="count-number">{{ activeTab === 0 ? submitTasks.length : resourceTasks.length }}</text>
|
||||||
<text class="count-label">个任务</text>
|
<text class="count-label">{{ activeTab === 0 ? '个任务' : '项资源' }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 独立项目选择提示 -->
|
<view class="tab-bar">
|
||||||
<view class="selection-tip" v-if="taskList.length > 0">
|
<view class="tab-item" :class="{ active: activeTab === 0 }" @click="activeTab = 0">
|
||||||
<view class="tip-content">
|
<text class="tab-text">作品任务</text>
|
||||||
<text class="tip-title">独立项目选择</text>
|
</view>
|
||||||
<text class="tip-desc">请从下方{{ taskList.length }}个任务中选择1个完成,可自主挑战更高层级</text>
|
<view class="tab-item" :class="{ active: activeTab === 1 }" @click="activeTab = 1">
|
||||||
|
<text class="tab-text">作品资源</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@ -42,16 +45,18 @@
|
|||||||
<text class="loading-text">加载中...</text>
|
<text class="loading-text">加载中...</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<template v-else-if="taskList.length > 0">
|
<template v-else-if="displayList.length > 0">
|
||||||
<view
|
<view
|
||||||
v-for="task in taskList"
|
v-for="task in displayList"
|
||||||
:key="task.id"
|
:key="task.id"
|
||||||
class="task-card"
|
class="task-card"
|
||||||
|
:class="{ 'task-card--resource': activeTab === 1 }"
|
||||||
|
@click="onTaskCardClick(task)"
|
||||||
>
|
>
|
||||||
<view class="card-header">
|
<view class="card-header">
|
||||||
<view class="header-left">
|
<view class="header-left">
|
||||||
<view class="task-icon">📝</view>
|
<view class="task-icon">{{ activeTab === 0 ? '📝' : '📚' }}</view>
|
||||||
<text class="task-title">{{ task.zpmc || '作品任务' }}</text>
|
<text class="task-title">{{ task.zpmc || (activeTab === 0 ? '作品任务' : '作品资料') }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="status-badge" :class="getStatusClass(task.zpzt)">
|
<view class="status-badge" :class="getStatusClass(task.zpzt)">
|
||||||
<text class="status-text">{{ getStatusText(task.zpzt) }}</text>
|
<text class="status-text">{{ getStatusText(task.zpzt) }}</text>
|
||||||
@ -76,11 +81,15 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="card-footer">
|
<view class="card-footer">
|
||||||
<button class="action-btn challenge-btn" @click.stop="startChallenge(task)">
|
<button
|
||||||
|
v-if="activeTab === 0"
|
||||||
|
class="action-btn challenge-btn"
|
||||||
|
@click.stop="startChallenge(task)"
|
||||||
|
>
|
||||||
开始挑战
|
开始挑战
|
||||||
</button>
|
</button>
|
||||||
<button class="action-btn detail-btn" @click.stop="viewTaskDetail(task)">
|
<button class="action-btn detail-btn" @click.stop="activeTab === 0 ? viewTaskDetail(task) : viewResourceDetail(task)">
|
||||||
详情
|
{{ activeTab === 0 ? '详情' : '查看资料' }}
|
||||||
</button>
|
</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -88,7 +97,7 @@
|
|||||||
|
|
||||||
<view v-else class="empty-state">
|
<view v-else class="empty-state">
|
||||||
<view class="empty-icon">📭</view>
|
<view class="empty-icon">📭</view>
|
||||||
<text class="empty-text">暂无作品任务</text>
|
<text class="empty-text">{{ activeTab === 0 ? '暂无作品任务' : '暂无作品资源' }}</text>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
@ -96,7 +105,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, onUnmounted } from "vue";
|
import { ref, computed, onUnmounted } from "vue";
|
||||||
import { onShow, onLoad } from "@dcloudio/uni-app";
|
import { onShow, onLoad } from "@dcloudio/uni-app";
|
||||||
import { useUserStore } from "@/store/modules/user";
|
import { useUserStore } from "@/store/modules/user";
|
||||||
import { zpzxFindByKcParamsApi } from "@/api/base/server";
|
import { zpzxFindByKcParamsApi } from "@/api/base/server";
|
||||||
@ -114,9 +123,21 @@ interface TaskItem {
|
|||||||
kcId?: string;
|
kcId?: string;
|
||||||
xsId?: string;
|
xsId?: string;
|
||||||
ispj?: string; // A:已评价 B:未评价(待评价)
|
ispj?: string; // A:已评价 B:未评价(待评价)
|
||||||
|
/** SUBMIT 任务提交,VIEW 资料查看 */
|
||||||
|
rwms?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const taskList = ref<TaskItem[]>([]);
|
const taskList = ref<TaskItem[]>([]);
|
||||||
|
const activeTab = ref(0);
|
||||||
|
|
||||||
|
const isResourceTask = (t: TaskItem) =>
|
||||||
|
t.rwms != null && String(t.rwms).trim().toUpperCase() === "VIEW";
|
||||||
|
|
||||||
|
const submitTasks = computed(() => taskList.value.filter((t) => !isResourceTask(t)));
|
||||||
|
const resourceTasks = computed(() => taskList.value.filter((t) => isResourceTask(t)));
|
||||||
|
const displayList = computed(() =>
|
||||||
|
activeTab.value === 0 ? submitTasks.value : resourceTasks.value
|
||||||
|
);
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const kcId = ref<string>('');
|
const kcId = ref<string>('');
|
||||||
const xsId = ref<string>('');
|
const xsId = ref<string>('');
|
||||||
@ -252,7 +273,7 @@ const startChallenge = (task: TaskItem) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 仅当存在「另一任务已提交且已被评价」时才禁止换项;已提交但 ispj=B(待评价)时仍可点击开始挑战
|
// 仅当存在「另一任务已提交且已被评价」时才禁止换项;已提交但 ispj=B(待评价)时仍可点击开始挑战
|
||||||
const otherLocked = taskList.value.find(
|
const otherLocked = submitTasks.value.find(
|
||||||
(t) => t.id !== task.id && isTaskEvaluated(t)
|
(t) => t.id !== task.id && isTaskEvaluated(t)
|
||||||
);
|
);
|
||||||
if (otherLocked) {
|
if (otherLocked) {
|
||||||
@ -284,7 +305,7 @@ const startChallenge = (task: TaskItem) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 查看详情 - 跳转到详情页面
|
// 查看详情 - 跳转到任务详情页
|
||||||
const viewTaskDetail = (task: TaskItem) => {
|
const viewTaskDetail = (task: TaskItem) => {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.append('zpId', task.zpId);
|
params.append('zpId', task.zpId);
|
||||||
@ -299,6 +320,27 @@ const viewTaskDetail = (task: TaskItem) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 作品资源(资料查看)详情 */
|
||||||
|
const viewResourceDetail = (task: TaskItem) => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('zpId', task.zpId);
|
||||||
|
if (task.kcId || kcId.value) {
|
||||||
|
params.append('kcId', task.kcId || kcId.value);
|
||||||
|
}
|
||||||
|
if (task.xsId || xsId.value) {
|
||||||
|
params.append('xsId', task.xsId || xsId.value);
|
||||||
|
}
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/base/xszp/resource-detail?${params.toString()}`
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTaskCardClick = (task: TaskItem) => {
|
||||||
|
if (activeTab.value === 1) {
|
||||||
|
viewResourceDetail(task);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const getStatusClass = (status: string) => {
|
const getStatusClass = (status: string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'A':
|
case 'A':
|
||||||
@ -413,33 +455,37 @@ const formatTime = (timeStr?: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 独立项目选择提示样式
|
.tab-bar {
|
||||||
.selection-tip {
|
display: flex;
|
||||||
background: linear-gradient(135deg, #e6f4ff 0%, #f0f8ff 100%);
|
background: #ffffff;
|
||||||
margin: 0;
|
margin: 0 16px;
|
||||||
padding: 16px;
|
margin-top: -10px;
|
||||||
box-shadow: 0 2px 8px rgba(78, 115, 223, 0.1);
|
margin-bottom: 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 4px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
.tip-content {
|
.tab-item {
|
||||||
display: flex;
|
flex: 1;
|
||||||
flex-direction: column;
|
text-align: center;
|
||||||
align-items: center;
|
padding: 10px 0;
|
||||||
justify-content: center;
|
border-radius: 10px;
|
||||||
gap: 6px;
|
transition: background 0.2s ease, color 0.2s ease;
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.tip-title {
|
.tab-text {
|
||||||
font-size: 17px;
|
font-size: 15px;
|
||||||
font-weight: 700;
|
color: #666;
|
||||||
color: #2c3e50;
|
}
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tip-desc {
|
&.active {
|
||||||
font-size: 14px;
|
background: linear-gradient(135deg, #4e73df 0%, #2e59d9 100%);
|
||||||
color: #4e73df;
|
|
||||||
line-height: 1.5;
|
.tab-text {
|
||||||
font-weight: 500;
|
color: #ffffff;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -489,6 +535,10 @@ const formatTime = (timeStr?: string) => {
|
|||||||
position: relative;
|
position: relative;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
|
||||||
|
&--resource {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
transform: translateY(2px) scale(0.98);
|
transform: translateY(2px) scale(0.98);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
|
|||||||
445
src/pages/base/xszp/resource-detail.vue
Normal file
445
src/pages/base/xszp/resource-detail.vue
Normal file
@ -0,0 +1,445 @@
|
|||||||
|
<!-- 作品资源详情(资料查看) -->
|
||||||
|
<template>
|
||||||
|
<BasicLayout>
|
||||||
|
<view class="task-detail-page">
|
||||||
|
<view v-if="isLoading" class="loading-indicator">
|
||||||
|
<view class="loading-spinner"></view>
|
||||||
|
<text class="loading-text">加载中...</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else class="content-wrapper">
|
||||||
|
<view class="page-inner">
|
||||||
|
<view class="resource-meta-card">
|
||||||
|
<view class="meta-card-title">资源内容</view>
|
||||||
|
|
||||||
|
<view class="meta-block">
|
||||||
|
<text class="meta-label">资源名称</text>
|
||||||
|
<text class="meta-value meta-value--title">{{ taskInfo.zpmc || '作品资料' }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="taskInfo.zpms" class="meta-block meta-block--desc">
|
||||||
|
<text class="meta-label">资源描述</text>
|
||||||
|
<text class="meta-value meta-value--body">{{ taskInfo.zpms }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="taskInfo.fileUrl" class="attachment-card">
|
||||||
|
<view class="attachment-card-head">
|
||||||
|
<text class="attachment-icon">📎</text>
|
||||||
|
<view class="attachment-head-texts">
|
||||||
|
<text class="attachment-title">附件资料</text>
|
||||||
|
<text class="attachment-hint">点击下方文件名称,即可预览或下载</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="attachment-body">
|
||||||
|
<BasicFilePreview
|
||||||
|
:file-url="taskInfo.fileUrl"
|
||||||
|
:file-name="taskInfo.fileName"
|
||||||
|
:file-format="taskInfo.fileFormat"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="taskItemList.length > 0" class="task-items-section">
|
||||||
|
<view class="section-title">附加说明</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in taskItemList"
|
||||||
|
:key="item.id || index"
|
||||||
|
class="task-item-card"
|
||||||
|
>
|
||||||
|
<view class="item-header">
|
||||||
|
<text class="item-number">{{ index + 1 }}</text>
|
||||||
|
<text class="item-title">{{ item.zpbt || '说明' }}</text>
|
||||||
|
<view v-if="item.isbt === 1 || item.isbt === true" class="required-badge">
|
||||||
|
<text class="required-text">必填</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="item.remark" class="item-remark">
|
||||||
|
<text class="remark-text">{{ item.remark }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="item-type">
|
||||||
|
<text class="type-label">类型:</text>
|
||||||
|
<text class="type-value">{{ getTaskTypeText(item.zpfl) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</BasicLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { onLoad } from '@dcloudio/uni-app';
|
||||||
|
import { zpFindDetailByIdApi } from "@/api/base/server";
|
||||||
|
import BasicFilePreview from "@/components/BasicFile/preview.vue";
|
||||||
|
|
||||||
|
interface TaskInfo {
|
||||||
|
id?: string;
|
||||||
|
zpmc?: string;
|
||||||
|
zpms?: string;
|
||||||
|
zpkstime?: string;
|
||||||
|
zpjstime?: string;
|
||||||
|
fileUrl?: string;
|
||||||
|
fileName?: string;
|
||||||
|
fileFormat?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TaskItem {
|
||||||
|
id: string;
|
||||||
|
zpbt?: string;
|
||||||
|
zpfl?: string;
|
||||||
|
isbt?: number | boolean;
|
||||||
|
remark?: string;
|
||||||
|
zpfldm?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const taskInfo = ref<TaskInfo>({});
|
||||||
|
const taskItemList = ref<TaskItem[]>([]);
|
||||||
|
|
||||||
|
onLoad(async (options) => {
|
||||||
|
const zpId = options?.zpId || '';
|
||||||
|
|
||||||
|
if (!zpId) {
|
||||||
|
uni.showToast({ title: '缺少资源编号', icon: 'error' });
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateBack();
|
||||||
|
}, 1500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadResourceDetail(zpId);
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadResourceDetail = async (zpId: string) => {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
const response: any = await zpFindDetailByIdApi({ id: zpId });
|
||||||
|
const detailData = response?.result || response?.data || response;
|
||||||
|
|
||||||
|
if (!detailData) {
|
||||||
|
throw new Error('未找到资料');
|
||||||
|
}
|
||||||
|
|
||||||
|
const zpData = detailData.zp || {};
|
||||||
|
taskInfo.value = zpData;
|
||||||
|
taskItemList.value = detailData.zplxList || [];
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载资源详情失败:', error);
|
||||||
|
uni.showToast({
|
||||||
|
title: error instanceof Error ? error.message : '加载失败,请重试',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTaskTypeText = (type?: string) => {
|
||||||
|
const typeMap: Record<string, string> = {
|
||||||
|
'sctp': '上传图片',
|
||||||
|
'scsp': '上传视频',
|
||||||
|
'scwd': '上传文档',
|
||||||
|
'fwb': '富文本',
|
||||||
|
'text': '文本',
|
||||||
|
'dxsx': '多项选择',
|
||||||
|
'dxxz': '单项选择'
|
||||||
|
};
|
||||||
|
return typeMap[type || ''] || '未知类型';
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.task-detail-page {
|
||||||
|
background: linear-gradient(180deg, #eef2fb 0%, #f4f5f7 120px, #f4f5f7 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-indicator {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px 0;
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 3px solid #f3f3f3;
|
||||||
|
border-top: 3px solid #4e73df;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
padding-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-inner {
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-meta-card {
|
||||||
|
padding: 18px 16px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 14px;
|
||||||
|
box-shadow: 0 4px 20px rgba(46, 89, 217, 0.08);
|
||||||
|
border: 1px solid rgba(78, 115, 223, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-card-title {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1a1a2e;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid #4e73df;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--desc {
|
||||||
|
padding-top: 4px;
|
||||||
|
border-top: 1px solid #f0f2f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-value {
|
||||||
|
color: #374151;
|
||||||
|
word-break: break-word;
|
||||||
|
|
||||||
|
&--title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #111827;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--body {
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.7;
|
||||||
|
color: #4b5563;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-card {
|
||||||
|
background: linear-gradient(145deg, #ffffff 0%, #f8faff 100%);
|
||||||
|
border-radius: 14px;
|
||||||
|
border: 1px solid rgba(78, 115, 223, 0.22);
|
||||||
|
box-shadow: 0 6px 24px rgba(46, 89, 217, 0.12);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-card-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 16px 16px 12px;
|
||||||
|
background: linear-gradient(135deg, rgba(78, 115, 223, 0.1) 0%, rgba(78, 115, 223, 0.02) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-icon {
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-head-texts {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-title {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1e3a5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-hint {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #5c6b8a;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-body {
|
||||||
|
padding: 8px 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-items-section {
|
||||||
|
padding: 18px 16px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 14px;
|
||||||
|
box-shadow: 0 4px 20px rgba(46, 89, 217, 0.06);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #333;
|
||||||
|
border-bottom: 2px solid #4e73df;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item-card {
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
border-left: 3px solid #4e73df;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
.item-number {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
background: #4e73df;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-title {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.required-badge {
|
||||||
|
padding: 2px 8px;
|
||||||
|
background: #fff3cd;
|
||||||
|
color: #856404;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
margin-left: 8px;
|
||||||
|
|
||||||
|
.required-text {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-remark {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding-left: 32px;
|
||||||
|
|
||||||
|
.remark-text {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-type {
|
||||||
|
padding-left: 32px;
|
||||||
|
font-size: 13px;
|
||||||
|
|
||||||
|
.type-label {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-value {
|
||||||
|
color: #4e73df;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 放大附件列表点击区域与字号,便于查阅 */
|
||||||
|
.attachment-body :deep(.basic-file-preview .file-list) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-body :deep(.basic-file-preview .file-item) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding: 14px 16px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||||
|
min-height: 52px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-body :deep(.basic-file-preview .file-item:active) {
|
||||||
|
background: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-body :deep(.basic-file-preview .file-label) {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-right: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-body :deep(.basic-file-preview .file-name) {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding: 4px 0;
|
||||||
|
color: #2563eb;
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px solid rgba(37, 99, 235, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-body :deep(.basic-file-preview .file-name:active) {
|
||||||
|
color: #1d4ed8;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -5,6 +5,16 @@
|
|||||||
|
|
||||||
import { KK_FILE_VIEW_URL } from '@/config';
|
import { KK_FILE_VIEW_URL } from '@/config';
|
||||||
|
|
||||||
|
export const isVideo = (fileType: string): boolean => {
|
||||||
|
const videoTypes = ['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mkv', '3gp', 'm4v'];
|
||||||
|
return videoTypes.includes(fileType.toLowerCase());
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isImage = (fileType: string): boolean => {
|
||||||
|
const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
|
||||||
|
return imageTypes.includes(fileType.toLowerCase());
|
||||||
|
};
|
||||||
|
|
||||||
// 检查是否是压缩包文件
|
// 检查是否是压缩包文件
|
||||||
const isCompressedFile = (fileName: string): boolean => {
|
const isCompressedFile = (fileName: string): boolean => {
|
||||||
const compressedExtensions = ['zip', 'rar', '7z', 'tar', 'gz', 'bz2'];
|
const compressedExtensions = ['zip', 'rar', '7z', 'tar', 'gz', 'bz2'];
|
||||||
@ -13,7 +23,7 @@ const isCompressedFile = (fileName: string): boolean => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 检查文件是否可以预览
|
// 检查文件是否可以预览
|
||||||
const canPreview = (fileName: string): boolean => {
|
export const canPreview = (fileName: string): boolean => {
|
||||||
const previewableExtensions = [
|
const previewableExtensions = [
|
||||||
'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
|
'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
|
||||||
'txt', 'rtf', 'odt', 'ods', 'odp',
|
'txt', 'rtf', 'odt', 'ods', 'odp',
|
||||||
@ -269,3 +279,15 @@ export const previewVideo = (videoUrl: string, videoTitle: string) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 图片预览
|
||||||
|
export const previewImage = (imageUrl: string) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
uni.previewImage({
|
||||||
|
urls: [imageUrl],
|
||||||
|
current: imageUrl,
|
||||||
|
success: resolve,
|
||||||
|
fail: resolve,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user