权限调整

This commit is contained in:
hb 2025-08-02 11:15:22 +08:00
parent b0a8ead56a
commit c7e0c6402a
14 changed files with 2474 additions and 745 deletions

View File

@ -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}`);
};

View File

@ -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": {

View File

@ -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 {
// userdataID
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;

View File

@ -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>

View File

@ -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);

View File

@ -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

View 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>

View 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>

View File

@ -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);
// URLinspectItemId
const inspectItemId = options.inspectItemId || '';
// ID
let userId = '';
const userDataStr = uni.getStorageSync('app-user');
if (userDataStr) {
try {
const userData = typeof userDataStr === 'string' ? JSON.parse(userDataStr) : userDataStr;
if (userData && userData.jsData && userData.jsData.id) {
userId = userData.jsData.id;
console.log('从缓存获取到教师ID:', userId);
}
} catch (error) {
console.error('解析用户数据失败:', error);
}
}
//
pageParams.value = {
inspectItemId: inspectItemId,
userId: userId
};
console.log('最终页面参数:', pageParams.value);
//
loadDetailData();
});
</script>
@ -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;
}
}
}

View 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>

View File

@ -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`
});

View File

@ -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>

View File

@ -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({})

View File

@ -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();
}
};