新增食堂巡查
This commit is contained in:
parent
87d90244aa
commit
ee5e5c335f
30
src/api/base/hcApi.ts
Normal file
30
src/api/base/hcApi.ts
Normal file
@ -0,0 +1,30 @@
|
||||
// 食堂巡查相关API接口
|
||||
import { get, post } from "@/utils/request";
|
||||
|
||||
/**
|
||||
* 分页查询食堂巡查记录
|
||||
*/
|
||||
export function hcFindPageApi(params: any) {
|
||||
return get("/api/hc/findPage", params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增/修改食堂巡查记录
|
||||
*/
|
||||
export function hcSaveApi(params: any) {
|
||||
return post("/api/hc/save", params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询食堂巡查记录
|
||||
*/
|
||||
export function hcFindByIdApi(params: any) {
|
||||
return get("/api/hc/findById", params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除食堂巡查记录
|
||||
*/
|
||||
export function hcLogicDeleteApi(params: any) {
|
||||
return post("/api/hc/logicDelete", params);
|
||||
}
|
||||
@ -181,6 +181,27 @@
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/routine/ShiTangXunCha/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "食堂巡查",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/routine/ShiTangXunCha/add",
|
||||
"style": {
|
||||
"navigationBarTitleText": "新增食堂巡查",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/routine/ShiTangXunCha/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "食堂巡查详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/routine/JiFenPingJia/detail",
|
||||
"style": {
|
||||
|
||||
@ -181,6 +181,7 @@ const sections = reactive<Section[]>([
|
||||
show: true,
|
||||
path: "/pages/view/routine/GongZuoLiang/index",
|
||||
},
|
||||
|
||||
// {
|
||||
// id: "r4",
|
||||
// icon: "file-paper-2-fill",
|
||||
@ -195,7 +196,13 @@ const sections = reactive<Section[]>([
|
||||
show: true,
|
||||
path: "/pages/view/routine/RengJiaoRengZhi/index",
|
||||
},
|
||||
|
||||
{
|
||||
id: "r9",
|
||||
icon: "hc-fill",
|
||||
text: "食堂巡查",
|
||||
show: true,
|
||||
path: "/pages/view/routine/ShiTangXunCha/index",
|
||||
},
|
||||
{
|
||||
id: "r6",
|
||||
icon: "pass-pending-fill",
|
||||
@ -252,13 +259,13 @@ const sections = reactive<Section[]>([
|
||||
show: true,
|
||||
path: "/pages/view/homeSchool/parentAddressBook/index",
|
||||
},
|
||||
{
|
||||
/*{
|
||||
id: "hs4",
|
||||
icon: "newspaper-fill",
|
||||
text: "通知列表",
|
||||
show: true,
|
||||
path: "/pages/view/notice/index",
|
||||
},
|
||||
},*/
|
||||
{
|
||||
id: "hs6",
|
||||
icon: "filechart2fil",
|
||||
|
||||
@ -207,7 +207,7 @@ watch(studentList, (list) => {
|
||||
<style scoped lang="scss">
|
||||
.notice-detail-page {
|
||||
background-color: #f4f5f7;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 70px;
|
||||
padding: 15px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@
|
||||
type="primary"
|
||||
@click="goToPublish"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</BasicListLayout>
|
||||
<!-- 用uniqueList渲染 -->
|
||||
|
||||
@ -130,6 +130,21 @@
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 回执说明 -->
|
||||
<view class="info-card" v-if="formData.signatureRequired">
|
||||
<view class="form-item">
|
||||
<text class="form-label">回执说明</text>
|
||||
<uni-easyinput
|
||||
type="textarea"
|
||||
autoHeight
|
||||
v-model="formData.mdhz"
|
||||
placeholder="请输入回执说明"
|
||||
:inputBorder="false"
|
||||
class="receipt-input"
|
||||
></uni-easyinput>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-card list-item-card">
|
||||
<uni-datetime-picker type="datetime" v-model="formData.startTime">
|
||||
<view class="list-item-row">
|
||||
@ -160,9 +175,6 @@
|
||||
<button class="action-btn draft-btn" @click="saveDraft">
|
||||
保存草稿
|
||||
</button>
|
||||
<button class="action-btn preview-btn" @click="previewNotice">
|
||||
预览
|
||||
</button>
|
||||
<button class="action-btn publish-btn" @click="publishNotice" :disabled="isPublishing">
|
||||
{{ isPublishing ? '发布中...' : '立即发布' }}
|
||||
</button>
|
||||
@ -222,7 +234,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, nextTick } from "vue";
|
||||
import { ref, reactive, computed, nextTick, watch } from "vue";
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||
import CustomUpload from "/src/components/BasicUpload/CustomUpload.vue";
|
||||
import BasicTree from "@/components/BasicTree/Tree.vue";
|
||||
@ -254,6 +266,7 @@ interface FormData {
|
||||
signatureRequired: boolean;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
mdhz: string; // 回执说明
|
||||
}
|
||||
|
||||
const noticeId = ref<string | null>(null);
|
||||
@ -273,6 +286,7 @@ const formData = reactive<FormData>({
|
||||
signatureRequired: true,
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
mdhz: "", // 新增回执说明字段
|
||||
});
|
||||
|
||||
const signatureOptions = ["启用" , "不启用"];
|
||||
@ -281,7 +295,7 @@ const signatureStatusText = computed(() => {
|
||||
});
|
||||
|
||||
// 树形数据
|
||||
const treeData = ref([]);
|
||||
const treeData = ref<any[]>([]);
|
||||
const treeRef = ref();
|
||||
|
||||
// 学生显示相关
|
||||
@ -296,14 +310,23 @@ const displayNames = computed(() => {
|
||||
// 添加 ref 来引用封面上传组件
|
||||
const coverUploadRef = ref();
|
||||
|
||||
// 统一接口返回类型声明
|
||||
interface ApiResponse<T = any> {
|
||||
resultCode: number;
|
||||
result?: T;
|
||||
resultMsg?: string;
|
||||
data?: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// 加载树形数据
|
||||
const loadTreeData = async () => {
|
||||
try {
|
||||
const res = await findAllNjBjTree();
|
||||
if (res.resultCode === 1 && res.result) {
|
||||
const res = await findAllNjBjTree() as ApiResponse<any[]>;
|
||||
if (res && res.resultCode === 1 && res.result) {
|
||||
// 递归转换数据格式以适配 BasicTree 组件,同时保留 njmcId 字段
|
||||
const convertTreeData = (items: any[]): any[] => {
|
||||
return items.map((item: any) => ({
|
||||
const convertTreeData = (items: Array<{ key: string; title: string; njmcId?: string; children?: any[] }>): any[] => {
|
||||
return items.map((item) => ({
|
||||
key: item.key,
|
||||
title: item.title,
|
||||
njmcId: item.njmcId, // 保留 njmcId 字段
|
||||
@ -355,9 +378,10 @@ const resetForm = () => {
|
||||
targetNjIds: [],
|
||||
targetNjmcIds: [],
|
||||
targetBjIds: [],
|
||||
signatureRequired: false,
|
||||
signatureRequired: true, // 修正:与初始值保持一致
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
mdhz: "", // 重置回执说明
|
||||
});
|
||||
|
||||
// 重置发布状态
|
||||
@ -384,9 +408,7 @@ const resetForm = () => {
|
||||
const loadJlData = async (jlId: string) => {
|
||||
try {
|
||||
uni.showLoading({ title: "加载数据中..." });
|
||||
|
||||
// 调用获取接龙详情的接口
|
||||
const response = await jlFindByIdApi({ id: jlId });
|
||||
const response = await jlFindByIdApi({ id: jlId }) as ApiResponse<any>;
|
||||
|
||||
if (response && response.resultCode === 1 && response.result) {
|
||||
const jlData = response.result;
|
||||
@ -399,14 +421,15 @@ const loadJlData = async (jlId: string) => {
|
||||
formData.startTime = jlData.jlkstime || "";
|
||||
formData.endTime = jlData.jljstime || "";
|
||||
formData.signatureRequired = jlData.mdqz === "1";
|
||||
formData.mdhz = jlData.mdhz || ""; // 加载回执说明
|
||||
|
||||
// 处理附件
|
||||
if (jlData.jlfj) {
|
||||
const attachmentUrls = jlData.jlfj.split(",");
|
||||
formData.attachments = attachmentUrls.map(url => ({
|
||||
formData.attachments = attachmentUrls.map((url: string) => ({
|
||||
name: url.split("/").pop() || "附件",
|
||||
type: "file",
|
||||
url: url
|
||||
url: url as string
|
||||
}));
|
||||
}
|
||||
|
||||
@ -573,7 +596,7 @@ const getAttachmentIcon = (type: string): string => {
|
||||
const previewAttachment = (attachment: Attachment) => {
|
||||
// 如果是图片类型,可以预览
|
||||
if (attachment.type === "image") {
|
||||
const fullUrl = imagUrl(attachment.url);
|
||||
const fullUrl = imagUrl(attachment.url as string);
|
||||
uni.previewImage({
|
||||
urls: [fullUrl],
|
||||
current: fullUrl,
|
||||
@ -594,11 +617,11 @@ const showClassTree = () => {
|
||||
};
|
||||
|
||||
// 树形选择确认
|
||||
const onTreeConfirm = async (selectedItems: any[]) => {
|
||||
const onTreeConfirm = async (selectedItems: Array<any>) => {
|
||||
|
||||
if (selectedItems.length > 0) {
|
||||
// 处理多选情况
|
||||
const classNames = selectedItems.map((item) => item.title);
|
||||
const classNames = selectedItems.map((item: any) => item.title);
|
||||
formData.targetClass = classNames.join(", ");
|
||||
formData.targetNames = [];
|
||||
formData.targetStudentIds = [];
|
||||
@ -615,16 +638,24 @@ const onTreeConfirm = async (selectedItems: any[]) => {
|
||||
const bjIds: string[] = [];
|
||||
const gradeNames: string[] = []; // 用于存储年级名称,用于错误提示
|
||||
|
||||
selectedItems.forEach((item) => {
|
||||
selectedItems.forEach((item: any) => {
|
||||
// 如果选择的是班级(有parents表示是班级)
|
||||
if (item.parents && item.parents.length > 0) {
|
||||
const parent = item.parents[0]; // 年级信息
|
||||
|
||||
const njId = parent.key; // 年级ID:parents[0].key
|
||||
const njmcId = parent.njmcId; // 年级名称ID:从年级信息中获取
|
||||
const bjId = item.key; // 班级ID:item.key
|
||||
const gradeName = parent.title; // 年级名称
|
||||
|
||||
// 通过年级key从treeData中查找对应的njmcId
|
||||
let njmcId: string | undefined;
|
||||
for (const grade of treeData.value || []) {
|
||||
if (grade.key === njId) {
|
||||
njmcId = grade.njmcId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 确保年级信息完整
|
||||
if (njId && bjId && gradeName) {
|
||||
njIds.push(njId);
|
||||
@ -669,7 +700,7 @@ const onTreeConfirm = async (selectedItems: any[]) => {
|
||||
bjId: bjIds.join(","),
|
||||
};
|
||||
|
||||
const response = await mobilejlstudentListApi(params);
|
||||
const response = await mobilejlstudentListApi(params) as ApiResponse<any[]>;
|
||||
|
||||
if (response && response.resultCode === 1 && response.result) {
|
||||
// 提取学生姓名,尝试多个可能的字段名
|
||||
@ -699,7 +730,6 @@ const onTreeConfirm = async (selectedItems: any[]) => {
|
||||
icon: "success",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("获取学生列表失败:", error);
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: "获取学生列表失败", icon: "error" });
|
||||
|
||||
@ -733,7 +763,16 @@ const removeStudent = (index: number) => {
|
||||
|
||||
const handleSignatureChange = (e: any) => {
|
||||
const index = e.detail.value;
|
||||
formData.signatureRequired = index == 1;
|
||||
formData.signatureRequired = index == 0; // 修正:索引0是"启用",索引1是"不启用"
|
||||
|
||||
// 当启用按名单签名时,自动设置默认的回执说明
|
||||
if (formData.signatureRequired) {
|
||||
const title = formData.title.trim() || "通知";
|
||||
formData.mdhz = `我已认真阅读《${title}》内容,加强对孩子安全教育和安全监管,对孩子的安全全面负责。`;
|
||||
} else {
|
||||
// 当不启用时,清空回执说明
|
||||
formData.mdhz = "";
|
||||
}
|
||||
};
|
||||
|
||||
const validateForm = () => {
|
||||
@ -814,6 +853,7 @@ const buildJlDto = (status: string) => {
|
||||
jlkstime: formatDate(formData.startTime), // 接龙开始时间,格式化为 yyyy-MM-dd HH:mm:ss
|
||||
jljstime: formatDate(formData.endTime), // 接龙结束时间,格式化为 yyyy-MM-dd HH:mm:ss
|
||||
mdqz: formData.signatureRequired ? "1" : "0", // 按名单签字:1启用,0不启用
|
||||
mdhz: formData.mdhz || "", // 回执说明
|
||||
njId: formData.targetNjIds.join(","), // 关联年级ID列表
|
||||
njmcId: formData.targetNjmcIds.join(","), // 关联年级名称ID列表
|
||||
bjId: formData.targetBjIds.join(","), // 关联班级ID列表
|
||||
@ -838,13 +878,13 @@ const saveDraft = async () => {
|
||||
const jlDto = buildJlDto("B"); // B表示暂存
|
||||
|
||||
// 调用后端接口
|
||||
const response = await jlSaveApi(jlDto);
|
||||
const response = await jlSaveApi(jlDto) as ApiResponse<any>;
|
||||
|
||||
uni.hideLoading();
|
||||
|
||||
if (response && response.resultCode === 1) {
|
||||
// 获取接龙ID
|
||||
const jlId = response.result || response.data || jlDto.id;
|
||||
const jlId = response.result || (response as any).data || jlDto.id;
|
||||
|
||||
// 更新表单中的ID
|
||||
if (jlId) {
|
||||
@ -863,15 +903,12 @@ const saveDraft = async () => {
|
||||
}, 2000);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: response?.resultMsg || "保存草稿失败",
|
||||
title: (response as any)?.resultMsg || "保存草稿失败",
|
||||
icon: "error"
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
console.error("保存草稿失败:", error);
|
||||
|
||||
// 处理数据验证错误
|
||||
if (error instanceof Error) {
|
||||
uni.showToast({
|
||||
title: error.message,
|
||||
@ -886,11 +923,6 @@ const saveDraft = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const previewNotice = () => {
|
||||
if (!validateForm()) return;
|
||||
uni.showToast({ title: "预览功能待实现", icon: "none" });
|
||||
};
|
||||
|
||||
const publishNotice = async () => {
|
||||
if (!validateForm()) return;
|
||||
|
||||
@ -909,7 +941,7 @@ const publishNotice = async () => {
|
||||
// 如果有ID,检查接龙是否已存在且已发布
|
||||
if (formData.id) {
|
||||
try {
|
||||
const response = await jlFindByIdApi({ id: formData.id });
|
||||
const response = await jlFindByIdApi({ id: formData.id }) as ApiResponse<any>;
|
||||
if (response && response.resultCode === 1 && response.result) {
|
||||
const jlData = response.result;
|
||||
if (jlData.jlStatus === 'A') {
|
||||
@ -917,9 +949,7 @@ const publishNotice = async () => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("检查接龙状态失败:", error);
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
try {
|
||||
@ -931,20 +961,19 @@ const publishNotice = async () => {
|
||||
|
||||
// 调用后端接口,设置超时处理
|
||||
const response = await Promise.race([
|
||||
jlSaveApi(jlDto),
|
||||
jlSaveApi(jlDto) as Promise<ApiResponse<any>>,
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error("请求超时")), 300000) // 5分钟超时
|
||||
)
|
||||
]);
|
||||
]) as ApiResponse<any>;
|
||||
|
||||
uni.hideLoading();
|
||||
|
||||
if (response && response.resultCode === 1) {
|
||||
// 获取接龙ID,优先使用返回的ID,如果没有则使用请求中的ID
|
||||
const jlId = response.result || response.data || jlDto.id;
|
||||
const jlId = response.result || (response as any).data || jlDto.id;
|
||||
|
||||
if (!jlId) {
|
||||
console.error("发布成功但未获取到接龙ID");
|
||||
uni.showToast({
|
||||
title: "发布成功,但获取接龙ID失败",
|
||||
icon: "error"
|
||||
@ -971,16 +1000,16 @@ const publishNotice = async () => {
|
||||
});
|
||||
}, 2000);
|
||||
} else {
|
||||
// 处理业务错误
|
||||
const errorMsg = (response as any)?.resultMsg || (response as any)?.message || "发布失败";
|
||||
uni.showToast({
|
||||
title: response?.resultMsg || "发布失败",
|
||||
icon: "error"
|
||||
title: errorMsg,
|
||||
icon: "none",
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
console.error("发布接龙失败:", error);
|
||||
|
||||
// 处理不同类型的错误
|
||||
if (error instanceof Error) {
|
||||
if (error.message === "请求超时") {
|
||||
uni.showToast({
|
||||
@ -996,7 +1025,7 @@ const publishNotice = async () => {
|
||||
}
|
||||
} else if (error && typeof error === 'object' && 'errMsg' in error) {
|
||||
// uni-app 错误对象
|
||||
if (error.errMsg && error.errMsg.includes('timeout')) {
|
||||
if ((error as any).errMsg && (error as any).errMsg.includes('timeout')) {
|
||||
uni.showToast({
|
||||
title: "请求超时,请检查网络连接或稍后重试",
|
||||
icon: "none",
|
||||
@ -1004,7 +1033,7 @@ const publishNotice = async () => {
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: error.errMsg || "发布失败,请重试",
|
||||
title: (error as any).errMsg || "发布失败,请重试",
|
||||
icon: "error"
|
||||
});
|
||||
}
|
||||
@ -1018,6 +1047,15 @@ const publishNotice = async () => {
|
||||
isPublishing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 监听标题变化,自动更新回执说明
|
||||
watch(() => formData.title, (newTitle) => {
|
||||
if (formData.signatureRequired && newTitle) {
|
||||
const title = newTitle.trim() || "通知";
|
||||
formData.mdhz = `我已认真阅读《${title}》内容,加强对孩子安全教育和安全监管,对孩子的安全全面负责。`;
|
||||
}
|
||||
}, { immediate: false });
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@ -1154,6 +1192,14 @@ const publishNotice = async () => {
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.receipt-input ::v-deep .uni-easyinput__content-textarea {
|
||||
font-size: 14px !important;
|
||||
color: #606266 !important;
|
||||
padding: 0 !important;
|
||||
line-height: 1.5;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.attachments-section {
|
||||
padding-top: 15px;
|
||||
.form-label {
|
||||
|
||||
489
src/pages/view/routine/ShiTangXunCha/add.vue
Normal file
489
src/pages/view/routine/ShiTangXunCha/add.vue
Normal file
@ -0,0 +1,489 @@
|
||||
<!-- src/pages/view/routine/ShiTangXunCha/add.vue -->
|
||||
<template>
|
||||
<view class="add-page">
|
||||
<view class="form-container">
|
||||
<view class="form-title">新增食堂巡查</view>
|
||||
|
||||
<!-- 工作名称 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">工作名称 *</text>
|
||||
<input
|
||||
v-model="formData.gzmc"
|
||||
class="form-input"
|
||||
placeholder="请输入工作名称"
|
||||
maxlength="100"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 工作描述 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">工作描述</text>
|
||||
<textarea
|
||||
v-model="formData.gzms"
|
||||
class="form-textarea"
|
||||
placeholder="请输入工作描述"
|
||||
maxlength="500"
|
||||
:show-count="true"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 上传照片 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">上传照片</text>
|
||||
<view class="upload-section">
|
||||
<view class="upload-list">
|
||||
<view
|
||||
v-for="(image, index) in imageList"
|
||||
:key="index"
|
||||
class="upload-item"
|
||||
>
|
||||
<image
|
||||
:src="image.url ? imagUrl(image.url) : image.tempPath"
|
||||
mode="aspectFill"
|
||||
class="upload-image"
|
||||
@click="previewImage(index)"
|
||||
/>
|
||||
<view class="upload-delete" @click="deleteImage(index)">
|
||||
<text class="delete-icon">×</text>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="imageList.length < 9"
|
||||
class="upload-add"
|
||||
@click="chooseImage"
|
||||
>
|
||||
<text class="add-icon">+</text>
|
||||
<text class="add-text">添加图片</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提交人 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">提交人</text>
|
||||
<input
|
||||
:value="formData.jsxm"
|
||||
class="form-input"
|
||||
disabled
|
||||
placeholder="自动获取当前用户"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 提交时间 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">提交时间</text>
|
||||
<picker
|
||||
mode="date"
|
||||
:value="formData.tjtime"
|
||||
@change="onTimeChange"
|
||||
class="form-picker"
|
||||
>
|
||||
<view class="picker-display">
|
||||
{{ formData.tjtime || '请选择提交时间' }}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<view class="submit-section">
|
||||
<u-button
|
||||
text="提交"
|
||||
type="primary"
|
||||
:loading="submitting"
|
||||
@click="handleSubmit"
|
||||
class="submit-btn"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { hcSaveApi } from "@/api/base/hcApi";
|
||||
import { attachmentUpload } from "@/api/system/upload";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { showLoading, hideLoading, showToast } from "@/utils/uniapp";
|
||||
import { imagUrl } from "@/utils";
|
||||
import { ref, onMounted } from "vue";
|
||||
|
||||
const { getJs } = useUserStore();
|
||||
|
||||
// 表单数据
|
||||
const formData = ref({
|
||||
gzmc: '', // 工作名称
|
||||
gzms: '', // 工作描述
|
||||
jsId: '', // 提交老师ID
|
||||
jsxm: '', // 提交老师姓名
|
||||
tjtime: '', // 提交时间
|
||||
tjfj: '' // 提交附件
|
||||
});
|
||||
|
||||
// 图片列表 - 修改为包含临时路径和服务器路径的对象
|
||||
interface ImageItem {
|
||||
tempPath?: string; // 临时路径(用于预览)
|
||||
url?: string; // 服务器路径(上传成功后)
|
||||
name?: string; // 文件名
|
||||
}
|
||||
|
||||
const imageList = ref<ImageItem[]>([]);
|
||||
|
||||
// 提交状态
|
||||
const submitting = ref(false);
|
||||
|
||||
// 初始化数据
|
||||
onMounted(() => {
|
||||
const js = getJs;
|
||||
formData.value.jsId = js.id;
|
||||
formData.value.jsxm = js.jsxm;
|
||||
|
||||
// 设置默认时间为今天
|
||||
const today = new Date();
|
||||
formData.value.tjtime = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
|
||||
});
|
||||
|
||||
// 选择图片
|
||||
const chooseImage = () => {
|
||||
uni.chooseImage({
|
||||
count: 9 - imageList.value.length,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: async (res) => {
|
||||
// 添加临时图片到列表
|
||||
const newImages = res.tempFilePaths.map(path => ({
|
||||
tempPath: path,
|
||||
name: path.split('/').pop() || 'image.jpg'
|
||||
}));
|
||||
|
||||
imageList.value = [...imageList.value, ...newImages];
|
||||
|
||||
// 自动上传图片
|
||||
await uploadImages(newImages);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 上传图片到服务器
|
||||
const uploadImages = async (images: ImageItem[]) => {
|
||||
try {
|
||||
showLoading('上传图片中...');
|
||||
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
const image = images[i];
|
||||
if (image.tempPath) {
|
||||
try {
|
||||
// 调用上传接口
|
||||
const uploadResult: any = await attachmentUpload(image.tempPath as any);
|
||||
|
||||
if (uploadResult && uploadResult.resultCode === 1 && uploadResult.result && uploadResult.result.length > 0) {
|
||||
// 保存服务器返回的文件路径
|
||||
const serverPath = uploadResult.result[0].filePath;
|
||||
|
||||
// 更新图片对象,保存服务器路径
|
||||
const index = imageList.value.findIndex(img => img.tempPath === image.tempPath);
|
||||
if (index !== -1) {
|
||||
imageList.value[index].url = serverPath;
|
||||
// 可以删除临时路径以节省内存
|
||||
delete imageList.value[index].tempPath;
|
||||
}
|
||||
} else {
|
||||
throw new Error('上传响应格式异常');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('图片上传失败:', error);
|
||||
showToast(`${image.name || '图片'}上传失败`, 'none');
|
||||
|
||||
// 从列表中移除上传失败的图片
|
||||
const index = imageList.value.findIndex(img => img.tempPath === image.tempPath);
|
||||
if (index !== -1) {
|
||||
imageList.value.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hideLoading();
|
||||
showToast('图片上传完成', 'success');
|
||||
} catch (error) {
|
||||
hideLoading();
|
||||
console.error('批量上传图片失败:', error);
|
||||
showToast('图片上传失败,请重试', 'none');
|
||||
}
|
||||
};
|
||||
|
||||
// 预览图片
|
||||
const previewImage = (index: number) => {
|
||||
const urls = imageList.value.map(img =>
|
||||
img.url ? imagUrl(img.url) : img.tempPath
|
||||
).filter(url => url);
|
||||
|
||||
uni.previewImage({
|
||||
urls: urls,
|
||||
current: index
|
||||
});
|
||||
};
|
||||
|
||||
// 删除图片
|
||||
const deleteImage = (index: number) => {
|
||||
imageList.value.splice(index, 1);
|
||||
};
|
||||
|
||||
// 时间选择
|
||||
const onTimeChange = (e: any) => {
|
||||
formData.value.tjtime = e.detail.value;
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
// 验证必填字段
|
||||
if (!formData.value.gzmc.trim()) {
|
||||
showToast('请输入工作名称', 'none');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.value.tjtime) {
|
||||
showToast('请选择提交时间', 'none');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有图片正在上传
|
||||
const hasUploadingImages = imageList.value.some(img => img.tempPath && !img.url);
|
||||
if (hasUploadingImages) {
|
||||
showToast('请等待图片上传完成', 'none');
|
||||
return;
|
||||
}
|
||||
|
||||
submitting.value = true;
|
||||
showLoading('提交中...');
|
||||
|
||||
try {
|
||||
// 处理图片路径
|
||||
let tjfj = '';
|
||||
const uploadedImages = imageList.value.filter(img => img.url);
|
||||
if (uploadedImages.length > 0) {
|
||||
// 使用服务器路径,用逗号分隔
|
||||
tjfj = uploadedImages.map(img => img.url).join(',');
|
||||
}
|
||||
|
||||
const submitData = {
|
||||
...formData.value,
|
||||
tjfj
|
||||
};
|
||||
|
||||
await hcSaveApi(submitData);
|
||||
|
||||
hideLoading();
|
||||
showToast('提交成功', 'success');
|
||||
|
||||
// 返回列表页
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
|
||||
} catch (error) {
|
||||
hideLoading();
|
||||
showToast('提交失败,请重试', 'none');
|
||||
console.error('提交失败:', error);
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.add-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f7fa;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
background-color: #ffffff;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #2c3e50;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
color: #495057;
|
||||
background-color: #ffffff;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:focus {
|
||||
border-color: #28a745;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: #f8f9fa;
|
||||
color: #6c757d;
|
||||
}
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
width: 100%;
|
||||
min-height: 100px;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
color: #495057;
|
||||
background-color: #ffffff;
|
||||
box-sizing: border-box;
|
||||
resize: none;
|
||||
|
||||
&:focus {
|
||||
border-color: #28a745;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.form-picker {
|
||||
.picker-display {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
color: #495057;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-section {
|
||||
.upload-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.upload-item {
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #e9ecef;
|
||||
|
||||
.upload-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.upload-delete {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.delete-icon {
|
||||
color: #ffffff;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-add {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border: 2px dashed #e9ecef;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f8f9fa;
|
||||
cursor: pointer;
|
||||
|
||||
.add-icon {
|
||||
font-size: 24px;
|
||||
color: #6c757d;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.add-text {
|
||||
font-size: 12px;
|
||||
color: #6c757d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submit-section {
|
||||
padding: 0 16px;
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
background: linear-gradient(135deg, #007aff 0%, #0056cc 100%);
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
box-shadow: 0 4px 16px rgba(0, 122, 255, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: translateY(1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 122, 255, 0.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式优化
|
||||
@media (max-width: 375px) {
|
||||
.add-page {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.form-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
284
src/pages/view/routine/ShiTangXunCha/detail.vue
Normal file
284
src/pages/view/routine/ShiTangXunCha/detail.vue
Normal file
@ -0,0 +1,284 @@
|
||||
<!-- src/pages/view/routine/ShiTangXunCha/detail.vue -->
|
||||
<template>
|
||||
<view class="detail-page">
|
||||
<view class="detail-container">
|
||||
<view class="detail-header">
|
||||
<text class="detail-title">{{ detailData.gzmc }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 工作描述 -->
|
||||
<view class="detail-section">
|
||||
<text class="section-title">工作描述</text>
|
||||
<view class="section-content">
|
||||
<rich-text class="detail-text" :nodes="detailData.gzms || '暂无描述'"></rich-text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 照片展示 -->
|
||||
<view v-if="imageList.length > 0" class="detail-section">
|
||||
<text class="section-title">巡查照片</text>
|
||||
<view class="image-grid">
|
||||
<view
|
||||
v-for="(image, index) in imageList"
|
||||
:key="index"
|
||||
class="image-item"
|
||||
@click="previewImage(index)"
|
||||
>
|
||||
<image
|
||||
:src="image"
|
||||
mode="aspectFill"
|
||||
class="detail-image"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提交信息 -->
|
||||
<view class="detail-section">
|
||||
<text class="section-title">提交信息</text>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="info-label">提交人:</text>
|
||||
<text class="info-value">{{ detailData.jsxm || '未知' }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-label">提交时间:</text>
|
||||
<text class="info-value">{{ formatTime(detailData.tjtime) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="action-section">
|
||||
<u-button
|
||||
text="返回列表"
|
||||
type="primary"
|
||||
@click="goBack"
|
||||
class="back-btn"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { hcFindByIdApi } from "@/api/base/hcApi";
|
||||
import { imagUrl } from "@/utils";
|
||||
import { ref, onMounted } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
|
||||
interface DetailData {
|
||||
id: string;
|
||||
gzmc: string;
|
||||
gzms: string;
|
||||
jsId: string;
|
||||
jsxm: string;
|
||||
tjtime: string;
|
||||
tjfj: string;
|
||||
}
|
||||
|
||||
// 详情数据
|
||||
const detailData = ref<DetailData>({
|
||||
id: '',
|
||||
gzmc: '',
|
||||
gzms: '',
|
||||
jsId: '',
|
||||
jsxm: '',
|
||||
tjtime: '',
|
||||
tjfj: ''
|
||||
});
|
||||
|
||||
// 图片列表
|
||||
const imageList = ref<string[]>([]);
|
||||
|
||||
// 获取页面参数
|
||||
onLoad((options) => {
|
||||
if (options.id) {
|
||||
loadDetail(options.id);
|
||||
}
|
||||
});
|
||||
|
||||
// 加载详情数据
|
||||
const loadDetail = async (id: string) => {
|
||||
try {
|
||||
const res = await hcFindByIdApi({ id });
|
||||
if (res && res.result) {
|
||||
detailData.value = res.result;
|
||||
|
||||
// 处理图片
|
||||
if (detailData.value.tjfj) {
|
||||
const images = detailData.value.tjfj.split(',').filter(img => img.trim());
|
||||
imageList.value = images.map(img => imagUrl(img));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载详情失败:', error);
|
||||
uni.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 预览图片
|
||||
const previewImage = (index: number) => {
|
||||
uni.previewImage({
|
||||
urls: imageList.value,
|
||||
current: index
|
||||
});
|
||||
};
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (timeStr: string) => {
|
||||
if (!timeStr) return "";
|
||||
const date = new Date(timeStr);
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
||||
};
|
||||
|
||||
// 返回列表
|
||||
const goBack = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.detail-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f7fa;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.detail-container {
|
||||
background-color: #ffffff;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.detail-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.detail-time {
|
||||
font-size: 14px;
|
||||
color: #6c757d;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
margin-bottom: 24px;
|
||||
|
||||
.section-title {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 12px;
|
||||
padding-left: 8px;
|
||||
border-left: 3px solid #28a745;
|
||||
}
|
||||
|
||||
.section-content {
|
||||
.detail-text {
|
||||
font-size: 14px;
|
||||
color: #5a6c7d;
|
||||
line-height: 1.6;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.image-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
.image-item {
|
||||
width: calc(33.33% - 6px);
|
||||
aspect-ratio: 1;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #e9ecef;
|
||||
|
||||
.detail-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-list {
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.info-label {
|
||||
font-size: 14px;
|
||||
color: #6c757d;
|
||||
min-width: 80px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 14px;
|
||||
color: #2c3e50;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-section {
|
||||
padding: 0 16px;
|
||||
|
||||
.back-btn {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
background: linear-gradient(135deg, #007aff 0%, #0056cc 100%);
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
box-shadow: 0 4px 16px rgba(0, 122, 255, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: translateY(1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 122, 255, 0.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式优化
|
||||
@media (max-width: 375px) {
|
||||
.detail-page {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.detail-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.detail-header .detail-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.image-grid .image-item {
|
||||
width: calc(50% - 4px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
477
src/pages/view/routine/ShiTangXunCha/index.vue
Normal file
477
src/pages/view/routine/ShiTangXunCha/index.vue
Normal file
@ -0,0 +1,477 @@
|
||||
<!-- src/pages/view/routine/ShiTangXunCha/index.vue -->
|
||||
<template>
|
||||
<view class="hc-list-page">
|
||||
<!-- 查询组件 -->
|
||||
<view class="query-component">
|
||||
<view class="search-card">
|
||||
<view class="search-item">
|
||||
<picker
|
||||
mode="date"
|
||||
:value="searchForm.startTime"
|
||||
@change="onStartTimeChange"
|
||||
class="date-picker"
|
||||
>
|
||||
<view class="picker-text">{{ searchForm.startTime || '开始时间' }}</view>
|
||||
</picker>
|
||||
<text class="date-separator">~</text>
|
||||
<picker
|
||||
mode="date"
|
||||
:value="searchForm.endTime"
|
||||
@change="onEndTimeChange"
|
||||
class="date-picker"
|
||||
>
|
||||
<view class="picker-text">{{ searchForm.endTime || '结束时间' }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="search-actions">
|
||||
<u-button
|
||||
text="查询"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleSearch"
|
||||
class="search-btn"
|
||||
/>
|
||||
<u-button
|
||||
text="重置"
|
||||
type="info"
|
||||
size="small"
|
||||
@click="handleReset"
|
||||
class="reset-btn"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表组件 -->
|
||||
<view class="list-component">
|
||||
<scroll-view scroll-y class="list-scroll-view">
|
||||
<view v-if="isLoading" class="loading-indicator">加载中...</view>
|
||||
<template v-else-if="dataList.length > 0">
|
||||
<view v-for="data in dataList" :key="data.id" class="hc-card" @click="goToDetail(data.id)">
|
||||
<view class="card-header">
|
||||
<text class="hc-title">{{ data.gzmc }}</text>
|
||||
<text class="hc-time">{{ formatTime(data.tjtime) }}</text>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<rich-text class="hc-excerpt" :nodes="data.gzms"></rich-text>
|
||||
<view v-if="data.tjfj" class="image-preview">
|
||||
<image
|
||||
:src="getImageUrl(data.tjfj)"
|
||||
mode="aspectFill"
|
||||
class="preview-image"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-footer">
|
||||
<text class="footer-item">提交人: {{ data.jsxm || '未知' }}</text>
|
||||
<text class="footer-item">提交时间: {{ formatTime(data.tjtime) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<view v-else class="empty-state">暂无巡查数据</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 新增按钮 - 固定在底部 -->
|
||||
<view class="add-button-fixed">
|
||||
<u-button
|
||||
text="新增巡查"
|
||||
type="primary"
|
||||
@click="goToAdd"
|
||||
class="add-btn"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { hcFindPageApi } from "@/api/base/hcApi";
|
||||
import { imagUrl } from "@/utils";
|
||||
import { ref, onMounted } from "vue";
|
||||
import { onShow } from "@dcloudio/uni-app";
|
||||
|
||||
interface HcItem {
|
||||
id: string;
|
||||
gzmc: string; // 工作名称
|
||||
gzms: string; // 工作描述
|
||||
jsId: string; // 提交老师ID
|
||||
jsxm: string; // 提交老师姓名
|
||||
tjtime: string; // 提交时间
|
||||
tjfj: string; // 提交附件
|
||||
}
|
||||
|
||||
// 获取当月开始时间
|
||||
const getCurrentMonthStart = () => {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
return `${year}-${month}-01`;
|
||||
};
|
||||
|
||||
// 获取当月结束时间
|
||||
const getCurrentMonthEnd = () => {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
const lastDay = new Date(year, now.getMonth() + 1, 0).getDate();
|
||||
return `${year}-${month}-${lastDay}`;
|
||||
};
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = ref({
|
||||
startTime: getCurrentMonthStart(),
|
||||
endTime: getCurrentMonthEnd()
|
||||
});
|
||||
|
||||
// 数据列表
|
||||
const dataList = ref<HcItem[]>([]);
|
||||
const isLoading = ref(false);
|
||||
|
||||
// 开始时间选择
|
||||
const onStartTimeChange = (e: any) => {
|
||||
searchForm.value.startTime = e.detail.value;
|
||||
};
|
||||
|
||||
// 结束时间选择
|
||||
const onEndTimeChange = (e: any) => {
|
||||
searchForm.value.endTime = e.detail.value;
|
||||
};
|
||||
|
||||
// 查询
|
||||
const handleSearch = () => {
|
||||
getHcList();
|
||||
};
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchForm.value.startTime = getCurrentMonthStart();
|
||||
searchForm.value.endTime = getCurrentMonthEnd();
|
||||
getHcList();
|
||||
};
|
||||
|
||||
// 获取巡查列表
|
||||
const getHcList = async () => {
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const res = await hcFindPageApi({
|
||||
startTime: searchForm.value.startTime,
|
||||
endTime: searchForm.value.endTime,
|
||||
page: 1,
|
||||
rows: 100
|
||||
});
|
||||
dataList.value = res.rows || [];
|
||||
} catch (error) {
|
||||
console.error('获取巡查列表失败:', error);
|
||||
dataList.value = [];
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 跳转到详情页
|
||||
const goToDetail = (hcId: string) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/view/routine/ShiTangXunCha/detail?id=${hcId}`,
|
||||
});
|
||||
};
|
||||
|
||||
// 跳转到新增页
|
||||
const goToAdd = () => {
|
||||
uni.navigateTo({
|
||||
url: "/pages/view/routine/ShiTangXunCha/add",
|
||||
});
|
||||
};
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (timeStr: string) => {
|
||||
if (!timeStr) return "";
|
||||
const date = new Date(timeStr);
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(
|
||||
2,
|
||||
"0"
|
||||
)}-${String(date.getDate()).padStart(2, "0")}`;
|
||||
};
|
||||
|
||||
// 获取图片URL
|
||||
const getImageUrl = (imagePath: string) => {
|
||||
if (!imagePath) return "";
|
||||
// 如果路径包含逗号,说明有多张图片,取第一张
|
||||
const firstImage = imagePath.split(',')[0];
|
||||
return imagUrl(firstImage);
|
||||
};
|
||||
|
||||
onShow(() => {
|
||||
getHcList();
|
||||
});
|
||||
|
||||
// 页面加载时也调用一次
|
||||
onMounted(() => {
|
||||
getHcList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.hc-list-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
// 查询组件
|
||||
.query-component {
|
||||
padding: 12px;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #eee;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
background-color: #ffffff;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.search-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
gap: 8px;
|
||||
|
||||
.date-picker {
|
||||
flex: 1;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
color: #495057;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.date-separator {
|
||||
font-size: 16px;
|
||||
color: #6c757d;
|
||||
margin: 0 8px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
|
||||
.search-btn, .reset-btn {
|
||||
flex: 1;
|
||||
max-width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
// 列表组件
|
||||
.list-component {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 12px;
|
||||
padding-bottom: 80px; // 为底部按钮留出空间
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.list-scroll-view {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-indicator,
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
padding: 30px 15px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.hc-card {
|
||||
background-color: #ffffff;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid #f0f0f0;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&:active {
|
||||
transform: translateY(1px);
|
||||
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 4px;
|
||||
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12px;
|
||||
gap: 12px;
|
||||
|
||||
.hc-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
flex: 1;
|
||||
line-height: 1.4;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.hc-time {
|
||||
font-size: 12px;
|
||||
color: #6c757d;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.hc-excerpt {
|
||||
font-size: 14px;
|
||||
color: #5a6c7d;
|
||||
line-height: 1.6;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
word-break: break-word;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.image-preview {
|
||||
.preview-image {
|
||||
width: 80px;
|
||||
height: 60px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 16px;
|
||||
align-items: center;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
|
||||
.footer-item {
|
||||
white-space: nowrap;
|
||||
font-size: 13px;
|
||||
color: #7f8c8d;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 180px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background-color: #bdc3c7;
|
||||
border-radius: 50%;
|
||||
margin-right: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 新增按钮 - 固定在底部
|
||||
.add-button-fixed {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 15px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
|
||||
z-index: 10;
|
||||
border-top: 1px solid #eee;
|
||||
|
||||
.add-btn {
|
||||
width: 100%;
|
||||
background: linear-gradient(135deg, #007aff 0%, #0056cc 100%);
|
||||
border-radius: 12px;
|
||||
padding: 12px 24px;
|
||||
box-shadow: 0 4px 16px rgba(0, 122, 255, 0.3);
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
|
||||
&:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式优化
|
||||
@media (max-width: 375px) {
|
||||
.query-component {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.hc-card {
|
||||
padding: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.card-header .hc-title {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.card-footer .footer-item {
|
||||
max-width: 150px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
// 加载动画
|
||||
.hc-card {
|
||||
animation: fadeInUp 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
BIN
src/static/base/home/hc-fill.png
Normal file
BIN
src/static/base/home/hc-fill.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
Loading…
x
Reference in New Issue
Block a user