权限调整
This commit is contained in:
parent
b0a8ead56a
commit
c7e0c6402a
@ -209,6 +209,16 @@ export const resourcesFindPageApi = async (params: any) => {
|
||||
return await get("/api/resources/findPage", params);
|
||||
};
|
||||
|
||||
// 教师积分查询
|
||||
export const jsFindPageJfApi = async (params: any) => {
|
||||
return await get("/api/js/findPageJf", params);
|
||||
};
|
||||
|
||||
// 检查项查询
|
||||
export const inspectItemFindAllApi = async (params: any) => {
|
||||
return await get("/api/inspectItem/findAlls", params);
|
||||
};
|
||||
|
||||
export const resourcesSaveApi = async (params: any) => {
|
||||
return await post("/api/resources/save", params);
|
||||
};
|
||||
@ -339,3 +349,13 @@ export const findAllNj = async () => {
|
||||
export const zwFindAllApi = async () => {
|
||||
return await get("/api/zw/findAll");
|
||||
};
|
||||
|
||||
// 获取积分详情数据
|
||||
export const getByUserIdAndInspectItemIdApi = async (params: any) => {
|
||||
return await get("/api/evaluation/getByUserIdAndInspectItemId", params);
|
||||
};
|
||||
|
||||
// 清空用户open_id
|
||||
export const clearUserOpenIdApi = async (params: { userId: string }) => {
|
||||
return await post(`/api/user/clearOpenId?userId=${params.userId}`);
|
||||
};
|
||||
|
||||
@ -389,14 +389,14 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/hr/teacherProfile/PersonalHonor",
|
||||
"path": "pages/view/routine/JiFenPingJia/PersonalHonor",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人荣誉",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/hr/teacherProfile/PublicClassAwards",
|
||||
"path": "pages/view/routine/JiFenPingJia/PublicClassAwards",
|
||||
"style": {
|
||||
"navigationBarTitleText": "公开课获奖",
|
||||
"enablePullDownRefresh": false
|
||||
@ -555,6 +555,16 @@
|
||||
"navigationBarTitleText": "确认签到",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/routine/JiaoXueZiYuan/add-resource",
|
||||
"style": {
|
||||
"navigationBarTitleText": "上传资源",
|
||||
"enablePullDownRefresh": false,
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"backgroundColor": "#f4f5f7"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
|
||||
@ -53,6 +53,7 @@
|
||||
class="section-block"
|
||||
v-for="(section, index) in sections"
|
||||
:key="section.id"
|
||||
v-show="hasSectionPermission(section)"
|
||||
>
|
||||
<view class="section-title">
|
||||
<view
|
||||
@ -65,7 +66,7 @@
|
||||
<view class="section-grid-card">
|
||||
<template v-for="item in section.items" :key="item.id">
|
||||
<view
|
||||
v-if="item.show"
|
||||
v-if="item.show && hasPermissionDirect(item.permissionKey)"
|
||||
class="grid-item"
|
||||
@click="handleGridItemClick(item)"
|
||||
>
|
||||
@ -89,12 +90,14 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { jsdfindJsByPhoneApi } from "@/api/base/server";
|
||||
import { jsdfindJsByPhoneApi, clearUserOpenIdApi } from "@/api/base/server";
|
||||
import { useCommonStore } from "@/store/modules/common";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { imagUrl } from "@/utils";
|
||||
import { hideLoading, showLoading } from "@/utils/uniapp";
|
||||
import { reactive, ref } from "vue";
|
||||
import { hasPermission } from "@/utils/permission";
|
||||
import { set } from "lodash";
|
||||
import { reactive, ref, computed, watch, onMounted } from "vue";
|
||||
const { logout, getUser, getJs, setJs } = useUserStore();
|
||||
const { getZwListByLx } = useCommonStore();
|
||||
|
||||
@ -105,10 +108,13 @@ const js = computed(() => getJs);
|
||||
const dzZwLabel = ref<any>("");
|
||||
const qtZwLabel = ref<any>("");
|
||||
|
||||
// 加载状态
|
||||
const isLoading = ref(true);
|
||||
|
||||
// 教师工作信息
|
||||
const jsWork = ref<any>({
|
||||
jf: 88,
|
||||
ks: 40,
|
||||
jf: 88,
|
||||
ks: 40
|
||||
});
|
||||
|
||||
interface GridItem {
|
||||
@ -116,13 +122,14 @@ interface GridItem {
|
||||
icon: string; // 图标文件名 (不含扩展名)
|
||||
text: string;
|
||||
show: boolean; // 是否显示
|
||||
// 可以添加 permissionKey 等字段
|
||||
permissionKey?: string; // 权限键
|
||||
path?: string; // 页面路径
|
||||
}
|
||||
|
||||
interface Section {
|
||||
id: number | string;
|
||||
title: string;
|
||||
permissionKey?: string; // 整个区域的权限编码
|
||||
items: GridItem[];
|
||||
}
|
||||
|
||||
@ -131,8 +138,30 @@ const handleLogout = () => {
|
||||
uni.showModal({
|
||||
title: "确认退出",
|
||||
content: "确定要退出登录吗?",
|
||||
success: (res) => {
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
// 从userdata中获取用户ID
|
||||
const userDataStr = uni.getStorageSync('app-user');
|
||||
let userData = null;
|
||||
|
||||
if (userDataStr) {
|
||||
try {
|
||||
userData = typeof userDataStr === 'string' ? JSON.parse(userDataStr) : userDataStr;
|
||||
} catch (e) {
|
||||
// 静默处理错误
|
||||
}
|
||||
}
|
||||
|
||||
// 清空用户open_id
|
||||
const userId = userData?.userdata?.id || userData?.id;
|
||||
if (userId) {
|
||||
await clearUserOpenIdApi({ userId: userId });
|
||||
}
|
||||
} catch (error) {
|
||||
// 静默处理错误
|
||||
}
|
||||
|
||||
// 清除用户数据
|
||||
logout();
|
||||
// 跳转到登录页面
|
||||
@ -149,62 +178,46 @@ const sections = reactive<Section[]>([
|
||||
{
|
||||
id: "routine",
|
||||
title: "常规",
|
||||
permissionKey: "routine", // 整个常规区域的权限编码
|
||||
items: [
|
||||
// {
|
||||
// id: 10,
|
||||
// text: "一师一策",
|
||||
// icon: "clipboardfill",
|
||||
// path: "/pages/view/routine/yishiyice/index",
|
||||
// show: true,
|
||||
// },
|
||||
{
|
||||
id: "r2",
|
||||
id: "r1",
|
||||
icon: "stack-fill",
|
||||
text: "教学资源",
|
||||
show: true,
|
||||
permissionKey: "routine-jszr", // 教学资源权限编码
|
||||
path: "/pages/view/routine/JiaoXueZiYuan/index",
|
||||
},
|
||||
{
|
||||
id: "r5",
|
||||
id: "r2",
|
||||
icon: "file-mark-fill",
|
||||
text: "积分评价",
|
||||
show: true,
|
||||
permissionKey: "routine-jfpj", // 积分评价权限编码
|
||||
path: "/pages/view/routine/JiFenPingJia/JiFenPingJia",
|
||||
},
|
||||
// {
|
||||
// id: "r3",
|
||||
// icon: "file-list-3-fil",
|
||||
// text: "活动资源",
|
||||
// show: true,
|
||||
// path: "/pages/view/routine/HuoDongZiYuan/index",
|
||||
// },
|
||||
{
|
||||
id: "r3",
|
||||
icon: "file-list-3-fil",
|
||||
text: "工作量",
|
||||
show: true,
|
||||
permissionKey: "routine-gzl", // 工作量权限编码
|
||||
path: "/pages/view/routine/GongZuoLiang/index",
|
||||
},
|
||||
|
||||
// {
|
||||
// id: "r4",
|
||||
// icon: "file-paper-2-fill",
|
||||
// text: "公文流转",
|
||||
// show: true,
|
||||
// path: "/pages/view/routine/GongWenLiuZhuan/index",
|
||||
// },
|
||||
{
|
||||
id: "r4",
|
||||
icon: "file-paper-2-fill",
|
||||
text: "任教任职",
|
||||
show: true,
|
||||
permissionKey: "routine-rzrj", // 任教任职权限编码
|
||||
path: "/pages/view/routine/RengJiaoRengZhi/index",
|
||||
},
|
||||
{
|
||||
id: "r9",
|
||||
id: "r5",
|
||||
icon: "hc-fill",
|
||||
text: "食堂巡查",
|
||||
show: true,
|
||||
permissionKey: "routine-stxc", // 食堂巡查权限编码
|
||||
path: "/pages/view/routine/ShiTangXunCha/index",
|
||||
},
|
||||
{
|
||||
@ -212,6 +225,7 @@ const sections = reactive<Section[]>([
|
||||
icon: "pass-pending-fill",
|
||||
text: "课服巡查",
|
||||
show: true,
|
||||
permissionKey: "routine-kfxc", // 课服巡查权限编码
|
||||
path: "/pages/view/routine/kefuxuncha/xcXkList",
|
||||
},
|
||||
{
|
||||
@ -219,21 +233,23 @@ const sections = reactive<Section[]>([
|
||||
icon: "file-text-fill-2",
|
||||
text: "课程介绍",
|
||||
show: true,
|
||||
permissionKey: "routine-kcjs", // 课程介绍权限编码
|
||||
path: "/pages/base/groupTeaching/xkList",
|
||||
},
|
||||
|
||||
{
|
||||
id: "r8",
|
||||
icon: "draftfill",
|
||||
text: "选课点名",
|
||||
show: true,
|
||||
permissionKey: "routine-kcdm", // 选课点名权限编码
|
||||
path: "/pages/base/groupTeaching/dmXkList",
|
||||
},
|
||||
{
|
||||
id: "r8",
|
||||
id: "r9",
|
||||
icon: "draftfill",
|
||||
text: "发布接龙",
|
||||
show: true,
|
||||
permissionKey: "routine-bjjl", // 发布接龙权限编码
|
||||
path: "/pages/view/notice/index",
|
||||
},
|
||||
{
|
||||
@ -241,6 +257,7 @@ const sections = reactive<Section[]>([
|
||||
icon: "draftfill",
|
||||
text: "签到发布",
|
||||
show: true,
|
||||
permissionKey: "routine-qdfb", // 签到发布权限编码
|
||||
path: "/pages/view/routine/qd/index",
|
||||
},
|
||||
],
|
||||
@ -248,12 +265,14 @@ const sections = reactive<Section[]>([
|
||||
{
|
||||
id: "home-school",
|
||||
title: "家校",
|
||||
permissionKey: "home", // 整个家校区域的权限编码
|
||||
items: [
|
||||
{
|
||||
id: "hs1",
|
||||
icon: "file-text-fill",
|
||||
text: "教师课表",
|
||||
show: true,
|
||||
permissionKey: "home-jskb", // 教师课表权限编码
|
||||
path: "/pages/view/homeSchool/JiaoShiKeBiao",
|
||||
},
|
||||
{
|
||||
@ -261,6 +280,7 @@ const sections = reactive<Section[]>([
|
||||
icon: "file-text-fill-2",
|
||||
text: "班级课表",
|
||||
show: true,
|
||||
permissionKey: "home-bjkb", // 班级课表权限编码
|
||||
path: "/pages/view/homeSchool/BanJiKeBiao",
|
||||
},
|
||||
{
|
||||
@ -268,20 +288,15 @@ const sections = reactive<Section[]>([
|
||||
icon: "file-paper-2-fill",
|
||||
text: "家长通讯录",
|
||||
show: true,
|
||||
permissionKey: "home-jztxl", // 家长通讯录权限编码
|
||||
path: "/pages/view/homeSchool/parentAddressBook/index",
|
||||
},
|
||||
/*{
|
||||
id: "hs4",
|
||||
icon: "newspaper-fill",
|
||||
text: "通知列表",
|
||||
show: true,
|
||||
path: "/pages/view/notice/index",
|
||||
},*/
|
||||
{
|
||||
id: "hs6",
|
||||
id: "hs4",
|
||||
icon: "filechart2fil",
|
||||
text: "成绩分析",
|
||||
show: true,
|
||||
permissionKey: "home-cjfx", // 成绩分析权限编码
|
||||
path: "/pages/view/homeSchool/ChengJiFenXi",
|
||||
},
|
||||
],
|
||||
@ -289,12 +304,14 @@ const sections = reactive<Section[]>([
|
||||
{
|
||||
id: "hr",
|
||||
title: "人事",
|
||||
permissionKey: "personnel", // 整个人事区域的权限编码
|
||||
items: [
|
||||
{
|
||||
id: "hr1",
|
||||
icon: "draftfill",
|
||||
text: "请假申请",
|
||||
show: true,
|
||||
permissionKey: "personnel-qjsq", // 请假申请权限编码
|
||||
path: "/pages/view/hr/jsQj/index",
|
||||
},
|
||||
{
|
||||
@ -302,6 +319,7 @@ const sections = reactive<Section[]>([
|
||||
icon: "file-user-fill",
|
||||
text: "教师档案",
|
||||
show: true,
|
||||
permissionKey: "personnel-jsda", // 教师档案权限编码
|
||||
path: "/pages/view/hr/teacherProfile/index",
|
||||
},
|
||||
{
|
||||
@ -309,6 +327,7 @@ const sections = reactive<Section[]>([
|
||||
icon: "newspaper-fill",
|
||||
text: "工资条",
|
||||
show: true,
|
||||
permissionKey: "personnel-gzt", // 工资条权限编码
|
||||
path: "/pages/view/hr/salarySlip/index",
|
||||
},
|
||||
],
|
||||
@ -330,9 +349,9 @@ const getIconBgColor = (index: number) => {
|
||||
];
|
||||
return colors[index % colors.length];
|
||||
};
|
||||
|
||||
// 处理网格项点击事件
|
||||
const handleGridItemClick = async (item: GridItem) => {
|
||||
console.log("Clicked item:", item);
|
||||
if (item.text == "教师档案") {
|
||||
showLoading("加载中...");
|
||||
const res = await jsdfindJsByPhoneApi({
|
||||
@ -355,6 +374,19 @@ const handleGridItemClick = async (item: GridItem) => {
|
||||
|
||||
// 初始化
|
||||
onMounted(async () => {
|
||||
// 初始化职务信息
|
||||
await initPositionInfo();
|
||||
|
||||
// 完成加载
|
||||
isLoading.value = false;
|
||||
|
||||
// 强制清除权限缓存,确保数据一致性
|
||||
const { clearPermissionCachePublic } = await import('@/utils/permission');
|
||||
clearPermissionCachePublic();
|
||||
});
|
||||
|
||||
// 初始化职务信息
|
||||
async function initPositionInfo() {
|
||||
let dzZw: any = [];
|
||||
let qtZw: any = [];
|
||||
if (getJs.dzzw && typeof getJs.dzzw == "string") {
|
||||
@ -363,25 +395,40 @@ onMounted(async () => {
|
||||
if (getJs.qtzw && typeof getJs.qtzw == "string") {
|
||||
qtZw = getJs.qtzw.split(",");
|
||||
}
|
||||
if (dzZw && dzZw.length) {
|
||||
const res = await getZwListByLx({ zwlx: "党政职务" });
|
||||
dzZwLabel.value = dzZw
|
||||
.map((zwId: string) => {
|
||||
const zw = res.result.find((zw: any) => zwId == zw.id);
|
||||
return zw ? zw.zwmc : "";
|
||||
})
|
||||
.join(", ");
|
||||
if (dzZw && dzZw.length){
|
||||
const res = await getZwListByLx({ zwlx: '党政职务' });
|
||||
dzZwLabel.value = dzZw.map((zwId: string) => {
|
||||
const zw = res.result.find((zw: any) => zwId == zw.id);
|
||||
return zw ? zw.zwmc : '';
|
||||
}).join(', ');
|
||||
};
|
||||
if (qtZw && qtZw.length){
|
||||
const res = await getZwListByLx({ zwlx: '其他职务' });
|
||||
qtZwLabel.value = qtZw.map((zwId: string) => {
|
||||
const zw = res.result.find((zw: any) => zwId == zw.id);
|
||||
return zw ? zw.zwmc : '';
|
||||
}).join(', ');
|
||||
};
|
||||
}
|
||||
|
||||
// 检查区域权限
|
||||
const hasSectionPermission = (section: Section) => {
|
||||
if (!section.permissionKey) {
|
||||
return true; // 没有权限编码的区域默认显示
|
||||
}
|
||||
if (qtZw && qtZw.length) {
|
||||
const res = await getZwListByLx({ zwlx: "其他职务" });
|
||||
qtZwLabel.value = qtZw
|
||||
.map((zwId: string) => {
|
||||
const zw = res.result.find((zw: any) => zwId == zw.id);
|
||||
return zw ? zw.zwmc : "";
|
||||
})
|
||||
.join(", ");
|
||||
}
|
||||
});
|
||||
return hasPermissionDirect(section.permissionKey);
|
||||
};
|
||||
|
||||
// 直接权限检查函数,避免缓存问题
|
||||
const hasPermissionDirect = (permissionKey: string) => {
|
||||
if (!permissionKey) return true;
|
||||
const userStore = useUserStore();
|
||||
const permissions = userStore.getAuth;
|
||||
if (!permissions || permissions.length === 0) return false;
|
||||
|
||||
const uniquePermissions = [...new Set(permissions)];
|
||||
return uniquePermissions.includes(permissionKey);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@ -391,6 +438,47 @@ onMounted(async () => {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
// 加载状态样式
|
||||
.loading-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.loading-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 3px solid #ffffff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
// 顶部 Header
|
||||
.header-section {
|
||||
position: relative;
|
||||
|
||||
@ -16,65 +16,104 @@ import { onLoad } from "@dcloudio/uni-app";
|
||||
import { useDataStore } from "@/store/modules/data";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { checkOpenId, findJsByPhoneApi } from "@/api/system/login";
|
||||
import { PermissionCacheManager } from "@/utils/permission";
|
||||
|
||||
const { setGlobal } = useDataStore();
|
||||
const { afterLoginAction } = useUserStore();
|
||||
const { setFile, getFile } = useDataStore();
|
||||
const isShow = ref(true);
|
||||
|
||||
function goByJs(js: any) {
|
||||
console.log(js);
|
||||
// if (js.jsType == "助教") {
|
||||
// uni.reLaunch({
|
||||
// url: "/pages/base/groupTeaching/zhujiao",
|
||||
// });
|
||||
// } else {
|
||||
if (js.confirmStatus == "A") {
|
||||
uni.switchTab({
|
||||
url: "/pages/base/message/index",
|
||||
});
|
||||
} else {
|
||||
setFile({
|
||||
...js,
|
||||
...getFile,
|
||||
});
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: "/pages/view/hr/teacherProfile/index",
|
||||
});
|
||||
}, 1500);
|
||||
}
|
||||
// }
|
||||
/**
|
||||
* 强制刷新权限
|
||||
*/
|
||||
async function forceRefreshPermission(changeTime?: string): Promise<void> {
|
||||
try {
|
||||
|
||||
// 重新获取用户权限
|
||||
const userStore = useUserStore();
|
||||
const currentUser = userStore.getUser;
|
||||
|
||||
if (currentUser && currentUser.id) {
|
||||
// 重新调用权限获取接口
|
||||
const { authenticationApi } = await import('@/api/system/login');
|
||||
const result = await authenticationApi({ userId: currentUser.id });
|
||||
|
||||
if (result && result.result) {
|
||||
|
||||
// 直接设置权限并刷新缓存,不调用setAuth避免重复操作
|
||||
userStore.auth = result.result;
|
||||
|
||||
// 手动刷新缓存,传递权限变更时间
|
||||
const { refreshPermissionCache } = await import('@/utils/permission');
|
||||
refreshPermissionCache(result.result, changeTime);
|
||||
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('强制刷新权限失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function goByJs(js: any) {
|
||||
if (js.confirmStatus == "A") {
|
||||
// 跳转到自助服务首页
|
||||
uni.switchTab({
|
||||
url: "/pages/base/service/index",
|
||||
});
|
||||
} else {
|
||||
setFile({
|
||||
...js,
|
||||
...getFile,
|
||||
});
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: "/pages/view/hr/teacherProfile/index",
|
||||
});
|
||||
}, 1500);
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(async (data: any) => {
|
||||
if (data && data.openId) {
|
||||
setGlobal(data);
|
||||
checkOpenId({
|
||||
openId: data.openId,
|
||||
appCode: "JS",
|
||||
})
|
||||
.then(async (res: any) => {
|
||||
if (res.resultCode == 1) {
|
||||
if (res.result) {
|
||||
afterLoginAction(res.result);
|
||||
// 跳转页面
|
||||
goByJs(res.result.js)
|
||||
return;
|
||||
|
||||
try {
|
||||
const res = await checkOpenId({
|
||||
openId: data.openId,
|
||||
appCode: "JS",
|
||||
});
|
||||
|
||||
if (res.resultCode == 1 && res.result) {
|
||||
// 执行登录操作
|
||||
afterLoginAction(res.result);
|
||||
|
||||
// 如果有changeTime参数,更新权限缓存
|
||||
if (data.changeTime) {
|
||||
const { refreshPermissionCache } = await import('@/utils/permission');
|
||||
const userStore = useUserStore();
|
||||
const currentPermissions = userStore.getAuth;
|
||||
|
||||
if (currentPermissions && currentPermissions.length > 0) {
|
||||
refreshPermissionCache(currentPermissions, data.changeTime);
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转页面
|
||||
goByJs(res.result.js);
|
||||
} else {
|
||||
uni.reLaunch({
|
||||
url: "/pages/system/login/login",
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
uni.reLaunch({
|
||||
url: "/pages/system/login/login"
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
uni.reLaunch({
|
||||
url: "/pages/system/login/login"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
uni.reLaunch({
|
||||
url: "/pages/system/login/login"
|
||||
})
|
||||
uni.reLaunch({
|
||||
url: "/pages/system/login/login"
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -151,7 +151,7 @@ async function submit() {
|
||||
const params = {
|
||||
...getJs,
|
||||
...value,
|
||||
sign_file: sign_file.value,
|
||||
signFile: sign_file.value,
|
||||
};
|
||||
setJs(params);
|
||||
const res = await jsConfirmJsDataApi(params);
|
||||
|
||||
@ -3,12 +3,12 @@
|
||||
<view class="container">
|
||||
<uni-card :is-shadow="false" is-full>
|
||||
<view class="header">
|
||||
<text class="score">我的得分: 84</text>
|
||||
<text class="score">我的得分: {{ totalScore }}</text>
|
||||
<view class="status">
|
||||
<text>我的状态: </text>
|
||||
<view class="status-indicator"></view>
|
||||
<view class="review-count" v-if="reviewingCount > 0">
|
||||
{{ reviewingCount }}项在审核
|
||||
<view class="review-count" v-if="reviewingCountComputed > 0">
|
||||
{{ reviewingCountComputed }}项在审核
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -19,7 +19,11 @@
|
||||
<text class="category-header">分类</text>
|
||||
<text class="value-header">分值</text>
|
||||
</view>
|
||||
<view v-if="loading" class="loading-container">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
<view
|
||||
v-else
|
||||
class="list-item"
|
||||
v-for="(item, index) in evaluationItems"
|
||||
:key="index"
|
||||
@ -42,13 +46,13 @@
|
||||
<view class="white-bg-color py-5">
|
||||
<view class="flex-row items-center pb-10 pt-5">
|
||||
<u-button
|
||||
text="上传个人荣誉"
|
||||
text="个人荣誉申请"
|
||||
class="ml-15 mr-7"
|
||||
:plain="true"
|
||||
type="primary"
|
||||
@click="scgrry"
|
||||
/>
|
||||
<u-button
|
||||
text="上传公开课获奖"
|
||||
text="公开课获奖申请"
|
||||
class="mr-15 mr-7"
|
||||
type="primary"
|
||||
@click="scgkkhj"
|
||||
@ -60,45 +64,213 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from "vue";
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { jsFindPageJfApi, inspectItemFindAllApi } from "@/api/base/server";
|
||||
|
||||
const evaluationItems = ref([
|
||||
{ category: "师德师风", value: 8, isUnderReview: false },
|
||||
{ category: "考勤管理", value: 4, isUnderReview: true },
|
||||
{ category: "资料上交", value: 1, isUnderReview: false },
|
||||
{ category: "教学质量", value: 1, isUnderReview: true },
|
||||
{ category: "教育科研课题", value: 8, isUnderReview: false },
|
||||
{ category: "个人材料发表和获奖", value: 9, isUnderReview: true },
|
||||
{ category: "展示交流和比赛", value: 4, isUnderReview: false },
|
||||
{ category: "工作室活动", value: 5, isUnderReview: false },
|
||||
{ category: "荣誉", value: 6, isUnderReview: true },
|
||||
{ category: "辅导学生", value: 7, isUnderReview: false },
|
||||
{ category: "安全管理", value: 2, isUnderReview: false },
|
||||
{ category: "班级常规管理", value: 7, isUnderReview: true },
|
||||
]);
|
||||
// 响应式数据
|
||||
const evaluationItems = ref([]);
|
||||
const totalScore = ref(0);
|
||||
const loading = ref(false);
|
||||
|
||||
// 计算正在审核的项目数量
|
||||
const reviewingCount = computed(() => {
|
||||
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 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);
|
||||
console.log('教师信息:', userData.jsData);
|
||||
} else {
|
||||
console.error('未找到教师ID信息');
|
||||
uni.showToast({
|
||||
title: '未找到教师信息',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析用户数据失败:', error);
|
||||
uni.showToast({
|
||||
title: '用户数据解析失败',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
console.error('未找到用户数据');
|
||||
uni.showToast({
|
||||
title: '未找到用户数据',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置查询参数,获取当前教师的积分数据
|
||||
const params = {
|
||||
startTime: new Date().getFullYear() + '-01-01', // 当年开始时间
|
||||
endTime: new Date().getFullYear() + '-12-31', // 当年结束时间
|
||||
pageSize: 100, // 增大页面大小,确保能获取到当前教师数据
|
||||
pageNum: 1,
|
||||
id: teacherId // 使用教师ID过滤
|
||||
};
|
||||
|
||||
const res = await jsFindPageJfApi(params);
|
||||
console.log('查询参数:', params);
|
||||
console.log('当前教师ID:', teacherId);
|
||||
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;
|
||||
});
|
||||
|
||||
evaluationItems.value = items;
|
||||
totalScore.value = total;
|
||||
|
||||
console.log('处理后的积分数据:', {
|
||||
items: items,
|
||||
totalScore: total,
|
||||
reviewingCount: reviewingCountComputed.value
|
||||
});
|
||||
} else {
|
||||
console.warn('查询结果为空或格式不正确');
|
||||
uni.showToast({
|
||||
title: '未获取到积分数据',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取教师积分失败:', error);
|
||||
uni.showToast({
|
||||
title: '获取积分数据失败',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const url = `/pages/view/routine/JiFenPingJia/detail?inspectItemId=${item.id}`;
|
||||
uni.navigateTo({
|
||||
url: `/pages/view/routine/JiFenPingJia/detail?id=${item.id}`,
|
||||
url: url,
|
||||
});
|
||||
}
|
||||
|
||||
function scgrry() {
|
||||
uni.navigateTo({
|
||||
url: `/pages/view/hr/teacherProfile/PersonalHonor`,
|
||||
url: `/pages/view/routine/JiFenPingJia/PersonalHonor`,
|
||||
});
|
||||
}
|
||||
|
||||
function scgkkhj() {
|
||||
uni.navigateTo({
|
||||
url: `/pages/view/hr/teacherProfile/PublicClassAwards`,
|
||||
url: `/pages/view/routine/JiFenPingJia/PublicClassAwards`,
|
||||
});
|
||||
}
|
||||
|
||||
// 页面加载时获取数据
|
||||
onMounted(() => {
|
||||
loadTeacherScore();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@ -147,6 +319,18 @@ function scgkkhj() {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40rpx 0;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
// Remove default card padding if needed
|
||||
::v-deep .uni-card .uni-card__content {
|
||||
padding: 10px 15px !important; // Overwrite default padding
|
||||
|
||||
370
src/pages/view/routine/JiFenPingJia/PersonalHonor.vue
Normal file
370
src/pages/view/routine/JiFenPingJia/PersonalHonor.vue
Normal file
@ -0,0 +1,370 @@
|
||||
<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 { evaluationSqApi, evaluationCxtjApi } from "@/api/base/evaluationApi";
|
||||
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: "提交中..." });
|
||||
|
||||
// 调用积分申请API
|
||||
const result = await evaluationSqApi(params);
|
||||
|
||||
if (result && result.resultCode === 1) {
|
||||
showToast({ title: "提交成功", icon: "success" });
|
||||
// 返回上一页
|
||||
uni.navigateBack();
|
||||
} else {
|
||||
showToast({
|
||||
title: result?.resultMsg || "提交失败",
|
||||
icon: "none"
|
||||
});
|
||||
}
|
||||
} 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>
|
||||
284
src/pages/view/routine/JiFenPingJia/PublicClassAwards.vue
Normal file
284
src/pages/view/routine/JiFenPingJia/PublicClassAwards.vue
Normal file
@ -0,0 +1,284 @@
|
||||
<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>
|
||||
@ -46,14 +46,14 @@
|
||||
</view>
|
||||
|
||||
<!-- 证明材料 -->
|
||||
<view class="detail-item file-section" v-if="detailData.files.length > 0">
|
||||
<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="previewFile(file)"
|
||||
@click="handlePreviewFile(file)"
|
||||
>
|
||||
<uni-icons type="paperplane" size="16" color="#409eff" />
|
||||
<text class="file-name">{{ file.name }}</text>
|
||||
@ -61,7 +61,7 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 转手机端按钮 -->
|
||||
<!-- 返回按钮 -->
|
||||
<view class="bottom-actions">
|
||||
<button class="mobile-btn" @click="goBack">返回</button>
|
||||
</view>
|
||||
@ -71,6 +71,18 @@
|
||||
|
||||
<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;
|
||||
@ -79,77 +91,208 @@ interface DetailData {
|
||||
department: string;
|
||||
evaluationDate: string;
|
||||
score: number | string;
|
||||
files: Array<{ name: string; url: string }>;
|
||||
files: Array<{ name: string; url: string; resSuf?: string }>;
|
||||
}
|
||||
|
||||
// 详情数据
|
||||
const detailData = ref<DetailData>({
|
||||
evaluationType: "考核评价",
|
||||
scoreType: "加分",
|
||||
scoreValue: 1,
|
||||
department: "教科处",
|
||||
evaluationDate: "2025-06-08",
|
||||
score: 90,
|
||||
files: [
|
||||
{ name: "教学成果证明.pdf", url: "/files/certificate.pdf" },
|
||||
{ name: "获奖证书.jpg", url: "/files/award.jpg" },
|
||||
],
|
||||
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 previewFile = (file: { name: string; url: string }) => {
|
||||
// 文件预览处理(参考教学资源的预览方式)
|
||||
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
|
||||
});
|
||||
|
||||
// 根据文件类型进行预览
|
||||
const ext = file.name.split(".").pop()?.toLowerCase();
|
||||
|
||||
if (["jpg", "jpeg", "png", "gif"].includes(ext || "")) {
|
||||
if (isVideo(fileExt)) {
|
||||
// 视频预览
|
||||
previewVideo(fileUrl, fileName)
|
||||
.then(() => {
|
||||
console.log('视频预览成功');
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('视频预览失败:', error);
|
||||
// 预览失败时尝试下载
|
||||
handleDownloadFile(file);
|
||||
});
|
||||
} else if (isImage(fileExt)) {
|
||||
// 图片预览
|
||||
uni.previewImage({
|
||||
urls: [file.url],
|
||||
current: file.url,
|
||||
});
|
||||
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 {
|
||||
// 其他文件类型提示下载
|
||||
uni.showModal({
|
||||
title: "提示",
|
||||
content: `是否下载文件: ${file.name}?`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// TODO: 实现文件下载逻辑
|
||||
uni.showToast({
|
||||
title: "开始下载",
|
||||
icon: "success",
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
// 不可预览文件,直接下载
|
||||
handleDownloadFile(file);
|
||||
}
|
||||
};
|
||||
|
||||
// 转手机端
|
||||
const transferToMobile = () => {
|
||||
// 文件下载处理
|
||||
const handleDownloadFile = (file: { name: string; url: string }) => {
|
||||
const fileUrl = imagUrl(file.url);
|
||||
const fileName = file.name;
|
||||
|
||||
uni.showModal({
|
||||
title: "提示",
|
||||
content: "确定要转到手机端处理吗?",
|
||||
content: `是否下载文件: ${fileName}?`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// TODO: 实现转手机端逻辑
|
||||
uni.showToast({
|
||||
title: "已转至手机端",
|
||||
icon: "success",
|
||||
});
|
||||
downloadFile(fileUrl, fileName)
|
||||
.then(() => {
|
||||
uni.showToast({
|
||||
title: "下载成功",
|
||||
icon: "success",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('下载失败:', error);
|
||||
uni.showToast({
|
||||
title: "下载失败",
|
||||
icon: "none",
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 页面加载时可以获取详情数据
|
||||
console.log("页面加载完成");
|
||||
// onLoad会自动执行,不需要在这里调用
|
||||
});
|
||||
|
||||
// 调用onLoad生命周期
|
||||
onLoad((options) => {
|
||||
console.log('onLoad被调用,参数:', options);
|
||||
|
||||
// 从URL获取inspectItemId
|
||||
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>
|
||||
|
||||
@ -225,11 +368,17 @@ onMounted(() => {
|
||||
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;
|
||||
}
|
||||
@ -259,7 +408,7 @@ onMounted(() => {
|
||||
.mobile-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background-color: #ff4757;
|
||||
background-color: #409eff;
|
||||
color: #ffffff;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
@ -270,7 +419,7 @@ onMounted(() => {
|
||||
justify-content: center;
|
||||
|
||||
&:active {
|
||||
background-color: #ff3742;
|
||||
background-color: #337ecc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
700
src/pages/view/routine/JiaoXueZiYuan/add-resource.vue
Normal file
700
src/pages/view/routine/JiaoXueZiYuan/add-resource.vue
Normal file
@ -0,0 +1,700 @@
|
||||
<template>
|
||||
<view class="add-resource-page">
|
||||
<!-- 页面头部 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<uni-icons type="left" size="20" color="#333"></uni-icons>
|
||||
<text class="back-text">返回</text>
|
||||
</view>
|
||||
<view class="header-title">上传资源</view>
|
||||
<view class="header-right"></view>
|
||||
</view>
|
||||
|
||||
<!-- 表单内容 -->
|
||||
<scroll-view scroll-y class="form-scroll-view">
|
||||
<view class="form-container">
|
||||
<!-- 资源目录 -->
|
||||
<view class="info-card">
|
||||
<view class="form-item">
|
||||
<text class="form-label">资源目录 <text class="required">*</text></text>
|
||||
<picker
|
||||
mode="selector"
|
||||
:range="treeData"
|
||||
range-key="title"
|
||||
@change="handleResourceTypeChange"
|
||||
>
|
||||
<view class="picker-row">
|
||||
<text :class="{ placeholder: !formData.resourType }">
|
||||
{{ getResourceTypeText() || '请选择资源目录' }}
|
||||
</text>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 课题名称 -->
|
||||
<view class="info-card">
|
||||
<view class="form-item">
|
||||
<text class="form-label">课题名称 <text class="required">*</text></text>
|
||||
<uni-easyinput
|
||||
v-model="formData.resourName"
|
||||
placeholder="请输入课题名称"
|
||||
:inputBorder="false"
|
||||
class="content-input"
|
||||
></uni-easyinput>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 课时 -->
|
||||
<view class="info-card">
|
||||
<view class="form-item">
|
||||
<text class="form-label">课时 <text class="required">*</text></text>
|
||||
<uni-easyinput
|
||||
v-model="formData.hour"
|
||||
placeholder="请输入课时"
|
||||
type="number"
|
||||
:inputBorder="false"
|
||||
class="content-input"
|
||||
></uni-easyinput>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 资源类别 -->
|
||||
<view class="info-card">
|
||||
<view class="form-item">
|
||||
<text class="form-label">资源类别 <text class="required">*</text></text>
|
||||
<picker
|
||||
mode="selector"
|
||||
:range="categoryOptions"
|
||||
range-key="label"
|
||||
@change="handleCategoryChange"
|
||||
>
|
||||
<view class="picker-row">
|
||||
<text :class="{ placeholder: !formData.category }">
|
||||
{{ getCategoryText() || '请选择资源类别' }}
|
||||
</text>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 资源描述 -->
|
||||
<view class="info-card">
|
||||
<view class="form-item">
|
||||
<text class="form-label">资源描述</text>
|
||||
<uni-easyinput
|
||||
type="textarea"
|
||||
autoHeight
|
||||
v-model="formData.content"
|
||||
placeholder="请输入资源描述"
|
||||
:inputBorder="false"
|
||||
class="content-input"
|
||||
></uni-easyinput>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 上传资源 -->
|
||||
<view class="info-card">
|
||||
<view class="form-item">
|
||||
<text class="form-label">上传资源 <text class="required">*</text></text>
|
||||
<view class="attachment-list">
|
||||
<view
|
||||
v-for="(att, index) in formData.attachments"
|
||||
:key="index"
|
||||
class="attachment-item"
|
||||
>
|
||||
<uni-icons
|
||||
:type="getAttachmentIcon(att.type)"
|
||||
size="20"
|
||||
color="#666"
|
||||
class="attachment-icon"
|
||||
></uni-icons>
|
||||
<text class="attachment-name" @click="previewAttachment(att)">{{
|
||||
att.name
|
||||
}}</text>
|
||||
<uni-icons
|
||||
type="closeempty"
|
||||
size="18"
|
||||
color="#999"
|
||||
class="remove-icon"
|
||||
@click="removeAttachment(index)"
|
||||
></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view class="add-attachment-placeholder" @click="addAttachment">
|
||||
<view class="add-icon"
|
||||
><uni-icons type="plusempty" size="20" color="#ccc"></uni-icons
|
||||
></view>
|
||||
<text class="placeholder-text">添加图文/视频/文件</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部提交按钮 -->
|
||||
<view class="bottom-actions">
|
||||
<button class="action-btn cancel-btn" @click="goBack">
|
||||
取消
|
||||
</button>
|
||||
<button class="action-btn confirm-btn" @click="handleSubmitForm" :disabled="isSubmitting">
|
||||
{{ isSubmitting ? '提交中...' : '提交' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { resourcesSaveApi } from "@/api/base/server";
|
||||
import { typesFindTreeApi } from "@/api/base/server";
|
||||
import { attachmentUpload } from "@/api/system/upload";
|
||||
import { imagUrl } from "@/utils";
|
||||
import { useDicStore } from "@/store/modules/dic";
|
||||
|
||||
const { findByPid } = useDicStore();
|
||||
|
||||
interface Attachment {
|
||||
name: string;
|
||||
type: string;
|
||||
url: string;
|
||||
size?: number;
|
||||
path?: string;
|
||||
id?: number;
|
||||
}
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
resourName: '',
|
||||
resourType: '',
|
||||
category: '',
|
||||
content: '',
|
||||
fileId: '',
|
||||
filePath: '',
|
||||
resSuf: '',
|
||||
hour: '',
|
||||
id: '',
|
||||
fileName: '',
|
||||
attachments: [] as Attachment[] // 新增附件列表
|
||||
});
|
||||
|
||||
// 树形数据
|
||||
const treeData = ref([]);
|
||||
|
||||
// 提交状态
|
||||
const isSubmitting = ref(false);
|
||||
|
||||
// 资源类别选项 - 从字典表获取
|
||||
const categoryOptions = ref([]);
|
||||
|
||||
// 加载资源类别数据
|
||||
const loadCategoryOptions = async () => {
|
||||
try {
|
||||
const result = await findByPid({ pid: 1391443399 });
|
||||
if (result && Array.isArray(result)) {
|
||||
categoryOptions.value = result.map(item => ({
|
||||
value: item.dictionaryCode,
|
||||
label: item.dictionaryValue
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载资源类别失败:', error);
|
||||
// 如果加载失败,使用默认选项
|
||||
categoryOptions.value = [
|
||||
{ value: '1', label: '课件' },
|
||||
{ value: '2', label: '教案' },
|
||||
{ value: '3', label: '学案' },
|
||||
{ value: '4', label: '作业' },
|
||||
{ value: '5', label: '试卷' },
|
||||
{ value: '6', label: '教材' },
|
||||
{ value: '7', label: '示范课' },
|
||||
{ value: '8', label: '音视频合集' },
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
// 返回上一页
|
||||
const goBack = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
// 资源目录选择
|
||||
const handleResourceTypeChange = (e: any) => {
|
||||
const index = e.detail.value;
|
||||
const selectedItem = treeData.value[index];
|
||||
formData.resourType = selectedItem.key;
|
||||
};
|
||||
|
||||
// 资源类别选择
|
||||
const handleCategoryChange = (e: any) => {
|
||||
const index = e.detail.value;
|
||||
const selectedItem = categoryOptions.value[index];
|
||||
formData.category = selectedItem.value;
|
||||
};
|
||||
|
||||
// 添加附件
|
||||
const addAttachment = () => {
|
||||
uni.chooseFile({
|
||||
count: 5,
|
||||
type: 'all',
|
||||
success: async (res) => {
|
||||
const tempFiles = res.tempFiles;
|
||||
if (Array.isArray(tempFiles) && tempFiles.length > 0) {
|
||||
uni.showLoading({ title: '上传中...' });
|
||||
|
||||
try {
|
||||
for (const file of tempFiles) {
|
||||
const fileInfo = file as any;
|
||||
let fileType = 'file';
|
||||
const fileName = fileInfo.name || '';
|
||||
const fileExtension = fileName.split('.').pop()?.toLowerCase();
|
||||
|
||||
// 根据文件扩展名判断类型
|
||||
if (['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'].includes(fileExtension || '')) {
|
||||
fileType = 'image';
|
||||
} else if (['mp4', 'mov', 'avi', 'wmv', 'flv'].includes(fileExtension || '')) {
|
||||
fileType = 'video';
|
||||
} else if (['mp3', 'wav', 'aac', 'ogg'].includes(fileExtension || '')) {
|
||||
fileType = 'audio';
|
||||
}
|
||||
|
||||
// 根据MIME类型判断
|
||||
if (fileInfo.type && typeof fileInfo.type === 'string' &&
|
||||
(fileInfo.type.startsWith('image/') || fileInfo.type.startsWith('video/') || fileInfo.type.startsWith('audio/'))) {
|
||||
fileType = fileInfo.type.split('/')[0];
|
||||
}
|
||||
|
||||
// 先添加到附件列表
|
||||
formData.attachments.push({
|
||||
name: fileName,
|
||||
type: fileType,
|
||||
url: '',
|
||||
size: fileInfo.size,
|
||||
path: fileInfo.path,
|
||||
id: Date.now()
|
||||
});
|
||||
|
||||
// 上传文件
|
||||
await uploadFile(fileInfo);
|
||||
}
|
||||
|
||||
uni.showToast({ title: '附件上传完成', icon: 'success' });
|
||||
} catch (error) {
|
||||
console.error('附件上传失败:', error);
|
||||
uni.showToast({ title: '附件上传失败', icon: 'error' });
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('选择附件失败:', err);
|
||||
if (err.errMsg && !err.errMsg.includes('cancel')) {
|
||||
uni.showToast({ title: '选择附件失败', icon: 'none' });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 移除附件
|
||||
const removeAttachment = (index: number) => {
|
||||
formData.attachments.splice(index, 1);
|
||||
};
|
||||
|
||||
// 预览附件
|
||||
const previewAttachment = (attachment: Attachment) => {
|
||||
// 如果是图片类型,可以预览
|
||||
if (attachment.type === 'image') {
|
||||
const fullUrl = imagUrl(attachment.url);
|
||||
uni.previewImage({
|
||||
urls: [fullUrl],
|
||||
current: fullUrl,
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: `预览 ${attachment.name} 功能待实现`,
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 获取附件图标
|
||||
const getAttachmentIcon = (type: string): string => {
|
||||
if (type === 'image') return 'image';
|
||||
if (type === 'video') return 'videocam';
|
||||
if (type === 'audio') return 'mic';
|
||||
return 'paperclip';
|
||||
};
|
||||
|
||||
// 上传文件
|
||||
const uploadFile = async (file: any) => {
|
||||
uni.showLoading({ title: '上传中...' });
|
||||
|
||||
try {
|
||||
// 使用 attachmentUpload 接口
|
||||
const uploadResult: any = await attachmentUpload(file.path as any);
|
||||
|
||||
if (uploadResult.resultCode === 1 && uploadResult.result && uploadResult.result.length > 0) {
|
||||
// 保存原始的 filePath(用于提交到服务器)
|
||||
const originalPath = uploadResult.result[0].filePath;
|
||||
const fileId = uploadResult.result[0].id;
|
||||
const fileType = uploadResult.result[0].fileType;
|
||||
|
||||
// 更新最后一个附件的信息
|
||||
const lastAttachment = formData.attachments[formData.attachments.length - 1];
|
||||
if (lastAttachment) {
|
||||
lastAttachment.url = originalPath;
|
||||
lastAttachment.id = fileId;
|
||||
}
|
||||
|
||||
// 同时更新原有的文件信息(保持兼容性)
|
||||
formData.fileId = fileId;
|
||||
formData.filePath = originalPath;
|
||||
formData.resSuf = fileType;
|
||||
formData.fileName = file.name;
|
||||
|
||||
uni.showToast({ title: '上传成功', icon: 'success' });
|
||||
} else {
|
||||
throw new Error('上传失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('上传失败:', error);
|
||||
uni.showToast({ title: '上传失败', icon: 'none' });
|
||||
// 移除上传失败的附件
|
||||
formData.attachments.pop();
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
const handleSubmitForm = async () => {
|
||||
// 表单验证
|
||||
if (!formData.resourName.trim()) {
|
||||
uni.showToast({ title: '请输入课题名称', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.resourType) {
|
||||
uni.showToast({ title: '请选择资源目录', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.hour.trim()) {
|
||||
uni.showToast({ title: '请输入课时', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.category) {
|
||||
uni.showToast({ title: '请选择资源类别', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (formData.attachments.length === 0) {
|
||||
uni.showToast({ title: '请上传资源文件', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSubmitting.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
isSubmitting.value = true;
|
||||
|
||||
try {
|
||||
const params = {
|
||||
resourName: formData.resourName,
|
||||
resourType: formData.resourType,
|
||||
category: formData.category,
|
||||
remark: formData.content,
|
||||
resourId: formData.fileId, // 保留原有的文件ID
|
||||
resourUrl: formData.filePath, // 保留原有的文件路径
|
||||
resSuf: formData.resSuf, // 保留原有的文件后缀
|
||||
hour: formData.hour,
|
||||
id: formData.id
|
||||
};
|
||||
|
||||
console.log('提交参数:', params);
|
||||
|
||||
const result = await resourcesSaveApi(params);
|
||||
|
||||
if (result.resultCode === 1) {
|
||||
uni.showToast({ title: '操作成功', icon: 'success' });
|
||||
// 返回上一页并刷新列表
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
// 通过事件总线通知列表页面刷新
|
||||
uni.$emit('refreshResourceList');
|
||||
}, 1500);
|
||||
} else {
|
||||
uni.showToast({ title: '操作失败', icon: 'none' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交失败:', error);
|
||||
uni.showToast({ title: '操作失败,请重试', icon: 'none' });
|
||||
} finally {
|
||||
isSubmitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 重置表单数据
|
||||
const resetFormData = () => {
|
||||
formData.resourName = '';
|
||||
formData.resourType = '';
|
||||
formData.category = '';
|
||||
formData.content = '';
|
||||
formData.fileId = '';
|
||||
formData.filePath = '';
|
||||
formData.resSuf = '';
|
||||
formData.hour = '';
|
||||
formData.id = '';
|
||||
formData.fileName = '';
|
||||
formData.attachments = []; // 重置附件列表
|
||||
};
|
||||
|
||||
// 加载树形数据
|
||||
const loadTreeData = async () => {
|
||||
try {
|
||||
const res = await typesFindTreeApi();
|
||||
treeData.value = res.result || [];
|
||||
console.log('树形数据加载完成:', treeData.value);
|
||||
} catch (error) {
|
||||
console.error('加载树形数据失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取资源目录文本
|
||||
const getResourceTypeText = () => {
|
||||
const selectedItem = treeData.value.find(item => item.key === formData.resourType);
|
||||
return selectedItem ? selectedItem.title : '';
|
||||
};
|
||||
|
||||
// 获取资源类别文本
|
||||
const getCategoryText = () => {
|
||||
const selectedItem = categoryOptions.value.find(item => item.value === formData.category);
|
||||
return selectedItem ? selectedItem.label : '';
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
loadTreeData();
|
||||
loadCategoryOptions(); // 加载资源类别选项
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.add-resource-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f4f5f7;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.back-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 80rpx;
|
||||
}
|
||||
|
||||
.form-scroll-view {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
background: white;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 10rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: #ff3b30;
|
||||
}
|
||||
|
||||
.content-input {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
|
||||
:deep(.uni-easyinput__content) {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
:deep(.uni-easyinput__content-input) {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
:deep(.uni-easyinput__placeholder-class) {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.picker-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
color: #333;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.attachment-list {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.attachment-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 8px;
|
||||
border: 1px solid #e9ecef;
|
||||
|
||||
.attachment-icon {
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.attachment-name {
|
||||
flex-grow: 1;
|
||||
font-size: 14px;
|
||||
color: #495057;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.remove-icon {
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
opacity: 0.7;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
color: #dc3545 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-attachment-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px dashed #d5d8de;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
background-color: #f8f8f8;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
.add-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 1px solid #d5d8de;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 12px;
|
||||
background-color: #fff;
|
||||
|
||||
.uni-icons {
|
||||
color: #999 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-actions {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
padding: 30rpx;
|
||||
background: white;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
flex: 1;
|
||||
padding: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
background: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
flex: 1;
|
||||
padding: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
background: linear-gradient(135deg, #007aff 0%, #0056cc 100%);
|
||||
color: white;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 122, 255, 0.3);
|
||||
}
|
||||
|
||||
.confirm-btn:disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
@ -97,10 +97,20 @@ const selectCategory = (type: any) => {
|
||||
}
|
||||
|
||||
const goTo = function () {
|
||||
setData({
|
||||
resourceType: curCe.value.key,
|
||||
const params = {
|
||||
resourType: curCe.value.key, // 修改参数名为resourType,与后端一致
|
||||
category: curCategory.value.key,
|
||||
};
|
||||
|
||||
console.log('选择的参数:', {
|
||||
科目: curType.value?.title,
|
||||
年级: curNj.value?.title,
|
||||
上下册: curCe.value?.title,
|
||||
资源类型: curCategory.value?.label,
|
||||
传递参数: params
|
||||
});
|
||||
|
||||
setData(params);
|
||||
uni.navigateTo({
|
||||
url: `/pages/view/routine/JiaoXueZiYuan/indexList`
|
||||
});
|
||||
|
||||
@ -88,146 +88,22 @@
|
||||
|
||||
<!-- 底部上传按钮 -->
|
||||
<template #bottom>
|
||||
<view class="bottom-actions">
|
||||
<button class="action-btn upload-btn" @click="showAddResourceModal">
|
||||
<uni-icons type="plus" size="20" color="#fff"></uni-icons>
|
||||
上传资源
|
||||
</button>
|
||||
<view class="flex-row items-center pb-10 pt-5">
|
||||
<u-button
|
||||
text="上传资源"
|
||||
class="mx-15"
|
||||
type="primary"
|
||||
@click="navigateToAddResource"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
</BasicListLayout>
|
||||
|
||||
<!-- 新增资源弹窗 -->
|
||||
<uni-popup ref="addResourcePopup" type="center" :mask-click="false">
|
||||
<view class="popup-content">
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">{{ modalTitle }}</text>
|
||||
<uni-icons type="close" size="20" color="#999" @click="closeAddResourceModal"></uni-icons>
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-y class="form-scroll-view">
|
||||
<view class="form-container">
|
||||
<!-- 资源目录 -->
|
||||
<view class="info-card">
|
||||
<view class="form-item">
|
||||
<text class="form-label">资源目录 <text class="required">*</text></text>
|
||||
<picker
|
||||
mode="selector"
|
||||
:range="treeData"
|
||||
range-key="title"
|
||||
@change="handleResourceTypeChange"
|
||||
>
|
||||
<view class="picker-row">
|
||||
<text :class="{ placeholder: !formData.resourType }">
|
||||
{{ getResourceTypeText() || '请选择资源目录' }}
|
||||
</text>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 课题名称 -->
|
||||
<view class="info-card">
|
||||
<view class="form-item">
|
||||
<text class="form-label">课题名称 <text class="required">*</text></text>
|
||||
<uni-easyinput
|
||||
v-model="formData.resourName"
|
||||
placeholder="请输入课题名称"
|
||||
:inputBorder="false"
|
||||
class="content-input"
|
||||
></uni-easyinput>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 课时 -->
|
||||
<view class="info-card">
|
||||
<view class="form-item">
|
||||
<text class="form-label">课时 <text class="required">*</text></text>
|
||||
<uni-easyinput
|
||||
v-model="formData.hour"
|
||||
placeholder="请输入课时"
|
||||
type="number"
|
||||
:inputBorder="false"
|
||||
class="content-input"
|
||||
></uni-easyinput>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 资源类别 -->
|
||||
<view class="info-card">
|
||||
<view class="form-item">
|
||||
<text class="form-label">资源类别 <text class="required">*</text></text>
|
||||
<picker
|
||||
mode="selector"
|
||||
:range="categoryOptions"
|
||||
range-key="label"
|
||||
@change="handleCategoryChange"
|
||||
>
|
||||
<view class="picker-row">
|
||||
<text :class="{ placeholder: !formData.category }">
|
||||
{{ getCategoryText() || '请选择资源类别' }}
|
||||
</text>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 资源描述 -->
|
||||
<view class="info-card">
|
||||
<view class="form-item">
|
||||
<text class="form-label">资源描述</text>
|
||||
<uni-easyinput
|
||||
type="textarea"
|
||||
autoHeight
|
||||
v-model="formData.content"
|
||||
placeholder="请输入资源描述"
|
||||
:inputBorder="false"
|
||||
class="content-input"
|
||||
></uni-easyinput>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 上传资源 -->
|
||||
<view class="info-card">
|
||||
<view class="form-item">
|
||||
<text class="form-label">上传资源 <text class="required">*</text></text>
|
||||
<view class="upload-area" @click="chooseFile">
|
||||
<view v-if="!formData.filePath" class="upload-placeholder">
|
||||
<uni-icons type="upload" size="40" color="#999"></uni-icons>
|
||||
<text class="upload-text">点击或拖拽文件到此区域上传</text>
|
||||
<text class="upload-hint">支持 .doc、.docx、.pdf、.ppt、.pptx 等格式</text>
|
||||
</view>
|
||||
<view v-else class="file-info">
|
||||
<uni-icons type="file" size="20" color="#007aff"></uni-icons>
|
||||
<text class="file-name">{{ formData.fileName }}</text>
|
||||
<uni-icons type="trash" size="16" color="#ff3b30" @click="removeFile"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 弹窗底部按钮 -->
|
||||
<view class="popup-bottom-actions">
|
||||
<button class="action-btn cancel-btn" @click="closeAddResourceModal">
|
||||
取消
|
||||
</button>
|
||||
<button class="action-btn confirm-btn" @click="handleSubmitForm" :disabled="isSubmitting">
|
||||
{{ isSubmitting ? '提交中...' : '提交' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { imagUrl } from "@/utils";
|
||||
import { useLayout } from "@/components/BasicListLayout/hooks/useLayout";
|
||||
import { resourcesFindPageApi, resourcesAddNumByTypeApi, resourcesSaveApi } from "@/api/base/server";
|
||||
import { typesFindTreeApi } from "@/api/base/server";
|
||||
import { resourcesFindPageApi, resourcesAddNumByTypeApi } from "@/api/base/server";
|
||||
import { useDataStore } from "@/store/modules/data";
|
||||
import {
|
||||
isVideo,
|
||||
@ -241,42 +117,13 @@ import {
|
||||
|
||||
const { getData } = useDataStore();
|
||||
|
||||
// 新增资源相关变量
|
||||
const addResourcePopup = ref();
|
||||
const modalTitle = ref('新增资源');
|
||||
const isSubmitting = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
resourName: '',
|
||||
resourType: '',
|
||||
category: '',
|
||||
content: '',
|
||||
fileId: '',
|
||||
filePath: '',
|
||||
resSuf: '',
|
||||
hour: '',
|
||||
id: '',
|
||||
fileName: ''
|
||||
});
|
||||
|
||||
// 树形数据
|
||||
const treeData = ref([]);
|
||||
|
||||
// 资源类别选项
|
||||
const categoryOptions = [
|
||||
{ value: '1', label: '课件' },
|
||||
{ value: '2', label: '教案' },
|
||||
{ value: '3', label: '学案' },
|
||||
{ value: '4', label: '作业' },
|
||||
{ value: '5', label: '试卷' },
|
||||
{ value: '6', label: '教材' },
|
||||
{ value: '7', label: '示范课' },
|
||||
{ value: '8', label: '音视频合集' },
|
||||
];
|
||||
|
||||
const buildParams = () => {
|
||||
setParam({ ...getData, ...{ keyword: searchKeyword.value } });
|
||||
const params = {
|
||||
...getData,
|
||||
keyword: searchKeyword.value
|
||||
};
|
||||
console.log('查询参数:', params); // 添加调试日志
|
||||
setParam(params);
|
||||
reload();
|
||||
}
|
||||
|
||||
@ -391,192 +238,26 @@ const downloadResouce = (item: any) => {
|
||||
});
|
||||
};
|
||||
|
||||
// --- 新增资源相关函数 ---
|
||||
const showAddResourceModal = () => {
|
||||
modalTitle.value = '新增资源';
|
||||
resetFormData();
|
||||
addResourcePopup.value.open();
|
||||
};
|
||||
|
||||
const closeAddResourceModal = () => {
|
||||
addResourcePopup.value.close();
|
||||
resetFormData();
|
||||
};
|
||||
|
||||
const resetFormData = () => {
|
||||
formData.resourName = '';
|
||||
formData.resourType = '';
|
||||
formData.category = '';
|
||||
formData.content = '';
|
||||
formData.fileId = '';
|
||||
formData.filePath = '';
|
||||
formData.resSuf = '';
|
||||
formData.hour = '';
|
||||
formData.id = '';
|
||||
formData.fileName = '';
|
||||
};
|
||||
|
||||
const handleResourceTypeChange = (e: any) => {
|
||||
const index = e.detail.value;
|
||||
const selectedItem = treeData.value[index];
|
||||
formData.resourType = selectedItem.key;
|
||||
};
|
||||
|
||||
const handleCategoryChange = (e: any) => {
|
||||
const index = e.detail.value;
|
||||
const selectedItem = categoryOptions[index];
|
||||
formData.category = selectedItem.value;
|
||||
};
|
||||
|
||||
const chooseFile = () => {
|
||||
uni.chooseFile({
|
||||
count: 1,
|
||||
type: 'all',
|
||||
success: (res) => {
|
||||
console.log('选择文件成功:', res);
|
||||
const file = res.tempFiles[0];
|
||||
formData.fileName = file.name;
|
||||
formData.filePath = file.path;
|
||||
formData.resSuf = file.name.split('.').pop()?.toLowerCase() || '';
|
||||
|
||||
// 上传文件
|
||||
uploadFile(file);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('选择文件失败:', err);
|
||||
uni.showToast({ title: '选择文件失败', icon: 'none' });
|
||||
}
|
||||
// --- 跳转到上传资源页面 ---
|
||||
const navigateToAddResource = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/view/routine/JiaoXueZiYuan/add-resource'
|
||||
});
|
||||
};
|
||||
|
||||
const uploadFile = (file: any) => {
|
||||
uni.showLoading({ title: '上传中...' });
|
||||
|
||||
uni.uploadFile({
|
||||
url: 'https://yufangzc.com/upload', // 替换为实际的上传地址
|
||||
filePath: file.path,
|
||||
name: 'files',
|
||||
success: (res) => {
|
||||
console.log('上传成功:', res);
|
||||
const data = JSON.parse(res.data);
|
||||
if (data.resultCode === 1) {
|
||||
formData.fileId = data.result[0].id;
|
||||
formData.filePath = data.result[0].filePath;
|
||||
formData.resSuf = data.result[0].fileType;
|
||||
uni.showToast({ title: '上传成功', icon: 'success' });
|
||||
} else {
|
||||
uni.showToast({ title: '上传失败', icon: 'none' });
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('上传失败:', err);
|
||||
uni.showToast({ title: '上传失败', icon: 'none' });
|
||||
},
|
||||
complete: () => {
|
||||
uni.hideLoading();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const removeFile = () => {
|
||||
formData.fileName = '';
|
||||
formData.filePath = '';
|
||||
formData.fileId = '';
|
||||
formData.resSuf = '';
|
||||
};
|
||||
|
||||
const handleSubmitForm = async () => {
|
||||
// 表单验证
|
||||
if (!formData.resourName.trim()) {
|
||||
uni.showToast({ title: '请输入课题名称', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.resourType) {
|
||||
uni.showToast({ title: '请选择资源目录', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.hour.trim()) {
|
||||
uni.showToast({ title: '请输入课时', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.category) {
|
||||
uni.showToast({ title: '请选择资源类别', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.filePath) {
|
||||
uni.showToast({ title: '请上传资源文件', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSubmitting.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
isSubmitting.value = true;
|
||||
|
||||
try {
|
||||
const params = {
|
||||
resourName: formData.resourName,
|
||||
resourType: formData.resourType,
|
||||
category: formData.category,
|
||||
remark: formData.content,
|
||||
resourId: formData.fileId,
|
||||
resourUrl: formData.filePath,
|
||||
resSuf: formData.resSuf,
|
||||
hour: formData.hour,
|
||||
id: formData.id
|
||||
};
|
||||
|
||||
console.log('提交参数:', params);
|
||||
|
||||
const result = await resourcesSaveApi(params);
|
||||
|
||||
if (result.resultCode === 1) {
|
||||
uni.showToast({ title: '操作成功', icon: 'success' });
|
||||
closeAddResourceModal();
|
||||
reload(); // 刷新列表
|
||||
} else {
|
||||
uni.showToast({ title: '操作失败', icon: 'none' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交失败:', error);
|
||||
uni.showToast({ title: '操作失败,请重试', icon: 'none' });
|
||||
} finally {
|
||||
isSubmitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载树形数据
|
||||
const loadTreeData = async () => {
|
||||
try {
|
||||
const res = await typesFindTreeApi();
|
||||
treeData.value = res.result || [];
|
||||
console.log('树形数据加载完成:', treeData.value);
|
||||
} catch (error) {
|
||||
console.error('加载树形数据失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
onMounted(() => {
|
||||
buildParams();
|
||||
loadTreeData();
|
||||
|
||||
// 监听刷新列表事件
|
||||
uni.$on('refreshResourceList', () => {
|
||||
reload();
|
||||
});
|
||||
});
|
||||
|
||||
// 获取资源目录文本
|
||||
const getResourceTypeText = () => {
|
||||
const selectedItem = treeData.value.find(item => item.key === formData.resourType);
|
||||
return selectedItem ? selectedItem.title : '';
|
||||
};
|
||||
|
||||
// 获取资源类别文本
|
||||
const getCategoryText = () => {
|
||||
const selectedItem = categoryOptions.find(item => item.value === formData.category);
|
||||
return selectedItem ? selectedItem.label : '';
|
||||
};
|
||||
onUnmounted(() => {
|
||||
// 移除事件监听
|
||||
uni.$off('refreshResourceList');
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@ -769,196 +450,4 @@ const getCategoryText = () => {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 底部上传按钮样式
|
||||
.bottom-actions {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
padding: 15px;
|
||||
background: white;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.upload-btn {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
background: linear-gradient(135deg, #007aff 0%, #0056cc 100%);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(0, 122, 255, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
// 弹窗样式
|
||||
.popup-content {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
width: 90vw;
|
||||
max-width: 600px;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-scroll-view {
|
||||
flex: 1;
|
||||
max-height: 60vh;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
background: white;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 10rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: #ff3b30;
|
||||
}
|
||||
|
||||
.content-input {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
|
||||
:deep(.uni-easyinput__content) {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
:deep(.uni-easyinput__content-input) {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
:deep(.uni-easyinput__placeholder-class) {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.picker-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
color: #333;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
border: 2rpx dashed #ddd;
|
||||
border-radius: 12rpx;
|
||||
padding: 40rpx;
|
||||
text-align: center;
|
||||
background: #f9f9f9;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.upload-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.upload-hint {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.file-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.popup-bottom-actions {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
padding: 30rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
flex: 1;
|
||||
padding: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
background: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
flex: 1;
|
||||
padding: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
background: linear-gradient(135deg, #007aff 0%, #0056cc 100%);
|
||||
color: white;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 122, 255, 0.3);
|
||||
}
|
||||
|
||||
.confirm-btn:disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
@ -3,12 +3,14 @@ import {authenticationApi, loginCode, loginPass, weChatLogin} from "@/api/system
|
||||
import {AUTH_KEY} from "@/config";
|
||||
import { useDicStore } from "@/store/modules/dic";
|
||||
import { useCommonStore } from "@/store/modules/common";
|
||||
import { refreshPermissionCache, clearPermissionCachePublic } from "@/utils/permission";
|
||||
|
||||
interface UserState {
|
||||
userdata: any;
|
||||
jsData: any;
|
||||
token: string;
|
||||
auth: string[]
|
||||
auth: string[];
|
||||
changeTime: string; // 添加权限变更时间
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore({
|
||||
@ -22,6 +24,8 @@ export const useUserStore = defineStore({
|
||||
token: '',
|
||||
//用户注册信息
|
||||
auth: [],
|
||||
//权限变更时间
|
||||
changeTime: '',
|
||||
}),
|
||||
getters: {
|
||||
getToken(): string {
|
||||
@ -35,6 +39,9 @@ export const useUserStore = defineStore({
|
||||
},
|
||||
getAuth(): string[] {
|
||||
return this.auth;
|
||||
},
|
||||
getChangeTime(): string {
|
||||
return this.changeTime;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
@ -47,8 +54,33 @@ export const useUserStore = defineStore({
|
||||
setJs(data: any) {
|
||||
this.jsData = data;
|
||||
},
|
||||
setAuth(data: string[]) {
|
||||
setChangeTime(changeTime: string) {
|
||||
this.changeTime = changeTime;
|
||||
},
|
||||
setAuth(data: string[], autoRefreshCache: boolean = true) {
|
||||
console.log('=== setAuth 开始 ===');
|
||||
console.log('传入的权限数据:', data);
|
||||
console.log('权限数量:', data ? data.length : 0);
|
||||
|
||||
// 检查是否与当前权限相同
|
||||
if (this.auth && data) {
|
||||
const isSame = JSON.stringify(this.auth.sort()) === JSON.stringify(data.sort());
|
||||
if (isSame) {
|
||||
console.log('权限数据未变化,跳过更新');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('设置新的权限数据');
|
||||
this.auth = data;
|
||||
|
||||
// 权限数据更新时,自动刷新缓存(可选)
|
||||
if (autoRefreshCache && data && data.length > 0) {
|
||||
console.log('自动刷新权限缓存...');
|
||||
refreshPermissionCache(data);
|
||||
}
|
||||
|
||||
console.log('=== setAuth 完成 ===');
|
||||
},
|
||||
/**
|
||||
* @description: 验证码登录
|
||||
@ -58,7 +90,7 @@ export const useUserStore = defineStore({
|
||||
const {result} = await loginCode({phone: params.phone, code: params.code});
|
||||
this.afterLoginAction(result)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
// 静默处理错误
|
||||
}
|
||||
},
|
||||
/**
|
||||
@ -69,7 +101,7 @@ export const useUserStore = defineStore({
|
||||
const {result} = await loginPass({username: params.name, password: params.password});
|
||||
this.afterLoginAction(result)
|
||||
} catch (e) {
|
||||
|
||||
// 静默处理错误
|
||||
}
|
||||
},
|
||||
/**
|
||||
@ -80,28 +112,49 @@ export const useUserStore = defineStore({
|
||||
const {result} = await weChatLogin({code: params.code})
|
||||
this.afterLoginAction(result)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
// 静默处理错误
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description: 登录成功后的操作
|
||||
*/
|
||||
afterLoginAction(value: any) {
|
||||
console.log('=== afterLoginAction 开始 ===');
|
||||
console.log('用户数据:', value);
|
||||
|
||||
this.setUser(value)
|
||||
this.setJs(value.js);
|
||||
if (value[AUTH_KEY]) {
|
||||
this.setToken(value[AUTH_KEY])
|
||||
}
|
||||
authenticationApi({userId: value.id}).then(({result}) => {
|
||||
if (result) {
|
||||
this.setAuth(result)
|
||||
}
|
||||
})
|
||||
|
||||
// 检查用户数据中是否已经包含权限信息
|
||||
if (value.auth && Array.isArray(value.auth) && value.auth.length > 0) {
|
||||
console.log('✅ 用户数据中包含权限信息:', value.auth);
|
||||
console.log('权限数量:', value.auth.length);
|
||||
this.setAuth(value.auth, false);
|
||||
} else {
|
||||
console.log('用户数据中无权限信息,调用 authenticationApi 获取权限...');
|
||||
authenticationApi({userId: value.id}).then(({result}) => {
|
||||
if (result) {
|
||||
console.log('✅ 获取到权限数据:', result);
|
||||
console.log('权限数量:', result.length);
|
||||
this.setAuth(result, false);
|
||||
} else {
|
||||
console.log('❌ authenticationApi 返回空结果');
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('❌ authenticationApi 调用失败:', error);
|
||||
})
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description: 注销
|
||||
*/
|
||||
logout() {
|
||||
// 清除权限缓存
|
||||
clearPermissionCachePublic();
|
||||
|
||||
this.setToken('')
|
||||
this.setUser({})
|
||||
this.setJs({})
|
||||
|
||||
@ -2,28 +2,101 @@ import {ISROUTERINTERCEPT} from "@/config";
|
||||
import {getRouter} from "@/utils/uniapp";
|
||||
import {useUserStore} from "@/store/modules/user";
|
||||
|
||||
export function _auth(autd: string) {
|
||||
const {getAuth} = useUserStore()
|
||||
return getAuth.includes(autd);
|
||||
// 权限缓存相关常量
|
||||
const PERMISSION_CACHE_KEY = 'user_permissions_cache';
|
||||
|
||||
// 权限缓存接口
|
||||
interface PermissionCache {
|
||||
permissions: string[];
|
||||
timestamp: number;
|
||||
userId: string;
|
||||
changeTime: string;
|
||||
}
|
||||
|
||||
function loginPage(url: string) {
|
||||
uni.redirectTo({
|
||||
url: "/pages/system/login/login?redirect=" + url,
|
||||
});
|
||||
// 存储工具函数
|
||||
function setStorage(key: string, value: any): void {
|
||||
try {
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
const jsonValue = JSON.stringify(value);
|
||||
localStorage.setItem(key, jsonValue);
|
||||
} else {
|
||||
uni.setStorageSync(key, value);
|
||||
}
|
||||
} catch (error) {
|
||||
// 静默处理错误
|
||||
}
|
||||
}
|
||||
|
||||
function getStorage(key: string): any {
|
||||
try {
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
const value = localStorage.getItem(key);
|
||||
return value ? JSON.parse(value) : null;
|
||||
} else {
|
||||
const value = uni.getStorageSync(key);
|
||||
return value;
|
||||
}
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 从app-user中获取权限变更时间
|
||||
function getChangeTimeFromAppUser(): string | null {
|
||||
try {
|
||||
const userStore = useUserStore();
|
||||
if (userStore.getChangeTime && userStore.getChangeTime.trim() !== '') {
|
||||
return userStore.getChangeTime;
|
||||
}
|
||||
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
const appUser = localStorage.getItem('app-user');
|
||||
if (appUser) {
|
||||
const userData = JSON.parse(appUser);
|
||||
if (userData.changeTime) {
|
||||
return userData.changeTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 将权限变更时间存储到app-user中
|
||||
function setChangeTimeToAppUser(changeTime: string): void {
|
||||
try {
|
||||
const userStore = useUserStore();
|
||||
userStore.setChangeTime(changeTime);
|
||||
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
const appUser = localStorage.getItem('app-user');
|
||||
if (appUser) {
|
||||
const userData = JSON.parse(appUser);
|
||||
userData.changeTime = changeTime;
|
||||
localStorage.setItem('app-user', JSON.stringify(userData));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 静默处理错误
|
||||
}
|
||||
}
|
||||
|
||||
// 路由拦截器 - 默认导出
|
||||
export default function (whitelist: WhiteList) {
|
||||
const WHITELIST = ['/', ...whitelist, {pattern: /^\/pages\/system\/.*/}];
|
||||
const list = ["navigateTo", "redirectTo", "reLaunch", "switchTab"];
|
||||
// 用遍历的方式分别为,uni.navigateTo,uni.redirectTo,uni.reLaunch,uni.switchTab这4个路由方法添加拦截器
|
||||
|
||||
list.forEach((item) => {
|
||||
uni.addInterceptor(item, {
|
||||
invoke(e) {
|
||||
// 获取要跳转的页面路径(url去掉"?"和"?"后的参数)
|
||||
const url = e.url.split("?")[0];
|
||||
|
||||
// 判断当前窗口是白名单,如果是则不重定向路由
|
||||
let pass;
|
||||
let pass = false;
|
||||
if (WHITELIST) {
|
||||
pass = WHITELIST.some((item) => {
|
||||
if (typeof item === "object" && item.pattern) {
|
||||
@ -32,6 +105,7 @@ export default function (whitelist: WhiteList) {
|
||||
return url === item;
|
||||
});
|
||||
}
|
||||
|
||||
// 不是白名单并且没有token
|
||||
const store = useUserStore();
|
||||
if (!pass && !store.getToken) {
|
||||
@ -41,15 +115,206 @@ export default function (whitelist: WhiteList) {
|
||||
return e;
|
||||
},
|
||||
fail(err) {
|
||||
// 失败回调拦截
|
||||
console.log(err);
|
||||
// 静默处理错误
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//判断是否登录
|
||||
export function getLogin(): boolean {
|
||||
/**
|
||||
* 获取权限缓存
|
||||
*/
|
||||
function getPermissionCache(): PermissionCache | null {
|
||||
try {
|
||||
const cacheData = getStorage(PERMISSION_CACHE_KEY);
|
||||
const changeTime = getChangeTimeFromAppUser();
|
||||
|
||||
if (!cacheData || !changeTime) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
permissions: cacheData.permissions,
|
||||
timestamp: cacheData.timestamp,
|
||||
userId: cacheData.userId,
|
||||
changeTime: cacheData.changeTime
|
||||
};
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置权限缓存
|
||||
* @param permissions 权限列表
|
||||
* @param userId 用户ID
|
||||
* @param changeTime 权限变更时间(可选)
|
||||
*/
|
||||
function setPermissionCache(permissions: string[], userId: string, changeTime?: string): void {
|
||||
try {
|
||||
const defaultChangeTime = '2024-01-01 00:00:00';
|
||||
const finalChangeTime = changeTime || defaultChangeTime;
|
||||
|
||||
const cacheData: PermissionCache = {
|
||||
permissions,
|
||||
timestamp: Date.now(),
|
||||
userId,
|
||||
changeTime: finalChangeTime
|
||||
};
|
||||
|
||||
setStorage(PERMISSION_CACHE_KEY, cacheData);
|
||||
setChangeTimeToAppUser(finalChangeTime);
|
||||
} catch (error) {
|
||||
// 静默处理错误
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除权限缓存
|
||||
*/
|
||||
function clearPermissionCache(): void {
|
||||
try {
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.removeItem(PERMISSION_CACHE_KEY);
|
||||
} else {
|
||||
uni.removeStorageSync(PERMISSION_CACHE_KEY);
|
||||
}
|
||||
} catch (error) {
|
||||
// 静默处理错误
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证缓存是否有效
|
||||
*/
|
||||
function isCacheValid(cache: PermissionCache, currentUserId: string): boolean {
|
||||
return cache.userId === currentUserId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户权限列表(带缓存)
|
||||
* @param currentChangeTime 当前服务器返回的权限变更时间
|
||||
* @returns 权限列表
|
||||
*/
|
||||
function getUserPermissionsWithCache(currentChangeTime?: string): string[] {
|
||||
const userStore = useUserStore();
|
||||
const currentUser = userStore.getUser;
|
||||
const currentUserId = currentUser?.id || currentUser?.userdata?.id;
|
||||
|
||||
if (!currentUserId) {
|
||||
return userStore.getAuth;
|
||||
}
|
||||
|
||||
const cache = getPermissionCache();
|
||||
if (cache && isCacheValid(cache, currentUserId)) {
|
||||
if (currentChangeTime) {
|
||||
const serverTime = new Date(currentChangeTime).getTime();
|
||||
const cacheTime = new Date(cache.changeTime).getTime();
|
||||
|
||||
if (serverTime > cacheTime) {
|
||||
const permissions = userStore.getAuth;
|
||||
if (permissions && permissions.length > 0) {
|
||||
setPermissionCache(permissions, currentUserId, currentChangeTime);
|
||||
}
|
||||
return permissions;
|
||||
} else {
|
||||
return cache.permissions;
|
||||
}
|
||||
} else {
|
||||
return cache.permissions;
|
||||
}
|
||||
}
|
||||
|
||||
const permissions = userStore.getAuth;
|
||||
if (permissions && permissions.length > 0) {
|
||||
setPermissionCache(permissions, currentUserId, currentChangeTime);
|
||||
}
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新权限缓存
|
||||
* @param permissions 新的权限列表
|
||||
* @param changeTime 权限变更时间(可选)
|
||||
*/
|
||||
export function refreshPermissionCache(permissions?: string[], changeTime?: string): void {
|
||||
const userStore = useUserStore();
|
||||
const currentUser = userStore.getUser;
|
||||
const currentUserId = currentUser?.id || currentUser?.userdata?.id;
|
||||
|
||||
if (!currentUserId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const permissionList = permissions || userStore.getAuth;
|
||||
|
||||
const currentCache = getPermissionCache();
|
||||
if (currentCache && currentCache.permissions && permissionList) {
|
||||
const isSame = JSON.stringify(currentCache.permissions.sort()) === JSON.stringify(permissionList.sort());
|
||||
if (isSame && currentCache.changeTime === changeTime) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setPermissionCache(permissionList, currentUserId, changeTime);
|
||||
|
||||
if (changeTime) {
|
||||
userStore.setChangeTime(changeTime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除权限缓存(供外部调用)
|
||||
*/
|
||||
export function clearPermissionCachePublic(): void {
|
||||
clearPermissionCache();
|
||||
}
|
||||
|
||||
// 权限检查函数
|
||||
export function _auth(autd: string, changeTime?: string) {
|
||||
const permissions = getUserPermissionsWithCache(changeTime);
|
||||
return permissions.includes(autd);
|
||||
}
|
||||
|
||||
export function hasPermission(permissionKey: string, changeTime?: string): boolean {
|
||||
if (!permissionKey) return true;
|
||||
const permissions = getUserPermissionsWithCache(changeTime);
|
||||
// 去重处理,避免重复权限影响判断
|
||||
const uniquePermissions = [...new Set(permissions)];
|
||||
return uniquePermissions.includes(permissionKey);
|
||||
}
|
||||
|
||||
export function hasAnyPermission(permissionKeys: string[], changeTime?: string): boolean {
|
||||
if (!permissionKeys || permissionKeys.length === 0) return true;
|
||||
const permissions = getUserPermissionsWithCache(changeTime);
|
||||
// 去重处理,避免重复权限影响判断
|
||||
const uniquePermissions = [...new Set(permissions)];
|
||||
return permissionKeys.some(key => uniquePermissions.includes(key));
|
||||
}
|
||||
|
||||
export function hasAllPermissions(permissionKeys: string[], changeTime?: string): boolean {
|
||||
if (!permissionKeys || permissionKeys.length === 0) return true;
|
||||
const permissions = getUserPermissionsWithCache(changeTime);
|
||||
// 去重处理,避免重复权限影响判断
|
||||
const uniquePermissions = [...new Set(permissions)];
|
||||
return permissionKeys.every(key => uniquePermissions.includes(key));
|
||||
}
|
||||
|
||||
export function getUserPermissions(changeTime?: string): string[] {
|
||||
const permissions = getUserPermissionsWithCache(changeTime);
|
||||
// 返回去重后的权限列表
|
||||
return permissions ? [...new Set(permissions)] : [];
|
||||
}
|
||||
|
||||
export function getLogin(): void {
|
||||
clearPermissionCache();
|
||||
uni.reLaunch({
|
||||
url: "/pages/system/login/login",
|
||||
});
|
||||
}
|
||||
|
||||
export function isLogin(): boolean {
|
||||
if (ISROUTERINTERCEPT) {
|
||||
const store = useUserStore();
|
||||
if (!store.getToken) {
|
||||
@ -65,4 +330,72 @@ export function getLogin(): boolean {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function loginPage(url: string) {
|
||||
uni.redirectTo({
|
||||
url: "/pages/system/login/login?redirect=" + url,
|
||||
});
|
||||
}
|
||||
|
||||
export const ROUTINE_PERMISSIONS = {
|
||||
JIAO_XUE_ZI_YUAN: 'JiaoXueZiYuan',
|
||||
JI_FEN_PING_JIA: 'JiFenPingJia',
|
||||
GONG_ZUO_LIANG: 'GongZuoLiang',
|
||||
RENG_JIAO_RENG_ZHI: 'RengJiaoRengZhi',
|
||||
SHI_TANG_XUN_CHA: 'ShiTangXunCha',
|
||||
KE_FU_XUN_CHA: 'kefuxuncha',
|
||||
GROUP_TEACHING: 'groupTeaching',
|
||||
NOTICE: 'notice',
|
||||
ROUTINE: 'routine',
|
||||
} as const;
|
||||
|
||||
export function isTeacherUser(): boolean {
|
||||
const userStore = useUserStore();
|
||||
const user = userStore.getUser;
|
||||
return user && user.userType === 'teacher';
|
||||
}
|
||||
|
||||
export function isAdminUser(): boolean {
|
||||
const userStore = useUserStore();
|
||||
const user = userStore.getUser;
|
||||
return user && user.userType === 'admin';
|
||||
}
|
||||
|
||||
export const PermissionCacheManager = {
|
||||
getCacheInfo() {
|
||||
const cache = getPermissionCache();
|
||||
const changeTime = getChangeTimeFromAppUser();
|
||||
|
||||
return {
|
||||
hasCache: !!cache,
|
||||
changeTime: changeTime ? new Date(changeTime).toLocaleString() : null,
|
||||
isExpired: cache ? Date.now() > cache.timestamp : true,
|
||||
cacheSize: cache ? cache.permissions.length : 0
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 显示详细的缓存信息(调试用)
|
||||
*/
|
||||
debugCache() {
|
||||
// 移除所有调试信息
|
||||
},
|
||||
|
||||
forceRefresh() {
|
||||
const userStore = useUserStore();
|
||||
const permissions = userStore.getAuth;
|
||||
const currentUser = userStore.getUser;
|
||||
const currentUserId = currentUser?.id || currentUser?.userdata?.id;
|
||||
|
||||
if (currentUserId && permissions) {
|
||||
setPermissionCache(permissions, currentUserId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
clear() {
|
||||
clearPermissionCache();
|
||||
}
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user