Merge branch 'master' of http://119.29.194.155:8894/zwq/zhxy-jsd
This commit is contained in:
commit
9a5d8cf606
@ -55,3 +55,10 @@ export const getXcCourseListApi = async (params: any) => {
|
|||||||
export const getKyXcCourseListApi = async (params: any) => {
|
export const getKyXcCourseListApi = async (params: any) => {
|
||||||
return await get("/api/pb/getKyXcCourseList", params);
|
return await get("/api/pb/getKyXcCourseList", params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 值周巡查接口 - 获取值周排班数据
|
||||||
|
*/
|
||||||
|
export const getZbXcListApi = async (params: any) => {
|
||||||
|
return await get("/api/pbZb/getZbXcList", params);
|
||||||
|
};
|
||||||
|
|||||||
@ -149,6 +149,11 @@ export const xsQjFindByIdApi = async (params: any) => {
|
|||||||
return await get("/api/xsQj/getDetail", params);
|
return await get("/api/xsQj/getDetail", params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 学生请假确认放行
|
||||||
|
export const xsQjConfirmReleaseApi = async (params: any) => {
|
||||||
|
return await post("/api/xsQj/confirmRelease", params);
|
||||||
|
};
|
||||||
|
|
||||||
// 学生请假审批
|
// 学生请假审批
|
||||||
export const xsQjSpApi = async (params: any) => {
|
export const xsQjSpApi = async (params: any) => {
|
||||||
return await post("/api/xsQj/sp", params);
|
return await post("/api/xsQj/sp", params);
|
||||||
|
|||||||
@ -41,3 +41,10 @@ export const xcXmFindAllApi = async () => {
|
|||||||
export const xcXmFindByXcLxApi = async (xcLx: string) => {
|
export const xcXmFindByXcLxApi = async (xcLx: string) => {
|
||||||
return await get("/api/xcXm/findByXcLx", { xcLx });
|
return await get("/api/xcXm/findByXcLx", { xcLx });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据排班类型ID查询项目列表
|
||||||
|
*/
|
||||||
|
export const xcXmFindByPbLxIdApi = async (pbLxId: string) => {
|
||||||
|
return await get("/api/xcXm/findByPbLxId", { pbLxId });
|
||||||
|
};
|
||||||
61
src/api/base/xsQjApi.ts
Normal file
61
src/api/base/xsQjApi.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { get, post } from "@/utils/request";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询学生请假列表
|
||||||
|
*/
|
||||||
|
export const findXsQjListApi = async (params: any) => {
|
||||||
|
return await get("/api/xsQj/findPage", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID获取学生请假详情
|
||||||
|
*/
|
||||||
|
export const findXsQjByIdApi = async (params: { id: string }) => {
|
||||||
|
return await get("/api/xsQj/getDetail", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学生请假审批
|
||||||
|
*/
|
||||||
|
export const xsQjSpApi = async (params: any) => {
|
||||||
|
return await post("/api/xsQj/sp", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学生请假审批终止
|
||||||
|
*/
|
||||||
|
export const xsQjStopApi = async (params: any) => {
|
||||||
|
return await post("/api/xsQj/stop", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学生请假审批转办
|
||||||
|
*/
|
||||||
|
export const xsQjTransferApi = async (params: any) => {
|
||||||
|
return await post("/api/xsQj/transfer", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学生请假统计
|
||||||
|
* @param date 统计日期 格式:yyyy-MM-dd,不传默认当天
|
||||||
|
*/
|
||||||
|
export const xsQjStatisticsApi = async (params?: { date?: string }) => {
|
||||||
|
return await get("/api/xsQj/statistics", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取学生请假审批流程
|
||||||
|
* @param ywId 业务ID
|
||||||
|
* @param ywType 业务类型
|
||||||
|
*/
|
||||||
|
export const getXsQjApprovalProcessApi = (ywId: string, ywType: string = 'XS_QJ') => {
|
||||||
|
return get("/api/lcglSp/getByYwIdAndYwType", { ywId, ywType });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确认放行
|
||||||
|
*/
|
||||||
|
export const confirmReleaseApi = async (params: { id: string; fxjsId: string; fxjsxm: string }) => {
|
||||||
|
return await post("/api/xsQj/confirmRelease", params);
|
||||||
|
};
|
||||||
|
|
||||||
37
src/api/base/zbXcApi.ts
Normal file
37
src/api/base/zbXcApi.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { get, post } from "@/utils/request";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询值周巡查记录
|
||||||
|
*/
|
||||||
|
export const zbXcFindPageApi = async (params: any) => {
|
||||||
|
return await get("/api/zbXc/findPage", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增/修改值周巡查记录
|
||||||
|
*/
|
||||||
|
export const zbXcSaveApi = async (params: any) => {
|
||||||
|
return await post("/api/zbXc/save", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除值周巡查记录
|
||||||
|
*/
|
||||||
|
export const zbXcLogicDeleteApi = async (params: any) => {
|
||||||
|
return await post("/api/zbXc/logicDelete", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据id查询值周巡查记录
|
||||||
|
*/
|
||||||
|
export const zbXcFindByIdApi = async (params: any) => {
|
||||||
|
return await get("/api/zbXc/findById", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询全部值周巡查记录
|
||||||
|
*/
|
||||||
|
export const zbXcFindAllApi = async () => {
|
||||||
|
return await get("/api/zbXc/findAll");
|
||||||
|
};
|
||||||
|
|
||||||
@ -706,6 +706,14 @@
|
|||||||
"enablePullDownRefresh": false
|
"enablePullDownRefresh": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/view/routine/jstxl/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "教师通讯录",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"path": "pages/view/homeSchool/parentAddressBook/index",
|
"path": "pages/view/homeSchool/parentAddressBook/index",
|
||||||
"style": {
|
"style": {
|
||||||
@ -863,6 +871,27 @@
|
|||||||
"navigationBarTitleText": "课业巡查记录"
|
"navigationBarTitleText": "课业巡查记录"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/view/routine/kefuxuncha/zbList",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "值周巡查",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/view/routine/kefuxuncha/zbDetail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "值周巡查详情",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/view/routine/kefuxuncha/zbRecord",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "值周巡查记录",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/base/xs/qj/sp",
|
"path": "pages/base/xs/qj/sp",
|
||||||
"style": {
|
"style": {
|
||||||
@ -877,6 +906,20 @@
|
|||||||
"enablePullDownRefresh": false
|
"enablePullDownRefresh": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/base/xs/qj/detailPush",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "学生请假推送",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/base/xs/qj/statistics",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "学生请假统计",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/view/analysis/xs/studentArchive",
|
"path": "pages/view/analysis/xs/studentArchive",
|
||||||
"style": {
|
"style": {
|
||||||
|
|||||||
@ -544,6 +544,14 @@ const sections = reactive<Section[]>([
|
|||||||
permissionKey: "home-dmtj", // 点名统计权限编码
|
permissionKey: "home-dmtj", // 点名统计权限编码
|
||||||
path: "/pages/view/analysis/xk/dmXkList",
|
path: "/pages/view/analysis/xk/dmXkList",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "hs6",
|
||||||
|
icon: "xsqj",
|
||||||
|
text: "学生请假",
|
||||||
|
show: true,
|
||||||
|
permissionKey: "home-xsqj", // 点名统计权限编码
|
||||||
|
path: "/pages/base/xs/qj/statistics",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -583,6 +591,14 @@ const sections = reactive<Section[]>([
|
|||||||
permissionKey: "personnel-gzt", // 工资条权限编码
|
permissionKey: "personnel-gzt", // 工资条权限编码
|
||||||
path: "/pages/view/hr/salarySlip/index",
|
path: "/pages/view/hr/salarySlip/index",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "hr4",
|
||||||
|
icon: "jstxl",
|
||||||
|
text: "教师通讯录",
|
||||||
|
show: true,
|
||||||
|
permissionKey: "personnel-jstxl", // 教师通讯录权限编码
|
||||||
|
path: "/pages/view/routine/jstxl/index",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -54,10 +54,10 @@
|
|||||||
<view class="white-bg-color py-5">
|
<view class="white-bg-color py-5">
|
||||||
<view class="flex-row items-center pb-10 pt-5">
|
<view class="flex-row items-center pb-10 pt-5">
|
||||||
<u-button
|
<u-button
|
||||||
text="返回首页"
|
text="返回上一页"
|
||||||
class="ml-15 mr-7"
|
class="ml-15 mr-7"
|
||||||
:plain="true"
|
type="primary"
|
||||||
@click="goHome"
|
@click="goBack"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -66,43 +66,108 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue';
|
import { computed, nextTick } from 'vue';
|
||||||
|
import { onLoad } from "@dcloudio/uni-app";
|
||||||
import { useDataStore } from "@/store/modules/data";
|
import { useDataStore } from "@/store/modules/data";
|
||||||
import { getXsQjDetailApi } from "@/api/base/server";
|
import { useUserStore } from "@/store/modules/user";
|
||||||
|
import { xsQjFindByIdApi } from "@/api/base/server";
|
||||||
|
import { xxtsFindByIdApi } from "@/api/base/xxtsApi";
|
||||||
import LcglSp from "@/components/LcglSp/index.vue";
|
import LcglSp from "@/components/LcglSp/index.vue";
|
||||||
|
|
||||||
const { getData } = useDataStore();
|
const { loginByOpenId } = useUserStore();
|
||||||
|
const dataStore = useDataStore();
|
||||||
|
const { setQjData } = dataStore;
|
||||||
|
|
||||||
// 从URL参数获取请假ID
|
// 请假ID - 从 store 中通过 computed 访问,保持响应式
|
||||||
const qjId = getData.id || '';
|
const qjId = computed(() => {
|
||||||
|
return (dataStore.qjData as any)?.id || '';
|
||||||
|
});
|
||||||
|
|
||||||
// 请假基础数据
|
// 请假基础数据 - 直接访问 store 的 state,保持响应式
|
||||||
const qjData = ref<any>({});
|
const qjData = computed(() => (dataStore.qjData || {}) as any);
|
||||||
|
|
||||||
// 获取请假详情
|
const goBack = () => {
|
||||||
const loadQjDetail = async () => {
|
uni.navigateBack({
|
||||||
if (!qjId) {
|
delta: 1
|
||||||
console.error('请假ID不能为空');
|
});
|
||||||
return;
|
};
|
||||||
}
|
|
||||||
|
onLoad(async (options: any) => {
|
||||||
|
console.log('detail.vue onLoad 接收到的参数:', options);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await getXsQjDetailApi(qjId);
|
uni.showLoading({ title: "加载中..." });
|
||||||
if (res.resultCode === 1 && res.result) {
|
|
||||||
qjData.value = res.result;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取请假详情失败:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const goHome = () => {
|
// 场景1: 从待办过来的(from=db),需要先获取待办信息
|
||||||
uni.reLaunch({ url: '/pages/base/message/index' });
|
if (options && options.from === "db") {
|
||||||
};
|
console.log('从待办过来,参数:', options);
|
||||||
|
|
||||||
// 页面加载时获取数据
|
// 检查登录状态
|
||||||
onMounted(() => {
|
if (options.openId) {
|
||||||
loadQjDetail();
|
const isLoggedIn = await loginByOpenId(options.openId);
|
||||||
|
if (!isLoggedIn) {
|
||||||
|
console.log("用户未登录,跳过处理");
|
||||||
|
uni.hideLoading();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await nextTick();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优先从后端根据url中的id去查询Xxts(待办信息)
|
||||||
|
if (options.id) {
|
||||||
|
const xxtsRes = await xxtsFindByIdApi({ id: options.id });
|
||||||
|
if (xxtsRes && xxtsRes.result) {
|
||||||
|
const xxts = xxtsRes.result;
|
||||||
|
console.log('获取到待办信息:', xxts);
|
||||||
|
|
||||||
|
// 根据待办中的主表ID获取请假详情
|
||||||
|
const qjRes = await xsQjFindByIdApi({ id: xxts.xxzbId });
|
||||||
|
if (qjRes && qjRes.result) {
|
||||||
|
setQjData(qjRes.result);
|
||||||
|
await nextTick();
|
||||||
|
console.log('获取到请假详情:', qjRes.result);
|
||||||
|
} else {
|
||||||
|
throw new Error('获取请假详情失败');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('获取待办信息失败');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('缺少待办ID参数');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 场景2: 直接传请假ID(内部跳转)
|
||||||
|
else if (options && options.id) {
|
||||||
|
console.log('直接传请假ID:', options.id);
|
||||||
|
const qjRes = await xsQjFindByIdApi({ id: options.id });
|
||||||
|
if (qjRes && qjRes.result) {
|
||||||
|
setQjData(qjRes.result);
|
||||||
|
await nextTick();
|
||||||
|
console.log('获取到请假详情:', qjRes.result);
|
||||||
|
} else {
|
||||||
|
throw new Error('获取请假详情失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 场景3: 从 store 获取(兼容某些场景)
|
||||||
|
else if (dataStore.qjData && (dataStore.qjData as any).id) {
|
||||||
|
console.log('从 store 获取请假ID:', (dataStore.qjData as any).id);
|
||||||
|
// store 中已有数据,无需重新获取
|
||||||
|
} else {
|
||||||
|
throw new Error('缺少请假ID参数');
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.hideLoading();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('加载请假详情失败:', error);
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || "加载失败",
|
||||||
|
icon: "none"
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateBack();
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
355
src/pages/base/xs/qj/detailPush.vue
Normal file
355
src/pages/base/xs/qj/detailPush.vue
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
<template>
|
||||||
|
<view class="qj-push-page">
|
||||||
|
<!-- 顶部区域 - Logo -->
|
||||||
|
<view class="header-section">
|
||||||
|
<image class="logo" src="@/static/system/login/logo.png" mode="aspectFit"></image>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 中间白色卡片 -->
|
||||||
|
<view class="content-section">
|
||||||
|
<view class="info-card">
|
||||||
|
<!-- 审批印章 - 右上角,如果已审批通过 -->
|
||||||
|
<view v-if="qjData.spResult === 'B'" class="approval-stamp">
|
||||||
|
<image class="stamp-image" src="@/static/base/view/sptg.png" mode="aspectFit"></image>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 申请人头像 - 中央 -->
|
||||||
|
<view class="student-photo-center">
|
||||||
|
<BasicImage :src="qjData.xstx || '/static/base/default-avatar.png'" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 请假信息 -->
|
||||||
|
<view class="info-list">
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="label">请假类型:</text>
|
||||||
|
<text class="value">{{ qjData.qjlx }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="label">开始时间:</text>
|
||||||
|
<text class="value">{{ qjData.qjkstime }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="label">结束时间:</text>
|
||||||
|
<text class="value">{{ qjData.qjjstime }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="label">时长(天):</text>
|
||||||
|
<text class="value">{{ qjData.qjsc }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="label">是否离校:</text>
|
||||||
|
<text class="value">{{ qjData.sflx === 1 ? '是' : '否' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-item info-item-vertical">
|
||||||
|
<text class="label">请假事由:</text>
|
||||||
|
<text class="value">{{ qjData.qjsy }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 确认放行按钮 -->
|
||||||
|
<view class="action-button" @click="handleConfirm">
|
||||||
|
<text class="button-text">确认放行</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部区域 -->
|
||||||
|
<view class="footer-section"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, nextTick } from 'vue';
|
||||||
|
import { onLoad } from "@dcloudio/uni-app";
|
||||||
|
import { useDataStore } from "@/store/modules/data";
|
||||||
|
import { useUserStore } from "@/store/modules/user";
|
||||||
|
import { xsQjFindByIdApi, xsQjConfirmReleaseApi } from "@/api/base/server";
|
||||||
|
import { xxtsFindByIdApi } from "@/api/base/xxtsApi";
|
||||||
|
import BasicImage from "@/components/BasicImage/Image.vue";
|
||||||
|
|
||||||
|
const { loginByOpenId, getJs } = useUserStore();
|
||||||
|
const dataStore = useDataStore();
|
||||||
|
const { setQjData } = dataStore;
|
||||||
|
|
||||||
|
// 请假ID - 从 store 中通过 computed 访问,保持响应式
|
||||||
|
const qjId = computed(() => {
|
||||||
|
return (dataStore.qjData as any)?.id || '';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 请假基础数据 - 直接访问 store 的 state,保持响应式
|
||||||
|
const qjData = computed(() => (dataStore.qjData || {}) as any);
|
||||||
|
|
||||||
|
const goHome = () => {
|
||||||
|
uni.reLaunch({ url: '/pages/base/message/index' });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 确认放行
|
||||||
|
const handleConfirm = async () => {
|
||||||
|
// 检查是否有请假ID
|
||||||
|
if (!qjId.value) {
|
||||||
|
uni.showToast({ title: '请假ID不存在', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前教师信息
|
||||||
|
const js = getJs;
|
||||||
|
if (!js || !js.id) {
|
||||||
|
uni.showToast({ title: '获取教师信息失败', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.showModal({
|
||||||
|
title: '确认放行',
|
||||||
|
content: '确认该学生已成功离校?',
|
||||||
|
success: async (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
try {
|
||||||
|
uni.showLoading({ title: '放行中...' });
|
||||||
|
|
||||||
|
// 调用放行接口
|
||||||
|
const result = await xsQjConfirmReleaseApi({
|
||||||
|
id: qjId.value,
|
||||||
|
fxjsId: js.id,
|
||||||
|
fxjsxm: js.jsxm
|
||||||
|
});
|
||||||
|
|
||||||
|
uni.hideLoading();
|
||||||
|
|
||||||
|
if (result.resultCode === 1) {
|
||||||
|
uni.showToast({ title: '放行成功', icon: 'success' });
|
||||||
|
setTimeout(() => {
|
||||||
|
goHome();
|
||||||
|
}, 1500);
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: result.message || '放行失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('放行失败:', error);
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({ title: '放行失败,请重试', icon: 'none' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onLoad(async (options: any) => {
|
||||||
|
console.log('detail.vue onLoad 接收到的参数:', options);
|
||||||
|
|
||||||
|
try {
|
||||||
|
uni.showLoading({ title: "加载中..." });
|
||||||
|
|
||||||
|
// 场景1: 从待办过来的(from=db),需要先获取待办信息
|
||||||
|
if (options && options.from === "db") {
|
||||||
|
console.log('从待办过来,参数:', options);
|
||||||
|
|
||||||
|
// 检查登录状态
|
||||||
|
if (options.openId) {
|
||||||
|
const isLoggedIn = await loginByOpenId(options.openId);
|
||||||
|
if (!isLoggedIn) {
|
||||||
|
console.log("用户未登录,跳过处理");
|
||||||
|
uni.hideLoading();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await nextTick();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优先从后端根据url中的id去查询Xxts(待办信息)
|
||||||
|
if (options.id) {
|
||||||
|
const xxtsRes = await xxtsFindByIdApi({ id: options.id });
|
||||||
|
if (xxtsRes && xxtsRes.result) {
|
||||||
|
const xxts = xxtsRes.result;
|
||||||
|
console.log('获取到待办信息:', xxts);
|
||||||
|
|
||||||
|
// 根据待办中的主表ID获取请假详情
|
||||||
|
const qjRes = await xsQjFindByIdApi({ id: xxts.xxzbId });
|
||||||
|
if (qjRes && qjRes.result) {
|
||||||
|
setQjData(qjRes.result);
|
||||||
|
await nextTick();
|
||||||
|
console.log('获取到请假详情:', qjRes.result);
|
||||||
|
} else {
|
||||||
|
throw new Error('获取请假详情失败');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('获取待办信息失败');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('缺少待办ID参数');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 场景2: 直接传请假ID(内部跳转)
|
||||||
|
else if (options && options.id) {
|
||||||
|
console.log('直接传请假ID:', options.id);
|
||||||
|
const qjRes = await xsQjFindByIdApi({ id: options.id });
|
||||||
|
if (qjRes && qjRes.result) {
|
||||||
|
setQjData(qjRes.result);
|
||||||
|
await nextTick();
|
||||||
|
console.log('获取到请假详情:', qjRes.result);
|
||||||
|
} else {
|
||||||
|
throw new Error('获取请假详情失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 场景3: 从 store 获取(兼容某些场景)
|
||||||
|
else if (dataStore.qjData && (dataStore.qjData as any).id) {
|
||||||
|
console.log('从 store 获取请假ID:', (dataStore.qjData as any).id);
|
||||||
|
// store 中已有数据,无需重新获取
|
||||||
|
} else {
|
||||||
|
throw new Error('缺少请假ID参数');
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.hideLoading();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('加载请假详情失败:', error);
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || "加载失败",
|
||||||
|
icon: "none"
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateBack();
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.qj-push-page {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100vh;
|
||||||
|
background: url("@/static/base/bg.jpg") no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 顶部区域 - Logo */
|
||||||
|
.header-section {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 80rpx 30rpx 40rpx;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 500rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 中间白色卡片区域 */
|
||||||
|
.content-section {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
padding: 0 30rpx 40rpx;
|
||||||
|
|
||||||
|
.info-card {
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 40rpx 40rpx 20rpx 20rpx;
|
||||||
|
padding: 40rpx 30rpx;
|
||||||
|
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.15);
|
||||||
|
position: relative;
|
||||||
|
min-height: 700rpx;
|
||||||
|
|
||||||
|
/* 审批印章 - 右上角 */
|
||||||
|
.approval-stamp {
|
||||||
|
position: absolute;
|
||||||
|
top: 30rpx;
|
||||||
|
right: 30rpx;
|
||||||
|
width: 160rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
.stamp-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 学生头像 - 中央 */
|
||||||
|
.student-photo-center {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
padding-top: 20rpx;
|
||||||
|
|
||||||
|
// BasicImage 组件的容器样式
|
||||||
|
:deep(.wh-full) {
|
||||||
|
width: 280rpx !important;
|
||||||
|
height: 280rpx !important;
|
||||||
|
border-radius: 32rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #F5F5F5;
|
||||||
|
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15);
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 信息列表 */
|
||||||
|
.info-list {
|
||||||
|
padding-top: 0;
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
line-height: 1.6;
|
||||||
|
|
||||||
|
&.info-item-vertical {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999999;
|
||||||
|
width: 160rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333333;
|
||||||
|
flex: 1;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确认按钮 */
|
||||||
|
.action-button {
|
||||||
|
margin-top: 60rpx;
|
||||||
|
height: 88rpx;
|
||||||
|
background: linear-gradient(90deg, #FF9500 0%, #FF6B00 100%);
|
||||||
|
border-radius: 44rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 8rpx 20rpx rgba(255, 107, 0, 0.3);
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-text {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #FFFFFF;
|
||||||
|
letter-spacing: 4rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部区域 */
|
||||||
|
.footer-section {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
height: 80rpx;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -4,8 +4,7 @@
|
|||||||
<!-- 请假信息卡片 -->
|
<!-- 请假信息卡片 -->
|
||||||
<view class="info-card">
|
<view class="info-card">
|
||||||
<view class="card-header">
|
<view class="card-header">
|
||||||
<text class="applicant-name" v-if="dbFlag">{{ xxtsData.xxzy }}</text>
|
<text class="applicant-name">学生【{{ qjData.xsxm }}】的请假申请</text>
|
||||||
<text class="applicant-name" v-else>学生{{ qjData.xsxm }}的请假申请</text>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="divider"></view>
|
<view class="divider"></view>
|
||||||
<view class="card-body">
|
<view class="card-body">
|
||||||
@ -43,8 +42,16 @@
|
|||||||
<LcglSp :yw-id="qjId" yw-type="XS_QJ" />
|
<LcglSp :yw-id="qjId" yw-type="XS_QJ" />
|
||||||
</view>
|
</view>
|
||||||
<template #bottom>
|
<template #bottom>
|
||||||
<YwConfirm :spApi="xsQjSpApi" :stopApi="xsQjStopApi"
|
<YwConfirm
|
||||||
:transferApi="xsQjTransferApi" :params="spParams" />
|
:spApi="xsQjSpApi"
|
||||||
|
:stopApi="xsQjStopApi"
|
||||||
|
:transferApi="xsQjTransferApi"
|
||||||
|
:params="spParams"
|
||||||
|
:showReject="true"
|
||||||
|
:showTransfer="false"
|
||||||
|
:showApprove="true"
|
||||||
|
:showStop="false"
|
||||||
|
:showXtDk="false" />
|
||||||
</template>
|
</template>
|
||||||
</BasicLayout>
|
</BasicLayout>
|
||||||
</template>
|
</template>
|
||||||
@ -61,24 +68,25 @@ import YwConfirm from "@/pages/components/YwConfirm/index.vue";
|
|||||||
import { XkTfPageUtils } from "@/utils/xkTfPageUtils";
|
import { XkTfPageUtils } from "@/utils/xkTfPageUtils";
|
||||||
|
|
||||||
const { getJs, loginByOpenId } = useUserStore();
|
const { getJs, loginByOpenId } = useUserStore();
|
||||||
const { getQjData, setXxts, setQjData, getXxts } = useDataStore();
|
const dataStore = useDataStore();
|
||||||
|
const { setXxts, setQjData } = dataStore;
|
||||||
|
|
||||||
const dbFlag = ref(false);
|
const dbFlag = ref(false);
|
||||||
|
|
||||||
// 从URL参数获取请假ID
|
// 从URL参数获取请假ID
|
||||||
const qjId = computed(() => {
|
const qjId = computed(() => {
|
||||||
return getQjData.id || '';
|
return (dataStore.qjData as any)?.id || '';
|
||||||
})
|
})
|
||||||
|
|
||||||
const spParams = computed(() => {
|
const spParams = computed(() => {
|
||||||
return {
|
return {
|
||||||
xxtsId: getXxts.id,
|
xxtsId: (dataStore.xxts as any)?.id,
|
||||||
ywId: qjId.value
|
ywId: qjId.value
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// 请假基础数据
|
// 请假基础数据 - 直接访问 store 的 state,保持响应式
|
||||||
const qjData = computed(() => getQjData || {});
|
const qjData = computed(() => (dataStore.qjData || {}) as any);
|
||||||
const xxtsData = ref<any>({})
|
const xxtsData = ref<any>({})
|
||||||
|
|
||||||
onLoad(async (data: any) => {
|
onLoad(async (data: any) => {
|
||||||
@ -93,6 +101,9 @@ onLoad(async (data: any) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 等待下一个 tick 确保响应式更新完成
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 优先从后端根据url中的id去查询Xxts
|
// 优先从后端根据url中的id去查询Xxts
|
||||||
const xxtsRes = await xxtsFindByIdApi({ id: data.id });
|
const xxtsRes = await xxtsFindByIdApi({ id: data.id });
|
||||||
@ -108,22 +119,27 @@ onLoad(async (data: any) => {
|
|||||||
}
|
}
|
||||||
setXxts(xxts);
|
setXxts(xxts);
|
||||||
|
|
||||||
|
// 等待下一个 tick 确保响应式更新完成
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
// 根据主表ID去查询学生请假信息
|
// 根据主表ID去查询学生请假信息
|
||||||
const res = await xsQjFindByIdApi({ id: xxts.xxzbId });
|
const res = await xsQjFindByIdApi({ id: xxts.xxzbId });
|
||||||
const xsQj = res.result || {};
|
const xsQj = res.result || {};
|
||||||
if (xsQj.spResult != "A" && getXxts && getXxts.dbZt === "A") {
|
|
||||||
|
if (xsQj.spResult != "A" && dataStore.xxts && (dataStore.xxts as any).dbZt === "A") {
|
||||||
uni.reLaunch({ url: '/pages/base/xs/qj/detail' });
|
uni.reLaunch({ url: '/pages/base/xs/qj/detail' });
|
||||||
const flag = await XkTfPageUtils.updateXxts();
|
const flag = await XkTfPageUtils.updateXxts();
|
||||||
} else {
|
} else {
|
||||||
nextTick(() => {
|
|
||||||
setQjData(xsQj);
|
setQjData(xsQj);
|
||||||
});
|
|
||||||
|
// 等待下一个 tick 确保响应式更新完成
|
||||||
|
await nextTick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取待办信息失败", error);
|
console.error("获取待办信息失败", error);
|
||||||
// 如果获取Xxts失败,回退到原来的逻辑
|
// 如果获取Xxts失败,回退到原来的逻辑
|
||||||
const xxtsData = getXxts;
|
const xxtsData = dataStore.xxts as any;
|
||||||
if (xxtsData && xxtsData.dbZt === "B") {
|
if (xxtsData && xxtsData.dbZt === "B") {
|
||||||
setQjData({ id: data.id });
|
setQjData({ id: data.id });
|
||||||
let url = "/pages/base/xs/qj/detail";
|
let url = "/pages/base/xs/qj/detail";
|
||||||
@ -131,9 +147,10 @@ onLoad(async (data: any) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const res = await xsQjFindByIdApi({ id: data.id });
|
const res = await xsQjFindByIdApi({ id: data.id });
|
||||||
nextTick(() => {
|
|
||||||
setQjData(res.result);
|
setQjData(res.result);
|
||||||
});
|
|
||||||
|
// 等待下一个 tick 确保响应式更新完成
|
||||||
|
await nextTick();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dbFlag.value = false;
|
dbFlag.value = false;
|
||||||
@ -159,6 +176,7 @@ onLoad(async (data: any) => {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #333;
|
color: #333;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
.applicant-name {
|
.applicant-name {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|||||||
547
src/pages/base/xs/qj/statistics.vue
Normal file
547
src/pages/base/xs/qj/statistics.vue
Normal file
@ -0,0 +1,547 @@
|
|||||||
|
<template>
|
||||||
|
<BasicLayout>
|
||||||
|
<view class="statistics-page">
|
||||||
|
<!-- 时间范围选择 -->
|
||||||
|
<view class="section">
|
||||||
|
<view class="section-title">选择时间范围</view>
|
||||||
|
<view class="date-range-selector">
|
||||||
|
<view class="date-item">
|
||||||
|
<view class="date-label">开始时间</view>
|
||||||
|
<uni-datetime-picker
|
||||||
|
v-model="startDate"
|
||||||
|
type="date"
|
||||||
|
:clear-icon="false"
|
||||||
|
@change="onStartDateChange"
|
||||||
|
>
|
||||||
|
<view class="date-picker">
|
||||||
|
<text>{{ startDate }}</text>
|
||||||
|
<uni-icons type="calendar" size="20" color="#999"></uni-icons>
|
||||||
|
</view>
|
||||||
|
</uni-datetime-picker>
|
||||||
|
</view>
|
||||||
|
<view class="date-separator">至</view>
|
||||||
|
<view class="date-item">
|
||||||
|
<view class="date-label">结束时间</view>
|
||||||
|
<uni-datetime-picker
|
||||||
|
v-model="endDate"
|
||||||
|
type="date"
|
||||||
|
:clear-icon="false"
|
||||||
|
@change="onEndDateChange"
|
||||||
|
>
|
||||||
|
<view class="date-picker">
|
||||||
|
<text>{{ endDate }}</text>
|
||||||
|
<uni-icons type="calendar" size="20" color="#999"></uni-icons>
|
||||||
|
</view>
|
||||||
|
</uni-datetime-picker>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 统计卡片 -->
|
||||||
|
<view class="section">
|
||||||
|
<view class="section-title">请假统计</view>
|
||||||
|
<view class="stats-container">
|
||||||
|
<view
|
||||||
|
class="stat-item"
|
||||||
|
:class="{ active: selectedStatType === 'total' }"
|
||||||
|
@click="onStatItemClick('total')"
|
||||||
|
>
|
||||||
|
<view class="stat-number stat-number-total">{{ totalCount }}</view>
|
||||||
|
<view class="stat-label">总请假人数</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
v-for="(count, type, index) in typeCount"
|
||||||
|
:key="type"
|
||||||
|
class="stat-item"
|
||||||
|
:class="{ active: selectedStatType === type }"
|
||||||
|
@click="onStatItemClick(type)"
|
||||||
|
>
|
||||||
|
<view class="stat-number" :class="getStatNumberClass(type, index)">{{ count }}</view>
|
||||||
|
<view class="stat-label">{{ type }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 学生列表 -->
|
||||||
|
<view class="section" v-if="filteredStudentList.length > 0 || studentList.length > 0">
|
||||||
|
<view class="section-title">
|
||||||
|
请假明细 ({{ filteredStudentList.length }})
|
||||||
|
</view>
|
||||||
|
<view class="student-list-content">
|
||||||
|
<view
|
||||||
|
v-for="item in filteredStudentList"
|
||||||
|
:key="item.id"
|
||||||
|
class="student-item"
|
||||||
|
@click="goDetail(item.id)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
class="avatar"
|
||||||
|
:src="item.xstx || '/static/base/default-avatar.png'"
|
||||||
|
mode="aspectFill"
|
||||||
|
></image>
|
||||||
|
<view class="student-info">
|
||||||
|
<view class="info-row">
|
||||||
|
<text class="name">{{ item.xsxm }}</text>
|
||||||
|
<text class="type-tag">{{ item.qjlx }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-detail">
|
||||||
|
<text class="detail-text">所在年级:{{ item.bc || '暂无' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-detail">
|
||||||
|
<text class="detail-text">家长:{{ item.jzxm || '暂无' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<uni-icons type="right" size="18" color="#999"></uni-icons>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<view class="empty-state" v-if="!loading && filteredStudentList.length === 0 && hasSearched">
|
||||||
|
<view class="empty-icon">📝</view>
|
||||||
|
<view class="empty-text">暂无请假记录</view>
|
||||||
|
<view class="empty-tip">当前时间范围内没有请假数据</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 加载状态 -->
|
||||||
|
<view class="loading-state" v-if="loading">
|
||||||
|
<view class="loading-text">正在加载...</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部按钮 -->
|
||||||
|
<template #bottom>
|
||||||
|
<view class="white-bg-color py-5">
|
||||||
|
<view class="flex-row items-center pb-10 pt-5">
|
||||||
|
<u-button
|
||||||
|
text="返回"
|
||||||
|
class="mx-15"
|
||||||
|
type="primary"
|
||||||
|
@click="goBack"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</BasicLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, computed } from 'vue';
|
||||||
|
import { xsQjStatisticsApi } from "@/api/base/xsQjApi";
|
||||||
|
import { showToast } from "@/utils/uniapp";
|
||||||
|
|
||||||
|
const startDate = ref('');
|
||||||
|
const endDate = ref('');
|
||||||
|
const totalCount = ref(0);
|
||||||
|
const typeCount = ref<Record<string, number>>({});
|
||||||
|
const studentList = ref<any[]>([]);
|
||||||
|
const selectedStatType = ref('total'); // 当前选中的统计类型
|
||||||
|
const loading = ref(false);
|
||||||
|
const hasSearched = ref(false);
|
||||||
|
|
||||||
|
// 初始化日期为当天
|
||||||
|
onMounted(() => {
|
||||||
|
const today = new Date();
|
||||||
|
startDate.value = formatDate(today);
|
||||||
|
endDate.value = formatDate(today);
|
||||||
|
loadStatistics();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 格式化日期 yyyy-MM-dd
|
||||||
|
const formatDate = (date: Date) => {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化时间显示
|
||||||
|
const formatTime = (timeStr: string) => {
|
||||||
|
if (!timeStr) return '';
|
||||||
|
// 如果是完整的日期时间格式,只取日期和时间部分
|
||||||
|
return timeStr.replace(/(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}):\d{2}/, '$1 $2');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 开始日期改变
|
||||||
|
const onStartDateChange = (e: any) => {
|
||||||
|
startDate.value = e;
|
||||||
|
// 如果开始日期大于结束日期,自动调整结束日期
|
||||||
|
if (startDate.value > endDate.value) {
|
||||||
|
endDate.value = startDate.value;
|
||||||
|
}
|
||||||
|
loadStatistics();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 结束日期改变
|
||||||
|
const onEndDateChange = (e: any) => {
|
||||||
|
endDate.value = e;
|
||||||
|
// 如果结束日期小于开始日期,自动调整开始日期
|
||||||
|
if (endDate.value < startDate.value) {
|
||||||
|
startDate.value = endDate.value;
|
||||||
|
}
|
||||||
|
loadStatistics();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 点击统计项
|
||||||
|
const onStatItemClick = (statType: string) => {
|
||||||
|
selectedStatType.value = statType;
|
||||||
|
console.log('选中统计类型:', statType);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取统计数字的颜色类名
|
||||||
|
const getStatNumberClass = (type: string, index: number) => {
|
||||||
|
// 根据请假类型返回不同的颜色类
|
||||||
|
const colorClasses = [
|
||||||
|
'stat-number-type1', // 红色
|
||||||
|
'stat-number-type2', // 橙色
|
||||||
|
'stat-number-type3', // 蓝色
|
||||||
|
'stat-number-type4', // 紫色
|
||||||
|
'stat-number-type5', // 青色
|
||||||
|
];
|
||||||
|
|
||||||
|
// 根据请假类型名称返回特定颜色
|
||||||
|
if (type.includes('病假')) {
|
||||||
|
return 'stat-number-sick'; // 红色
|
||||||
|
} else if (type.includes('事假')) {
|
||||||
|
return 'stat-number-personal'; // 橙色
|
||||||
|
} else if (type.includes('公假')) {
|
||||||
|
return 'stat-number-official'; // 蓝色
|
||||||
|
} else {
|
||||||
|
// 其他类型按索引循环使用颜色
|
||||||
|
return colorClasses[index % colorClasses.length];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 过滤后的学生列表
|
||||||
|
const filteredStudentList = computed(() => {
|
||||||
|
if (selectedStatType.value === 'total') {
|
||||||
|
return studentList.value;
|
||||||
|
} else {
|
||||||
|
// 按请假类型过滤
|
||||||
|
return studentList.value.filter(item => item.qjlx === selectedStatType.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载统计数据
|
||||||
|
const loadStatistics = async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
hasSearched.value = true;
|
||||||
|
uni.showLoading({ title: '加载中...' });
|
||||||
|
|
||||||
|
// 注意:这里仍然使用单个date参数,如果需要范围查询,需要修改后端接口
|
||||||
|
// 暂时使用开始日期作为查询日期
|
||||||
|
const res = await xsQjStatisticsApi({ date: startDate.value });
|
||||||
|
|
||||||
|
if (res.resultCode === 1 && res.result) {
|
||||||
|
totalCount.value = res.result.totalCount || 0;
|
||||||
|
typeCount.value = res.result.typeCount || {};
|
||||||
|
studentList.value = res.result.list || [];
|
||||||
|
} else {
|
||||||
|
showToast({ title: res.message || '加载失败', icon: 'error' });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载统计数据失败:', error);
|
||||||
|
showToast({ title: '加载失败', icon: 'error' });
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
uni.hideLoading();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 跳转详情
|
||||||
|
const goDetail = (id: string) => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/base/xs/qj/detail?id=${id}`
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 返回
|
||||||
|
const goBack = () => {
|
||||||
|
uni.navigateBack({
|
||||||
|
delta: 1
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.statistics-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-selector {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-item {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.date-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16rpx 20rpx;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-separator {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
padding: 0 10rpx;
|
||||||
|
margin-top: 36rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 60rpx;
|
||||||
|
padding: 20rpx;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 120rpx;
|
||||||
|
padding: 20rpx 10rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #e6f7ff;
|
||||||
|
border: 2rpx solid #007aff;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-number {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
|
||||||
|
// 总请假人数 - 绿色
|
||||||
|
&.stat-number-total {
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 病假 - 红色
|
||||||
|
&.stat-number-sick {
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 事假 - 橙色/红色
|
||||||
|
&.stat-number-personal {
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 公假 - 蓝色
|
||||||
|
&.stat-number-official {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他类型颜色
|
||||||
|
&.stat-number-type1 {
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.stat-number-type2 {
|
||||||
|
color: #fa8c16;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.stat-number-type3 {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.stat-number-type4 {
|
||||||
|
color: #722ed1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.stat-number-type5 {
|
||||||
|
color: #13c2c2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.student-list-content {
|
||||||
|
.student-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 24rpx;
|
||||||
|
border-bottom: 1rpx solid #f0f0f0;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
transform: translateY(-2rpx);
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 24rpx;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.student-info {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.info-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-tag {
|
||||||
|
font-size: 20rpx;
|
||||||
|
padding: 6rpx 16rpx;
|
||||||
|
background-color: #e6f7ff;
|
||||||
|
color: #007aff;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-detail {
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
|
||||||
|
.detail-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 100rpx 40rpx;
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
font-size: 120rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-tip {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-state {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 100rpx;
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.white-bg-color {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-5 {
|
||||||
|
padding: 10rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pb-10 {
|
||||||
|
padding-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pt-5 {
|
||||||
|
padding-top: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-center {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-15 {
|
||||||
|
margin: 0 30rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@ -2,7 +2,7 @@
|
|||||||
<view class="white-bg-color py-5 yw-confirm">
|
<view class="white-bg-color py-5 yw-confirm">
|
||||||
<view class="flex-row items-center pt-5 pb-10" v-if="showReject || showTransfer || showApprove || showXtDk">
|
<view class="flex-row items-center pt-5 pb-10" v-if="showReject || showTransfer || showApprove || showXtDk">
|
||||||
<u-button v-if="showReject" text="驳回" class="flex-1 mx-2 reject-btn" @click="showDlg('reject')" />
|
<u-button v-if="showReject" text="驳回" class="flex-1 mx-2 reject-btn" @click="showDlg('reject')" />
|
||||||
<u-button v-if="showTransfer" text="转办" class="flex-1 mx-2 transfer-btn" @click="showTransfer" />
|
<u-button v-if="showTransfer" text="转办" class="flex-1 mx-2 transfer-btn" @click="handleShowTransfer" />
|
||||||
<u-button v-if="showApprove" text="同意" class="flex-1 mx-2" type="primary" @click="submit" />
|
<u-button v-if="showApprove" text="同意" class="flex-1 mx-2" type="primary" @click="submit" />
|
||||||
<u-button v-if="showXtDk" text="协调代课" class="flex-1 mx-2" type="primary" @click="showXtDlg" />
|
<u-button v-if="showXtDk" text="协调代课" class="flex-1 mx-2" type="primary" @click="showXtDlg" />
|
||||||
</view>
|
</view>
|
||||||
@ -108,7 +108,7 @@ const closeDlg = () => {
|
|||||||
dlgFlag.value = false;
|
dlgFlag.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const showTransfer = () => {
|
const handleShowTransfer = () => {
|
||||||
Transferflag.value = true;
|
Transferflag.value = true;
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
transferRef.value.showDlg();
|
transferRef.value.showDlg();
|
||||||
|
|||||||
526
src/pages/view/routine/jstxl/index.vue
Normal file
526
src/pages/view/routine/jstxl/index.vue
Normal file
@ -0,0 +1,526 @@
|
|||||||
|
<template>
|
||||||
|
<view class="teacher-contact-page">
|
||||||
|
<!-- 搜索栏 -->
|
||||||
|
<view class="search-container">
|
||||||
|
<view class="search-bar">
|
||||||
|
<uni-icons type="search" size="18" color="#999"></uni-icons>
|
||||||
|
<input
|
||||||
|
v-model="searchKeyword"
|
||||||
|
class="search-input"
|
||||||
|
placeholder="搜索教师姓名"
|
||||||
|
@input="handleSearch"
|
||||||
|
/>
|
||||||
|
<view v-if="searchKeyword" class="clear-btn" @click="clearSearch">
|
||||||
|
<uni-icons type="clear" size="16" color="#999"></uni-icons>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 字母索引 -->
|
||||||
|
<view class="alphabet-index">
|
||||||
|
<view
|
||||||
|
v-for="letter in alphabetList"
|
||||||
|
:key="letter"
|
||||||
|
class="alphabet-item"
|
||||||
|
:class="{ active: currentLetter === letter }"
|
||||||
|
@click="scrollToLetter(letter)"
|
||||||
|
>
|
||||||
|
{{ letter }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 教师列表 -->
|
||||||
|
<scroll-view
|
||||||
|
class="teacher-list"
|
||||||
|
scroll-y
|
||||||
|
:scroll-with-animation="true"
|
||||||
|
:enable-back-to-top="true"
|
||||||
|
@scroll="onScroll"
|
||||||
|
>
|
||||||
|
<view v-if="filteredTeachers.length === 0" class="empty-state">
|
||||||
|
<uni-icons type="person-filled" size="48" color="#ddd"></uni-icons>
|
||||||
|
<text class="empty-text">暂无教师信息</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
v-for="(group, letter) in groupedTeachers"
|
||||||
|
:key="letter"
|
||||||
|
:id="`letter-${letter}`"
|
||||||
|
class="letter-group"
|
||||||
|
>
|
||||||
|
<view class="letter-header">
|
||||||
|
<text class="letter-title">{{ letter }}</text>
|
||||||
|
<text class="letter-count">{{ group.length }}人</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
v-for="teacher in group"
|
||||||
|
:key="teacher.id"
|
||||||
|
class="teacher-item"
|
||||||
|
>
|
||||||
|
<view class="teacher-avatar">
|
||||||
|
<image
|
||||||
|
v-if="teacher.headPic"
|
||||||
|
class="avatar-image"
|
||||||
|
:src="teacher.headPic"
|
||||||
|
mode="aspectFill"
|
||||||
|
/>
|
||||||
|
<view v-else class="avatar-placeholder">
|
||||||
|
<text class="avatar-text">{{ getFirstChar(teacher.jsxm) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="teacher-info">
|
||||||
|
<view class="teacher-name">{{ teacher.jsxm }}</view>
|
||||||
|
<view v-if="teacher.bjmc" class="teacher-class">{{ teacher.bjmc }}</view>
|
||||||
|
<view v-if="shouldShowTeacherType(teacher)" class="teacher-type">{{ teacher.js_type }}</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="teacher-phone" @click.stop="callTeacher(teacher)">
|
||||||
|
<uni-icons type="phone" size="20" color="#447ade"></uni-icons>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted, watch } from 'vue';
|
||||||
|
import { useCommonStore } from '@/store/modules/common';
|
||||||
|
import { imagUrl } from '@/utils';
|
||||||
|
import { pinyin } from 'pinyin-pro';
|
||||||
|
|
||||||
|
const commonStore = useCommonStore();
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const searchKeyword = ref('');
|
||||||
|
const currentLetter = ref('');
|
||||||
|
const allTeachers = ref<any[]>([]);
|
||||||
|
|
||||||
|
// 完整字母列表(用于排序)
|
||||||
|
const fullAlphabetList = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '#'];
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const filteredTeachers = computed(() => {
|
||||||
|
if (!searchKeyword.value) {
|
||||||
|
return allTeachers.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyword = searchKeyword.value;
|
||||||
|
return allTeachers.value.filter(teacher => {
|
||||||
|
const name = teacher.jsxm || '';
|
||||||
|
return name.includes(keyword);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupedTeachers = computed(() => {
|
||||||
|
const groups: { [key: string]: any[] } = {};
|
||||||
|
|
||||||
|
filteredTeachers.value.forEach(teacher => {
|
||||||
|
const firstChar = getPinyinFirstChar(teacher.jsxm);
|
||||||
|
const letter = firstChar || '#';
|
||||||
|
|
||||||
|
if (!groups[letter]) {
|
||||||
|
groups[letter] = [];
|
||||||
|
}
|
||||||
|
groups[letter].push(teacher);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 按字母顺序排序
|
||||||
|
const sortedGroups: { [key: string]: any[] } = {};
|
||||||
|
fullAlphabetList.forEach(letter => {
|
||||||
|
if (groups[letter]) {
|
||||||
|
sortedGroups[letter] = groups[letter].sort((a, b) =>
|
||||||
|
(a.jsxm || '').localeCompare(b.jsxm || '', 'zh-CN')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return sortedGroups;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 实际有教师的字母列表(用于显示侧边栏索引)
|
||||||
|
const alphabetList = computed(() => {
|
||||||
|
return Object.keys(groupedTeachers.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 方法
|
||||||
|
const loadTeachers = async () => {
|
||||||
|
try {
|
||||||
|
const res = await commonStore.getAllJs();
|
||||||
|
if (res && res.resultCode === 1 && res.result) {
|
||||||
|
allTeachers.value = res.result.map((teacher: any) => ({
|
||||||
|
...teacher,
|
||||||
|
headPic: teacher.headPic ? imagUrl(teacher.headPic) : null
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载教师数据失败:', error);
|
||||||
|
uni.showToast({
|
||||||
|
title: '加载教师数据失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
// 搜索时重置当前字母
|
||||||
|
currentLetter.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearSearch = () => {
|
||||||
|
searchKeyword.value = '';
|
||||||
|
currentLetter.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollToLetter = (letter: string) => {
|
||||||
|
currentLetter.value = letter;
|
||||||
|
|
||||||
|
// 使用 uni-app 的滚动方法
|
||||||
|
setTimeout(() => {
|
||||||
|
// 先获取当前滚动位置
|
||||||
|
uni.createSelectorQuery().selectViewport().scrollOffset((scrollOffset: any) => {
|
||||||
|
// 再获取目标元素位置
|
||||||
|
uni.createSelectorQuery().select(`#letter-${letter}`).boundingClientRect((rect: any) => {
|
||||||
|
if (rect && rect.top !== undefined) {
|
||||||
|
// 计算正确的滚动位置:当前滚动位置 + 元素相对视口位置 - 搜索栏高度
|
||||||
|
const targetScrollTop = scrollOffset.scrollTop + rect.top - 100;
|
||||||
|
|
||||||
|
uni.pageScrollTo({
|
||||||
|
scrollTop: Math.max(0, targetScrollTop), // 确保不滚动到负数位置
|
||||||
|
duration: 300
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).exec();
|
||||||
|
}).exec();
|
||||||
|
|
||||||
|
// 延迟清除active状态
|
||||||
|
setTimeout(() => {
|
||||||
|
currentLetter.value = '';
|
||||||
|
}, 2000);
|
||||||
|
}, 50);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onScroll = (e: any) => {
|
||||||
|
// 可以根据滚动位置更新当前字母
|
||||||
|
// 这里简化处理,实际可以计算当前可见的字母组
|
||||||
|
};
|
||||||
|
|
||||||
|
const callTeacher = (teacher: any) => {
|
||||||
|
if (!teacher.lxdh) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '该教师暂无联系电话',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直接拨打电话,无需确认
|
||||||
|
uni.makePhoneCall({
|
||||||
|
phoneNumber: teacher.lxdh,
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('拨打电话失败:', err);
|
||||||
|
uni.showToast({
|
||||||
|
title: '拨打电话失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFirstChar = (name: string) => {
|
||||||
|
if (!name) return '';
|
||||||
|
return name.charAt(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 判断是否应该显示教师类型
|
||||||
|
const shouldShowTeacherType = (teacher: any) => {
|
||||||
|
const jsType = teacher.js_type;
|
||||||
|
return jsType && jsType !== '教师' && jsType !== '非在编教师';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取拼音首字母(使用专业拼音库)
|
||||||
|
const getPinyinFirstChar = (name: string) => {
|
||||||
|
if (!name) return '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 使用 pinyin-pro 库获取拼音首字母
|
||||||
|
const pinyinStr = pinyin(name.charAt(0), { toneType: 'none' });
|
||||||
|
return pinyinStr.charAt(0).toUpperCase();
|
||||||
|
} catch (error) {
|
||||||
|
// 如果拼音转换失败,返回原字符的大写形式
|
||||||
|
return name.charAt(0).toUpperCase();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生命周期
|
||||||
|
onMounted(() => {
|
||||||
|
loadTeachers();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.teacher-contact-page {
|
||||||
|
min-height: 100vh; /* 改为最小高度,允许内容超出 */
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-right: 50px; /* 为整个页面添加右边距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding: 15px;
|
||||||
|
border-bottom: 1px solid #e4e7ed;
|
||||||
|
z-index: 200; /* 确保搜索栏在最上层 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 8px 15px;
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #303133;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-btn {
|
||||||
|
padding: 4px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alphabet-index {
|
||||||
|
position: fixed;
|
||||||
|
right: 8px; /* 距离屏幕边缘8px */
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
z-index: 100;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 8px 4px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alphabet-item {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #447ade;
|
||||||
|
margin: 2px 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #e6f0ff; /* 浅蓝色背景 */
|
||||||
|
color: #447ade; /* 蓝色文字 */
|
||||||
|
font-weight: bold; /* 加粗 */
|
||||||
|
transform: scale(1.1); /* 稍微放大突出效果 */
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(68, 122, 222, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.teacher-list {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0 15px;
|
||||||
|
background-color: #f5f7fa; /* 确保滚动区域背景色与页面一致 */
|
||||||
|
height: calc(100vh - 70px); /* 设置固定高度,减去搜索栏高度 */
|
||||||
|
margin-top: 70px; /* 为固定搜索栏留出空间 */
|
||||||
|
overflow-y: auto; /* 确保可以滚动 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.letter-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background-color: #f5f7fa; /* 确保字母组背景色一致 */
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 50px; /* 最后一个组增加底部边距 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.letter-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 10px 0 5px;
|
||||||
|
border-bottom: 1px solid #e4e7ed;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.letter-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #447ade;
|
||||||
|
}
|
||||||
|
|
||||||
|
.letter-count {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
margin-right: 10px; /* 数字往左移一点 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.teacher-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
min-height: 60px;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.teacher-avatar {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.avatar-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-placeholder {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(135deg, #447ade 0%, #5a8cff 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.avatar-text {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.teacher-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
margin-right: 10px;
|
||||||
|
|
||||||
|
.teacher-name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.teacher-class {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.teacher-type {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #447ade;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.teacher-phone {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #f0f7ff;
|
||||||
|
margin-left: 12px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: #e6f0ff;
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式优化
|
||||||
|
@media (max-width: 375px) {
|
||||||
|
.search-container {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.teacher-list {
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.teacher-item {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.teacher-avatar {
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.teacher-name {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.teacher-position {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -264,6 +264,11 @@ const goXc = (pb: any) => {
|
|||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/view/routine/kefuxuncha/kyXkList`,
|
url: `/pages/view/routine/kefuxuncha/kyXkList`,
|
||||||
});
|
});
|
||||||
|
} else if (pb.xclx === 'C') {
|
||||||
|
// 值周巡查,跳转到值周巡查列表
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/view/routine/kefuxuncha/zbList`,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '未知的巡查类型',
|
title: '未知的巡查类型',
|
||||||
|
|||||||
@ -733,11 +733,11 @@ onBeforeUnmount(() => {
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
|
|
||||||
// 上课时间不换行
|
// 上课时间允许多行显示
|
||||||
&.study-time {
|
&.study-time {
|
||||||
white-space: nowrap;
|
white-space: normal;
|
||||||
overflow: hidden;
|
word-break: break-word;
|
||||||
text-overflow: ellipsis;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
977
src/pages/view/routine/kefuxuncha/zbDetail.vue
Normal file
977
src/pages/view/routine/kefuxuncha/zbDetail.vue
Normal file
@ -0,0 +1,977 @@
|
|||||||
|
<template>
|
||||||
|
<BasicLayout>
|
||||||
|
<!-- 待巡查内容 -->
|
||||||
|
<view class="pending-inspection">
|
||||||
|
<!-- 值周信息卡片 -->
|
||||||
|
<view class="duty-card mx-15 my-15 bg-white white-bg-color r-md p-15">
|
||||||
|
<view class="flex-row items-center mb-15">
|
||||||
|
<view class="duty-icon flex-center mr-10">
|
||||||
|
<u-icon name="account" color="#4080ff" size="20"></u-icon>
|
||||||
|
</view>
|
||||||
|
<text class="font-16 font-bold">{{ zb.jsxm || '值周教师' }}</text>
|
||||||
|
<text class="font-14 cor-999 ml-10">{{ todayInfo.weekName }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 值周详细信息 -->
|
||||||
|
<view class="duty-time-info">
|
||||||
|
<view class="time-item">
|
||||||
|
<view class="time-label">值周周次:</view>
|
||||||
|
<view class="time-value">第{{ zb.zbzc }}周</view>
|
||||||
|
</view>
|
||||||
|
<view class="time-item">
|
||||||
|
<view class="time-label">值周星期:</view>
|
||||||
|
<view class="time-value">{{ zb.zbxq || '全周' }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="time-item">
|
||||||
|
<view class="time-label">值周区域:</view>
|
||||||
|
<view class="time-value">{{ zb.zbqy || '暂无' }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="time-item">
|
||||||
|
<view class="time-label">值周位置:</view>
|
||||||
|
<view class="time-value">{{ zb.zbwz || '暂无' }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="time-item">
|
||||||
|
<view class="time-label">值周角色:</view>
|
||||||
|
<view class="time-value">{{ zb.zbjs || '暂无' }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 值周时间信息 -->
|
||||||
|
<view class="inspection-time-info" v-if="zb.zbkstime && zb.zbjstime">
|
||||||
|
<view class="time-item">
|
||||||
|
<u-icon name="clock" color="#4080ff" size="16"></u-icon>
|
||||||
|
<text class="time-label">值周时间:</text>
|
||||||
|
<text class="time-value">{{ formatTime(zb.zbkstime) }} - {{ formatTime(zb.zbjstime) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 巡查时间状态 -->
|
||||||
|
<view class="inspection-status" v-if="!canInspect">
|
||||||
|
<u-icon name="clock" color="#ff9900" size="16"></u-icon>
|
||||||
|
<text class="status-text">{{ inspectionStatusText }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="canInspect">
|
||||||
|
<!-- 巡查项目 -->
|
||||||
|
<view class="section mx-15 mb-15">
|
||||||
|
<view class="section-title-bar">
|
||||||
|
<view class="decorator"></view>
|
||||||
|
<text class="title-text">巡查项目</text>
|
||||||
|
</view>
|
||||||
|
<view class="check-card bg-white r-md p-15">
|
||||||
|
<template v-if="checkItems && checkItems.length > 0">
|
||||||
|
<view class="check-list">
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in checkItems"
|
||||||
|
:key="item.id"
|
||||||
|
class="check-item"
|
||||||
|
>
|
||||||
|
<view class="item-info flex-1">
|
||||||
|
<!-- 项目名称单独一行 -->
|
||||||
|
<text class="item-text"
|
||||||
|
>{{ index + 1 }}、{{ item.xcMc }}</text
|
||||||
|
>
|
||||||
|
<!-- 分值和结果同一行 -->
|
||||||
|
<view class="item-score-result">
|
||||||
|
<text class="item-deduction mr-20">
|
||||||
|
分值:{{ item.xmFz }}分
|
||||||
|
</text>
|
||||||
|
<view class="item-result">
|
||||||
|
<radio-group
|
||||||
|
:name="'result_' + item.id"
|
||||||
|
class="item-radio-group"
|
||||||
|
@change="onCheckItemChange($event, item)"
|
||||||
|
>
|
||||||
|
<label class="item-radio-label mr-10">
|
||||||
|
<radio
|
||||||
|
:value="'A'"
|
||||||
|
:checked="item.xcJg === 'A'"
|
||||||
|
color="#52c41a"
|
||||||
|
class="item-radio"
|
||||||
|
/>
|
||||||
|
<text class="ml-2">优点</text>
|
||||||
|
</label>
|
||||||
|
<label class="item-radio-label">
|
||||||
|
<radio
|
||||||
|
:value="'B'"
|
||||||
|
:checked="item.xcJg === 'B'"
|
||||||
|
color="#ff4d4f"
|
||||||
|
class="item-radio"
|
||||||
|
/>
|
||||||
|
<text class="ml-2">缺点</text>
|
||||||
|
</label>
|
||||||
|
</radio-group>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 显示输入的评价内容 -->
|
||||||
|
<view v-if="item.xcPj" class="item-comment" @click="editComment(item)">
|
||||||
|
<view class="comment-header">
|
||||||
|
<text class="comment-label">{{ item.xcJg === 'A' ? '优点' : '缺点' }}:</text>
|
||||||
|
<text class="comment-edit-hint">点击编辑</text>
|
||||||
|
</view>
|
||||||
|
<text class="comment-text">{{ item.xcPj }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<view
|
||||||
|
class="no-check-items"
|
||||||
|
style="text-align: center; color: #999; padding: 20px 0"
|
||||||
|
>
|
||||||
|
暂无巡查项目
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 图片视频上传组件 -->
|
||||||
|
<view class="section mx-15 mb-30">
|
||||||
|
<view class="section-title-bar">
|
||||||
|
<view class="decorator"></view>
|
||||||
|
<text class="title-text">图片视频上传</text>
|
||||||
|
</view>
|
||||||
|
<view class="upload-card bg-white r-md p-15">
|
||||||
|
<ImageVideoUpload
|
||||||
|
v-model:image-list="imageList"
|
||||||
|
v-model:video-list="videoList"
|
||||||
|
:max-image-count="5"
|
||||||
|
:max-video-count="3"
|
||||||
|
:compress-config="compressConfig"
|
||||||
|
:upload-api="attachmentUpload"
|
||||||
|
@image-upload-success="onImageUploadSuccess"
|
||||||
|
@video-upload-success="onVideoUploadSuccess"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<template #bottom>
|
||||||
|
<view
|
||||||
|
v-if="canInspect"
|
||||||
|
class="submit-btn-wrap py-10 px-20 bg-white"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="submit-btn"
|
||||||
|
:class="{ 'submit-btn-disabled': isSubmitting }"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
@click="submit"
|
||||||
|
>
|
||||||
|
{{ isSubmitting ? (xcRecordId ? '更新中...' : '提交中...') : (xcRecordId ? '更新巡查' : '提交巡查') }}
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</BasicLayout>
|
||||||
|
|
||||||
|
<!-- 富文本输入弹窗 -->
|
||||||
|
<view v-if="showInputModal" class="input-modal-overlay" @click="closeInputModal">
|
||||||
|
<view class="input-modal-content" @click.stop>
|
||||||
|
<view class="input-modal-header">
|
||||||
|
<text class="input-modal-title">请输入{{ currentInputLabel }}</text>
|
||||||
|
<view class="input-modal-close" @click="closeInputModal">
|
||||||
|
<u-icon name="close" size="20" color="#666"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="input-modal-body">
|
||||||
|
<textarea
|
||||||
|
v-model="inputContent"
|
||||||
|
class="input-textarea"
|
||||||
|
:placeholder="`请输入${currentInputLabel}内容`"
|
||||||
|
:maxlength="500"
|
||||||
|
:show-confirm-bar="false"
|
||||||
|
:auto-height="false"
|
||||||
|
/>
|
||||||
|
<view class="input-counter">{{ inputContent.length }}/500</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="input-modal-footer">
|
||||||
|
<button class="input-modal-btn input-modal-btn-cancel" @click="closeInputModal">
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
|
<button class="input-modal-btn input-modal-btn-confirm" @click="confirmInput">
|
||||||
|
确定
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { xcXmFindByPbLxIdApi } from "@/api/base/xcXmApi";
|
||||||
|
import { zbXcSaveApi } from "@/api/base/zbXcApi";
|
||||||
|
import { attachmentUpload } from "@/api/system/upload";
|
||||||
|
import BasicLayout from "@/components/BasicLayout/Layout.vue";
|
||||||
|
import { ImageVideoUpload } from "@/components/ImageVideoUpload";
|
||||||
|
import { useDataStore } from "@/store/modules/data";
|
||||||
|
import { useUserStore } from "@/store/modules/user";
|
||||||
|
import { computed, onMounted, ref } from "vue";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { imagUrl } from "@/utils";
|
||||||
|
import { showLoading, hideLoading, showToast } from "@/utils/uniapp";
|
||||||
|
|
||||||
|
const { getJs } = useUserStore();
|
||||||
|
const { getData } = useDataStore();
|
||||||
|
|
||||||
|
const js = computed(() => getJs);
|
||||||
|
const zb = computed(() => getData);
|
||||||
|
|
||||||
|
const now = dayjs();
|
||||||
|
let wDay = now.day();
|
||||||
|
if (wDay === 0) {
|
||||||
|
wDay = 7;
|
||||||
|
}
|
||||||
|
const wdNameList = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
|
||||||
|
const todayInfo = ref({
|
||||||
|
date: now.format("YYYY-MM-DD"),
|
||||||
|
weekName: wdNameList[wDay - 1],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 巡查项目
|
||||||
|
const checkItems = ref<any[]>([]);
|
||||||
|
|
||||||
|
// 巡查记录ID(用于更新)
|
||||||
|
const xcRecordId = ref('');
|
||||||
|
|
||||||
|
// 导入类型和配置
|
||||||
|
import { type ImageItem, type VideoItem, COMPRESS_PRESETS } from '@/components/ImageVideoUpload'
|
||||||
|
|
||||||
|
// 压缩配置
|
||||||
|
const compressConfig = ref(COMPRESS_PRESETS.high)
|
||||||
|
|
||||||
|
const imageList = ref<ImageItem[]>([]);
|
||||||
|
const videoList = ref<VideoItem[]>([]);
|
||||||
|
|
||||||
|
// 巡查状态相关
|
||||||
|
const inspectionStatusText = ref("");
|
||||||
|
const canInspect = ref(true);
|
||||||
|
|
||||||
|
// 提交状态控制
|
||||||
|
const isSubmitting = ref(false);
|
||||||
|
|
||||||
|
// 输入弹窗相关
|
||||||
|
const showInputModal = ref(false);
|
||||||
|
const inputContent = ref('');
|
||||||
|
const currentInputLabel = ref('');
|
||||||
|
const currentInputValue = ref('');
|
||||||
|
const currentItem = ref<any>(null);
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
|
const formatTime = (timestamp: string) => {
|
||||||
|
if (!timestamp) return '';
|
||||||
|
return dayjs(timestamp).format('MM-DD HH:mm');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载巡查项目
|
||||||
|
const loadCheckItems = async () => {
|
||||||
|
try {
|
||||||
|
// 根据排班类型ID查询巡查项目
|
||||||
|
const pbLxId = zb.value.pbLxId;
|
||||||
|
|
||||||
|
if (!pbLxId) {
|
||||||
|
console.warn('未找到排班类型ID,无法加载巡查项目');
|
||||||
|
checkItems.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('加载巡查项目,pbLxId:', pbLxId);
|
||||||
|
const res = await xcXmFindByPbLxIdApi(pbLxId);
|
||||||
|
|
||||||
|
if (res && res.resultCode === 1) {
|
||||||
|
checkItems.value = (res.result || []).map((item: any) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
xcJg: '', // 巡查结果:A-优点,B-缺点
|
||||||
|
xcPj: '', // 巡查评价内容
|
||||||
|
};
|
||||||
|
});
|
||||||
|
console.log('巡查项目列表:', checkItems.value);
|
||||||
|
|
||||||
|
// 如果有巡查记录,回显数据
|
||||||
|
if (zb.value.xcRecord) {
|
||||||
|
restoreXcRecord(zb.value.xcRecord);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
checkItems.value = [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("加载巡查项目失败:", error);
|
||||||
|
checkItems.value = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 回显巡查记录数据
|
||||||
|
const restoreXcRecord = (xcRecord: any) => {
|
||||||
|
try {
|
||||||
|
console.log('========== 开始回显巡查记录 ==========');
|
||||||
|
console.log('巡查记录ID:', xcRecord.id);
|
||||||
|
console.log('巡查记录 zbXcXmList:', xcRecord.zbXcXmList);
|
||||||
|
console.log('当前巡查项目列表:', checkItems.value.map(item => ({
|
||||||
|
id: item.id,
|
||||||
|
xcMc: item.xcMc
|
||||||
|
})));
|
||||||
|
|
||||||
|
// 保存巡查记录ID
|
||||||
|
xcRecordId.value = xcRecord.id || '';
|
||||||
|
console.log('保存的巡查记录ID:', xcRecordId.value);
|
||||||
|
|
||||||
|
// 回显巡查项目数据
|
||||||
|
if (xcRecord.zbXcXmList && xcRecord.zbXcXmList.length > 0) {
|
||||||
|
console.log('开始回显', xcRecord.zbXcXmList.length, '个巡查项目');
|
||||||
|
|
||||||
|
let matchedCount = 0;
|
||||||
|
xcRecord.zbXcXmList.forEach((xcXm: any, index: number) => {
|
||||||
|
console.log(`\n[${index + 1}] 尝试匹配项目:`);
|
||||||
|
console.log(' xcXm.id:', xcXm.id);
|
||||||
|
console.log(' xcXm.xcXmId:', xcXm.xcXmId);
|
||||||
|
console.log(' xcXm.xcMc:', xcXm.xcMc);
|
||||||
|
console.log(' xcXm.xcJg:', xcXm.xcJg);
|
||||||
|
console.log(' xcXm.xcPj:', xcXm.xcPj);
|
||||||
|
|
||||||
|
const checkItem = checkItems.value.find(item => item.id === xcXm.xcXmId);
|
||||||
|
if (checkItem) {
|
||||||
|
console.log(' ✓ 找到匹配项目:', checkItem.xcMc);
|
||||||
|
checkItem.xcJg = xcXm.xcJg || '';
|
||||||
|
checkItem.xcPj = xcXm.xcPj || '';
|
||||||
|
checkItem.xcXmRecordId = xcXm.id || ''; // 保存巡查项目记录ID
|
||||||
|
matchedCount++;
|
||||||
|
console.log(' 设置后 - xcJg:', checkItem.xcJg, ', xcPj:', checkItem.xcPj);
|
||||||
|
} else {
|
||||||
|
console.log(' ✗ 未找到匹配项目!');
|
||||||
|
console.log(' 可用的项目ID:', checkItems.value.map(item => item.id).join(', '));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`\n成功匹配 ${matchedCount}/${xcRecord.zbXcXmList.length} 个巡查项目`);
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ 没有巡查项目记录需要回显(zbXcXmList 为空或不存在)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回显图片
|
||||||
|
if (xcRecord.zp) {
|
||||||
|
const imageUrls = xcRecord.zp.split(',').map((url: string) => url.trim()).filter((url: string) => url);
|
||||||
|
imageList.value = imageUrls.map((url: string) => ({
|
||||||
|
url: url,
|
||||||
|
path: imagUrl(url),
|
||||||
|
uploaded: true
|
||||||
|
}));
|
||||||
|
console.log('✓ 回显图片:', imageList.value.length, '张');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回显视频
|
||||||
|
if (xcRecord.sp) {
|
||||||
|
const videoUrls = xcRecord.sp.split(',').map((url: string) => url.trim()).filter((url: string) => url);
|
||||||
|
videoList.value = videoUrls.map((url: string) => ({
|
||||||
|
url: url,
|
||||||
|
path: imagUrl(url),
|
||||||
|
uploaded: true
|
||||||
|
}));
|
||||||
|
console.log('✓ 回显视频:', videoList.value.length, '个');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n回显完成,最终巡查项目状态:');
|
||||||
|
checkItems.value.forEach((item, index) => {
|
||||||
|
console.log(` [${index + 1}] ${item.xcMc} - xcJg: ${item.xcJg || '未选择'}, xcPj: ${item.xcPj || '无'}`);
|
||||||
|
});
|
||||||
|
console.log('========== 回显完成 ==========\n');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 回显巡查记录失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCheckItemChange = (e: any, item: any) => {
|
||||||
|
const value = e.detail.value; // 'A' 或 'B'
|
||||||
|
const label = value === 'A' ? '优点' : '缺点';
|
||||||
|
|
||||||
|
// 打开自定义输入弹窗
|
||||||
|
currentItem.value = item;
|
||||||
|
currentInputValue.value = value;
|
||||||
|
currentInputLabel.value = label;
|
||||||
|
inputContent.value = item.xcPj || '';
|
||||||
|
showInputModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭输入弹窗
|
||||||
|
const closeInputModal = () => {
|
||||||
|
showInputModal.value = false;
|
||||||
|
// 如果用户之前没有输入内容,清空选择
|
||||||
|
if (currentItem.value && !currentItem.value.xcPj) {
|
||||||
|
currentItem.value.xcJg = '';
|
||||||
|
currentItem.value.xcPj = '';
|
||||||
|
}
|
||||||
|
inputContent.value = '';
|
||||||
|
currentItem.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 确认输入
|
||||||
|
const confirmInput = () => {
|
||||||
|
const value = inputContent.value.trim();
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
uni.showToast({
|
||||||
|
title: `请输入${currentInputLabel.value}内容`,
|
||||||
|
icon: 'none',
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentItem.value) {
|
||||||
|
currentItem.value.xcJg = currentInputValue.value;
|
||||||
|
currentItem.value.xcPj = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
showInputModal.value = false;
|
||||||
|
inputContent.value = '';
|
||||||
|
currentItem.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 编辑评价内容
|
||||||
|
const editComment = (item: any) => {
|
||||||
|
const label = item.xcJg === 'A' ? '优点' : '缺点';
|
||||||
|
|
||||||
|
currentItem.value = item;
|
||||||
|
currentInputValue.value = item.xcJg;
|
||||||
|
currentInputLabel.value = label;
|
||||||
|
inputContent.value = item.xcPj || '';
|
||||||
|
showInputModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 上传成功回调
|
||||||
|
const onImageUploadSuccess = (image: ImageItem, index: number) => {
|
||||||
|
console.log('图片上传成功:', image, index);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onVideoUploadSuccess = (video: VideoItem, index: number) => {
|
||||||
|
console.log('视频上传成功:', video, index);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查巡查时间
|
||||||
|
const checkInspectionTime = () => {
|
||||||
|
const zbkstime = zb.value.zbkstime;
|
||||||
|
const zbjstime = zb.value.zbjstime;
|
||||||
|
|
||||||
|
// 如果没有设置巡查时间,直接允许巡查
|
||||||
|
if (!zbkstime || !zbjstime) {
|
||||||
|
canInspect.value = true;
|
||||||
|
inspectionStatusText.value = "可以巡查";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = dayjs(zbkstime);
|
||||||
|
const endTime = dayjs(zbjstime);
|
||||||
|
|
||||||
|
// 获取日期(不包含时间)
|
||||||
|
const currentDate = now.format('YYYY-MM-DD');
|
||||||
|
const startDate = startTime.format('YYYY-MM-DD');
|
||||||
|
const endDate = endTime.format('YYYY-MM-DD');
|
||||||
|
|
||||||
|
// 检查当前日期是否在值周日期范围内(包含边界)
|
||||||
|
if (currentDate < startDate) {
|
||||||
|
canInspect.value = false;
|
||||||
|
inspectionStatusText.value = `还未到值周时间,无法巡查(值周时间:${formatTime(zbkstime)} - ${formatTime(zbjstime)})`;
|
||||||
|
} else if (currentDate > endDate) {
|
||||||
|
canInspect.value = false;
|
||||||
|
inspectionStatusText.value = `值周时间已结束,无法巡查(值周时间:${formatTime(zbkstime)} - ${formatTime(zbjstime)})`;
|
||||||
|
} else {
|
||||||
|
canInspect.value = true;
|
||||||
|
inspectionStatusText.value = `可以巡查(值周时间:${formatTime(zbkstime)} - ${formatTime(zbjstime)})`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 提交数据
|
||||||
|
const submit = async () => {
|
||||||
|
// 防抖动
|
||||||
|
if (isSubmitting.value) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "正在提交中,请勿重复点击",
|
||||||
|
icon: "none",
|
||||||
|
duration: 1500,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新检查巡查时间
|
||||||
|
checkInspectionTime();
|
||||||
|
|
||||||
|
if (!canInspect.value) {
|
||||||
|
uni.showToast({
|
||||||
|
title: inspectionStatusText.value,
|
||||||
|
icon: "none",
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证巡查项目是否已选择
|
||||||
|
const hasCheckedItems = checkItems.value.some(item => item.xcJg && item.xcPj);
|
||||||
|
if (!hasCheckedItems) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "请至少选择一个巡查项目并填写评价",
|
||||||
|
icon: "none",
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSubmitting.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 构建巡查项目列表(只提交已选择的项目)
|
||||||
|
const zbXcXmList = checkItems.value
|
||||||
|
.filter((item: any) => item.xcJg && item.xcPj)
|
||||||
|
.map((item: any) => {
|
||||||
|
const newItem = {
|
||||||
|
...item,
|
||||||
|
xcXmId: item.id,
|
||||||
|
xcJg: item.xcJg, // A-优点,B-缺点
|
||||||
|
xcPj: item.xcPj, // 评价内容
|
||||||
|
zbXcId: xcRecordId.value || "", // 巡查记录ID(更新时使用)
|
||||||
|
};
|
||||||
|
// 如果是更新操作且有巡查项目记录ID,保留该ID
|
||||||
|
if (xcRecordId.value && item.xcXmRecordId) {
|
||||||
|
newItem.id = item.xcXmRecordId;
|
||||||
|
} else {
|
||||||
|
newItem.id = "";
|
||||||
|
}
|
||||||
|
return newItem;
|
||||||
|
});
|
||||||
|
|
||||||
|
const submitData: any = {
|
||||||
|
jsId: js.value.id,
|
||||||
|
jsxm: js.value.xm || js.value.jsxm,
|
||||||
|
pbZbId: zb.value.id, // 值周排班记录ID
|
||||||
|
pbLxId: zb.value.pbLxId, // 排班类型ID
|
||||||
|
xctime: now.format("YYYY-MM-DD HH:mm:ss"),
|
||||||
|
zp: getImageUrls(),
|
||||||
|
sp: getVideoUrls(),
|
||||||
|
zbXcXmList: zbXcXmList,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果是更新操作,添加记录ID
|
||||||
|
if (xcRecordId.value) {
|
||||||
|
submitData.id = xcRecordId.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('提交值周巡查数据:', submitData);
|
||||||
|
|
||||||
|
const res = await zbXcSaveApi(submitData);
|
||||||
|
|
||||||
|
if (res && res.resultCode === 1) {
|
||||||
|
uni.showToast({
|
||||||
|
title: xcRecordId.value ? "更新成功" : "提交成功",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateBack({
|
||||||
|
success: () => {
|
||||||
|
// 通知列表页面刷新
|
||||||
|
uni.$emit('refreshZbList');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 1500);
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: xcRecordId.value ? "更新失败" : "提交失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('提交值周巡查失败:', error);
|
||||||
|
uni.showToast({
|
||||||
|
title: xcRecordId.value ? "更新失败" : "提交失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
isSubmitting.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取图片URL列表
|
||||||
|
const getImageUrls = () => {
|
||||||
|
const urls = imageList.value
|
||||||
|
.filter(img => img.url)
|
||||||
|
.map(img => img.url)
|
||||||
|
.filter((url): url is string => !!url);
|
||||||
|
|
||||||
|
return urls.length > 0 ? urls.join(',') : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取视频URL列表
|
||||||
|
const getVideoUrls = () => {
|
||||||
|
const urls = videoList.value
|
||||||
|
.filter(video => video.url)
|
||||||
|
.map(video => video.url)
|
||||||
|
.filter((url): url is string => !!url);
|
||||||
|
|
||||||
|
return urls.length > 0 ? urls.join(',') : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面加载时获取状态选项
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadCheckItems();
|
||||||
|
checkInspectionTime();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-white {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pending-inspection {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duty-card {
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.duty-icon {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: rgba(64, 128, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.duty-time-info {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 10px 15px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
|
||||||
|
.time-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-value {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.inspection-time-info {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 10px 15px;
|
||||||
|
background-color: #f0f8ff;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #d6e4ff;
|
||||||
|
|
||||||
|
.time-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.time-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-value {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #4080ff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.inspection-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 10px 15px;
|
||||||
|
background-color: #fffbe6;
|
||||||
|
border: 1px solid #ffe58f;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #faad14;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
.section-title-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.decorator {
|
||||||
|
width: 4px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: #4080ff;
|
||||||
|
margin-right: 8px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-card {
|
||||||
|
background-color: white;
|
||||||
|
|
||||||
|
.check-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
|
||||||
|
.item-score-result {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-deduction {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-comment {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-radius: 6px;
|
||||||
|
border-left: 3px solid #4080ff;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-edit-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #4080ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-text {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.8;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-card {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
background-color: #4080ff;
|
||||||
|
color: #fff;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 22px;
|
||||||
|
font-size: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn-disabled {
|
||||||
|
background-color: #d9d9d9 !important;
|
||||||
|
color: #999 !important;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cor-999 {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 输入弹窗样式 */
|
||||||
|
.input-modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 9999;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-modal-content {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
max-height: 80vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-modal-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
|
||||||
|
.input-modal-title {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-modal-close {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-modal-body {
|
||||||
|
padding: 20px;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.input-textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 200px;
|
||||||
|
max-height: 400px;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
background-color: #fafafa;
|
||||||
|
resize: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: #4080ff;
|
||||||
|
background-color: #fff;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-counter {
|
||||||
|
margin-top: 8px;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-modal-footer {
|
||||||
|
display: flex;
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.input-modal-btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 22px;
|
||||||
|
font-size: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&.input-modal-btn-cancel {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #666;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: #e8e8e8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.input-modal-btn-confirm {
|
||||||
|
background-color: #4080ff;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: #3070ef;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
720
src/pages/view/routine/kefuxuncha/zbList.vue
Normal file
720
src/pages/view/routine/kefuxuncha/zbList.vue
Normal file
@ -0,0 +1,720 @@
|
|||||||
|
<template>
|
||||||
|
<view class="duty-inspection">
|
||||||
|
<!-- 值周信息头部 - 固定部分 -->
|
||||||
|
<view class="selection-header">
|
||||||
|
<view class="header-content">
|
||||||
|
<view class="title-section">
|
||||||
|
<view class="title">
|
||||||
|
<text v-if="pbData && pbData.xcbt">{{ pbData.xcbt }}</text>
|
||||||
|
<text v-else>值周巡查</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 可滚动的内容区域 -->
|
||||||
|
<view class="scrollable-content">
|
||||||
|
<!-- 值周列表 -->
|
||||||
|
<view class="duty-list" v-if="zbList && zbList.length > 0">
|
||||||
|
<view
|
||||||
|
v-for="(zb, index) in zbList"
|
||||||
|
:key="zb.id || index"
|
||||||
|
class="duty-item"
|
||||||
|
>
|
||||||
|
<!-- 巡查状态标识 -->
|
||||||
|
<view class="duty-status" :class="zb.sfxc === '是' ? 'status-done' : 'status-pending'">
|
||||||
|
{{ zb.sfxc === '是' ? '已巡查' : '待巡查' }}
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="duty-name">{{ getDutyDisplayName(zb) }}</view>
|
||||||
|
|
||||||
|
<!-- 值周信息 -->
|
||||||
|
<view class="duty-info-row">
|
||||||
|
<view class="duty-info-item">
|
||||||
|
<view class="info-label">值周周次:</view>
|
||||||
|
<view class="info-data">第{{ zb.zbzc }}周</view>
|
||||||
|
</view>
|
||||||
|
<view class="duty-info-item">
|
||||||
|
<view class="info-label">值周星期:</view>
|
||||||
|
<view class="info-data">{{ zb.zbxq || '全周' }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="duty-info-row">
|
||||||
|
<view class="duty-info-item">
|
||||||
|
<view class="info-label">值周区域:</view>
|
||||||
|
<view class="info-data">{{ zb.zbqy || '暂无' }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="duty-info-item">
|
||||||
|
<view class="info-label">值周位置:</view>
|
||||||
|
<view class="info-data">{{ zb.zbwz || '暂无' }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="duty-info-row">
|
||||||
|
<view class="duty-info-item">
|
||||||
|
<view class="info-label">值周时间:</view>
|
||||||
|
<view class="info-data time">{{ formatDutyTime(zb) }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="separator-line"></view>
|
||||||
|
<view class="duty-btn-group">
|
||||||
|
<view class="xc-btn" @click.stop="goXc(zb)">巡查</view>
|
||||||
|
<view class="record-btn" @click.stop="goRecord(zb)">巡查记录</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 暂无数据提示 -->
|
||||||
|
<view v-else class="empty-duty-list">
|
||||||
|
<view class="empty-icon">
|
||||||
|
<u-icon name="list" size="50" color="#C8C9CC"></u-icon>
|
||||||
|
</view>
|
||||||
|
<view class="empty-text">暂无值周数据</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { getZbXcListApi } from "@/api/base/pbApi";
|
||||||
|
import { zbXcFindPageApi } from "@/api/base/zbXcApi";
|
||||||
|
import { useDataStore } from "@/store/modules/data";
|
||||||
|
import { useUserStore } from "@/store/modules/user";
|
||||||
|
import { onBeforeUnmount, onMounted, ref } from "vue";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
const { getJs } = useUserStore();
|
||||||
|
const dataStore = useDataStore();
|
||||||
|
|
||||||
|
// 值周列表数据
|
||||||
|
const zbList = ref<any[]>([]);
|
||||||
|
|
||||||
|
// 排班数据
|
||||||
|
const pbData = ref<any>(null);
|
||||||
|
|
||||||
|
// 星期名称列表
|
||||||
|
const wdNameList = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
|
||||||
|
|
||||||
|
// 检查当前时间是否符合值周时间安排
|
||||||
|
const isCurrentTimeMatch = (zb: any) => {
|
||||||
|
const now = dayjs();
|
||||||
|
|
||||||
|
// 检查是否在值周时间范围内
|
||||||
|
if (zb.zbkstime && zb.zbjstime) {
|
||||||
|
const startTime = dayjs(zb.zbkstime);
|
||||||
|
const endTime = dayjs(zb.zbjstime);
|
||||||
|
|
||||||
|
// 获取日期(不包含时间)
|
||||||
|
const currentDate = now.format('YYYY-MM-DD');
|
||||||
|
const startDate = startTime.format('YYYY-MM-DD');
|
||||||
|
const endDate = endTime.format('YYYY-MM-DD');
|
||||||
|
|
||||||
|
// 当前日期在值周日期范围内(包含开始和结束日期)
|
||||||
|
if (currentDate >= startDate && currentDate <= endDate) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取值周显示名称
|
||||||
|
const getDutyDisplayName = (zb: any) => {
|
||||||
|
let displayName = '';
|
||||||
|
|
||||||
|
if (zb.jsxm) {
|
||||||
|
displayName = zb.jsxm;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zb.zbjs) {
|
||||||
|
displayName += ` - ${zb.zbjs}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return displayName || '值周教师';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化值周时间
|
||||||
|
const formatDutyTime = (zb: any) => {
|
||||||
|
if (zb.zbkstime && zb.zbjstime) {
|
||||||
|
const startTime = dayjs(zb.zbkstime).format('MM-DD HH:mm');
|
||||||
|
const endTime = dayjs(zb.zbjstime).format('MM-DD HH:mm');
|
||||||
|
return `${startTime} ~ ${endTime}`;
|
||||||
|
}
|
||||||
|
return '暂无';
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
uni.showLoading({
|
||||||
|
title: "加载中...",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 从 data 获取排班数据
|
||||||
|
let tempPbData = dataStore.getData;
|
||||||
|
|
||||||
|
console.log('初始获取的data数据:', tempPbData);
|
||||||
|
|
||||||
|
// 如果data没有数据,再从global获取
|
||||||
|
if (!tempPbData || !tempPbData.xcbt) {
|
||||||
|
tempPbData = dataStore.getGlobal;
|
||||||
|
console.log('从global获取的数据:', tempPbData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查数据是否有效
|
||||||
|
if (!tempPbData || !tempPbData.xcbt) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '数据异常,请重新选择排班',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
uni.navigateBack();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保有 pbId 字段
|
||||||
|
const pbId = tempPbData.pbId || tempPbData.id;
|
||||||
|
const pbDataWithPbId = {
|
||||||
|
...tempPbData,
|
||||||
|
pbId: pbId,
|
||||||
|
pbLxId: tempPbData.pbLxId || ''
|
||||||
|
};
|
||||||
|
delete pbDataWithPbId.id;
|
||||||
|
|
||||||
|
dataStore.setGlobal(pbDataWithPbId);
|
||||||
|
pbData.value = pbDataWithPbId;
|
||||||
|
|
||||||
|
console.log('排班数据检查:', {
|
||||||
|
pbData: pbDataWithPbId,
|
||||||
|
pbId: pbId,
|
||||||
|
pbLxId: pbDataWithPbId.pbLxId
|
||||||
|
});
|
||||||
|
|
||||||
|
await loadZbXcList(pbDataWithPbId);
|
||||||
|
|
||||||
|
// 监听刷新事件
|
||||||
|
uni.$on('refreshZbList', async () => {
|
||||||
|
console.log('收到刷新事件,重新加载值周列表');
|
||||||
|
await refreshZbList();
|
||||||
|
});
|
||||||
|
|
||||||
|
uni.hideLoading();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载值周巡查列表
|
||||||
|
const loadZbXcList = async (pbData: any) => {
|
||||||
|
try {
|
||||||
|
// 检查pbData是否有效
|
||||||
|
if (!pbData || !pbData.pbId) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '排班数据无效,请重新选择',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pbId = pbData.pbId;
|
||||||
|
|
||||||
|
console.log('API调用参数:', {
|
||||||
|
jsId: getJs.id,
|
||||||
|
pbId: pbId,
|
||||||
|
pbData: pbData
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await getZbXcListApi({
|
||||||
|
jsId: getJs.id,
|
||||||
|
pbId: pbId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res && res.resultCode == 1) {
|
||||||
|
const list = res.result || [];
|
||||||
|
|
||||||
|
// 映射数据
|
||||||
|
let mappedList = list.map((item: any) => ({
|
||||||
|
id: item.id, // 值周记录ID
|
||||||
|
pbId: pbId, // 排班ID
|
||||||
|
jsId: item.jsId, // 教师ID
|
||||||
|
jsxm: item.jsxm, // 教师姓名
|
||||||
|
zbzc: item.zbzc, // 值周周次
|
||||||
|
zbxq: item.zbxq, // 值周星期
|
||||||
|
zbqy: item.zbqy, // 值周区域
|
||||||
|
zbwz: item.zbwz, // 值周位置
|
||||||
|
zbjs: item.zbjs, // 值周角色
|
||||||
|
zbkstime: item.zbkstime, // 值周开始时间
|
||||||
|
zbjstime: item.zbjstime, // 值周结束时间
|
||||||
|
pbLxId: item.pbLxId || pbData.pbLxId, // 排班类型ID
|
||||||
|
sfxc: item.sfxc || '否' // 是否已巡查
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 过滤出当前时间符合的值周记录
|
||||||
|
zbList.value = mappedList.filter((zb: any) => isCurrentTimeMatch(zb));
|
||||||
|
|
||||||
|
console.log('值周列表数据:', zbList.value);
|
||||||
|
} else {
|
||||||
|
zbList.value = [];
|
||||||
|
uni.showToast({
|
||||||
|
title: (res as any).resultMessage || '获取值周巡查数据失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载值周巡查数据失败:', error);
|
||||||
|
zbList.value = [];
|
||||||
|
uni.showToast({
|
||||||
|
title: '加载值周巡查数据失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 刷新值周列表
|
||||||
|
const refreshZbList = async () => {
|
||||||
|
try {
|
||||||
|
uni.showLoading({
|
||||||
|
title: "刷新中...",
|
||||||
|
});
|
||||||
|
|
||||||
|
const pbData = dataStore.getGlobal;
|
||||||
|
|
||||||
|
if (pbData && pbData.pbId) {
|
||||||
|
await loadZbXcList(pbData);
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({
|
||||||
|
title: "刷新成功",
|
||||||
|
icon: "success",
|
||||||
|
duration: 1000
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('刷新值周列表失败:', error);
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({
|
||||||
|
title: "刷新失败",
|
||||||
|
icon: "none"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 跳转到巡查
|
||||||
|
const goXc = async (zb: any) => {
|
||||||
|
const pbData = dataStore.getGlobal;
|
||||||
|
|
||||||
|
// 检查排班数据是否有效
|
||||||
|
if (!pbData || !pbData.xcbt) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '数据异常,请重新选择排班',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并排班数据和值周数据
|
||||||
|
const combinedData = {
|
||||||
|
...zb,
|
||||||
|
pbId: pbData.pbId,
|
||||||
|
pbLxId: zb.pbLxId || pbData.pbLxId,
|
||||||
|
xclx: pbData.xclx,
|
||||||
|
xcbt: pbData.xcbt,
|
||||||
|
xqmc: pbData.xqmc
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果已巡查,查询巡查记录数据
|
||||||
|
if (zb.sfxc === '是') {
|
||||||
|
try {
|
||||||
|
uni.showLoading({
|
||||||
|
title: '加载中...'
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('========== 查询已巡查记录 ==========');
|
||||||
|
console.log('查询参数 pbZbId:', zb.id);
|
||||||
|
|
||||||
|
const res = await zbXcFindPageApi({
|
||||||
|
rows: 10,
|
||||||
|
page: 1,
|
||||||
|
pbZbId: zb.id
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('查询结果完整数据:', res);
|
||||||
|
console.log('查询结果 resultCode:', res?.resultCode);
|
||||||
|
console.log('查询结果 result 数量:', res?.result?.length);
|
||||||
|
console.log('查询结果 rows:', res?.rows);
|
||||||
|
console.log('查询结果 rows 数量:', res?.rows?.length);
|
||||||
|
|
||||||
|
uni.hideLoading();
|
||||||
|
|
||||||
|
// 尝试从不同的字段获取数据
|
||||||
|
let xcRecordList = res?.result || res?.rows || [];
|
||||||
|
|
||||||
|
if (res && (res.resultCode === 1 || res.resultCode === '1' || xcRecordList.length > 0)) {
|
||||||
|
if (xcRecordList.length > 0) {
|
||||||
|
// 获取最新的一条巡查记录
|
||||||
|
const xcRecord = xcRecordList[0];
|
||||||
|
combinedData.xcRecord = xcRecord;
|
||||||
|
console.log('✓ 获取到巡查记录ID:', xcRecord.id);
|
||||||
|
console.log(' - jsxm:', xcRecord.jsxm);
|
||||||
|
console.log(' - xctime:', xcRecord.xctime);
|
||||||
|
console.log(' - zbXcXmList 数量:', xcRecord.zbXcXmList?.length || 0);
|
||||||
|
if (xcRecord.zbXcXmList && xcRecord.zbXcXmList.length > 0) {
|
||||||
|
console.log(' - zbXcXmList 详情:', xcRecord.zbXcXmList.map((xm: any) => ({
|
||||||
|
xcXmId: xm.xcXmId,
|
||||||
|
xcMc: xm.xcMc,
|
||||||
|
xcJg: xm.xcJg,
|
||||||
|
xcPj: xm.xcPj
|
||||||
|
})));
|
||||||
|
} else {
|
||||||
|
console.log(' ⚠️ zbXcXmList 为空!');
|
||||||
|
}
|
||||||
|
console.log('========== 查询完成 ==========');
|
||||||
|
} else {
|
||||||
|
console.log('❌ 返回数据为空');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('❌ 未查询到巡查记录或查询失败');
|
||||||
|
console.log(' 返回数据结构:', Object.keys(res || {}));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
uni.hideLoading();
|
||||||
|
console.error('查询巡查记录失败:', error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('该值周记录尚未巡查,无需回显');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('点击巡查,传递数据:', combinedData);
|
||||||
|
|
||||||
|
dataStore.setData(combinedData);
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/view/routine/kefuxuncha/zbDetail`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 跳转到巡查记录
|
||||||
|
const goRecord = (zb: any) => {
|
||||||
|
const pbData = dataStore.getGlobal;
|
||||||
|
|
||||||
|
// 检查排班数据是否有效
|
||||||
|
if (!pbData || !pbData.xcbt) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '数据异常,请重新选择排班',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并排班数据和值周数据
|
||||||
|
const combinedData = {
|
||||||
|
...zb,
|
||||||
|
pbId: pbData.pbId,
|
||||||
|
pbLxId: zb.pbLxId || pbData.pbLxId,
|
||||||
|
xclx: pbData.xclx,
|
||||||
|
xcbt: pbData.xcbt,
|
||||||
|
xqmc: pbData.xqmc
|
||||||
|
};
|
||||||
|
|
||||||
|
dataStore.setData(combinedData);
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/view/routine/kefuxuncha/zbRecord`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面卸载前清除事件监听器
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
uni.$off('refreshZbList');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.duty-inspection {
|
||||||
|
min-height: 100%;
|
||||||
|
background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-header {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
padding: 25px 20px;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 0 0 20px 20px;
|
||||||
|
box-shadow: 0 8px 32px rgba(102, 126, 234, 0.3);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 10;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(45deg, rgba(255, 255, 255, 0.1) 0%, transparent 50%, rgba(255, 255, 255, 0.05) 100%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.title-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollable-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duty-list {
|
||||||
|
padding: 15px 15px 0 15px;
|
||||||
|
|
||||||
|
.duty-item {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
animation: fadeInUp 0.6s ease-out;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.12);
|
||||||
|
border-color: #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duty-status {
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
right: 15px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
z-index: 2;
|
||||||
|
animation: fadeInRight 0.5s ease-out 0.2s both;
|
||||||
|
|
||||||
|
&.status-done {
|
||||||
|
background: linear-gradient(135deg, #67c23a, #85ce61);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 2px 8px rgba(103, 194, 58, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.status-pending {
|
||||||
|
background: linear-gradient(135deg, #e6a23c, #f0c78a);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 2px 8px rgba(230, 162, 60, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.duty-name {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1a1a1a;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
line-height: 1.4;
|
||||||
|
animation: fadeInLeft 0.5s ease-out 0.1s both;
|
||||||
|
padding-right: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duty-btn-group {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.xc-btn {
|
||||||
|
display: inline-block;
|
||||||
|
color: #ff6b35;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 8px 18px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: linear-gradient(135deg, rgba(255, 107, 53, 0.1), rgba(255, 107, 53, 0.05));
|
||||||
|
border: 1px solid #ff6b35;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: 0 2px 8px rgba(255, 107, 53, 0.15);
|
||||||
|
animation: fadeInUp 0.5s ease-out 0.3s both;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: linear-gradient(135deg, rgba(255, 107, 53, 0.15), rgba(255, 107, 53, 0.1));
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 15px rgba(255, 107, 53, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: linear-gradient(135deg, rgba(255, 107, 53, 0.2), rgba(255, 107, 53, 0.15));
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 2px 8px rgba(255, 107, 53, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-btn {
|
||||||
|
display: inline-block;
|
||||||
|
color: #409EFF;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 8px 18px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: linear-gradient(135deg, rgba(64, 158, 255, 0.1), rgba(64, 158, 255, 0.05));
|
||||||
|
border: 1px solid #409EFF;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15);
|
||||||
|
animation: fadeInUp 0.5s ease-out 0.3s both;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: linear-gradient(135deg, rgba(64, 158, 255, 0.15), rgba(64, 158, 255, 0.1));
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 15px rgba(64, 158, 255, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: linear-gradient(135deg, rgba(64, 158, 255, 0.2), rgba(64, 158, 255, 0.15));
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.duty-info-row {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
gap: 20px;
|
||||||
|
animation: fadeInUp 0.5s ease-out 0.15s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duty-info-item {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
font-size: 13px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
color: #666;
|
||||||
|
flex: 0 0 70px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-data {
|
||||||
|
flex: 1;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 400;
|
||||||
|
word-break: break-all;
|
||||||
|
|
||||||
|
&.time {
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator-line {
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(90deg, transparent, #e8e8e8, transparent);
|
||||||
|
margin: 18px 0;
|
||||||
|
opacity: 0.8;
|
||||||
|
animation: fadeIn 0.5s ease-out 0.25s both;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加动画关键帧
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInLeft {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInRight {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-duty-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 80px 20px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
|
||||||
|
width: 90px;
|
||||||
|
height: 90px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #475569;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
421
src/pages/view/routine/kefuxuncha/zbRecord.vue
Normal file
421
src/pages/view/routine/kefuxuncha/zbRecord.vue
Normal file
@ -0,0 +1,421 @@
|
|||||||
|
<template>
|
||||||
|
<BasicLayout>
|
||||||
|
<!-- 巡查记录列表 -->
|
||||||
|
<view class="inspection-list">
|
||||||
|
<BasicListLayout
|
||||||
|
@register="registerInspection"
|
||||||
|
style="position: absolute"
|
||||||
|
>
|
||||||
|
<template v-slot="{ data, index }">
|
||||||
|
<view class="inspection-record bg-white r-md p-15 mb-15 timeline-item">
|
||||||
|
<!-- 时间轴连接线 -->
|
||||||
|
<view class="timeline-line" v-if="index < 10"></view>
|
||||||
|
|
||||||
|
<!-- 时间轴节点 -->
|
||||||
|
<view class="timeline-dot">
|
||||||
|
<view class="dot-inner"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="record-header">
|
||||||
|
<view class="record-time">
|
||||||
|
<u-icon name="clock" color="#4080ff" size="16"></u-icon>
|
||||||
|
<text class="time-text">{{ formatTime(data.xctime) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="record-status">
|
||||||
|
<text class="status-text">已巡查</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="record-content">
|
||||||
|
<view class="content-item">
|
||||||
|
<text class="item-label">巡查教师:</text>
|
||||||
|
<text class="item-value">{{ data.jsxm }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="content-item">
|
||||||
|
<text class="item-label">值周周次:</text>
|
||||||
|
<text class="item-value">第{{ data.zbzc }}周</text>
|
||||||
|
</view>
|
||||||
|
<view class="content-item">
|
||||||
|
<text class="item-label">值周星期:</text>
|
||||||
|
<text class="item-value">{{ data.zbxq || '全周' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="content-item">
|
||||||
|
<text class="item-label">值周区域:</text>
|
||||||
|
<text class="item-value">{{ data.zbqy || '暂无' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="content-item">
|
||||||
|
<text class="item-label">值周位置:</text>
|
||||||
|
<text class="item-value">{{ data.zbwz || '暂无' }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="content-item flex-col">
|
||||||
|
<text class="item-label" style="flex: 0 0 25px">巡查项目:</text>
|
||||||
|
<view class="item-value" style="width: 100%">
|
||||||
|
<template v-if="data.zbXcXmList && data.zbXcXmList.length > 0">
|
||||||
|
<view
|
||||||
|
v-for="(xm, idx) in data.zbXcXmList"
|
||||||
|
:key="xm.xcXmId"
|
||||||
|
style="margin-bottom: 4px"
|
||||||
|
>
|
||||||
|
<view>
|
||||||
|
<view style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px;">
|
||||||
|
<view style="display: flex; align-items: center; flex: 1;">
|
||||||
|
<text style="margin-right: 8px;">{{ idx + 1 }}、{{ xm.xcMc }}</text>
|
||||||
|
<view style="display: flex; align-items: center;">
|
||||||
|
<u-icon
|
||||||
|
:name="xm.xcJg === 'B' ? 'checkmark-circle-fill' : 'close-circle-fill'"
|
||||||
|
:color="xm.xcJg === 'B' ? '#67c23a' : '#f56c6c'"
|
||||||
|
size="18"
|
||||||
|
></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view style="font-size: 12px; color: #666;">
|
||||||
|
<text v-if="xm.xcJg === 'A'" style="color: #f56c6c;">
|
||||||
|
扣分:-{{ xm.xmFz }}分
|
||||||
|
</text>
|
||||||
|
<text v-else style="color: #67c23a;">
|
||||||
|
不扣分
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="xm.xcPj" style="font-size: 12px; color: #666; margin-top: 4px; padding-left: 20px; line-height: 1.5;">
|
||||||
|
<text style="color: #999;">评价:</text>
|
||||||
|
<text style="color: #666;">{{ xm.xcPj }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<template v-else> 无巡查项目 </template>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="content-item" v-if="data.zp && data.zp.length > 0">
|
||||||
|
<text class="item-label">巡查图片:</text>
|
||||||
|
<view class="item-value" style="display: flex; flex-wrap: wrap; gap: 8px">
|
||||||
|
<image
|
||||||
|
v-for="(img, imgIdx) in getImageArray(data.zp)"
|
||||||
|
:key="imgIdx"
|
||||||
|
:src="imagUrl(img)"
|
||||||
|
mode="aspectFill"
|
||||||
|
style="
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
cursor: pointer;
|
||||||
|
"
|
||||||
|
@click="handlePreviewImage(img, getImageArray(data.zp))"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="content-item" v-if="data.sp && data.sp.length > 0">
|
||||||
|
<text class="item-label">巡查视频:</text>
|
||||||
|
<view class="item-value" style="display: flex; flex-wrap: wrap; gap: 8px">
|
||||||
|
<view
|
||||||
|
v-for="(video, vIdx) in getVideoArray(data.sp)"
|
||||||
|
:key="vIdx"
|
||||||
|
style="
|
||||||
|
width: 80px;
|
||||||
|
height: 60px;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #000;
|
||||||
|
"
|
||||||
|
@click="handlePreviewVideo(getVideoArray(data.sp), vIdx)"
|
||||||
|
>
|
||||||
|
<video
|
||||||
|
:src="video"
|
||||||
|
style="width: 100%; height: 100%; object-fit: cover"
|
||||||
|
:controls="false"
|
||||||
|
:show-center-play-btn="false"
|
||||||
|
:show-play-btn="false"
|
||||||
|
:show-fullscreen-btn="false"
|
||||||
|
:show-progress="false"
|
||||||
|
:show-mute-btn="false"
|
||||||
|
:enable-progress-gesture="false"
|
||||||
|
:enable-play-gesture="false"
|
||||||
|
:loop="false"
|
||||||
|
:muted="true"
|
||||||
|
:poster="''"
|
||||||
|
></video>
|
||||||
|
<view
|
||||||
|
style="
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<u-icon name="play-right-fill" color="#fff" size="28"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</BasicListLayout>
|
||||||
|
</view>
|
||||||
|
</BasicLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { zbXcFindPageApi } from "@/api/base/zbXcApi";
|
||||||
|
import BasicLayout from "@/components/BasicLayout/Layout.vue";
|
||||||
|
import { useLayout } from "@/components/BasicListLayout/hooks/useLayout";
|
||||||
|
import { useDataStore } from "@/store/modules/data";
|
||||||
|
import { useUserStore } from "@/store/modules/user";
|
||||||
|
import { computed, onMounted } from "vue";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { imagUrl } from "@/utils";
|
||||||
|
|
||||||
|
const { getJs } = useUserStore();
|
||||||
|
const { getData } = useDataStore();
|
||||||
|
|
||||||
|
const js = computed(() => getJs);
|
||||||
|
const zb = computed(() => getData);
|
||||||
|
|
||||||
|
// 巡查记录列表参数
|
||||||
|
let inspectionParams = {
|
||||||
|
rows: 10,
|
||||||
|
pbZbId: zb.value.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('值周巡查记录查询参数:', inspectionParams);
|
||||||
|
|
||||||
|
// 巡查记录列表
|
||||||
|
const [registerInspection, { reload }] = useLayout({
|
||||||
|
api: zbXcFindPageApi,
|
||||||
|
componentProps: {},
|
||||||
|
param: inspectionParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 图片预览
|
||||||
|
const handlePreviewImage = (img: string, images: string[]) => {
|
||||||
|
const processedImages = images.map(image => imagUrl(image));
|
||||||
|
uni.previewImage({
|
||||||
|
current: imagUrl(img),
|
||||||
|
urls: processedImages,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 视频预览
|
||||||
|
const handlePreviewVideo = (videos: string[], index: number) => {
|
||||||
|
uni.previewMedia({
|
||||||
|
current: index,
|
||||||
|
sources: videos.map((url) => ({
|
||||||
|
url: imagUrl(url),
|
||||||
|
type: "video",
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
|
const formatTime = (timestamp: string) => {
|
||||||
|
const date = dayjs(timestamp);
|
||||||
|
const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||||||
|
const weekDay = weekDays[date.day()];
|
||||||
|
return `${weekDay} ${date.format("YYYY-MM-DD HH:mm")}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 将逗号分隔的字符串转换为数组
|
||||||
|
const getImageArray = (str: string) => {
|
||||||
|
if (!str) return [];
|
||||||
|
return str.split(",").map((item) => item.trim());
|
||||||
|
};
|
||||||
|
|
||||||
|
// 将逗号分隔的字符串转换为数组
|
||||||
|
const getVideoArray = (str: string) => {
|
||||||
|
if (!str) return [];
|
||||||
|
return str.split(",").map((item) => item.trim());
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面加载时重新加载巡查记录
|
||||||
|
onMounted(() => {
|
||||||
|
reload();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.inspection-list {
|
||||||
|
position: relative;
|
||||||
|
height: calc(100vh - 50px);
|
||||||
|
padding-left: 12px;
|
||||||
|
|
||||||
|
.inspection-record {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 0;
|
||||||
|
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||||
|
border: 1px solid #e8f4fd;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
|
||||||
|
border-color: #4080ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-line {
|
||||||
|
position: absolute;
|
||||||
|
left: -12px;
|
||||||
|
top: 0;
|
||||||
|
width: 2px;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(180deg, #4080ff 0%, #e8f4fd 100%);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-dot {
|
||||||
|
position: absolute;
|
||||||
|
left: -18px;
|
||||||
|
top: 20px;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
background: #4080ff;
|
||||||
|
border-radius: 50%;
|
||||||
|
z-index: 2;
|
||||||
|
box-shadow: 0 0 0 4px #ffffff, 0 0 0 6px #e8f4fd;
|
||||||
|
|
||||||
|
.dot-inner {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(135deg, #4080ff 0%, #66b3ff 100%);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding: 12px 8px;
|
||||||
|
background: linear-gradient(135deg, #f0f7ff 0%, #e8f4fd 100%);
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
border-bottom: 1px solid #d1e7ff;
|
||||||
|
|
||||||
|
.record-time {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 15px;
|
||||||
|
color: #2c5aa0;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
.time-text {
|
||||||
|
margin-left: 8px;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-status {
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: linear-gradient(135deg, #4080ff 0%, #66b3ff 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
box-shadow: 0 2px 8px rgba(64, 128, 255, 0.3);
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-content {
|
||||||
|
padding: 0 8px 12px 8px;
|
||||||
|
|
||||||
|
.content-item {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
padding: 6px 8px;
|
||||||
|
background: #fafbfc;
|
||||||
|
border-radius: 6px;
|
||||||
|
border-left: 3px solid #e8f4fd;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f0f7ff;
|
||||||
|
border-left-color: #4080ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-label {
|
||||||
|
font-weight: 600;
|
||||||
|
flex: 0 0 80px;
|
||||||
|
color: #2c5aa0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-value {
|
||||||
|
color: #4a5568;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.inspection-record:first-child {
|
||||||
|
.timeline-dot {
|
||||||
|
background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%);
|
||||||
|
box-shadow: 0 0 0 4px #ffffff, 0 0 0 6px #f0f9ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.inspection-record:last-child {
|
||||||
|
.timeline-line {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.1);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item {
|
||||||
|
animation: fadeInUp 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .zp-loading-fixed {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .d-load-main {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- 使用 BasicListLayout 包裹记录列表 -->
|
<!-- 使用 BasicListLayout 包裹记录列表 -->
|
||||||
<BasicListLayout @register="register" :fixed="false" class="flex-1">
|
<BasicListLayout @register="register" :fixed="true" class="flex-1">
|
||||||
<template #top>
|
<template #top>
|
||||||
<!-- 课程信息卡片 -->
|
<!-- 课程信息卡片 -->
|
||||||
<view class="course-card">
|
<view class="course-card">
|
||||||
|
|||||||
BIN
src/static/base/home/jstxl.png
Normal file
BIN
src/static/base/home/jstxl.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
BIN
src/static/base/home/xsqj.png
Normal file
BIN
src/static/base/home/xsqj.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
BIN
src/static/base/view/sptg.png
Normal file
BIN
src/static/base/view/sptg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
@ -1,64 +0,0 @@
|
|||||||
.global-bg-color {
|
|
||||||
background-color: #F8F8F8
|
|
||||||
}
|
|
||||||
|
|
||||||
/* #ifndef APP-PLUS-NVUE */
|
|
||||||
page {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
|
||||||
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
|
||||||
"Microsoft Yahei", sans-serif;
|
|
||||||
@extend .global-bg-color
|
|
||||||
}
|
|
||||||
|
|
||||||
.amap-logo, .amap-copyright {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.isSafe {
|
|
||||||
padding-bottom: constant(safe-area-inset-bottom) !important;
|
|
||||||
padding-bottom: env(safe-area-inset-bottom) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
uni-toast{
|
|
||||||
z-index: 9999999 !important;
|
|
||||||
}
|
|
||||||
.basic-item:not(:last-child) {
|
|
||||||
border-bottom: 1px solid #EFEFEF;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* #endif */
|
|
||||||
|
|
||||||
//input错误样式
|
|
||||||
.uni-forms-item__error {
|
|
||||||
padding-top: 0 !important;
|
|
||||||
top: 89% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-color {
|
|
||||||
color: var(--themeColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-color-border {
|
|
||||||
border: 1px solid var(--themeColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-bg-color {
|
|
||||||
background: var(--themeColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
padding: 20rpx 0;
|
|
||||||
color: white;
|
|
||||||
background: #2672FF;
|
|
||||||
margin: 20rpx 30rpx;
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user