This commit is contained in:
Net 2025-07-02 22:43:30 +08:00
parent 66bc9dc920
commit 4f220f1855
5 changed files with 514 additions and 303 deletions

View File

@ -32,7 +32,7 @@ export const jsdFindPageTaskApi = async (params: any) => {
};
export const rwflFindRwlxsByRwId = async (params: any) => {
return await get("/api/rwlx/findRwlxsByRwId", params);
}
};
export const rwzxSaveApi = async (params: any) => {
return await post("/api/rwzx/save", params);
};
@ -40,10 +40,9 @@ export const rwzxExecutedInfoByRwIdAndJsApi = async (params: any) => {
return await get("/api/rwzx/executedInfoByRwIdAndJs", params);
};
export const rwFindInfoByRwId = async (params: any) => {
return await get("/api/rw/findInfoByRwId", params);
}
};
export const fractionRuleApi = async () => {
return await get(
@ -75,6 +74,14 @@ export const jsdXkkcSaveApi = async (params: any) => {
export const jsdXkXsListApi = async (params: any) => {
return await get("/mobile/js/xkxs/list", params);
};
export const mobilejlstudentListApi = async (params: any) => {
return await get("/mobile/jl/studentList", params);
};
export const mobilejllistApi = async (params: any) => {
const res = await get("/mobile/jl/list", params);
return res.result;
};
// 提交点名信息
export const jsdXkdmListApi = async (params: any) => {

View File

@ -1,4 +1,5 @@
const ip: string = "192.168.239.1:8897";
// const ip: string = "192.168.239.1:8897";
const ip: string = "yufangzc.com";
// const ip: string = "yufangzc.com";
const fwqip: string = "yufangzc.com";
//打包服务器接口代理标识

View File

@ -226,29 +226,6 @@ if (getFile.rgqkList && getFile.rgqkList.length > 0) {
});
}
//
const calculateAllPositionYears = () => {
education.xl.forEach((item: any, index: number) => {
if (item.value.gwrzkstime) {
const years = calculatePositionYears(
item.value.gwrzkstime,
item.value.gwrzjstime
);
item.value.gwrznx = years;
console.log(`表单项${index}计算结果:`, {
gwrzkstime: item.value.gwrzkstime,
gwrzjstime: item.value.gwrzjstime,
gwrznx: years,
是否为0: years === "0",
});
} else {
//
item.value.gwrznx = "";
}
});
forceUpdateKey.value++;
};
function addEducation() {
education.xl.push({ value: {} });
//

View File

@ -1,38 +1,55 @@
<!-- src/pages/view/notice/index.vue -->
<template>
<view class="jl-list-page">
<!-- 新建接龙按钮 -->
<view class="fab-button" @click="goToPublish">
<uni-icons type="plusempty" size="24" color="#fff"></uni-icons>
</view>
<!-- 列表内容 -->
<view v-if="jlList.length > 0">
<view v-for="item in jlList" :key="item.id" class="jl-card" @click="goToDetail(item.id)">
<BasicListLayout @register="register">
<template v-slot="{ data }">
<view class="jl-card" @click="goToDetail(data.id)">
<view class="card-header">
<text class="jl-title">{{ item.jlmc }}</text>
<text class="jl-status" :class="getStatusClass(item.jlStatus)">
{{ getStatusText(item.jlStatus) }}
<text class="jl-title">{{ data.jlmc }}</text>
<text class="jl-status" :class="getStatusClass(data.jlStatus)">
{{ getStatusText(data.jlStatus) }}
</text>
</view>
<view class="card-body">
<image v-if="item.jlfm" :src="item.jlfm" mode="aspectFill" class="cover-thumbnail"></image>
<text class="jl-excerpt" v-html="item.jlms"></text>
<image
v-if="data.jlfm"
:src="imagUrl(data.jlfm)"
mode="aspectFill"
class="cover-thumbnail"
></image>
<rich-text class="jl-excerpt" :nodes="data.jlms"></rich-text>
</view>
<view class="card-footer">
<text class="footer-item">发布者: {{ item.jsxm }}</text>
<text class="footer-item">{{ formatTime(item.jlFbtime) }}</text>
<text class="footer-item" v-if="item.bjmc">范围: {{ item.njmc+item.bjmc }}</text>
<text class="footer-item">发布者: {{ data.jsxm }}</text>
<text class="footer-item">{{ formatTime(data.jlFbtime) }}</text>
<text class="footer-item" v-if="data.bjmc"
>范围: {{ data.njmc + data.bjmc }}</text
>
</view>
</view>
</template>
<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="goToPublish"
/>
</view>
<view v-else class="empty-list">暂无数据</view>
</view>
</template>
</BasicListLayout>
</view>
</template>
<script lang="ts" setup>
import {ref, onMounted} from "vue";
import {BASE_URL} from "@/config";
import { useLayout } from "@/components/BasicListLayout/hooks/useLayout";
import { mobilejllistApi } from "@/api/base/server";
import { imagUrl } from "@/utils";
import BasicDragButton from "@/components/BasicDragButton/DragButton.vue";
interface JlItem {
id: string;
@ -49,39 +66,10 @@ interface JlItem {
jsxm?: string;
}
const jlList = ref<JlItem[]>([]);
const total = ref(0);
const fetchJlList = async () => {
try {
const response = await uni.request({
url: `${BASE_URL}/mobile/jl/list`,
method: "GET",
data: {pageNo: 1, pageSize: 10},
header: {"Content-Type": "application/json"}
});
if (response.statusCode === 200 && response.data) {
const result = response.data;
const pageData = result.data || result;
if (pageData.result && Array.isArray(pageData.result.rows)) {
jlList.value = pageData.result.rows;
total.value = pageData.result.total || 0;
} else {
jlList.value = [];
total.value = 0;
}
} else {
jlList.value = [];
total.value = 0;
}
} catch (e) {
jlList.value = [];
total.value = 0;
}
};
onMounted(() => {
fetchJlList();
// 使 BasicListLayout
const [register, { reload }] = useLayout({
api: mobilejllistApi, // api使使 query
componentProps: {},
});
//
@ -114,9 +102,12 @@ const getStatusText = (status: string) => {
//
const formatTime = (timeStr: string) => {
if (!timeStr) return '';
if (!timeStr) return "";
const date = new Date(timeStr);
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(
2,
"0"
)}-${String(date.getDate()).padStart(2, "0")}`;
};
</script>
@ -215,11 +206,8 @@ const formatTime = (timeStr: string) => {
}
}
// FAB
// FAB - DragButton
.fab-button {
position: fixed;
right: 20px;
bottom: 40px; //
width: 50px;
height: 50px;
background-color: #4477ee;
@ -228,12 +216,7 @@ const formatTime = (timeStr: string) => {
justify-content: center;
align-items: center;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
z-index: 10;
}
.empty-list {
text-align: center;
color: #999;
margin-top: 40px;
}
/* 空列表样式已由 BasicListLayout 组件处理,移除此样式 */
</style>

View File

@ -15,7 +15,7 @@
<view class="cover-upload-wrapper">
<CustomUpload
field="coverImage"
:value="formData.coverImage"
:value="formData.coverImage ? imagUrl(formData.coverImage) : ''"
@select="handleCoverSelected"
@close="handleCoverClosed"
>
@ -80,21 +80,13 @@
<view class="add-icon"
><uni-icons type="plusempty" size="20" color="#ccc"></uni-icons
></view>
<text class="placeholder-text"
>添加图文/视频/文件/公众号/小程序等</text
>
<text class="placeholder-text">添加图文/视频/文件</text>
</view>
</view>
</view>
<view class="info-card">
<picker
mode="selector"
:range="combinedClassRange"
:value="selectedClassIndex"
@change="onClassPickerChange"
>
<view class="card-header picker-header">
<view class="card-header picker-header" @click="showClassTree">
<text class="section-title">按名单填写</text>
<view class="target-class">
<text :class="{ placeholder: !formData.targetClass }">{{
@ -103,22 +95,20 @@
<uni-icons type="right" size="16" color="#999"></uni-icons>
</view>
</view>
</picker>
<view class="name-tags">
<text
v-for="name in formData.targetNames"
:key="name"
v-for="(name, index) in displayNames"
:key="name + index"
class="name-tag"
>{{ name }}</text
>
<button
v-if="formData.targetNames.length > 0"
size="mini"
type="default"
class="modify-btn"
@click="handleModifyNames"
</view>
<view
v-if="formData.targetNames.length > maxDisplayCount"
class="more-btn-container"
>
修改
<button size="mini" class="more-btn-full" @click="showAllStudents">
更多({{ formData.targetNames.length - maxDisplayCount }})
</button>
</view>
</view>
@ -166,7 +156,9 @@
<!-- Bottom slot -->
<template #bottom>
<view class="bottom-actions">
<button class="action-btn draft-btn" @click="saveDraft">保存草稿</button>
<button class="action-btn draft-btn" @click="saveDraft">
保存草稿
</button>
<button class="action-btn preview-btn" @click="previewNotice">
预览
</button>
@ -176,6 +168,51 @@
</view>
</template>
<!-- 班级选择树 -->
<BasicTree
ref="treeRef"
:range="treeData"
idKey="key"
rangeKey="title"
title="选择班级"
:multiple="true"
:selectParent="false"
@confirm="onTreeConfirm"
@cancel="onTreeCancel"
/>
<!-- 学生名单弹窗 -->
<view
v-if="showStudentModal"
class="student-modal-mask"
@click="closeStudentModal"
>
<view class="student-modal" @click.stop>
<view class="student-modal-header">
<text class="modal-title">全部学生名单</text>
<text class="student-count"
>{{ formData.targetNames.length }}</text
>
<uni-icons
type="closeempty"
size="20"
color="#999"
@click="closeStudentModal"
class="close-icon"
></uni-icons>
</view>
<scroll-view scroll-y class="student-modal-content">
<view class="all-student-tags">
<text
v-for="(name, index) in formData.targetNames"
:key="name + index"
class="student-tag"
>{{ name }}</text
>
</view>
</scroll-view>
</view>
</view>
</BasicLayout>
</template>
@ -183,6 +220,10 @@
import { ref, reactive, computed } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import CustomUpload from "/src/components/BasicUpload/CustomUpload.vue";
import BasicTree from "@/components/BasicTree/Tree.vue";
import { attachmentUpload } from "@/api/system/upload";
import { imagUrl } from "@/utils";
import { findAllNjBjTree, mobilejlstudentListApi } from "@/api/base/server";
interface Attachment {
name: string;
@ -200,6 +241,8 @@ interface FormData {
targetClass: string;
targetNames: string[];
targetStudentIds: string[];
targetNjIds: string[]; // ID
targetBjIds: string[]; // ID
signatureRequired: boolean;
startTime: string;
endTime: string;
@ -215,6 +258,8 @@ const formData = reactive<FormData>({
targetClass: "",
targetNames: [],
targetStudentIds: [],
targetNjIds: [],
targetBjIds: [],
signatureRequired: false,
startTime: "",
endTime: "",
@ -225,38 +270,40 @@ const signatureStatusText = computed(() => {
return formData.signatureRequired ? "启用" : "不启用";
});
const classList = ref([
{ id: "g1c1", name: "一年级1班" },
{ id: "g1c2", name: "一年级2班" },
{ id: "g1c3", name: "一年级3班" },
{ id: "g1c4", name: "一年级4班" },
{ id: "g1c5", name: "一年级5班" },
{ id: "g2c1", name: "二年级1班" },
{ id: "g2c2", name: "二年级2班" },
{ id: "g2c3", name: "二年级3班" },
{ id: "g3c1", name: "三年级1班" },
{ id: "g3c2", name: "三年级2班" },
{ id: "g3c3", name: "三年级3班" },
{ id: "g3c4", name: "三年级4班" },
{ id: "g4c1", name: "四年级1班" },
{ id: "g4c2", name: "四年级2班" },
{ id: "g5c1", name: "五年级1班" },
{ id: "g5c2", name: "五年级2班" },
{ id: "g5c3", name: "五年级3班" },
{ id: "g6c1", name: "六年级1班" },
{ id: "g6c2", name: "六年级2班" },
]);
//
const treeData = ref([]);
const treeRef = ref();
const combinedClassRange = computed(() => {
return classList.value.map((cls) => cls.name);
//
const maxDisplayCount = 24; // 244×6
const showStudentModal = ref(false);
// 24
const displayNames = computed(() => {
return formData.targetNames.slice(0, maxDisplayCount);
});
const selectedClassIndex = computed(() => {
const index = classList.value.findIndex(
(cls) => cls.name === formData.targetClass
);
return index >= 0 ? index : 0;
});
//
const loadTreeData = async () => {
try {
const res = await findAllNjBjTree();
if (res.resultCode === 1 && res.result) {
// BasicTree
treeData.value = res.result.map((item: any) => ({
key: item.key,
title: item.title,
children: item.children || [],
}));
console.log("树形数据加载成功:", treeData.value);
}
} catch (error) {
console.error("加载树形数据失败:", error);
uni.showToast({ title: "加载班级数据失败", icon: "error" });
}
};
//
loadTreeData();
const fetchStudentsByClass = async (className: string): Promise<string[]> => {
console.log(`模拟获取班级 [${className}] 的学生列表...`);
@ -281,40 +328,42 @@ const fetchStudentsByClass = async (className: string): Promise<string[]> => {
return mockStudents;
};
onLoad((options) => {
if (options && options.id) {
noticeId.value = options.id;
uni.setNavigationBarTitle({ title: "编辑通知" });
formData.title = "关于五一放假的通知 (编辑)";
formData.content = "根据校历安排现将2024年五一劳动节放假安排通知如下...";
formData.targetClass = "二年级1班";
formData.targetNames = ["张三", "李四"];
formData.targetStudentIds = ["s201-mock", "s202-mock"];
formData.signatureRequired = true;
formData.startTime = "2024-04-30 18:00:00";
formData.endTime = "2024-05-05 23:59:59";
if (formData.targetClass && noticeId.value) {
fetchStudentsByClass(formData.targetClass).then((students) => {
formData.targetNames = students;
});
}
} else {
uni.setNavigationBarTitle({ title: "发布通知" });
}
});
onLoad((options) => {});
const handleCoverSelected = (e: any) => {
console.log("选择封面 (CustomUpload):", e);
const handleCoverSelected = async (e: any) => {
if (e.tempFilePaths && e.tempFilePaths.length > 0) {
formData.coverImage = e.tempFilePaths[0];
console.log("封面临时路径:", formData.coverImage);
const tempFilePath = e.tempFilePaths[0];
try {
uni.showLoading({ title: "上传中..." });
// 使 tempFilePath Blob
const uploadResult: any = await attachmentUpload(tempFilePath as any);
if (
uploadResult.resultCode === 1 &&
uploadResult.result &&
uploadResult.result.length > 0
) {
// filePath
const originalPath = uploadResult.result[0].filePath;
formData.coverImage = originalPath;
uni.showToast({ title: "封面上传成功", icon: "success" });
} else {
throw new Error("上传响应格式异常");
}
} catch (error) {
uni.showToast({ title: "封面上传失败", icon: "error" });
formData.coverImage = null;
} finally {
uni.hideLoading();
}
} else {
console.error("无法从选择事件中获取封面路径:", e);
}
};
const handleCoverClosed = (field: string) => {
console.log(`删除封面 (CustomUpload): field=${field}`);
if (field === "coverImage") {
formData.coverImage = null;
}
@ -324,12 +373,16 @@ const addAttachment = () => {
uni.chooseFile({
count: 5,
type: "all",
success: (res) => {
success: async (res) => {
const tempFiles = res.tempFiles;
if (Array.isArray(tempFiles) && tempFiles.length > 0) {
tempFiles.forEach((file: any) => {
uni.showLoading({ title: "上传中..." });
try {
for (const file of tempFiles) {
const fileInfo = file as any; //
let fileType = "file";
const fileName = file.name || "";
const fileName = fileInfo.name || "";
const fileExtension = fileName.split(".").pop()?.toLowerCase();
if (
@ -349,22 +402,45 @@ const addAttachment = () => {
}
if (
file.type &&
typeof file.type === "string" &&
(file.type.startsWith("image/") ||
file.type.startsWith("video/") ||
file.type.startsWith("audio/"))
fileInfo.type &&
typeof fileInfo.type === "string" &&
(fileInfo.type.startsWith("image/") ||
fileInfo.type.startsWith("video/") ||
fileInfo.type.startsWith("audio/"))
) {
fileType = file.type.split("/")[0];
fileType = fileInfo.type.split("/")[0];
}
// 使 Blob
const uploadResult: any = await attachmentUpload(
fileInfo.path as any
);
if (
uploadResult.resultCode === 1 &&
uploadResult.result &&
uploadResult.result.length > 0
) {
// filePath
const originalPath = uploadResult.result[0].filePath;
formData.attachments.push({
name: fileName,
type: fileType,
url: file.path,
size: file.size,
});
url: originalPath, //
size: fileInfo.size,
});
} else {
uni.showToast({ title: `${fileName} 上传失败`, icon: "error" });
}
}
uni.showToast({ title: "附件上传完成", icon: "success" });
} catch (error) {
console.error("附件上传失败:", error);
uni.showToast({ title: "附件上传失败", icon: "error" });
} finally {
uni.hideLoading();
}
} else {
console.log("未选择任何文件或返回结果异常,或 tempFiles 不是数组");
}
@ -391,38 +467,124 @@ const getAttachmentIcon = (type: string): string => {
const previewAttachment = (attachment: Attachment) => {
console.log("预览附件:", attachment);
uni.showToast({ title: `预览 ${attachment.name} 功能待实现`, icon: "none" });
//
if (attachment.type === "image") {
const fullUrl = imagUrl(attachment.url);
uni.previewImage({
urls: [fullUrl],
current: fullUrl,
});
} else {
uni.showToast({
title: `预览 ${attachment.name} 功能待实现`,
icon: "none",
});
}
};
const onClassPickerChange = async (e: any) => {
const index = e.detail.value;
const selectedClass = classList.value[index];
if (selectedClass && selectedClass.name !== formData.targetClass) {
formData.targetClass = selectedClass.name;
//
const showClassTree = () => {
if (treeRef.value) {
treeRef.value._show();
}
};
//
const onTreeConfirm = async (selectedItems: any[]) => {
console.log("选择的班级:", selectedItems);
if (selectedItems.length > 0) {
//
const classNames = selectedItems.map((item) => item.title);
formData.targetClass = classNames.join(", ");
formData.targetNames = [];
formData.targetStudentIds = [];
formData.targetNjIds = [];
formData.targetBjIds = [];
try {
const students = await fetchStudentsByClass(formData.targetClass);
formData.targetNames = students;
uni.showLoading({ title: "获取学生列表中..." });
// IDID
const njIds: string[] = [];
const bjIds: string[] = [];
selectedItems.forEach((item) => {
// parents
if (item.parents && item.parents.length > 0) {
const njId = item.parents[0].key; // IDparents[0].key
const bjId = item.key; // IDitem.key
njIds.push(njId);
bjIds.push(bjId);
console.log(
`选择班级: ${item.title}, 年级ID: ${njId}, 班级ID: ${bjId}`
);
}
});
if (njIds.length > 0 && bjIds.length > 0) {
// ID
const uniqueNjIds = [...new Set(njIds)];
// IDIDformData
formData.targetNjIds = uniqueNjIds;
formData.targetBjIds = bjIds;
//
const params = {
njId: uniqueNjIds.join(","),
bjId: bjIds.join(","),
};
const response = await mobilejlstudentListApi(params);
if (response && response.resultCode === 1 && response.result) {
//
const studentNames = response.result.map(
(student: any) =>
student.xsxm ||
student.name ||
student.studentName ||
student.xm ||
"未知姓名"
);
formData.targetNames = studentNames;
formData.targetStudentIds = response.result.map(
(student: any) =>
student.id || student.xsId || student.studentId || ""
);
} else {
throw new Error("获取学生列表失败");
}
}
uni.hideLoading();
uni.showToast({
title: `已选择 ${selectedItems.length} 个班级`,
icon: "success",
});
} catch (error) {
console.error("获取学生列表失败:", error);
uni.showToast({ title: "获取学生列表失败", icon: "none" });
uni.hideLoading();
uni.showToast({ title: "获取学生列表失败", icon: "error" });
// 使
formData.targetNames = await fetchStudentsByClass(formData.targetClass);
}
}
};
const handleModifyNames = () => {
const selectedClassObj = classList.value.find(
(cls) => cls.name === formData.targetClass
);
if (!selectedClassObj) {
uni.showToast({ title: "请先选择班级", icon: "none" });
return;
}
const classId = selectedClassObj.id;
uni.navigateTo({
url: `/pages/view/notice/selectStudents?classId=${classId}`,
});
//
const onTreeCancel = () => {
console.log("取消选择班级");
};
//
const showAllStudents = () => {
showStudentModal.value = true;
};
//
const closeStudentModal = () => {
showStudentModal.value = false;
};
const handleSignatureChange = (e: any) => {
@ -464,13 +626,25 @@ const previewNotice = () => {
const publishNotice = () => {
if (!validateForm()) return;
console.log("发布通知", formData);
uni.showLoading({ title: "发布中..." });
setTimeout(() => {
uni.hideLoading();
uni.showToast({ title: "发布成功 (模拟)", icon: "success" });
uni.navigateBack();
}, 1000);
// IDID
const publishData = {
...formData,
njIds: formData.targetNjIds.join(","), // ID
bjIds: formData.targetBjIds.join(","), // ID
};
console.log("发布通知数据:", publishData);
console.log("年级ID:", publishData.njIds);
console.log("班级ID:", publishData.bjIds);
// TODO:
// uni.showLoading({ title: "..." });
// setTimeout(() => {
// uni.hideLoading();
// uni.showToast({ title: " ()", icon: "success" });
// uni.navigateBack();
// }, 1000);
};
</script>
@ -734,8 +908,7 @@ const publishNotice = () => {
position: relative;
min-height: 30px;
.name-tag,
.modify-btn {
.name-tag {
font-size: 13px;
padding: 5px 0;
border-radius: 4px;
@ -746,29 +919,10 @@ const publishNotice = () => {
flex-basis: calc((100% - 50px) / 6);
height: 28px;
line-height: 18px;
}
.name-tag {
background-color: #f4f4f5;
color: #909399;
}
.modify-btn {
background-color: #ecf5ff;
color: #409eff;
border: 1px solid #d9ecff;
padding: 0;
margin-left: 0;
margin-top: 0;
margin-bottom: 0;
&::after {
border: none;
}
display: flex;
justify-content: center;
align-items: center;
}
.loading-spinner {
position: absolute;
top: 5px;
@ -778,6 +932,26 @@ const publishNotice = () => {
}
}
.more-btn-container {
width: 100%;
}
.more-btn-full {
width: 100%;
height: 35px;
background-color: #fff2e8;
color: #e6a23c;
border: 1px solid #f5dab1;
border-radius: 4px;
font-size: 14px;
&::after {
border: none;
}
&:active {
background-color: #ffecd1;
}
}
.list-item-card {
padding: 0;
.uni-datetime-picker,
@ -890,4 +1064,73 @@ const publishNotice = () => {
color: #909399;
}
/* 学生名单弹窗样式 */
.student-modal-mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9999;
display: flex;
justify-content: center;
align-items: center;
}
.student-modal {
width: 90%;
max-height: 80%;
background-color: #ffffff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.student-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
border-bottom: 1px solid #f0f0f0;
background-color: #fafafa;
}
.modal-title {
font-size: 16px;
font-weight: 500;
color: #303133;
}
.student-count {
font-size: 14px;
color: #909399;
}
.close-icon {
cursor: pointer;
padding: 5px;
}
.student-modal-content {
max-height: 400px;
padding: 20px;
}
.all-student-tags {
display: flex;
flex-wrap: wrap;
gap: 8px 10px;
}
.student-tag {
font-size: 13px;
padding: 6px 12px;
border-radius: 4px;
text-align: center;
background-color: #f4f4f5;
color: #909399;
min-width: 60px;
box-sizing: border-box;
}
</style>