功能调整

This commit is contained in:
hebo 2025-12-08 20:03:34 +08:00
parent 65a5992e57
commit eaf54b9048
22 changed files with 6377 additions and 1443 deletions

View File

@ -261,6 +261,76 @@ export const jsFindPageJfApi = async (params: any) => {
return await get("/api/js/findPageJf", params);
};
// 教师积分类型汇总
export const jfSummaryByJfTypeApi = async (params: any) => {
return await get("/api/jf/summaryByJfType", params);
};
// 顶级类型及下级可加分结构
export const jfTypeStructureApi = async (params: any) => {
return await get("/api/jf/typeStructure", params);
};
// 顶级类型下的积分结构(按类型节点汇总)
export const jfScoreStructureApi = async (params: any) => {
return await get("/api/jf/scoreStructure", params);
};
// 查询积分记录列表
export const jfFindPageApi = async (params: any) => {
return await get("/api/jf/findPage", params);
};
// 积分申请(走审批流程)
export const jfSqApi = async (params: any) => {
return await post("/api/jf/sq", params);
};
// 积分待办/已办列表
export const jfFindUserTodosPageApi = async (params: any) => {
return await get("/api/jf/findUserTodosPage", params);
};
// 保存积分记录(新增/修改)
export const jfSaveApi = async (params: any) => {
return await post("/api/jf/save", params);
};
// 积分审批-同意/驳回
export const jfSpApi = async (params: any) => {
return await post("/api/jf/sp", params);
};
// 积分审批-转办
export const jfTransferApi = async (params: any) => {
return await post("/api/jf/transfer", params);
};
// 积分审批-终止
export const jfStopApi = async (params: any) => {
return await post("/api/jf/stop", params);
};
// 积分审批-重新提交
export const jfReSubmitApi = async (params: any) => {
return await post("/api/jf/reSubmit", params);
};
// 积分审批-流程详情
export const jfFlowByIdApi = async (params: any) => {
return await get("/api/jf/flowById", params);
};
// 查询积分项目列表(树形结构)
export const jfTypeFindPageApi = async (params: any) => {
return await get("/api/jfType/findPage", params);
};
// 查询积分规则列表
export const jfRuleFindPageApi = async (params: any) => {
return await get("/api/jfRule/findPage", params);
};
// 检查项查询
export const inspectItemFindAllApi = async (params: any) => {
return await get("/api/inspectItem/findAlls", params);

View File

@ -24,3 +24,11 @@ export const srFindPageApi = async (params: any) => {
return await get("/api/srTsRecord/findPage", params);
};
/**
* ID获取贺卡详情
* @param params { recordId: 记录ID, receiverId: 接收人ID }
*/
export const srGetCardDetailByRecordApi = async (params: { recordId: string; receiverId?: string }) => {
return await get("/api/srTsRecord/getCardDetailByRecord", params);
};

View File

@ -47,6 +47,14 @@ export const findAllZw = () => {

View File

@ -711,7 +711,7 @@
}
},
{
"path": "pages/view/routine/JiFenPingJia/JiFenPingJia",
"path": "pages/view/routine/JiFenPingJia/jfself/JiFenPingJia",
"style": {
"navigationBarTitleText": "积分评价",
"enablePullDownRefresh": false
@ -791,12 +791,40 @@
}
},
{
"path": "pages/view/routine/JiFenPingJia/detail",
"path": "pages/view/routine/JiFenPingJia/jfself/apply",
"style": {
"navigationBarTitleText": "评价详情",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/JiFenPingJia/jfself/MyScoreDetail",
"style": {
"navigationBarTitleText": "积分详情",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/JiFenPingJia/jfself/IntegralApply",
"style": {
"navigationBarTitleText": "积分申请",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/JiFenPingJia/jfsp/index",
"style": {
"navigationBarTitleText": "积分审批",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/JiFenPingJia/jfsp/JfFlow",
"style": {
"navigationBarTitleText": "积分审批",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/kefuxuncha/xcXkList",
"style": {
@ -958,20 +986,7 @@
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/JiFenPingJia/PersonalHonor",
"style": {
"navigationBarTitleText": "个人荣誉",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/JiFenPingJia/PublicClassAwards",
"style": {
"navigationBarTitleText": "公开课获奖",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/hr/teacherProfile/RecordMaterials",
"style": {
@ -1081,19 +1096,29 @@
{
"path": "pages/view/routine/sr/envelope",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "生日祝福",
"enablePullDownRefresh": false,
"backgroundColor": "#ff9a9e"
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/sr/card",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "生日祝福",
"enablePullDownRefresh": false,
"backgroundColor": "#ff9a9e"
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/sr/list",
"style": {
"navigationBarTitleText": "生日清单",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/sr/viewCard",
"style": {
"navigationBarTitleText": "生日祝福",
"enablePullDownRefresh": false
}
},
{

View File

@ -385,6 +385,7 @@ const sections = reactive<Section[]>([
title: "功能应用",
permissionKey: "gnyycommon", //
items: [
{
id: "gnyy1",
icon: "kctb",
@ -641,6 +642,40 @@ const sections = reactive<Section[]>([
permissionKey: "routine-tzfb",
path: "/pages/view/routine/tz/index",
},
{
id: "xxfb-srqd",
icon: "srqd",
text: "生日清单",
show: true,
permissionKey: "routine-srqd",
path: "/pages/view/routine/sr/list",
},
],
},
{
id: "routine-jf",
icon: "jfpj",
text: "业绩积分",
show: true,
permissionKey: "routine-jf",
isFolder: true,
folderItems: [
{
id: "routine-jf-pj",
icon: "jfpj",
text: "积分评价",
show: true,
permissionKey: "routine-jfpj",
path: "/pages/view/routine/JiFenPingJia/jfself/JiFenPingJia",
},
{
id: "routine-jf-sp",
icon: "gw",
text: "积分审批",
show: true,
permissionKey: "routine-jfsp",
path: "/pages/view/routine/JiFenPingJia/jfsp/index",
},
],
},
],
@ -650,14 +685,6 @@ const sections = reactive<Section[]>([
title: "教学常规",
permissionKey: "routine", //
items: [
{
id: "r2",
icon: "jfpj",
text: "积分评价",
show: true,
permissionKey: "routine-jfpj", //
path: "/pages/view/routine/JiFenPingJia/JiFenPingJia",
},
{
id: "r3",
icon: "gzltj",

View File

@ -1,357 +0,0 @@
<template>
<BasicLayout>
<view class="p-15">
<view v-if="education.xl.length > 0">
<template v-for="(item, index) in education.xl" :key="index">
<view class="po-re mb-15">
<BasicForm
v-model="item.value"
:schema="getSchemaForIndex(index)"
:index="index"
:key="`form-${index}-${forceUpdateKey}`"
:formsProps="{ labelWidth: 100 }"
/>
<view
@click="deleteMemberFamily(index, item.value)"
class="delete-icon"
>
<BasicIcon type="clear" size="30" />
</view>
</view>
</template>
</view>
<view
class="flex-row items-center justify-center pb-10 pt-5"
style="border: 1px solid #e8e8e8"
@click="addEducation"
>
<uni-icons type="plus" size="16" color="#447ADE"></uni-icons>
<view class="ml-5 cor-447ADE">新增</view>
</view>
</view>
<template #bottom>
<view class="flex-row items-center pb-10 pt-5">
<u-button
text="提交"
class="mx-15"
type="primary"
@click="submit"
/>
</view>
</template>
</BasicLayout>
</template>
<script lang="ts" setup>
import { showToast } from "@/utils/uniapp";
import { cloneDeep, map } from "lodash";
import { fractionRuleApi } from "@/api/base/server";
import { useUserStore } from "@/store/modules/user";
const { getJs, getUser } = useUserStore();
//
const education = reactive<any>({
xl: [{ value: {} }],
});
//
const honorCategories = ref<any[]>([]);
//
const awardLevelsMap = reactive<Record<number, any>>({});
//
const forceUpdateKey = ref(0);
//
const baseSchema = [
{
field: "rymc",
label: "荣誉名称",
component: "BasicInput",
componentProps: {},
},
{
field: "hilb_id",
label: "荣誉类别",
component: "BasicPicker",
componentProps: {
api: fractionRuleApi,
rangeKey: "inspectStandard",
savaKey: "id",
ok: (selectedIndex: number, form: any, list: any, attrs: any) => {
const selectedCategory = list[selectedIndex];
const formIndex = attrs.index;
//
updateAwardLevels(formIndex, selectedCategory);
//
if (education.xl[formIndex].value.hjjbId) {
education.xl[formIndex].value.hjjbId = "";
}
//
forceUpdateKey.value++;
},
},
},
{
field: "hjjbId",
label: "获奖级别",
component: "BasicPicker",
componentProps: {
range: [],
rangeKey: "name",
savaKey: "id",
open: (value: any, attrs: any, model: any) => {
const formIndex = attrs.index;
//
if (!model?.hilb_id) {
showToast({
title: "请先选择荣誉类别",
icon: "none",
});
return false;
}
//
const awardLevels = awardLevelsMap[formIndex] || [];
if (awardLevels.length === 0) {
showToast({
title: "该荣誉类别暂无获奖级别数据",
icon: "none",
});
return false;
}
return true;
},
},
},
{
field: "bjdw",
label: "颁奖单位",
component: "BasicInput",
componentProps: {},
},
{
field: "hjtime",
label: "获奖时间",
component: "BasicDateTimes",
componentProps: {},
},
{
field: "jf",
label: "积分",
component: "BasicInput",
componentProps: {},
},
{
field: "hjfjId",
label: "上传证书",
component: "BasicUpload",
itemProps: {
labelPosition: "top",
},
componentProps: {},
},
];
// schema
const getSchemaForIndex = (index: number) => {
const schema = cloneDeep(baseSchema);
//
const hjjbField = schema.find((item) => item.field === "hjjbId");
if (hjjbField) {
hjjbField.componentProps.range = awardLevelsMap[index] || [];
}
return schema;
};
//
const updateAwardLevels = (formIndex: number, category: any) => {
if (category?.ruleItemList && category.ruleItemList.length > 0) {
awardLevelsMap[formIndex] = category.ruleItemList;
} else {
awardLevelsMap[formIndex] = [];
}
};
//
const initHonorCategories = async () => {
try {
const result = await fractionRuleApi();
honorCategories.value = result.result || result || [];
} catch (error) {
console.error("获取荣誉类别数据失败:", error);
}
};
//
const initEchoData = async () => {
await initHonorCategories();
//
education.xl.forEach((formItem: any, index: number) => {
if (formItem.value?.hilb_id) {
const category = honorCategories.value.find(
(cat: any) => cat.id === formItem.value.hilb_id
);
if (category) {
//
updateAwardLevels(index, category);
} else {
console.log(`未找到荣誉类别 ID: ${formItem.value.hilb_id}`);
}
} else {
console.log(`表单项 ${index} 没有荣誉类别 ID`);
}
});
//
forceUpdateKey.value++;
};
//
function addEducation() {
const newIndex = education.xl.length;
education.xl.push({ value: {} });
//
awardLevelsMap[newIndex] = [];
}
//
function deleteMemberFamily(index: number, item: any) {
//
delete awardLevelsMap[index];
// awardLevelsMap
const newAwardLevelsMap: Record<number, any[]> = {};
education.xl.forEach((_: any, i: number) => {
if (i < index) {
newAwardLevelsMap[i] = awardLevelsMap[i] || [];
} else if (i > index) {
newAwardLevelsMap[i - 1] = awardLevelsMap[i] || [];
}
});
//
education.xl.splice(index, 1);
// awardLevelsMap
Object.keys(awardLevelsMap).forEach(
(key: string) => delete awardLevelsMap[Number(key)]
);
Object.assign(awardLevelsMap, newAwardLevelsMap);
}
//
async function submit() {
try {
//
const grRyList = map(education.xl, (item) => {
return { ...item.value, hjlxId: "GRRY" };
});
if (grRyList.length === 0) {
showToast({
title: "请至少添加一条荣誉记录",
icon: "none",
});
return;
}
//
for (let i = 0; i < grRyList.length; i++) {
const item = grRyList[i];
if (!item.rymc) {
showToast({
title: `${i + 1}条记录:请填写荣誉名称`,
icon: "none",
});
return;
}
if (!item.hilb_id) {
showToast({
title: `${i + 1}条记录:请选择荣誉类别`,
icon: "none",
});
return;
}
if (!item.hjjbId) {
showToast({
title: `${i + 1}条记录:请选择获奖级别`,
icon: "none",
});
return;
}
if (!item.bjdw) {
showToast({
title: `${i + 1}条记录:请填写颁奖单位`,
icon: "none",
});
return;
}
if (!item.hjtime) {
showToast({
title: `${i + 1}条记录:请选择获奖时间`,
icon: "none",
});
return;
}
if (!item.jf) {
showToast({
title: `${i + 1}条记录:请填写积分`,
icon: "none",
});
return;
}
}
//
const params = {
id: null, //
jsId: getJs.id,
jsName: getJs.jsxm,
grRyList: grRyList,
//
inspectStandard: "个人荣誉申请",
scoreType: "1", //
examineTime: new Date(),
remark: "个人荣誉积分申请",
};
uni.showLoading({ title: "提交中..." });
} catch (error) {
console.error("提交积分申请失败:", error);
showToast({
title: "提交失败,请稍后重试",
icon: "none"
});
} finally {
uni.hideLoading();
}
}
//
onMounted(() => {
initHonorCategories();
});
</script>
<style>
.delete-icon {
position: absolute;
right: -13px;
top: -14px;
z-index: 1;
}
</style>

View File

@ -1,284 +0,0 @@
<template>
<BasicLayout>
<view class="p-15">
<view v-if="education.xl.length > 0">
<template v-for="(item, index) in education.xl" :key="index">
<view class="po-re mb-15">
<BasicForm
v-model="item.value"
:schema="getSchemaForIndex(index)"
:index="index"
:key="`form-${index}-${forceUpdateKey}`"
:formsProps="{ labelWidth: 100 }"
/>
<view
@click="deleteMemberFamily(index, item.value)"
class="delete-icon"
>
<BasicIcon type="clear" size="30" />
</view>
</view>
</template>
</view>
<view
class="flex-row items-center justify-center pb-10 pt-5"
style="border: 1px solid #e8e8e8"
@click="addEducation"
>
<uni-icons type="plus" size="16" color="#447ADE"></uni-icons>
<view class="ml-5 cor-447ADE">新增</view>
</view>
</view>
<template #bottom>
<view class="flex-row items-center pb-10 pt-5">
<u-button text="提交" class="mx-15" type="primary" @click="submit" />
</view>
</template>
</BasicLayout>
</template>
<script lang="ts" setup>
import { fractionRuleApi1 } from "@/api/base/server";
import { showToast } from "@/utils/uniapp";
import { cloneDeep, map } from "lodash";
//
const education = reactive<any>({
xl: [{ value: {} }],
});
//
const honorCategories = ref<any[]>([]);
//
const awardLevelsMap = reactive<Record<number, any>>({});
//
const forceUpdateKey = ref(0);
//
const baseSchema = [
{
field: "rymc",
label: "荣誉名称",
component: "BasicInput",
componentProps: {},
},
{
field: "hilb_id",
label: "荣誉类别",
component: "BasicPicker",
componentProps: {
api: fractionRuleApi1,
rangeKey: "inspectStandard",
savaKey: "id",
ok: (selectedIndex: number, form: any, list: any, attrs: any) => {
const selectedCategory = list[selectedIndex];
const formIndex = attrs.index;
//
updateAwardLevels(formIndex, selectedCategory);
//
if (education.xl[formIndex].value.xm) {
education.xl[formIndex].value.xm = "";
}
//
forceUpdateKey.value++;
},
},
},
{
field: "xm",
label: "获奖级别",
component: "BasicPicker",
componentProps: {
range: [],
rangeKey: "name",
savaKey: "id",
open: (value: any, attrs: any, model: any) => {
const formIndex = attrs.index;
//
if (!model?.hilb_id) {
showToast({
title: "请先选择荣誉类别",
icon: "none",
});
return false;
}
//
const awardLevels = awardLevelsMap[formIndex] || [];
if (awardLevels.length === 0) {
showToast({
title: "该荣誉类别暂无获奖级别数据",
icon: "none",
});
return false;
}
return true;
},
},
},
{
field: "bjdw",
label: "颁奖单位",
component: "BasicInput",
componentProps: {},
},
{
field: "hjtime",
label: "获奖时间",
component: "BasicDateTimes",
componentProps: {},
},
{
field: "jf",
label: "积分",
component: "BasicInput",
componentProps: {},
},
{
field: "hjfjId",
label: "上传证书",
component: "BasicUpload",
required: true,
itemProps: {
labelPosition: "top",
},
componentProps: {},
},
];
// schema
const getSchemaForIndex = (index: number) => {
const schema = cloneDeep(baseSchema);
//
const hjjbField = schema.find((item) => item.field === "xm");
if (hjjbField) {
hjjbField.componentProps.range = awardLevelsMap[index] || [];
}
return schema;
};
//
const updateAwardLevels = (formIndex: number, category: any) => {
if (category?.ruleItemList && category.ruleItemList.length > 0) {
awardLevelsMap[formIndex] = category.ruleItemList;
} else {
awardLevelsMap[formIndex] = [];
}
};
//
const initHonorCategories = async () => {
try {
const result = await fractionRuleApi1();
honorCategories.value = result.result || result || [];
} catch (error) {
console.error("获取荣誉类别数据失败:", error);
}
};
//
const initEchoData = async () => {
await initHonorCategories();
//
education.xl.forEach((formItem: any, index: number) => {
if (formItem.value?.hilb_id) {
const category = honorCategories.value.find(
(cat: any) => cat.id === formItem.value.hilb_id
);
if (category) {
//
updateAwardLevels(index, category);
} else {
console.log(`未找到荣誉类别 ID: ${formItem.value.hilb_id}`);
}
}
});
//
forceUpdateKey.value++;
};
//
function addEducation() {
const newIndex = education.xl.length;
education.xl.push({ value: {} });
//
awardLevelsMap[newIndex] = [];
}
//
function deleteMemberFamily(index: number, item: any) {
//
delete awardLevelsMap[index];
// awardLevelsMap
const newAwardLevelsMap: Record<number, any[]> = {};
education.xl.forEach((_: any, i: number) => {
if (i < index) {
newAwardLevelsMap[i] = awardLevelsMap[i] || [];
} else if (i > index) {
newAwardLevelsMap[i - 1] = awardLevelsMap[i] || [];
}
});
//
education.xl.splice(index, 1);
// awardLevelsMap
Object.keys(awardLevelsMap).forEach(
(key: string) => delete awardLevelsMap[Number(key)]
);
Object.assign(awardLevelsMap, newAwardLevelsMap);
}
//
function submit() {
const gkkRyList = map(education.xl, (item) => {
return { ...item.value, hjlxId: "GKKHJQK" };
});
}
// //
// const { getFile, setFile } = useDataStore();
// //
// if (getFile.gkkRyList && getFile.gkkRyList.length > 0) {
// education.xl = map(getFile.gkkRyList, (item) => {
// return { value: item };
// });
// }
//
onMounted(() => {
// if (getFile.gkkRyList && getFile.gkkRyList.length > 0) {
// //
// nextTick(() => {
// initEchoData();
// });
// } else {
// //
// initHonorCategories();
// }
});
</script>
<style>
.delete-icon {
position: absolute;
right: -13px;
top: -14px;
z-index: 1;
}
</style>

View File

@ -1,426 +0,0 @@
<template>
<view class="detail-container">
<view class="content-wrapper">
<!-- 评分标准 -->
<view class="form-section">
<view class="section-title">评分标准</view>
<view class="standard-text">
1.一二年级考核语文数学两个学科平行班教学质量评价差距均在2分内的按一...
</view>
</view>
<!-- 考核评价 -->
<view class="detail-item">
<text class="detail-label">考核评价</text>
<text class="detail-value">{{ detailData.evaluationType }}</text>
</view>
<!-- 积分类型 -->
<view class="detail-item">
<text class="detail-label">积分类型</text>
<text class="detail-value">{{ detailData.scoreType }}</text>
</view>
<!-- 积分分值 -->
<view class="detail-item">
<text class="detail-label">积分分值</text>
<text class="detail-value">{{ detailData.scoreValue }}</text>
</view>
<!-- 考核处室 -->
<view class="detail-item">
<text class="detail-label">考核处室</text>
<text class="detail-value">{{ detailData.department }}</text>
</view>
<!-- 考核时间 -->
<view class="detail-item">
<text class="detail-label">考核时间</text>
<text class="detail-value">{{ detailData.evaluationDate }}</text>
</view>
<!-- 考核打分 -->
<view class="detail-item">
<text class="detail-label">考核打分</text>
<text class="detail-value">{{ detailData.score }}</text>
</view>
<!-- 证明材料 -->
<view class="detail-item file-section" v-if="detailData.files && detailData.files.length > 0">
<text class="detail-label">证明材料</text>
<view class="file-list">
<view
class="file-item"
v-for="(file, index) in detailData.files"
:key="index"
@click="handlePreviewFile(file)"
>
<uni-icons type="paperplane" size="16" color="#409eff" />
<text class="file-name">{{ file.name }}</text>
</view>
</view>
</view>
<!-- 返回按钮 -->
<view class="bottom-actions">
<button class="mobile-btn" @click="goBack">返回</button>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, onMounted } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { imagUrl } from "@/utils";
import { getByUserIdAndInspectItemIdApi } from "@/api/base/server";
import {
isVideo,
isImage,
canPreview,
previewFile,
previewVideo,
previewImage,
downloadFile
} from "@/utils/filePreview";
interface DetailData {
evaluationType: string;
scoreType: string;
scoreValue: number | string;
department: string;
evaluationDate: string;
score: number | string;
files: Array<{ name: string; url: string; resSuf?: string }>;
}
//
const detailData = ref<DetailData>({
evaluationType: "",
scoreType: "",
scoreValue: 0,
department: "",
evaluationDate: "",
score: 0,
files: [],
});
//
const pageParams = ref({
inspectItemId: '',
userId: ''
});
//
const loadDetailData = async () => {
try {
const { inspectItemId, userId } = pageParams.value;
console.log('页面参数:', pageParams.value);
if (!inspectItemId || !userId) {
console.error('缺少必要参数');
uni.showToast({
title: '参数错误',
icon: 'none'
});
return;
}
// API
const res = await getByUserIdAndInspectItemIdApi({
userId: userId,
inspectItemId: inspectItemId
});
console.log('API返回数据:', res);
if (res && res.resultCode === 1 && res.result) {
const data = res.result;
//
detailData.value = {
evaluationType: data.evaluations?.[0]?.itemName || "考核评价",
scoreType: data.evaluations?.[0]?.scoreType === "1" ? "加分" : "扣分",
scoreValue: data.evaluations?.[0]?.score || 0,
department: data.evaluations?.[0]?.departmentName || "教科处",
evaluationDate: data.evaluations?.[0]?.examineTime || "",
score: data.score || 0,
files: data.evaluations?.[0]?.pic ? [
{
name: "证明材料",
url: data.evaluations[0].pic,
resSuf: data.evaluations[0].pic.split(".").pop()?.toLowerCase() || ""
}
] : []
};
console.log('处理后的详情数据:', detailData.value);
} else {
console.warn('API返回数据为空或格式不正确');
uni.showToast({
title: '未获取到详情数据',
icon: 'none'
});
}
} catch (error) {
console.error('获取详情数据失败:', error);
uni.showToast({
title: '获取数据失败',
icon: 'none'
});
}
};
//
const goBack = () => {
uni.navigateBack();
};
//
const handlePreviewFile = (file: { name: string; url: string; resSuf?: string }) => {
const fileUrl = imagUrl(file.url);
const fileName = file.name;
const fileExt = file.resSuf || file.name.split(".").pop()?.toLowerCase() || "";
console.log('预览文件:', {
name: fileName,
url: fileUrl,
ext: fileExt
});
//
if (isVideo(fileExt)) {
//
previewVideo(fileUrl, fileName)
.then(() => {
console.log('视频预览成功');
})
.catch((error) => {
console.error('视频预览失败:', error);
//
handleDownloadFile(file);
});
} else if (isImage(fileExt)) {
//
previewImage(fileUrl)
.then(() => {
console.log('图片预览成功');
})
.catch((error) => {
console.error('图片预览失败:', error);
handleDownloadFile(file);
});
} else if (canPreview(fileExt)) {
//
previewFile(fileUrl, fileName, fileExt)
.then(() => {
console.log('文件预览成功');
})
.catch((error) => {
console.error('文件预览失败:', error);
handleDownloadFile(file);
});
} else {
//
handleDownloadFile(file);
}
};
//
const handleDownloadFile = (file: { name: string; url: string }) => {
const fileUrl = imagUrl(file.url);
const fileName = file.name;
uni.showModal({
title: "提示",
content: `是否下载文件: ${fileName}?`,
success: (res) => {
if (res.confirm) {
downloadFile(fileUrl, fileName)
.then(() => {
uni.showToast({
title: "下载成功",
icon: "success",
});
})
.catch((error) => {
console.error('下载失败:', error);
uni.showToast({
title: "下载失败",
icon: "none",
});
});
}
},
});
};
onMounted(() => {
// onLoad
});
// onLoad
onLoad((options) => {
console.log('onLoad被调用参数:', options);
// URLinspectItemId
const inspectItemId = options.inspectItemId || '';
// ID
let userId = '';
const userDataStr = uni.getStorageSync('app-user');
if (userDataStr) {
try {
const userData = typeof userDataStr === 'string' ? JSON.parse(userDataStr) : userDataStr;
if (userData && userData.jsData && userData.jsData.id) {
userId = userData.jsData.id;
console.log('从缓存获取到教师ID:', userId);
}
} catch (error) {
console.error('解析用户数据失败:', error);
}
}
//
pageParams.value = {
inspectItemId: inspectItemId,
userId: userId
};
console.log('最终页面参数:', pageParams.value);
//
loadDetailData();
});
</script>
<style scoped lang="scss">
.detail-container {
min-height: 100vh;
background-color: #f5f5f5;
}
.content-wrapper {
padding: 30rpx 30rpx 40rpx;
}
.form-section {
background-color: #ffffff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.standard-text {
font-size: 28rpx;
color: #666;
line-height: 1.6;
}
}
.detail-item {
display: flex;
align-items: center;
background-color: #ffffff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
.detail-label {
font-size: 28rpx;
color: #333;
width: 160rpx;
flex-shrink: 0;
}
.detail-value {
flex: 1;
font-size: 28rpx;
color: #333;
text-align: right;
}
}
.file-section {
flex-direction: column;
align-items: flex-start;
.detail-label {
margin-bottom: 20rpx;
}
.file-list {
width: 100%;
.file-item {
display: flex;
align-items: center;
padding: 15rpx 20rpx;
background-color: #f5f5f5;
border-radius: 8rpx;
margin-bottom: 10rpx;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background-color: #e6f7ff;
}
&:active {
transform: translateY(1px);
background-color: #d4f1ff;
}
uni-icons {
margin-right: 10rpx;
}
.file-name {
font-size: 26rpx;
color: #333;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.bottom-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
background-color: #ffffff;
border-top: 1rpx solid #eee;
.mobile-btn {
width: 100%;
height: 88rpx;
background-color: #409eff;
color: #ffffff;
font-size: 32rpx;
font-weight: bold;
border-radius: 44rpx;
border: none;
display: flex;
align-items: center;
justify-content: center;
&:active {
background-color: #337ecc;
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -3,14 +3,7 @@
<view class="container">
<uni-card :is-shadow="false" is-full>
<view class="header">
<text class="score">我的得分: {{ totalScore }}</text>
<view class="status">
<text>我的状态: </text>
<view class="status-indicator"></view>
<view class="review-count" v-if="reviewingCountComputed > 0">
{{ reviewingCountComputed }}项在审核
</view>
</view>
<text class="score">业绩积分: {{ totalScore }}</text>
</view>
</uni-card>
@ -46,16 +39,10 @@
<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"
text="业绩填报"
class="ml-15 mr-15"
type="primary"
@click="scgrry"
/>
<u-button
text="公开课获奖申请"
class="mr-15 mr-7"
type="primary"
@click="scgkkhj"
@click="handleIntegralApply"
/>
</view>
</view>
@ -65,47 +52,35 @@
<script lang="ts" setup>
import { ref, computed, onMounted } from "vue";
import { jsFindPageJfApi, inspectItemFindAllApi } from "@/api/base/server";
import { jfSummaryByJfTypeApi } from "@/api/base/server";
interface EvaluationItem {
id: string;
category: string;
value: number;
isUnderReview: boolean;
}
//
const evaluationItems = ref([]);
const evaluationItems = ref<EvaluationItem[]>([]);
const totalScore = ref(0);
const loading = ref(false);
//
const reviewingCountComputed = computed(() => {
return evaluationItems.value.filter(item => item.isUnderReview).length;
});
//
const loadInspectItems = async () => {
try {
const res = await inspectItemFindAllApi({ type: 2 });
if (res && res.resultCode === 1 && res.result) {
return res.result;
}
return [];
} catch (error) {
console.error('获取检查项失败:', error);
return [];
}
};
//
const loadTeacherScore = async () => {
loading.value = true;
try {
// ID
const userDataStr = uni.getStorageSync('app-user');
let teacherId = '';
let jsId = '';
let userData = null;
if (userDataStr) {
try {
userData = typeof userDataStr === 'string' ? JSON.parse(userDataStr) : userDataStr;
if (userData && userData.jsData && userData.jsData.id) {
teacherId = userData.jsData.id;
console.log('当前教师ID:', teacherId);
jsId = userData.jsData.id;
console.log('当前教师ID:', jsId);
console.log('教师信息:', userData.jsData);
} else {
console.error('未找到教师ID信息');
@ -136,89 +111,29 @@ const loadTeacherScore = async () => {
const params = {
startTime: new Date().getFullYear() + '-01-01', //
endTime: new Date().getFullYear() + '-12-31', //
pageSize: 100, //
pageNum: 1,
id: teacherId // 使ID
jsId: jsId // 使ID
};
const res = await jsFindPageJfApi(params);
const res: any = await jfSummaryByJfTypeApi(params);
console.log('查询参数:', params);
console.log('当前教师ID:', teacherId);
console.log('API返回结果:', res);
console.log('当前教师ID:', jsId);
console.log('汇总API返回结果:', res);
if (res && res.rows && res.rows.length > 0) {
//
const teacherData = res.rows.find(row => row.id === teacherId);
if (!teacherData) {
console.warn('未找到当前教师的积分数据');
uni.showToast({
title: '未找到积分数据',
icon: 'none'
});
return;
}
console.log('找到的教师积分数据:', teacherData);
console.log('教师姓名:', teacherData.jsxm);
console.log('总分:', teacherData.totalScore);
console.log('积分汇总数据:', teacherData.summaries);
//
const inspectItems = await loadInspectItems();
if (inspectItems.length === 0) {
console.warn('未获取到检查项数据');
return;
}
console.log('检查项数据:', inspectItems);
// getScore
const items = [];
let total = 0;
console.log('检查项数据:', inspectItems);
console.log('教师积分汇总:', teacherData.summaries);
inspectItems.forEach((item, index) => {
// getScore
let score = 0;
let num = 0;
if (teacherData.summaries && teacherData.summaries.length > 0) {
for (let i = 0; i < teacherData.summaries.length; i++) {
const summaryId = teacherData.summaries[i].inspectItemId;
const itemId = item.id;
// 使
if (summaryId == itemId || summaryId?.toUpperCase() == itemId?.toUpperCase()) {
score = parseFloat(teacherData.summaries[i].score) || 0;
num = parseInt(teacherData.summaries[i].num) || 0;
console.log(`✅ 匹配成功: ${item.name}, score=${score}, num=${num}`);
break;
}
}
}
items.push({
id: item.id,
category: item.name,
value: score,
isUnderReview: num > 0, //
num: num
});
total += score;
});
const list = res?.result || res?.data || res || [];
if (Array.isArray(list) && list.length > 0) {
const items: EvaluationItem[] = list.map((it: any) => ({
id: it.jfTypeId,
category: it.jfTypeName || '未命名',
value: Number(it.totalScore || 0),
isUnderReview: false //
}));
evaluationItems.value = items;
totalScore.value = total;
totalScore.value = items.reduce((sum, cur) => sum + (cur.value || 0), 0);
console.log('处理后的积分数据:', {
items: items,
totalScore: total,
reviewingCount: reviewingCountComputed.value
items,
totalScore: totalScore.value
});
} else {
console.warn('查询结果为空或格式不正确');
@ -238,32 +153,40 @@ const loadTeacherScore = async () => {
}
};
function handleItemClick(item: any) {
// 0
if (!item.value || item.value === 0 || item.value === '0' || item.value === 0.0 || item.value === null || item.value === undefined) {
uni.showToast({
title: '积分为0',
icon: 'none',
duration: 2000
});
return;
function handleItemClick(item: EvaluationItem) {
// 0
if (item.value && item.value > 0) {
// ID
const userDataStr = uni.getStorageSync('app-user');
let jsId = '';
if (userDataStr) {
try {
const userData = typeof userDataStr === 'string' ? JSON.parse(userDataStr) : userDataStr;
if (userData && userData.jsData && userData.jsData.id) {
jsId = userData.jsData.id;
}
} catch (error) {
console.error('解析用户数据失败:', error);
}
}
const url = `/pages/view/routine/JiFenPingJia/detail?inspectItemId=${item.id}`;
const url = `/pages/view/routine/JiFenPingJia/jfself/MyScoreDetail?jfTypeId=${item.id}&jsId=${jsId}`;
uni.navigateTo({
url: url,
});
}
function scgrry() {
} else {
// 0
const url = `/pages/view/routine/JiFenPingJia/jfself/apply?jfTypeId=${item.id}&score=${item.value || 0}`;
uni.navigateTo({
url: `/pages/view/routine/JiFenPingJia/PersonalHonor`,
url: url,
});
}
}
function scgkkhj() {
function handleIntegralApply() {
//
uni.navigateTo({
url: `/pages/view/routine/JiFenPingJia/PublicClassAwards`,
url: `/pages/view/routine/JiFenPingJia/jfself/IntegralApply`,
});
}
@ -294,31 +217,6 @@ onMounted(() => {
font-weight: bold;
}
.status {
display: flex;
align-items: center;
color: #666;
}
.status-indicator {
width: 12px;
height: 12px;
background-color: #4caf50; // Green color from the image
margin-left: 8px;
border-radius: 2px; // Slightly rounded corners
}
.review-count {
background-color: #fff2e8;
color: #ff6b35;
font-size: 12px;
padding: 2px 8px;
border-radius: 12px;
margin-left: 12px;
border: 1px solid #ff6b35;
font-weight: bold;
}
.loading-container {
display: flex;
justify-content: center;

View File

@ -0,0 +1,764 @@
<template>
<BasicLayout>
<view class="container">
<view v-if="loading" class="loading-container">
<text class="loading-text">加载中...</text>
</view>
<view v-else-if="!scoreList.length" class="empty-container">
<text class="empty-text">暂无积分记录</text>
</view>
<view v-else class="score-list">
<view
v-for="(item, index) in scoreList"
:key="index"
class="score-item"
>
<uni-card :is-shadow="false" is-full>
<view class="item-header">
<text class="item-title">{{ item.jfTypeName || '未命名' }}</text>
<text class="item-score">+{{ item.score || 0 }}</text>
</view>
<view class="item-content">
<!-- 积分标准区域 -->
<view class="section">
<view class="section-title">
<view class="title-line"></view>
<text class="title-text">积分标准</text>
</view>
<view class="field-row-vertical" v-if="item.ruleStandard">
<text class="field-label">检查标准</text>
<text class="field-value multiline-text">{{ item.ruleStandard }}</text>
</view>
<view class="field-row-vertical" v-if="item.scoreConfig">
<text class="field-label">业绩积分</text>
<view class="score-config-wrapper">
<view
v-for="(category, catIdx) in formatScoreConfig(item.scoreConfig)"
:key="catIdx"
class="score-config-category"
>
<view v-if="category.category" class="category-title">{{ category.category }}</view>
<!-- 单个分值显示表格形式包含"级别""分值"两列 -->
<view v-if="!category.hasMultipleGrades" class="score-config-table">
<view class="table-header">
<view class="table-cell header-cell">级别</view>
<view class="table-cell header-cell">分值</view>
</view>
<view v-for="(row, idx) in category.rows" :key="idx" class="table-row">
<view class="table-cell">{{ row.level }}</view>
<view class="table-cell score-cell">{{ row.scores }}</view>
</view>
</view>
<!-- 多个分值显示表格形式"一等奖、二等奖、三等奖"作为列 -->
<view v-else class="score-config-table">
<view class="table-header">
<view class="table-cell header-cell">级别</view>
<view class="table-cell header-cell">一等奖</view>
<view class="table-cell header-cell">二等奖</view>
<view class="table-cell header-cell">三等奖</view>
</view>
<view v-for="(row, idx) in category.rows" :key="idx" class="table-row">
<view class="table-cell">{{ row.level }}</view>
<view class="table-cell score-cell">{{ Array.isArray(row.scores) ? (row.scores[0] || '') : '' }}</view>
<view class="table-cell score-cell">{{ Array.isArray(row.scores) ? (row.scores[1] || '') : '' }}</view>
<view class="table-cell score-cell">{{ Array.isArray(row.scores) ? (row.scores[2] || '') : '' }}</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 积分考核区域 -->
<view class="section">
<view class="section-title">
<view class="title-line"></view>
<text class="title-text">获奖情况</text>
</view>
<view class="field-row" v-if="item.jsxm">
<text class="field-label">考核教师</text>
<text class="field-value">{{ item.jsxm }}</text>
</view>
<view class="field-row" v-if="item.rymc">
<text class="field-label">荣誉名称</text>
<text class="field-value">{{ item.rymc }}</text>
</view>
<view class="field-row" v-if="item.hjlx">
<text class="field-label">获奖类型</text>
<text class="field-value">{{ item.hjlx }}</text>
</view>
<view class="field-row" v-if="item.bjdw">
<text class="field-label">颁奖单位</text>
<text class="field-value">{{ item.bjdw }}</text>
</view>
<view class="field-row" v-if="item.hjtime">
<text class="field-label">获奖时间</text>
<text class="field-value">{{ item.hjtime }}</text>
</view>
<view class="field-row" v-if="item.category">
<text class="field-label">类别</text>
<text class="field-value">{{ item.category }}</text>
</view>
<view class="field-row" v-if="item.level">
<text class="field-label">级别</text>
<text class="field-value">{{ item.level }}</text>
</view>
<view class="field-row" v-if="item.grade">
<text class="field-label">等级</text>
<text class="field-value">{{ item.grade }}</text>
</view>
<view class="field-row" v-if="item.score">
<text class="field-label">分值</text>
<text class="field-value score-value">{{ item.score }}</text>
</view>
<view class="field-row" v-if="item.khTime">
<text class="field-label">评价时间</text>
<text class="field-value">{{ item.khTime }}</text>
</view>
<view class="field-row" v-if="item.khjsxm">
<text class="field-label">评价人</text>
<text class="field-value">{{ item.khjsxm }}</text>
</view>
<view class="field-row" v-if="item.khRemark">
<text class="field-label">备注</text>
<text class="field-value">{{ item.khRemark }}</text>
</view>
<view class="field-row" v-if="item.jfStatus">
<text class="field-label">状态</text>
<text class="field-value">{{ getStatusText(item.jfStatus) }}</text>
</view>
<view class="field-row" v-if="item.fileName">
<text class="field-label">附件</text>
<view class="file-wrapper">
<text class="file-name" @click="previewFile(item)">{{ item.fileName }}</text>
</view>
</view>
</view>
</view>
</uni-card>
</view>
</view>
</view>
<!-- 固定在底部的返回按钮 -->
<view v-if="scoreList.length > 0" class="fixed-bottom">
<button class="back-btn" @click="goBack">
返回
</button>
</view>
</BasicLayout>
</template>
<script lang="ts" setup>
import { ref, onMounted } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { jfFindPageApi, jfTypeStructureApi } from "@/api/base/server";
interface ScoreItem {
id?: string;
jfTypeId?: string;
jfTypeName?: string;
ruleStandard?: string;
scoreConfig?: any;
jsxm?: string;
rymc?: string;
hjlx?: string;
bjdw?: string;
hjtime?: string;
category?: string;
level?: string;
grade?: string;
score?: number;
khTime?: string;
khjsxm?: string;
khRemark?: string;
jfStatus?: string;
fileName?: string;
fileFormat?: string;
fileUrl?: string;
}
const loading = ref(false);
const scoreList = ref<ScoreItem[]>([]);
//
const defaultLevels = [
{ levelCode: 'national', levelName: '国', key: 'levelNational' },
{ levelCode: 'province', levelName: '省', key: 'levelProvince' },
{ levelCode: 'city', levelName: '市', key: 'levelCity' },
{ levelCode: 'district', levelName: '区', key: 'levelDistrict' },
{ levelCode: 'school_district', levelName: '学区/街道', key: 'levelSchoolDistrict' },
{ levelCode: 'school', levelName: '校', key: 'levelSchool' },
];
interface ScoreConfigRow {
level: string;
scores: string | number[]; //
hasMultipleGrades: boolean; //
}
interface ScoreConfigCategory {
category?: string;
rows: ScoreConfigRow[];
hasMultipleGrades: boolean; //
}
//
// "30"
// [28, 26, 24]
function formatScores(scores: number[]): { display: string | number[], hasMultiple: boolean } {
if (!scores || scores.length === 0) {
return { display: '', hasMultiple: false };
}
const validScores = scores.filter(s => s > 0);
if (validScores.length === 0) {
return { display: '', hasMultiple: false };
}
//
if (validScores.length === 1) {
return { display: String(validScores[0]), hasMultiple: false };
}
//
return { display: validScores, hasMultiple: true };
}
const formatScoreConfig = (config: any): ScoreConfigCategory[] => {
if (!config) return [];
try {
let configObj = config;
if (typeof config === "string") {
try {
configObj = JSON.parse(config);
} catch {
return [];
}
}
const result: ScoreConfigCategory[] = [];
if (configObj.categories && Array.isArray(configObj.categories)) {
configObj.categories.forEach((cat: any) => {
const categoryName = cat.category || '';
const rows: ScoreConfigRow[] = [];
let categoryHasMultiple = false;
defaultLevels.forEach(level => {
const scores = cat[level.key] as number[];
if (scores && scores.length > 0) {
const formatted = formatScores(scores);
if (formatted.display) {
if (formatted.hasMultiple) {
categoryHasMultiple = true;
}
rows.push({
level: level.levelName,
scores: formatted.display,
hasMultipleGrades: formatted.hasMultiple
});
}
}
});
if (rows.length > 0) {
result.push({
category: categoryName,
rows: rows,
hasMultipleGrades: categoryHasMultiple
});
}
});
}
else if (configObj.category && configObj.levels) {
const rows: ScoreConfigRow[] = [];
let categoryHasMultiple = false;
configObj.levels.forEach((level: any) => {
if (level.scores && level.scores.length > 0) {
const formatted = formatScores(level.scores);
if (formatted.display) {
if (formatted.hasMultiple) {
categoryHasMultiple = true;
}
const levelName = level.levelName || level.levelCode || '';
rows.push({
level: levelName,
scores: formatted.display,
hasMultipleGrades: formatted.hasMultiple
});
}
}
});
if (rows.length > 0) {
result.push({
category: configObj.category,
rows: rows,
hasMultipleGrades: categoryHasMultiple
});
}
}
return result;
} catch {
return [];
}
};
//
function getStatusText(status: string): string {
const statusMap: Record<string, string> = {
'D': '已完结',
'A': '待审核',
'B': '审核中',
'C': '已驳回'
};
return statusMap[status] || status;
}
//
function previewFile(item: ScoreItem) {
if (item.fileUrl) {
uni.previewImage({
urls: [item.fileUrl],
fail: (err) => {
console.error('预览文件失败:', err);
uni.showToast({
title: '预览文件失败',
icon: 'none'
});
}
});
}
}
const goBack = () => {
uni.navigateBack();
};
//
const loadScoreList = async (jfTypeId: string, jsId: string) => {
loading.value = true;
try {
// ID
const userDataStr = uni.getStorageSync('app-user');
if (!userDataStr) {
uni.showToast({
title: '未找到用户数据',
icon: 'none'
});
return;
}
let userData = null;
try {
userData = typeof userDataStr === 'string' ? JSON.parse(userDataStr) : userDataStr;
if (!userData || !userData.jsData || !userData.jsData.id) {
uni.showToast({
title: '未找到教师信息',
icon: 'none'
});
return;
}
} catch (error) {
console.error('解析用户数据失败:', error);
uni.showToast({
title: '用户数据解析失败',
icon: 'none'
});
return;
}
// 1. ID
const structureRes: any = await jfTypeStructureApi({
topTypeId: jfTypeId
});
const structureList = structureRes?.result || structureRes?.data || structureRes || [];
// ID
const allJfTypeIds: string[] = [jfTypeId]; //
structureList.forEach((item: any) => {
if (item.jfTypeId && !allJfTypeIds.includes(item.jfTypeId)) {
allJfTypeIds.push(item.jfTypeId);
}
});
console.log('顶级节点ID:', jfTypeId);
console.log('所有子节点ID:', allJfTypeIds);
// 2. 使 ID
const params = {
jfTypeIds: allJfTypeIds.join(','), // jfTypeId
jsId: jsId || userData.jsData.id,
page: 1,
rows: 1000 //
};
const res: any = await jfFindPageApi(params);
console.log('查询参数:', params);
console.log('积分记录API返回结果:', res);
const list = res?.result?.rows || res?.data?.rows || res?.rows || [];
if (Array.isArray(list) && list.length > 0) {
scoreList.value = list.map((it: any) => ({
id: it.id,
jfTypeId: it.jfTypeId,
jfTypeName: it.jfTypeName,
ruleStandard: it.ruleStandard,
scoreConfig: it.scoreConfig,
jsxm: it.jsxm,
rymc: it.rymc,
hjlx: it.hjlx,
bjdw: it.bjdw,
hjtime: it.hjtime,
category: it.category,
level: it.level,
grade: it.grade,
score: it.score,
khTime: it.khTime,
khjsxm: it.khjsxm,
khRemark: it.khRemark,
jfStatus: it.jfStatus,
fileName: it.fileName,
fileFormat: it.fileFormat,
fileUrl: it.fileUrl
}));
console.log('处理后的积分记录数据:', scoreList.value);
} else {
console.warn('查询结果为空');
scoreList.value = [];
}
} catch (error) {
console.error('获取积分记录失败:', error);
uni.showToast({
title: '获取积分记录失败',
icon: 'none'
});
} finally {
loading.value = false;
}
};
onLoad((options) => {
const jfTypeId = options?.jfTypeId || "";
const jsId = options?.jsId || "";
if (!jfTypeId) {
uni.showToast({
title: "缺少积分类型ID",
icon: "none"
});
return;
}
loadScoreList(jfTypeId, jsId);
});
</script>
<style scoped lang="scss">
.container {
min-height: 100vh;
background-color: #f5f5f5;
padding: 20rpx 20rpx 160rpx 20rpx; //
}
.loading-container,
.empty-container {
display: flex;
justify-content: center;
align-items: center;
padding: 80rpx 0;
}
.loading-text,
.empty-text {
color: #999;
font-size: 28rpx;
}
.score-list {
.score-item {
margin-bottom: 24rpx;
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 16rpx;
border-bottom: 1rpx solid #eee;
margin-bottom: 16rpx;
.item-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
flex: 1;
}
.item-score {
font-size: 36rpx;
font-weight: bold;
color: #f56c6c;
margin-left: 16rpx;
}
}
.item-content {
.section {
margin-top: 24rpx;
&:first-child {
margin-top: 0;
}
.section-title {
display: flex;
align-items: center;
margin-bottom: 16rpx;
padding: 0 12rpx;
.title-line {
width: 8rpx;
height: 32rpx;
background: #1890ff;
border-radius: 4rpx;
margin-right: 16rpx;
}
.title-text {
font-size: 32rpx;
font-weight: 600;
color: #262626;
}
}
.field-row {
display: flex;
margin-bottom: 16rpx;
line-height: 1.8;
align-items: flex-start;
&:last-child {
margin-bottom: 0;
}
.field-label {
font-size: 28rpx;
color: #666;
min-width: 160rpx;
flex-shrink: 0;
padding-top: 4rpx;
}
.field-value {
font-size: 28rpx;
color: #333;
flex: 1;
word-break: break-all;
&.score-value {
color: #f56c6c;
font-weight: 600;
}
&.multiline-text {
white-space: pre-wrap;
word-break: break-word;
line-height: 1.6;
}
}
.file-wrapper {
flex: 1;
.file-name {
font-size: 28rpx;
color: #1890ff;
text-decoration: underline;
}
}
}
.field-row-vertical {
display: flex;
flex-direction: column;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
.field-label {
font-size: 28rpx;
color: #666;
margin-bottom: 8rpx;
font-weight: 600;
}
.field-value {
font-size: 28rpx;
color: #333;
word-break: break-all;
&.multiline-text {
white-space: pre-wrap;
word-break: break-word;
line-height: 1.6;
}
}
.score-config-wrapper {
width: 100%;
}
}
.score-config-wrapper {
flex: 1;
.score-config-category {
margin-top: 16rpx;
&:first-child {
margin-top: 0;
}
.category-title {
font-size: 28rpx;
font-weight: 600;
color: #409eff;
margin-bottom: 12rpx;
padding: 8rpx 12rpx;
background-color: #ecf5ff;
border-radius: 6rpx;
border-left: 4rpx solid #409eff;
}
//
.score-config-table {
border: 1rpx solid #dcdfe6;
border-radius: 8rpx;
overflow: hidden;
.table-header {
display: flex;
background-color: #f5f7fa;
border-bottom: 1rpx solid #dcdfe6;
}
.table-row {
display: flex;
border-bottom: 1rpx solid #ebeef5;
&:last-child {
border-bottom: none;
}
}
.table-cell {
padding: 16rpx 12rpx;
font-size: 26rpx;
text-align: center;
border-right: 1rpx solid #ebeef5;
&:first-child {
width: 120rpx;
border-right: 1rpx solid #ebeef5;
}
//
&:nth-child(2):last-child {
flex: 1;
border-right: none;
}
//
&:nth-child(2):not(:last-child),
&:nth-child(3),
&:nth-child(4) {
flex: 1;
min-width: 100rpx;
}
&:last-child {
border-right: none;
}
&.header-cell {
font-weight: bold;
color: #303133;
background-color: #f5f7fa;
}
&.score-cell {
color: #409eff;
font-weight: 500;
}
}
}
}
}
}
}
}
}
// Remove default card padding
::v-deep .uni-card .uni-card__content {
padding: 20rpx !important;
}
//
.fixed-bottom {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #ffffff;
padding: 24rpx;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
z-index: 100;
}
.back-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background: #3b82f6;
color: #ffffff;
border: none;
border-radius: 16rpx;
font-size: 32rpx;
font-weight: 600;
box-shadow: 0 4rpx 16rpx rgba(59, 130, 246, 0.3);
transition: all 0.3s;
&:hover {
background: #2563eb;
box-shadow: 0 6rpx 20rpx rgba(59, 130, 246, 0.4);
}
&:active {
transform: scale(0.98);
background: #1d4ed8;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,652 @@
<template>
<BasicLayout>
<view class="apply-container">
<view class="content-wrapper">
<view v-if="loading" class="loading-text">加载中...</view>
<view v-else>
<view v-if="!treeData.length" class="loading-text">暂无数据</view>
<view v-else class="tree-wrapper">
<view v-for="node in treeData" :key="node.jfTypeId" class="tree-node level-1">
<view class="node-header">
<view class="node-title-wrapper">
<text class="node-title">{{ node.jfTypeName }}</text>
<text class="node-badge level-1-badge">一级</text>
</view>
<text class="node-score" v-if="!isZero && node.totalScore">分值{{ node.totalScore }}</text>
</view>
<view class="node-content">
<view class="node-field" v-if="node.ruleStandard">
<view class="field-label-title">检查标准</view>
<text class="field-value">{{ node.ruleStandard }}</text>
</view>
<view class="node-field" v-if="node.scoreConfig">
<view class="field-label-title">业绩积分</view>
<view v-for="(category, catIdx) in formatScoreConfig(node.scoreConfig)" :key="catIdx" class="score-config-category">
<view v-if="category.category" class="category-title">{{ category.category }}</view>
<!-- 单个分值显示表格形式包含"级别""分值"两列 -->
<view v-if="!category.hasMultipleGrades" class="score-config-table">
<view class="table-header">
<view class="table-cell header-cell">级别</view>
<view class="table-cell header-cell">分值</view>
</view>
<view v-for="(row, idx) in category.rows" :key="idx" class="table-row">
<view class="table-cell">{{ row.level }}</view>
<view class="table-cell score-cell">{{ row.scores }}</view>
</view>
</view>
<!-- 多个分值显示表格形式"一等奖、二等奖、三等奖"作为列 -->
<view v-else class="score-config-table">
<view class="table-header">
<view class="table-cell header-cell">级别</view>
<view class="table-cell header-cell">一等奖</view>
<view class="table-cell header-cell">二等奖</view>
<view class="table-cell header-cell">三等奖</view>
</view>
<view v-for="(row, idx) in category.rows" :key="idx" class="table-row">
<view class="table-cell">{{ row.level }}</view>
<view class="table-cell score-cell">{{ Array.isArray(row.scores) ? (row.scores[0] || '') : '' }}</view>
<view class="table-cell score-cell">{{ Array.isArray(row.scores) ? (row.scores[1] || '') : '' }}</view>
<view class="table-cell score-cell">{{ Array.isArray(row.scores) ? (row.scores[2] || '') : '' }}</view>
</view>
</view>
</view>
</view>
</view>
<view v-if="node.children && node.children.length" class="node-children">
<view v-for="child in node.children" :key="child.jfTypeId" class="tree-node level-2">
<view class="node-header">
<view class="node-title-wrapper">
<text class="node-title">{{ child.jfTypeName }}</text>
<text class="node-badge level-2-badge">二级</text>
</view>
<text class="node-score" v-if="!isZero && child.totalScore">分值{{ child.totalScore }}</text>
</view>
<view class="node-content">
<view class="node-field" v-if="child.ruleStandard">
<view class="field-label-title">检查标准</view>
<text class="field-value">{{ child.ruleStandard }}</text>
</view>
<view class="node-field" v-if="child.scoreConfig">
<view class="field-label-title">业绩积分</view>
<view v-for="(category, catIdx) in formatScoreConfig(child.scoreConfig)" :key="catIdx" class="score-config-category">
<view v-if="category.category" class="category-title">{{ category.category }}</view>
<!-- 单个分值显示表格形式包含"级别""分值"两列 -->
<view v-if="!category.hasMultipleGrades" class="score-config-table">
<view class="table-header">
<view class="table-cell header-cell">级别</view>
<view class="table-cell header-cell">分值</view>
</view>
<view v-for="(row, idx) in category.rows" :key="idx" class="table-row">
<view class="table-cell">{{ row.level }}</view>
<view class="table-cell score-cell">{{ row.scores }}</view>
</view>
</view>
<!-- 多个分值显示表格形式"一等奖、二等奖、三等奖"作为列 -->
<view v-else class="score-config-table">
<view class="table-header">
<view class="table-cell header-cell">级别</view>
<view class="table-cell header-cell">一等奖</view>
<view class="table-cell header-cell">二等奖</view>
<view class="table-cell header-cell">三等奖</view>
</view>
<view v-for="(row, idx) in category.rows" :key="idx" class="table-row">
<view class="table-cell">{{ row.level }}</view>
<view class="table-cell score-cell">{{ Array.isArray(row.scores) ? (row.scores[0] || '') : '' }}</view>
<view class="table-cell score-cell">{{ Array.isArray(row.scores) ? (row.scores[1] || '') : '' }}</view>
<view class="table-cell score-cell">{{ Array.isArray(row.scores) ? (row.scores[2] || '') : '' }}</view>
</view>
</view>
</view>
</view>
</view>
<view v-if="child.children && child.children.length" class="node-children">
<view v-for="g in child.children" :key="g.jfTypeId" class="tree-node level-3">
<view class="node-header">
<view class="node-title-wrapper">
<text class="node-title">{{ g.jfTypeName }}</text>
<text class="node-badge level-3-badge">三级</text>
</view>
<text class="node-score" v-if="!isZero && g.totalScore">分值{{ g.totalScore }}</text>
</view>
<view class="node-content">
<view class="node-field" v-if="g.ruleStandard">
<view class="field-label-title">检查标准</view>
<text class="field-value">{{ g.ruleStandard }}</text>
</view>
<view class="node-field" v-if="g.scoreConfig">
<view class="field-label-title">业绩积分</view>
<view v-for="(category, catIdx) in formatScoreConfig(g.scoreConfig)" :key="catIdx" class="score-config-category">
<view v-if="category.category" class="category-title">{{ category.category }}</view>
<!-- 单个分值显示表格形式包含"级别""分值"两列 -->
<view v-if="!category.hasMultipleGrades" class="score-config-table">
<view class="table-header">
<view class="table-cell header-cell">级别</view>
<view class="table-cell header-cell">分值</view>
</view>
<view v-for="(row, idx) in category.rows" :key="idx" class="table-row">
<view class="table-cell">{{ row.level }}</view>
<view class="table-cell score-cell">{{ row.scores }}</view>
</view>
</view>
<!-- 多个分值显示表格形式"一等奖、二等奖、三等奖"作为列 -->
<view v-else class="score-config-table">
<view class="table-header">
<view class="table-cell header-cell">级别</view>
<view class="table-cell header-cell">一等奖</view>
<view class="table-cell header-cell">二等奖</view>
<view class="table-cell header-cell">三等奖</view>
</view>
<view v-for="(row, idx) in category.rows" :key="idx" class="table-row">
<view class="table-cell">{{ row.level }}</view>
<view class="table-cell score-cell">{{ Array.isArray(row.scores) ? (row.scores[0] || '') : '' }}</view>
<view class="table-cell score-cell">{{ Array.isArray(row.scores) ? (row.scores[1] || '') : '' }}</view>
<view class="table-cell score-cell">{{ Array.isArray(row.scores) ? (row.scores[2] || '') : '' }}</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 固定在底部的返回按钮 -->
<view v-if="treeData.length > 0" class="fixed-bottom">
<button class="back-btn" @click="goBack">
返回
</button>
</view>
</BasicLayout>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { jfTypeStructureApi, jfScoreStructureApi } from "@/api/base/server";
interface TreeItem {
jfTypeId: string;
pid?: string;
jfTypeName: string;
ruleStandard?: string;
scoreConfig?: any;
totalScore?: number;
children?: TreeItem[];
}
const loading = ref(false);
const isZero = ref(false);
const treeData = ref<TreeItem[]>([]);
//
const defaultLevels = [
{ levelCode: 'national', levelName: '国', key: 'levelNational' },
{ levelCode: 'province', levelName: '省', key: 'levelProvince' },
{ levelCode: 'city', levelName: '市', key: 'levelCity' },
{ levelCode: 'district', levelName: '区', key: 'levelDistrict' },
{ levelCode: 'school_district', levelName: '学区/街道', key: 'levelSchoolDistrict' },
{ levelCode: 'school', levelName: '校', key: 'levelSchool' },
];
interface ScoreConfigRow {
level: string;
scores: string | number[]; //
hasMultipleGrades: boolean; //
}
interface ScoreConfigCategory {
category?: string;
rows: ScoreConfigRow[];
hasMultipleGrades: boolean; //
}
//
// "30"
// [28, 26, 24]
function formatScores(scores: number[]): { display: string | number[], hasMultiple: boolean } {
if (!scores || scores.length === 0) {
return { display: '', hasMultiple: false };
}
const validScores = scores.filter(s => s > 0);
if (validScores.length === 0) {
return { display: '', hasMultiple: false };
}
//
if (validScores.length === 1) {
return { display: String(validScores[0]), hasMultiple: false };
}
//
return { display: validScores, hasMultiple: true };
}
const formatScoreConfig = (config: any): ScoreConfigCategory[] => {
if (!config) return [];
try {
//
let configObj = config;
if (typeof config === "string") {
try {
configObj = JSON.parse(config);
} catch {
return [];
}
}
const result: ScoreConfigCategory[] = [];
// categories
if (configObj.categories && Array.isArray(configObj.categories)) {
configObj.categories.forEach((cat: any) => {
const categoryName = cat.category || '';
const rows: ScoreConfigRow[] = [];
let categoryHasMultiple = false;
defaultLevels.forEach(level => {
const scores = cat[level.key] as number[];
if (scores && scores.length > 0) {
const formatted = formatScores(scores);
if (formatted.display) {
if (formatted.hasMultiple) {
categoryHasMultiple = true;
}
rows.push({
level: level.levelName,
scores: formatted.display,
hasMultipleGrades: formatted.hasMultiple
});
}
}
});
if (rows.length > 0) {
result.push({
category: categoryName,
rows: rows,
hasMultipleGrades: categoryHasMultiple
});
}
});
}
// category + levels
else if (configObj.category && configObj.levels) {
const rows: ScoreConfigRow[] = [];
let categoryHasMultiple = false;
configObj.levels.forEach((level: any) => {
if (level.scores && level.scores.length > 0) {
const formatted = formatScores(level.scores);
if (formatted.display) {
if (formatted.hasMultiple) {
categoryHasMultiple = true;
}
const levelName = level.levelName || level.levelCode || '';
rows.push({
level: levelName,
scores: formatted.display,
hasMultipleGrades: formatted.hasMultiple
});
}
}
});
if (rows.length > 0) {
result.push({
category: configObj.category,
rows: rows,
hasMultipleGrades: categoryHasMultiple
});
}
}
return result;
} catch {
return [];
}
};
const goBack = () => {
uni.navigateBack();
};
function buildTree(list: any[]): TreeItem[] {
const map: Record<string, TreeItem> = {};
const roots: TreeItem[] = [];
//
list.forEach(it => {
map[it.jfTypeId] = {
jfTypeId: it.jfTypeId,
pid: it.pid,
jfTypeName: it.jfTypeName,
ruleStandard: it.ruleStandard,
scoreConfig: it.scoreConfig,
totalScore: it.totalScore
};
});
//
list.forEach(it => {
const node = map[it.jfTypeId];
if (it.pid && map[it.pid]) {
if (!map[it.pid].children) {
map[it.pid].children = [];
}
map[it.pid].children!.push(node);
} else {
roots.push(node);
}
});
return roots;
}
const loadData = async (jfTypeId: string, score: number, jsId: string) => {
loading.value = true;
try {
const params: any = {
topTypeId: jfTypeId,
startTime: new Date().getFullYear() + "-01-01",
endTime: new Date().getFullYear() + "-12-31",
};
let res: any;
if (score && score > 0) {
// 0
params.jsId = jsId;
res = await jfScoreStructureApi(params);
} else {
// 0
res = await jfTypeStructureApi(params);
}
const list = res?.result || res?.data || res || [];
treeData.value = buildTree(Array.isArray(list) ? list : []);
} catch (error) {
console.error("加载数据失败:", error);
uni.showToast({
title: "加载数据失败",
icon: "none"
});
} finally {
loading.value = false;
}
};
onLoad((options) => {
const jfTypeId = options?.jfTypeId || "";
const score = Number(options?.score || 0);
isZero.value = score <= 0;
let jsId = "";
const userDataStr = uni.getStorageSync("app-user");
if (userDataStr) {
try {
const userData = typeof userDataStr === "string" ? JSON.parse(userDataStr) : userDataStr;
if (userData && userData.jsData && userData.jsData.id) {
jsId = userData.jsData.id;
}
} catch (e) {
console.error("解析用户数据失败:", e);
}
}
if (!jfTypeId) {
uni.showToast({ title: "缺少积分类型", icon: "none" });
return;
}
loadData(jfTypeId, score, jsId);
});
</script>
<style scoped lang="scss">
.apply-container {
min-height: 100vh;
background-color: #f5f5f5;
}
.content-wrapper {
padding: 20rpx 20rpx 160rpx 20rpx; //
}
.loading-text {
text-align: center;
color: #999;
font-size: 28rpx;
padding: 80rpx 0;
}
.tree-wrapper {
.tree-node {
margin-bottom: 24rpx;
background-color: #ffffff;
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
&.level-1 {
padding: 24rpx;
}
&.level-2 {
padding: 20rpx;
margin-top: 16rpx;
}
&.level-3 {
padding: 16rpx;
margin-top: 12rpx;
}
.node-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16rpx;
padding-bottom: 12rpx;
border-bottom: 1rpx solid #f0f0f0;
.node-title-wrapper {
flex: 1;
display: flex;
align-items: center;
gap: 12rpx;
}
.node-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
line-height: 1.5;
}
.node-badge {
display: inline-block;
padding: 4rpx 12rpx;
border-radius: 4rpx;
font-size: 20rpx;
font-weight: normal;
&.level-1-badge {
background-color: #ecf5ff;
color: #409eff;
}
&.level-2-badge {
background-color: #f0f9ff;
color: #67c23a;
}
&.level-3-badge {
background-color: #fdf6ec;
color: #e6a23c;
}
}
.node-score {
font-size: 28rpx;
color: #f56c6c;
font-weight: 600;
white-space: nowrap;
margin-left: 16rpx;
}
}
.node-content {
.node-field {
margin-top: 20rpx;
&:first-child {
margin-top: 0;
}
.field-label-title {
font-size: 30rpx;
font-weight: bold;
color: #303133;
margin-bottom: 12rpx;
padding-bottom: 8rpx;
border-bottom: 2rpx solid #e4e7ed;
}
.field-value {
font-size: 28rpx;
color: #606266;
line-height: 1.8;
word-break: break-all;
margin-top: 8rpx;
}
.score-config-category {
margin-top: 16rpx;
&:first-child {
margin-top: 8rpx;
}
.category-title {
font-size: 28rpx;
font-weight: 600;
color: #409eff;
margin-bottom: 12rpx;
padding: 8rpx 12rpx;
background-color: #ecf5ff;
border-radius: 6rpx;
border-left: 4rpx solid #409eff;
}
//
.score-config-table {
border: 1rpx solid #dcdfe6;
border-radius: 8rpx;
overflow: hidden;
.table-header {
display: flex;
background-color: #f5f7fa;
border-bottom: 1rpx solid #dcdfe6;
}
.table-row {
display: flex;
border-bottom: 1rpx solid #ebeef5;
&:last-child {
border-bottom: none;
}
}
.table-cell {
padding: 16rpx 12rpx;
font-size: 26rpx;
text-align: center;
border-right: 1rpx solid #ebeef5;
&:first-child {
width: 120rpx;
border-right: 1rpx solid #ebeef5;
}
//
&:nth-child(2):last-child {
flex: 1;
border-right: none;
}
//
&:nth-child(2):not(:last-child),
&:nth-child(3),
&:nth-child(4) {
flex: 1;
min-width: 100rpx;
}
&:last-child {
border-right: none;
}
&.header-cell {
font-weight: bold;
color: #303133;
background-color: #f5f7fa;
}
&.score-cell {
color: #409eff;
font-weight: 500;
}
}
}
}
}
}
.node-children {
margin-top: 16rpx;
}
}
}
//
.fixed-bottom {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #ffffff;
padding: 24rpx;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
z-index: 100;
}
.back-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background: #3b82f6;
color: #ffffff;
border: none;
border-radius: 16rpx;
font-size: 32rpx;
font-weight: 600;
box-shadow: 0 4rpx 16rpx rgba(59, 130, 246, 0.3);
transition: all 0.3s;
&:hover {
background: #2563eb;
box-shadow: 0 6rpx 20rpx rgba(59, 130, 246, 0.4);
}
&:active {
transform: scale(0.98);
background: #1d4ed8;
}
}
</style>

View File

@ -0,0 +1,475 @@
<template>
<BasicLayout>
<view class="p-15">
<!-- 积分标准 -->
<view class="jf-info-section">
<view class="section-title">积分标准</view>
<view class="info-item">
<text class="label">积分项目</text>
<text class="value title-bold">{{ jfInfo.jfTypeName || '-' }}</text>
</view>
<view class="info-block">
<text class="block-label">检查标准</text>
<text class="block-value multi-text">{{ jfInfo.ruleStandard || '-' }}</text>
</view>
<view class="info-block">
<text class="block-label">业绩积分</text>
<view v-if="formattedScoreConfig.length" class="score-config-wrapper">
<view
v-for="(category, catIdx) in formattedScoreConfig"
:key="catIdx"
class="score-config-category"
>
<view v-if="category.category" class="category-title">{{ category.category }}</view>
<view v-if="!category.hasMultipleGrades" class="score-config-table">
<view class="table-header">
<view class="table-cell header-cell">级别</view>
<view class="table-cell header-cell">分值</view>
</view>
<view v-for="(row, idx) in category.rows" :key="idx" class="table-row">
<view class="table-cell">{{ row.level }}</view>
<view class="table-cell score-cell">{{ row.scores }}</view>
</view>
</view>
<view v-else class="score-config-table">
<view class="table-header">
<view class="table-cell header-cell">级别</view>
<view class="table-cell header-cell">一等奖</view>
<view class="table-cell header-cell">二等奖</view>
<view class="table-cell header-cell">三等奖</view>
</view>
<view v-for="(row, idx) in category.rows" :key="idx" class="table-row">
<view class="table-cell">{{ row.level }}</view>
<view class="table-cell score-cell">{{ Array.isArray(row.scores) ? (row.scores[0] || '') : '' }}</view>
<view class="table-cell score-cell">{{ Array.isArray(row.scores) ? (row.scores[1] || '') : '' }}</view>
<view class="table-cell score-cell">{{ Array.isArray(row.scores) ? (row.scores[2] || '') : '' }}</view>
</view>
</view>
</view>
</view>
<view v-else class="block-value placeholder">暂无配置</view>
</view>
</view>
<!-- 获奖情况 -->
<view class="jf-info-section">
<view class="section-title">获奖情况</view>
<view class="info-item">
<text class="label">考核教师</text>
<text class="value title-bold">{{ jfInfo.jsmx || '-' }}</text>
</view>
<view class="info-item">
<text class="label">荣誉名称</text>
<text class="value">{{ jfInfo.rymc || '-' }}</text>
</view>
<view class="info-item">
<text class="label">获奖类型</text>
<text class="value">{{ jfInfo.hjlx || '-' }}</text>
</view>
<view class="info-item">
<text class="label">颁奖单位</text>
<text class="value multi-text">{{ jfInfo.bjdw || '-' }}</text>
</view>
<view class="info-item">
<text class="label">获奖时间</text>
<text class="value">{{ formatYearMonth(jfInfo.hjtime) }}</text>
</view>
<view class="info-item">
<text class="label">类别</text>
<text class="value">{{ jfInfo.category || '-' }}</text>
</view>
<view class="info-item">
<text class="label">级别</text>
<text class="value">{{ jfInfo.level || '-' }}</text>
</view>
<view class="info-item" v-if="jfInfo.grade">
<text class="label">等级</text>
<text class="value">{{ jfInfo.grade }}</text>
</view>
<view class="info-item">
<text class="label">分值</text>
<text class="value score-value">{{ jfInfo.score ?? '-' }}</text>
</view>
</view>
<!-- 证书预览 -->
<BasicFilePreview
v-if="jfInfo.fileUrl"
:file-url="jfInfo.fileUrl"
:file-name="jfInfo.fileName"
:file-format="jfInfo.fileFormat"
class="mb-0"
/>
</view>
<!-- 审批流程展示 -->
<LcglSp :yw-id="jfId" yw-type="JF" />
<template #bottom>
<YwConfirm
v-if="showButton"
:spApi="jfSpApi"
:stopApi="jfStopApi"
:transferApi="jfTransferApi"
:params="spParams"
:showXt="false"
:showReject="true"
:showTransfer="true"
:showApprove="true"
:showReturn="false"
:showStop="true"
:showXtDk="false"
/>
</template>
</BasicLayout>
</template>
<script setup lang="ts">
import { onLoad } from "@dcloudio/uni-app";
import { ref, computed } from "vue";
import dayjs from "dayjs";
import BasicLayout from "@/components/BasicLayout/Layout.vue";
import LcglSp from "@/components/LcglSp/index.vue";
import YwConfirm from "@/pages/components/YwConfirm/index.vue";
import BasicFilePreview from "@/components/BasicFile/preview.vue";
import { jfFlowByIdApi, jfSpApi, jfTransferApi, jfStopApi } from "@/api/base/server";
import { useDataStore } from "@/store/modules/data";
const { getXxts } = useDataStore();
const jfId = ref<string>("");
const jfInfo = ref<any>({});
const showButton = ref<boolean>(false);
const spParams = computed(() => ({
xxtsId: getXxts?.id,
ywId: jfId.value
}));
//
const formatTime = (time: any) => {
if (!time) return "-";
return dayjs(time).format("YYYY-MM-DD HH:mm:ss");
};
const formatYearMonth = (time: any) => {
if (!time) return "-";
return dayjs(time).format("YYYY-MM");
};
// ===== MyScoreDetail =====
const defaultLevels = [
{ levelCode: 'national', levelName: '国', key: 'levelNational' },
{ levelCode: 'province', levelName: '省', key: 'levelProvince' },
{ levelCode: 'city', levelName: '市', key: 'levelCity' },
{ levelCode: 'district', levelName: '区', key: 'levelDistrict' },
{ levelCode: 'school_district', levelName: '学区/街道', key: 'levelSchoolDistrict' },
{ levelCode: 'school', levelName: '校', key: 'levelSchool' },
];
interface ScoreConfigRow {
level: string;
scores: string | number[]; //
hasMultipleGrades: boolean; //
}
interface ScoreConfigCategory {
category?: string;
rows: ScoreConfigRow[];
hasMultipleGrades: boolean; //
}
const formatScores = (scores: number[]): { display: string | number[], hasMultiple: boolean } => {
if (!scores || scores.length === 0) {
return { display: '', hasMultiple: false };
}
const validScores = scores.filter(s => s > 0);
if (validScores.length === 0) {
return { display: '', hasMultiple: false };
}
if (validScores.length === 1) {
return { display: String(validScores[0]), hasMultiple: false };
}
return { display: validScores, hasMultiple: true };
};
const formatScoreConfig = (config: any): ScoreConfigCategory[] => {
if (!config) return [];
try {
let configObj = config;
if (typeof config === "string") {
try {
configObj = JSON.parse(config);
} catch {
return [];
}
}
const result: ScoreConfigCategory[] = [];
if (configObj.categories && Array.isArray(configObj.categories)) {
configObj.categories.forEach((cat: any) => {
const categoryName = cat.category || '';
const rows: ScoreConfigRow[] = [];
let categoryHasMultiple = false;
defaultLevels.forEach(level => {
const scores = cat[level.key] as number[];
if (scores && scores.length > 0) {
const formatted = formatScores(scores);
if (formatted.display) {
if (formatted.hasMultiple) {
categoryHasMultiple = true;
}
rows.push({
level: level.levelName,
scores: formatted.display,
hasMultipleGrades: formatted.hasMultiple
});
}
}
});
if (rows.length > 0) {
result.push({
category: categoryName,
rows: rows,
hasMultipleGrades: categoryHasMultiple
});
}
});
} else if (configObj.category && configObj.levels) {
const rows: ScoreConfigRow[] = [];
let categoryHasMultiple = false;
configObj.levels.forEach((level: any) => {
if (level.scores && level.scores.length > 0) {
const formatted = formatScores(level.scores);
if (formatted.display) {
if (formatted.hasMultiple) {
categoryHasMultiple = true;
}
const levelName = level.levelName || level.levelCode || '';
rows.push({
level: levelName,
scores: formatted.display,
hasMultipleGrades: formatted.hasMultiple
});
}
}
});
if (rows.length > 0) {
result.push({
category: configObj.category,
rows: rows,
hasMultipleGrades: categoryHasMultiple
});
}
}
return result;
} catch {
return [];
}
};
const formattedScoreConfig = computed(() => formatScoreConfig(jfInfo.value?.scoreConfig));
//
const getJfInfo = async () => {
try {
const res: any = await jfFlowByIdApi({ id: jfId.value });
if (res && res.resultCode === 1 && res.result) {
jfInfo.value = res.result.jfInfo || {};
const spResult = jfInfo.value?.spResult;
//
if (spResult && spResult !== "A" && getXxts && getXxts.dbZt === "A") {
showButton.value = false;
} else {
showButton.value = true;
}
} else {
showButton.value = false;
}
} catch (error) {
console.error("获取积分流程失败:", error);
showButton.value = false;
}
};
onLoad((options: any) => {
if (options && options.id) {
jfId.value = options.id;
getJfInfo();
} else {
uni.showToast({ title: "缺少积分ID", icon: "none" });
setTimeout(() => uni.navigateBack(), 1500);
}
});
</script>
<style scoped lang="scss">
.p-15 {
padding: 15px;
}
.jf-info-section {
margin-bottom: 20px;
padding: 15px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.section-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
color: #333;
border-bottom: 2px solid #007aff;
padding-bottom: 5px;
}
.info-item {
display: flex;
margin-bottom: 12px;
.label {
width: 110rpx;
color: #666;
font-size: 28rpx;
}
.value {
flex: 1;
color: #333;
font-size: 28rpx;
&.title-bold {
font-weight: bold;
font-size: 32rpx;
}
&.score-value {
color: #f56c6c;
font-weight: 600;
}
}
}
.info-block {
margin-top: 12px;
.block-label {
display: inline-block;
font-size: 28rpx;
font-weight: 600;
color: #303133;
padding-bottom: 6rpx;
border-bottom: 2rpx solid #e4e7ed;
}
.block-value {
display: block;
margin-top: 8rpx;
font-size: 28rpx;
color: #606266;
&.multi-text {
white-space: pre-wrap;
word-break: break-word;
line-height: 1.6;
}
&.placeholder {
color: #999;
}
}
.score-config-wrapper {
width: 100%;
margin-top: 8rpx;
}
.score-config-category {
margin-top: 16rpx;
&:first-child {
margin-top: 8rpx;
}
.category-title {
font-size: 28rpx;
font-weight: 600;
color: #409eff;
margin-bottom: 12rpx;
padding: 8rpx 12rpx;
background-color: #ecf5ff;
border-radius: 6rpx;
border-left: 4rpx solid #409eff;
}
.score-config-table {
border: 1rpx solid #dcdfe6;
border-radius: 8rpx;
overflow: hidden;
.table-header {
display: flex;
background-color: #f5f7fa;
border-bottom: 1rpx solid #dcdfe6;
}
.table-row {
display: flex;
border-bottom: 1rpx solid #ebeef5;
&:last-child {
border-bottom: none;
}
}
.table-cell {
padding: 16rpx 12rpx;
font-size: 26rpx;
text-align: center;
border-right: 1rpx solid #ebeef5;
&:first-child {
width: 120rpx;
border-right: 1rpx solid #ebeef5;
}
&:nth-child(2):last-child {
flex: 1;
border-right: none;
}
&:nth-child(2):not(:last-child),
&:nth-child(3),
&:nth-child(4) {
flex: 1;
min-width: 100rpx;
}
&:last-child {
border-right: none;
}
&.header-cell {
font-weight: bold;
color: #303133;
background-color: #f5f7fa;
}
&.score-cell {
color: #409eff;
font-weight: 500;
}
}
}
}
}
</style>

View File

@ -0,0 +1,406 @@
<template>
<view class="jfsp-list-page">
<view class="top-section">
<view class="search-card">
<view class="search-item">
<view class="search-container">
<BasicSearch
placeholder="搜索积分标题或类型"
v-model="searchKeyword"
class="search-input"
@search="handleSearch"
/>
<u-button
text="查询"
type="primary"
size="small"
class="search-button"
@click="handleSearch(searchKeyword)"
/>
</view>
</view>
<view class="filter-tabs">
<view
v-for="tab in filterTabs"
:key="tab.key"
class="filter-tab"
:class="{ active: activeTab === tab.key }"
@click="switchTab(tab.key)"
>
{{ tab.label }}
</view>
</view>
</view>
</view>
<view class="middle-section">
<z-paging
ref="pagingRef"
v-model="dataList"
:auto="true"
:refresher-enabled="true"
:loading-more-enabled="true"
:loading-more-threshold="50"
:default-page-size="10"
:show-loading-more-no-more-view="true"
:show-empty-view-reload="false"
:fixed="false"
:use-page-scroll="true"
class="paging-container"
@query="queryData"
>
<view
v-for="(data, index) in dataList"
:key="data.id || data.ywId || index"
class="jf-card"
>
<view class="card-header">
<text class="jf-title">{{ getTitle(data) }}</text>
<text class="jf-status" :class="getStatusClass(data.spJd || data.dbZt)">
{{ getStatusText(data.spJd || data.dbZt) }}
</text>
</view>
<view class="card-body">
<view class="info-item">
<text class="info-label">考核教师</text>
<text class="info-value">{{ data.jsmx || "—" }}</text>
</view>
<view class="info-item">
<text class="info-label">获奖类型</text>
<text class="info-value">{{ data.hjlx || "—" }}</text>
</view>
<view class="info-item">
<text class="info-label">颁奖单位</text>
<text class="info-value multi-line">{{ data.bjdw || "—" }}</text>
</view>
<view class="info-item">
<text class="info-label">获奖时间</text>
<text class="info-value">{{ formatYearMonth(data.hjtime) }}</text>
</view>
</view>
<view class="card-footer">
<view class="footer-actions">
<u-button
text="查看"
size="mini"
type="primary"
@click="goToDetail(data)"
/>
</view>
</view>
</view>
</z-paging>
</view>
</view>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { onShow } from "@dcloudio/uni-app";
import dayjs from "dayjs";
import BasicSearch from "@/components/BasicSearch/Search.vue";
import { jfFindUserTodosPageApi } from "@/api/base/server";
import { navigateTo } from "@/utils/uniapp";
import { useUserStore } from "@/store/modules/user";
const filterTabs = [
{ key: "pending", label: "待办" },
{ key: "approved", label: "已办" },
{ key: "cc", label: "抄送" },
{ key: "all", label: "全部" },
];
const activeTab = ref("pending");
const searchKeyword = ref("");
const dataList = ref<any[]>([]);
const pagingRef = ref<any>(null);
const userStore = useUserStore();
const getCurrentTeacherId = () => {
const jsData = userStore.getJs;
return jsData?.id || null;
};
const handleSearch = (keyword: string) => {
searchKeyword.value = keyword;
pagingRef.value?.reload();
};
const switchTab = (tabKey: string) => {
activeTab.value = tabKey;
pagingRef.value?.reload();
};
const filterJfData = (list: any[]) => {
let filtered = list;
if (searchKeyword.value) {
const kw = searchKeyword.value.toLowerCase();
filtered = filtered.filter((item) =>
getTitle(item).toLowerCase().includes(kw)
);
}
return filtered;
};
const queryData = async (pageNo: number, pageSize: number) => {
try {
const jsId = getCurrentTeacherId();
if (!jsId) {
uni.showToast({ title: "无法获取用户信息", icon: "none" });
pagingRef.value?.complete([]);
return;
}
let response: any;
if (activeTab.value === "all") {
response = await jfFindUserTodosPageApi({ dbZt: "", jsId, spType: "", page: pageNo, rows: pageSize });
} else if (activeTab.value === "pending") {
response = await jfFindUserTodosPageApi({ dbZt: "A", jsId, spType: "SP", page: pageNo, rows: pageSize });
} else if (activeTab.value === "approved") {
response = await jfFindUserTodosPageApi({ dbZt: "B", jsId, spType: "SP", page: pageNo, rows: pageSize });
} else {
response = await jfFindUserTodosPageApi({ dbZt: "", jsId, spType: "CC", page: pageNo, rows: pageSize });
}
console.log("积分审批列表接口返回 raw:", response);
const result = response?.data || response;
let rows: any[] = [];
if (result?.rows && Array.isArray(result.rows)) {
rows = result.rows;
} else if (result?.resultCode === 1 && Array.isArray(result.result)) {
rows = result.result;
}
console.log("积分审批列表 rows:", rows);
const filtered = filterJfData(rows);
console.log("积分审批列表 filtered:", filtered);
// dataList
dataList.value = filtered;
// z-paging使 total/records
const total = result?.total ?? result?.records ?? filtered.length;
pagingRef.value?.complete(filtered, total);
} catch (error) {
console.error("加载积分审批列表失败:", error);
uni.showToast({ title: "加载失败", icon: "none" });
dataList.value = [];
pagingRef.value?.complete([], 0);
}
};
const getTitle = (item: any) => item.ywTitle || item.title || item.rymc || "积分申请";
const getStatusText = (spJd?: string) => {
const map: Record<string, string> = { A: "审批中", Z: "已完成", B: "已办", C: "已驳回" };
return map[spJd || ""] || "未知";
};
const getStatusClass = (spJd?: string) => {
if (spJd === "A") return "status-pending";
if (spJd === "Z" || spJd === "B") return "status-done";
if (spJd === "C") return "status-reject";
return "";
};
const formatDate = (val?: string) => (val ? dayjs(val).format("YYYY-MM-DD HH:mm") : "--");
const formatYearMonth = (val?: string) => (val ? dayjs(val).format("YYYY-MM") : "--");
const goToDetail = (item: any) => {
const id = item.ywId || item.id;
if (!id) {
uni.showToast({ title: "缺少业务ID", icon: "none" });
return;
}
navigateTo(`/pages/view/routine/JiFenPingJia/jfsp/JfFlow?id=${id}&from=db`);
};
onShow(() => {
pagingRef.value?.reload();
});
</script>
<style scoped lang="scss">
.jfsp-list-page {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f7fa;
}
.top-section {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background-color: #fff;
border-bottom: 1px solid #eee;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 12px;
}
.search-card {
background-color: #ffffff;
border-radius: 12px;
padding: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
border: 1px solid #f0f0f0;
}
.search-container {
display: flex;
gap: 8px;
align-items: center;
}
.search-input {
flex: 1;
min-width: 70%;
height: 42px;
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 0 15px;
font-size: 16px;
color: #333;
}
.filter-tabs {
display: flex;
gap: 8px;
margin-top: 10px;
}
.filter-tab {
flex: 1;
text-align: center;
padding: 8px 12px;
border-radius: 6px;
font-size: 14px;
color: #666;
transition: all 0.3s;
background-color: #f0f0f0;
border: 1px solid #e9ecef;
&.active {
background-color: #007aff;
color: white;
border-color: #007aff;
}
}
.middle-section {
flex: 1;
margin-top: 140px;
height: calc(100vh - 140px);
background-color: #f5f7fa;
}
.paging-container {
height: 100%;
padding: 15px;
box-sizing: border-box;
}
.jf-card {
background-color: white;
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
&:active {
transform: translateY(1px);
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.12);
}
}
.card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
gap: 12px;
}
.jf-title {
font-size: 16px;
font-weight: 600;
color: #2c3e50;
flex: 1;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
word-break: break-word;
}
.jf-status {
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
white-space: nowrap;
flex-shrink: 0;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.status-pending {
background: #fff7e6;
color: #f59e0b;
}
.status-done {
background: #e8fff3;
color: #10b981;
}
.status-reject {
background: #ffecec;
color: #f43f5e;
}
.card-body {
margin-bottom: 12px;
}
.info-item {
display: flex;
align-items: center;
margin-top: 6px;
}
.info-label {
color: #666;
font-size: 14px;
margin-right: 4px;
}
.info-value {
color: #333;
font-size: 14px;
flex: 1;
&.multi-line {
white-space: normal;
word-break: break-all;
line-height: 1.5;
}
}
.card-footer {
display: flex;
justify-content: flex-end;
align-items: center;
padding-top: 12px;
border-top: 1px solid #f0f0f0;
}
.footer-actions {
display: flex;
gap: 8px;
}
</style>

View File

@ -197,7 +197,9 @@ const currentFilter = ref('all');
const totalCount = computed(() => teacherList.value.length);
const signedCount = computed(() => teacherList.value.filter(t => t.qdStatus === '1').length);
const unsignedCount = computed(() => teacherList.value.filter(t => t.qdStatus === '0').length);
const unsignedCount = computed(() => teacherList.value.filter(
t => t.qdStatus === '0' && !(t.qjlx && t.qjlx.trim() !== '')
).length);
const lateCount = computed(() => {
if (!qdInfo.value.qdkstime) return 0;
const meetingStartTime = new Date(qdInfo.value.qdkstime);
@ -227,7 +229,9 @@ const filteredTeacherList = computed(() => {
return signInTime > meetingStartTime;
});
case 'unsigned':
return teacherList.value.filter(t => t.qdStatus === '0');
return teacherList.value.filter(
t => t.qdStatus === '0' && !(t.qjlx && t.qjlx.trim() !== '')
);
case 'qj':
return teacherList.value.filter(t => t.qjlx && t.qjlx.trim() !== '');
case 'qj_pending':

View File

@ -62,20 +62,11 @@
<text class="deco-icon">🎁</text>
<text class="deco-icon">🎈</text>
</view>
<!-- 右上角音乐图标 -->
<view
class="music-icon-wrapper"
:class="{ 'playing': isMusicPlaying, 'fade-out': isOpening }"
@click="toggleMusic"
>
<text class="music-icon-emoji">🎵</text>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, computed, onUnmounted } from "vue";
import { ref, computed } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import { useUserStore } from "@/store/modules/user";
@ -85,8 +76,6 @@ const openId = ref<string>("");
const receiverName = ref<string>("");
const isOpening = ref(false);
const isOpened = ref(false);
const audioContext = ref<any>(null);
const isMusicPlaying = ref(false);
//
const pageStyle = computed(() => {
@ -99,11 +88,6 @@ const pageStyle = computed(() => {
const openEnvelope = () => {
if (isOpening.value || isOpened.value) return;
//
if (!isMusicPlaying.value && audioContext.value) {
playMusic();
}
isOpening.value = true;
//
@ -118,124 +102,6 @@ const openEnvelope = () => {
}, 1500);
};
//
const initAudio = () => {
try {
// #ifdef H5
audioContext.value = new Audio('/static/base/music/srkl.mp3');
audioContext.value.loop = true;
//
audioContext.value.addEventListener('play', () => {
isMusicPlaying.value = true;
});
audioContext.value.addEventListener('pause', () => {
isMusicPlaying.value = false;
});
audioContext.value.addEventListener('ended', () => {
isMusicPlaying.value = false;
});
//
let hasTriedPlay = false;
//
const attemptPlay = () => {
if (!audioContext.value || hasTriedPlay || isMusicPlaying.value) {
return;
}
hasTriedPlay = true;
audioContext.value.play().then(() => {
isMusicPlaying.value = true;
}).catch(() => {
// 便
hasTriedPlay = false;
});
};
//
let hasInteracted = false;
const playOnInteraction = () => {
if (!hasInteracted && !isMusicPlaying.value) {
hasInteracted = true;
attemptPlay();
}
};
//
const events = ['click', 'touchstart'];
events.forEach(eventType => {
document.addEventListener(eventType, playOnInteraction, { once: true, passive: true });
});
//
attemptPlay();
// #endif
// #ifdef MP-WEIXIN
audioContext.value = uni.createInnerAudioContext();
audioContext.value.src = '/static/base/music/srkl.mp3';
audioContext.value.loop = true;
//
audioContext.value.onPlay(() => {
isMusicPlaying.value = true;
});
audioContext.value.onPause(() => {
isMusicPlaying.value = false;
});
audioContext.value.onStop(() => {
isMusicPlaying.value = false;
});
//
audioContext.value.play();
isMusicPlaying.value = true;
// #endif
} catch (e) {
console.error("初始化音频失败:", e);
}
};
//
const playMusic = () => {
if (!audioContext.value) return;
try {
audioContext.value.play();
isMusicPlaying.value = true;
} catch (e) {
console.error("播放音乐失败:", e);
}
};
//
const toggleMusic = () => {
if (!audioContext.value) return;
try {
if (isMusicPlaying.value) {
audioContext.value.pause();
isMusicPlaying.value = false;
} else {
audioContext.value.play();
isMusicPlaying.value = true;
}
} catch (e) {
console.error("音乐播放控制失败:", e);
}
};
//
onUnmounted(() => {
if (audioContext.value) {
try {
audioContext.value.pause();
// #ifdef MP-WEIXIN
audioContext.value.destroy?.();
// #endif
} catch (e) {
console.error("清理音频失败:", e);
}
}
});
//
const resetEnvelopeState = () => {
isOpening.value = false;
@ -260,9 +126,6 @@ onLoad(async (options) => {
if (!xxtsId.value) {
uni.showToast({ title: "缺少贺卡ID", icon: "none" });
}
//
initAudio();
});
//
@ -613,46 +476,4 @@ onShow(() => {
opacity: 0;
transform: translateY(30px);
}
/* 右上角音乐图标 */
.music-icon-wrapper {
position: fixed;
top: calc(20px + env(safe-area-inset-top));
right: 20px;
width: 50px;
height: 50px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.9);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
z-index: 100;
cursor: pointer;
transition: all 0.3s ease;
}
.music-icon-wrapper:active {
transform: scale(0.95);
}
.music-icon-wrapper.playing .music-icon-emoji {
animation: music-rotate 2s linear infinite;
}
.music-icon-wrapper.fade-out {
opacity: 0;
transform: scale(0.8);
}
.music-icon-emoji {
font-size: 28px;
display: block;
transition: transform 0.3s ease;
}
@keyframes music-rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>

View File

@ -0,0 +1,471 @@
<!-- src/pages/view/routine/sr/list.vue -->
<!-- 生日清单页面教师端 -->
<template>
<view class="birthday-list-page">
<!-- 顶部标题 -->
<view class="page-header">
<view class="header-title">
<text class="title-icon">🎂</text>
<text class="title-text">生日清单</text>
<text class="title-icon">🎂</text>
</view>
<!-- 日期范围选择器 -->
<view class="date-range-picker">
<picker
mode="date"
:value="startDate"
:start="minDate"
:end="maxDate"
@change="onStartDateChange"
>
<view class="date-input">{{ startDate }}</view>
</picker>
<text class="date-separator">~</text>
<picker
mode="date"
:value="endDate"
:start="startDate"
:end="maxDate"
@change="onEndDateChange"
>
<view class="date-input">{{ endDate }}</view>
</picker>
</view>
</view>
<!-- 加载状态 -->
<view v-if="isLoading" class="loading-container">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
</view>
<!-- 列表内容 -->
<view v-else-if="birthdayList.length > 0" class="list-container">
<!-- 教师生日 -->
<view v-if="teacherList.length > 0" class="section">
<view class="section-header">
<text class="section-icon">👨🏫</text>
<text class="section-title">教师生日</text>
<text class="section-count">({{ teacherList.length }})</text>
</view>
<view class="list-items">
<view
v-for="(item, index) in teacherList"
:key="index"
class="list-item"
@click="viewCard(item)"
>
<view class="item-avatar">
<text class="avatar-icon">👨🏫</text>
</view>
<view class="item-content">
<view class="item-name">{{ item.srPersonName }}</view>
<view class="item-info">
<text v-if="item.bc" class="info-text">{{ item.bc }}</text>
</view>
</view>
<view class="item-action">
<text class="action-icon">🎁</text>
</view>
</view>
</view>
</view>
<!-- 学生生日 -->
<view v-if="studentList.length > 0" class="section">
<view class="section-header">
<text class="section-icon">👨🎓</text>
<text class="section-title">学生生日</text>
<text class="section-count">({{ studentList.length }})</text>
</view>
<view class="list-items">
<view
v-for="(item, index) in studentList"
:key="index"
class="list-item"
@click="viewCard(item)"
>
<view class="item-avatar student-avatar">
<text class="avatar-icon">👨🎓</text>
</view>
<view class="item-content">
<view class="item-name">{{ item.srPersonName }}</view>
<view class="item-info">
<text v-if="item.bc" class="info-text">{{ item.bc }}</text>
<text v-if="item.receiverName" class="info-text">家长{{ item.receiverName }}</text>
</view>
</view>
<view class="item-action">
<text class="action-icon">🎁</text>
</view>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-else class="empty-container">
<text class="empty-icon">🎂</text>
<text class="empty-text">该日期范围内没有人生日哦~</text>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from "vue";
import { onLoad, onPullDownRefresh } from "@dcloudio/uni-app";
import { srFindPageApi } from "@/api/base/srApi";
//
const isLoading = ref(false);
const birthdayList = ref<any[]>([]);
//
const getTodayStr = () => {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
const startDate = ref<string>(getTodayStr());
const endDate = ref<string>(getTodayStr());
//
const minDate = ref<string>('2020-01-01');
const maxDate = computed(() => {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
});
//
const teacherList = computed(() => {
return birthdayList.value.filter(item => item.srPersonType === 'JS');
});
//
const studentList = computed(() => {
return birthdayList.value.filter(item => item.srPersonType === 'XS');
});
//
const onStartDateChange = (e: any) => {
const date = e.detail.value;
startDate.value = date;
//
if (date > endDate.value) {
endDate.value = date;
}
//
fetchBirthdayList();
};
//
const onEndDateChange = (e: any) => {
const date = e.detail.value;
endDate.value = date;
//
if (date < startDate.value) {
startDate.value = date;
}
//
fetchBirthdayList();
};
//
const fetchBirthdayList = async () => {
try {
isLoading.value = true;
// 使
const params: any = {
page: 1,
rows: 1000, //
birthdayDateStart: startDate.value, //
birthdayDateEnd: endDate.value, //
};
const res = await srFindPageApi(params);
// {total, page, records, rows: [...]}
// records rows
if (res && res.rows && Array.isArray(res.rows) && res.rows.length > 0) {
//
const uniqueMap = new Map();
res.rows.forEach((item: any) => {
const key = `${item.srPersonType}_${item.srPersonId}`;
if (!uniqueMap.has(key)) {
uniqueMap.set(key, item);
}
});
birthdayList.value = Array.from(uniqueMap.values());
} else {
birthdayList.value = [];
}
} catch (error) {
console.error('获取生日清单失败:', error);
uni.showToast({
title: '获取数据失败',
icon: 'none'
});
birthdayList.value = [];
} finally {
isLoading.value = false;
}
};
//
const viewCard = (item: any) => {
if (!item.id) {
uni.showToast({
title: '无法查看贺卡',
icon: 'none'
});
return;
}
//
uni.navigateTo({
url: `/pages/view/routine/sr/viewCard?recordId=${item.id}&receiverId=${item.receiverId || ''}`
});
};
//
onLoad(() => {
fetchBirthdayList();
});
//
onPullDownRefresh(() => {
fetchBirthdayList().finally(() => {
uni.stopPullDownRefresh();
});
});
</script>
<style lang="scss" scoped>
.birthday-list-page {
min-height: 100vh;
background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 50%, #fecfef 100%);
padding-bottom: 40rpx;
}
.page-header {
padding: 60rpx 40rpx 40rpx;
text-align: center;
background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);
}
.header-title {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20rpx;
}
.title-icon {
font-size: 48rpx;
margin: 0 20rpx;
}
.title-text {
font-size: 48rpx;
font-weight: bold;
color: #fff;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
/* 日期范围选择器 */
.date-range-picker {
display: flex;
align-items: center;
justify-content: center;
gap: 20rpx;
margin-top: 30rpx;
}
.date-input {
background: rgba(255, 255, 255, 0.95);
border-radius: 12rpx;
padding: 20rpx 30rpx;
font-size: 28rpx;
color: #333;
min-width: 200rpx;
text-align: center;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.date-input:active {
background: rgba(255, 255, 255, 0.85);
transform: scale(0.98);
}
.date-separator {
font-size: 32rpx;
color: rgba(255, 255, 255, 0.9);
font-weight: bold;
}
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 200rpx 0;
}
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 4rpx solid rgba(255, 255, 255, 0.3);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-text {
margin-top: 30rpx;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.9);
}
.list-container {
padding: 0 30rpx;
}
.section {
margin-bottom: 40rpx;
}
.section-header {
display: flex;
align-items: center;
padding: 30rpx 0 20rpx;
border-bottom: 2rpx solid rgba(255, 255, 255, 0.3);
margin-bottom: 20rpx;
}
.section-icon {
font-size: 36rpx;
margin-right: 15rpx;
}
.section-title {
font-size: 36rpx;
font-weight: bold;
color: #fff;
margin-right: 15rpx;
}
.section-count {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
}
.list-items {
background: rgba(255, 255, 255, 0.95);
border-radius: 20rpx;
overflow: hidden;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
}
.list-item {
display: flex;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
transition: background-color 0.3s;
&:last-child {
border-bottom: none;
}
&:active {
background-color: #f8f8f8;
}
}
.item-avatar {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);
display: flex;
align-items: center;
justify-content: center;
margin-right: 30rpx;
flex-shrink: 0;
}
.student-avatar {
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
}
.avatar-icon {
font-size: 50rpx;
}
.item-content {
flex: 1;
min-width: 0;
}
.item-name {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
}
.item-info {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.info-text {
font-size: 26rpx;
color: #666;
}
.item-action {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.action-icon {
font-size: 40rpx;
}
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 200rpx 0;
}
.empty-icon {
font-size: 120rpx;
margin-bottom: 30rpx;
opacity: 0.8;
}
.empty-text {
font-size: 32rpx;
color: rgba(255, 255, 255, 0.9);
}
</style>

View File

@ -0,0 +1,688 @@
<!-- src/pages/view/routine/sr/viewCard.vue -->
<!-- 生日贺卡查看页面从清单页面进入 -->
<template>
<view class="card-view-page" :class="bgColorClass">
<!-- 加载遮罩层 -->
<view v-if="isLoading" class="loading-overlay">
<view class="loading-content">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
</view>
</view>
<!-- 贺卡内容 -->
<view v-else-if="cardData" class="card-container">
<!-- 信纸主体 -->
<view class="letter-paper">
<!-- 信纸顶部装饰 -->
<view class="paper-header">
<view class="header-line"></view>
<view class="header-title">
<text class="title-icon">🎂</text>
<text class="title-text">生日祝福</text>
<text class="title-icon">🎂</text>
</view>
<view class="header-date">{{ formatBirthdayDate }}</view>
</view>
<!-- 称呼 -->
<view class="letter-greeting" v-if="greetingFromContent">
<view class="greeting-line">
<text class="greeting-text">{{ greetingFromContent }}</text>
</view>
</view>
<!-- 信件正文带虚线 -->
<view class="letter-content">
<view
v-for="(line, index) in contentLines"
:key="index"
class="content-line"
>
<text class="line-text">{{ line || '\u00A0' }}</text>
<view class="line-border"></view>
</view>
</view>
<!-- 签名区域 -->
<view class="letter-signature" v-if="cardData.signerName || cardData.signLabel">
<view class="signature-wrapper">
<!-- 签名文字 -->
<view class="signature-info">
<text class="signer-name">
<text v-if="cardData.signLabel" class="signer-label">{{ cardData.signLabel }}</text>
<text v-if="cardData.signerName">{{ cardData.signerName }}</text>
</text>
</view>
<!-- 日期 -->
<view class="signature-date">
<text>{{ currentDate }}</text>
</view>
</view>
</view>
<!-- 信纸底部装饰 -->
<view class="paper-footer">
<view class="footer-decoration">
<text class="footer-icon">🎉</text>
<text class="footer-icon">🎁</text>
<text class="footer-icon">🎈</text>
</view>
</view>
</view>
<!-- 右上角音乐图标 -->
<view
class="music-icon-wrapper"
:class="{ 'playing': isMusicPlaying }"
@click="toggleMusic"
>
<text class="music-icon-emoji">🎵</text>
</view>
</view>
<!-- 空状态 -->
<view v-else class="empty-state">
<text class="empty-icon">🎂</text>
<text class="empty-text">贺卡不存在或已过期</text>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, computed, onUnmounted } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { srGetCardDetailByRecordApi, srMarkAsViewedApi } from "@/api/base/srApi";
import { imagUrl } from "@/utils";
const recordId = ref<string>("");
const receiverId = ref<string>("");
const cardData = ref<any>(null);
const isLoading = ref(false);
const isMusicPlaying = ref(false);
const audioContext = ref<any>(null);
//
const formatBirthdayDate = computed(() => {
if (!cardData.value?.birthdayDate) return '';
const date = cardData.value.birthdayDate;
if (date.includes('月')) return date;
try {
const d = new Date(date);
return `${d.getMonth() + 1}${d.getDate()}`;
} catch {
return date;
}
});
//
const currentDate = computed(() => {
const now = new Date();
return `${now.getFullYear()}${now.getMonth() + 1}${now.getDate()}`;
});
//
const bgColorClass = computed(() => {
const colorType = cardData.value?.hkMb?.backgroundColor || cardData.value?.hkMb?.animationType || 'blue';
return `bg-${colorType}`;
});
// HTML
const stripHtmlTags = (html: string): string => {
if (!html) return '';
return html.replace(/<[^>]+>/g, '').trim();
};
//
const greetingFromContent = computed(() => {
if (!cardData.value?.zfContent) return '';
const content = cardData.value.zfContent;
const lines = content.split(/<\/p>|<\/div>|\n/).filter((line: string) => line.trim());
if (lines.length > 0) {
let firstLine = lines[0];
firstLine = firstLine.replace(/^<[^>]+>/, '');
firstLine = stripHtmlTags(firstLine).trim();
return firstLine || '';
}
return '';
});
// HTML
const contentLines = computed(() => {
if (!cardData.value?.zfContent) return [''];
const content = cardData.value.zfContent;
let lines = content.split(/<\/p>|<\/div>|\n/).filter((line: string) => line.trim());
//
if (lines.length > 0) {
lines = lines.slice(1);
}
// HTML
lines = lines.map((line: string) => {
line = line.replace(/^<[^>]+>/, '');
return stripHtmlTags(line).trim();
}).filter((line: string) => line);
// 5
const minLines = 5;
while (lines.length < minLines) {
lines.push('');
}
return lines;
});
//
const loadCardDetail = async () => {
if (!recordId.value) return;
isLoading.value = true;
try {
const res = await srGetCardDetailByRecordApi({
recordId: recordId.value,
receiverId: receiverId.value || ''
});
// {resultCode: 1, message: "", result: {...}}
//
const cardDetail = res?.result || res?.data;
const successCode = res?.resultCode === 1 || res?.code === 1 || res?.code === '1';
if (res && successCode && cardDetail) {
cardData.value = cardDetail;
//
if (cardDetail.xxtsId) {
await markAsViewed(cardDetail.xxtsId);
}
//
initAudio();
} else {
uni.showToast({
title: res?.message || res?.msg || '获取贺卡失败',
icon: 'none'
});
}
} catch (e) {
console.error("加载贺卡详情失败:", e);
uni.showToast({ title: "加载失败", icon: "none" });
} finally {
isLoading.value = false;
}
};
//
const markAsViewed = async (xxtsId: string) => {
if (!xxtsId) return;
try {
await srMarkAsViewedApi({ xxtsId });
} catch (e) {
console.error("标记已查阅失败:", e);
}
};
//
const initAudio = () => {
const musicUrl = cardData.value?.hkMb?.musicUrl
? imagUrl(cardData.value.hkMb.musicUrl)
: '/static/base/music/srkl.mp3';
try {
// #ifdef H5
audioContext.value = new Audio(musicUrl);
audioContext.value.loop = true;
audioContext.value.play().catch((e: any) => {
console.error("自动播放失败,可能需要用户交互:", e);
});
// #endif
// #ifdef MP-WEIXIN
audioContext.value = uni.createInnerAudioContext();
audioContext.value.src = musicUrl;
audioContext.value.loop = true;
audioContext.value.play();
// #endif
isMusicPlaying.value = true;
} catch (e) {
console.error("初始化音频失败:", e);
}
};
//
const toggleMusic = () => {
if (!audioContext.value) return;
try {
if (isMusicPlaying.value) {
audioContext.value.pause();
} else {
audioContext.value.play();
}
isMusicPlaying.value = !isMusicPlaying.value;
} catch (e) {
console.error("音乐播放控制失败:", e);
}
};
//
onUnmounted(() => {
if (audioContext.value) {
try {
audioContext.value.pause();
// #ifdef MP-WEIXIN
audioContext.value.destroy?.();
// #endif
} catch (e) {
console.error("清理音频失败:", e);
}
}
});
onLoad(async (options) => {
recordId.value = options?.recordId || '';
receiverId.value = options?.receiverId || '';
if (recordId.value) {
await loadCardDetail();
uni.setNavigationBarTitle({ title: "生日祝福" });
} else {
uni.showToast({ title: "缺少记录ID", icon: "none" });
}
});
</script>
<style scoped lang="scss">
.card-view-page {
min-height: 100vh;
display: flex;
flex-direction: column;
position: relative;
//
&.bg-blue {
background: linear-gradient(180deg, #e6f3ff 0%, #cce5ff 50%, #b3d9ff 100%);
.letter-paper {
background: linear-gradient(180deg, #f5faff 0%, #eef6ff 100%);
border-color: rgba(59, 130, 246, 0.2);
}
.header-line {
background: linear-gradient(90deg, transparent 0%, #3b82f6 50%, transparent 100%);
}
.title-text {
color: #1e40af;
}
.paper-footer {
border-top-color: #93c5fd;
}
.loading-overlay {
background-color: rgba(230, 243, 255, 0.95);
}
.loading-spinner {
border-color: #93c5fd;
border-top-color: #3b82f6;
}
}
//
&.bg-yellow {
background: linear-gradient(180deg, #fdf6e3 0%, #f5e6c8 50%, #efe0b9 100%);
.letter-paper {
background: linear-gradient(180deg, #fffef5 0%, #fff9e6 100%);
border-color: rgba(201, 162, 39, 0.2);
}
.header-line {
background: linear-gradient(90deg, transparent 0%, #c9a227 50%, transparent 100%);
}
.title-text {
color: #8b4513;
}
.paper-footer {
border-top-color: #e8d5a3;
}
.loading-overlay {
background-color: rgba(253, 246, 227, 0.95);
}
.loading-spinner {
border-color: #e8d5a3;
border-top-color: #c9a227;
}
}
//
&.bg-purple {
background: linear-gradient(180deg, #f3e8ff 0%, #e9d5ff 50%, #ddd6fe 100%);
.letter-paper {
background: linear-gradient(180deg, #faf5ff 0%, #f5f0ff 100%);
border-color: rgba(139, 92, 246, 0.2);
}
.header-line {
background: linear-gradient(90deg, transparent 0%, #8b5cf6 50%, transparent 100%);
}
.title-text {
color: #6b21a8;
}
.paper-footer {
border-top-color: #c4b5fd;
}
.loading-overlay {
background-color: rgba(243, 232, 255, 0.95);
}
.loading-spinner {
border-color: #c4b5fd;
border-top-color: #8b5cf6;
}
}
}
/* 加载遮罩 */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.loading-content {
display: flex;
flex-direction: column;
align-items: center;
}
.loading-spinner {
width: 50px;
height: 50px;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 15px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
font-size: 16px;
color: #666;
}
/* 贺卡容器 */
.card-container {
flex: 1;
display: flex;
flex-direction: column;
padding: 20px;
padding-top: calc(20px + env(safe-area-inset-top));
padding-bottom: calc(20px + env(safe-area-inset-bottom));
}
/* 信纸主体 */
.letter-paper {
flex: 1;
border-radius: 8px;
padding: 30px 25px;
box-shadow:
0 4px 20px rgba(0, 0, 0, 0.1),
0 1px 3px rgba(0, 0, 0, 0.05),
inset 0 1px 0 rgba(255, 255, 255, 0.8);
border: 1px solid;
position: relative;
display: flex;
flex-direction: column;
}
/* 信纸左边红色竖线 */
.letter-paper::before {
content: '';
position: absolute;
left: 20px;
top: 80px;
bottom: 100px;
width: 2px;
background: linear-gradient(180deg, #e74c3c 0%, #c0392b 100%);
opacity: 0.3;
}
/* 信纸顶部装饰 */
.paper-header {
text-align: center;
margin-bottom: 25px;
padding-bottom: 15px;
}
.header-line {
height: 2px;
margin-bottom: 15px;
}
.header-title {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
margin-bottom: 8px;
}
.title-icon {
font-size: 24px;
}
.title-text {
font-size: 26px;
font-weight: bold;
font-family: "KaiTi", "STKaiti", "楷体", serif;
}
.header-date {
font-size: 14px;
color: #a08060;
}
/* 称呼 */
.letter-greeting {
margin-bottom: 20px;
padding-left: 15px;
}
.greeting-line {
padding-bottom: 8px;
border-bottom: 1px dashed #d4c4a8;
}
.greeting-text {
font-size: 18px;
color: #5a4a3a;
font-family: "KaiTi", "STKaiti", "楷体", serif;
}
/* 信件正文 */
.letter-content {
flex: 1;
padding-left: 15px;
margin-bottom: 30px;
}
.content-line {
position: relative;
min-height: 40px;
display: flex;
align-items: flex-end;
padding-bottom: 8px;
}
.line-text {
font-size: 16px;
line-height: 1.8;
color: #4a3a2a;
font-family: "KaiTi", "STKaiti", "楷体", serif;
text-indent: 2em;
display: block;
width: 100%;
}
.line-border {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 1px;
border-bottom: 1px dashed #d4c4a8;
}
/* 签名区域 */
.letter-signature {
margin-top: auto;
padding-top: 20px;
}
.signature-wrapper {
display: flex;
flex-direction: column;
align-items: flex-end;
padding-right: 20px;
}
.signature-info {
text-align: right;
margin-bottom: 8px;
}
.signer-label {
font-size: 14px;
color: #8b7355;
display: inline;
margin-right: 8px;
}
.signer-name {
font-size: 18px;
color: #5a4a3a;
font-weight: 500;
font-family: "KaiTi", "STKaiti", "楷体", serif;
display: inline;
}
.signature-date {
font-size: 14px;
color: #a08060;
border-bottom: 1px dashed #d4c4a8;
padding-bottom: 5px;
}
/* 信纸底部装饰 */
.paper-footer {
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid;
}
.footer-decoration {
display: flex;
justify-content: center;
gap: 20px;
padding-top: 15px;
}
.footer-icon {
font-size: 24px;
animation: float-icon 3s ease-in-out infinite;
}
.footer-icon:nth-child(2) {
animation-delay: 0.5s;
}
.footer-icon:nth-child(3) {
animation-delay: 1s;
}
@keyframes float-icon {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-5px); }
}
/* 右上角音乐图标 */
.music-icon-wrapper {
position: fixed;
top: calc(20px + env(safe-area-inset-top));
right: 20px;
width: 50px;
height: 50px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.9);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
z-index: 100;
cursor: pointer;
transition: all 0.3s ease;
}
.music-icon-wrapper:active {
transform: scale(0.95);
}
.music-icon-wrapper.playing .music-icon-emoji {
animation: music-rotate 2s linear infinite;
}
.music-icon-emoji {
font-size: 28px;
display: block;
transition: transform 0.3s ease;
}
@keyframes music-rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 空状态 */
.empty-state {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px;
}
.empty-icon {
font-size: 80px;
margin-bottom: 20px;
opacity: 0.5;
}
.empty-text {
font-size: 16px;
color: #666;
margin-bottom: 30px;
}
</style>

View File

@ -250,7 +250,7 @@ import { attachmentUpload } from "@/api/system/upload";
import BasicLayout from "@/components/BasicLayout/Layout.vue";
import { ImageVideoUpload } from "@/components/ImageVideoUpload";
import BasicTree from '@/components/BasicTree/Tree.vue';
import { findAllNjBjTree } from '@/api/base/server';
import { findAllNjBjKzTreeApi } from '@/api/base/server';
import { useDataStore } from "@/store/modules/data";
import { useUserStore } from "@/store/modules/user";
import { computed, onMounted, ref } from "vue";
@ -325,7 +325,7 @@ const formatTime = (timestamp: string) => {
//
const loadTreeData = async () => {
try {
const res = await findAllNjBjTree();
const res = await findAllNjBjKzTreeApi();
if (res.resultCode === 1 && res.result) {
// BasicTree
const convertTreeData = (items: any[]): any[] => {

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.