接龙调整

This commit is contained in:
hebo 2025-09-28 21:13:07 +08:00
parent 66955f9ac7
commit 510d12282f
18 changed files with 3087 additions and 79 deletions

101
src/api/base/jlApi.ts Normal file
View File

@ -0,0 +1,101 @@
// 接龙相关API接口
import { get, post } from "@/utils/request";
/**
* ID和班级ID查询学生及家长信息
*/
export const mobilejlstudentListApi = async (params: any) => {
return await get("/mobile/jl/studentList", params);
};
/**
* IDID和学生ID查询家长接龙信息
*/
export const mobilejllistApi = async (params: any) => {
const res = await get("/mobile/jl/list", params);
return res.result;
};
/**
* jlId查询接龙列表数据
*/
export const getByJlIdApi = async (params: any) => {
const res = await get("/mobile/jl/getByJlId", params);
return res.result;
};
/**
* API
*/
export const getJlStatisticsApi = async (params: any) => {
const res = await get("/mobile/jl/statistics", params);
return res.result;
};
/**
* ID获取接龙详情
*/
export const jlFindByIdApi = async (params: { id: string }) => {
return await get("/api/jl/findById", params);
};
/**
* /
*/
export const jlSaveApi = async (params: any) => {
return await post("/api/jl/save", params);
};
/**
* ID查询接龙执行情况
*/
export const jlzxFindByJlParamsApi = async (params: {
jlId: string;
njId?: string;
njmcId?: string;
bjId?: string;
}) => {
return await get("/api/jlzx/findByJlParams", params);
};
/**
*
*/
export const xxtsSaveByJlzxParamsApi = async (params: { jlId: string }) => {
return await post("/api/xxts/saveByJlzxParams", params);
};
/**
* API
*/
export const getClassListApi = async () => {
const res = await get("/api/bj/findAll");
return res.result;
};
/**
* API
*/
export const getClassDetailApi = async (params: { jlId: string; njId: string }) => {
const res = await get("/mobile/jl/classDetail", params);
return res.result;
};
/**
* API
*/
export const getPersonnelListApi = async (params: { jlId: string; type: string; bjId?: string; njId?: string }) => {
const res = await get("/mobile/jl/personnelList", params);
return res.result;
};
/**
* API
*/
export const pushMessageApi = async (params: {
jlId: string;
njId?: string;
bjId?: string;
}) => {
return await post("/api/jlzx/pushMessage", params);
};

View File

@ -128,20 +128,6 @@ export const ksccKmFindByKsccIdApi = async (params: any) => {
return await get("/api/kscc/findKsccKmmcById", params); return await get("/api/kscc/findKsccKmmcById", params);
}; };
//根据年级ID和班级ID查询学生及家长信息
export const mobilejlstudentListApi = async (params: any) => {
return await get("/mobile/jl/studentList", params);
};
//根据年级ID、关联年级名称ID和学生ID查询家长接龙信息
export const mobilejllistApi = async (params: any) => {
const res = await get("/mobile/jl/list", params);
return res.result;
};
//根据jlId查询接龙列表数据
export const getByJlIdApi = async (params: any) => {
const res = await get("/mobile/jl/getByJlId", params);
return res.result;
};
// 提交点名信息 // 提交点名信息
export const jsdXkdmListApi = async (params: any) => { export const jsdXkdmListApi = async (params: any) => {
@ -236,31 +222,6 @@ export const getJsPjGzlApi = async () => {
return await get("/api/comConfig/getJsPjGzl"); return await get("/api/comConfig/getJsPjGzl");
}; };
// 接龙相关API
// 根据ID获取接龙详情
export const jlFindByIdApi = async (params: { id: string }) => {
return await get("/api/jl/findById", params);
};
// 保存接龙(新增/编辑)
export const jlSaveApi = async (params: any) => {
return await post("/api/jl/save", params);
};
// 根据接龙ID查询接龙执行情况学生列表
export const jlzxFindByJlParamsApi = async (params: {
jlId: string;
njId?: string;
njmcId?: string;
bjId?: string;
}) => {
return await get("/api/jlzx/findByJlParams", params);
};
// 保存接龙消息推送
export const xxtsSaveByJlzxParamsApi = async (params: { jlId: string }) => {
return await post("/api/xxts/saveByJlzxParams", params);
};
/** /**
* *

View File

@ -493,6 +493,34 @@
"enablePullDownRefresh": false "enablePullDownRefresh": false
} }
}, },
{
"path": "pages/view/analysis/jl/statistics",
"style": {
"navigationBarTitleText": "接龙情况",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/analysis/jl/classDetail",
"style": {
"navigationBarTitleText": "班级详情",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/analysis/jl/personnelList",
"style": {
"navigationBarTitleText": "人员名单",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/analysis/jl/index",
"style": {
"navigationBarTitleText": "接龙统计",
"enablePullDownRefresh": false
}
},
{ {
"path": "pages/view/routine/RengJiaoRengZhi/index", "path": "pages/view/routine/RengJiaoRengZhi/index",
"style": { "style": {
@ -790,3 +818,795 @@
] ]
} }
} }
{
"path": "pages/view/hr/teacherProfile/ExperienceInfo",
"style": {
"navigationBarTitleText": "学习及工作经历",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/hr/teacherProfile/FamilyInfo",
"style": {
"navigationBarTitleText": "家庭成员情况",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/hr/teacherProfile/EmergencyContact",
"style": {
"navigationBarTitleText": "紧急联系人",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/JiFenPingJia/PersonalHonor",
"style": {
"navigationBarTitleText": "个人荣誉",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/JiFenPingJia/PublicClassAwards",
"style": {
"navigationBarTitleText": "公开课获奖",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/hr/teacherProfile/RecordMaterials",
"style": {
"navigationBarTitleText": "备案资料",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/hr/salarySlip/detail",
"style": {
"navigationBarTitleText": "工资条详情",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/homeSchool/parentAddressBook/index",
"style": {
"navigationBarTitleText": "家长通讯录",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/homeSchool/parentAddressBook/detail",
"style": {
"navigationBarTitleText": "学生详情",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/notice/index",
"style": {
"navigationBarTitleText": "发布接龙",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/notice/detail",
"style": {
"navigationBarTitleText": "接龙详情",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/notice/publish",
"style": {
"navigationBarTitleText": "接龙推送",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/notice/push-list",
"style": {
"navigationBarTitleText": "推送清单",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/notice/selectStudents",
"style": {
"navigationBarTitleText": "选择学生",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/RengJiaoRengZhi/index",
"style": {
"navigationBarTitleText": "任教任职",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/GongZuoLiang/index",
"style": {
"navigationBarTitleText": "工作量",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/xk/xkList",
"style": {
"navigationBarTitleText": "选课列表",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/xk/xkkcDetail",
"style": {
"navigationBarTitleText": "选课课程详情"
}
},
{
"path": "pages/view/routine/xk/dmIndex",
"style": {
"navigationBarTitleText": "点名选课列表"
}
},
{
"path": "pages/view/routine/xk/dm",
"style": {
"navigationBarTitleText": "学生点名",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/xk/dmList",
"style": {
"navigationBarTitleText": "点名列表",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/xk/dmXsList",
"style": {
"navigationBarTitleText": "点名学生列表",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/xk/tf/detail",
"style": {
"navigationBarTitleText": "选课退费详情",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/xk/tf/sp",
"style": {
"navigationBarTitleText": "选课退费审批",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/kefuxuncha/xcRecord",
"style": {
"navigationBarTitleText": "选课巡查记录"
}
},
{
"path": "pages/view/routine/kefuxuncha/kyRecord",
"style": {
"navigationBarTitleText": "课业巡查记录"
}
},
{
"path": "pages/base/xs/qj/sp",
"style": {
"navigationBarTitleText": "学生请假审批",
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/xs/qj/detail",
"style": {
"navigationBarTitleText": "学生请假详情",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/analysis/xs/studentArchive",
"style": {
"navigationBarTitleText": "学生档案",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/analysis/xk/xkCourse",
"style": {
"navigationBarTitleText": "课程明单",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/analysis/xk/xkList",
"style": {
"navigationBarTitleText": "选课清单",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/analysis/xk/dmStatistics",
"style": {
"navigationBarTitleText": "点名统计",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/analysis/xk/dmXkList",
"style": {
"navigationBarTitleText": "点名选课列表",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/qd/index",
"style": {
"navigationBarTitleText": "签到发布"
}
},
{
"path": "pages/view/routine/qd/publish",
"style": {
"navigationBarTitleText": "新增签到"
}
},
{
"path": "pages/view/routine/qd/push-list",
"style": {
"navigationBarTitleText": "推送清单"
}
},
{
"path": "pages/view/routine/qd/detail",
"style": {
"navigationBarTitleText": "签到详情"
}
},
{
"path": "pages/view/routine/qd/selectTeachers",
"style": {
"navigationBarTitleText": "选择教师"
}
},
{
"path": "pages/view/routine/qd/qr-code",
"style": {
"navigationBarTitleText": "签到二维码",
"navigationStyle": "custom"
}
},
{
"path": "pages/view/routine/qd/confirm",
"style": {
"navigationBarTitleText": "确认签到",
"navigationStyle": "custom"
}
},
{
"path": "pages/view/routine/JiaoXueZiYuan/add-resource",
"style": {
"navigationBarTitleText": "上传资源",
"enablePullDownRefresh": false,
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"backgroundColor": "#f4f5f7"
}
},
{
"path": "pages/view/routine/jc/bzList",
"style": {
"navigationBarTitleText": "就餐标准列表"
}
},
{
"path": "pages/view/routine/jc/index",
"style": {
"navigationBarTitleText": "就餐点名"
}
},
{
"path": "pages/view/routine/jc/detail",
"style": {
"navigationBarTitleText": "就餐点名详情"
}
},
{
"path": "pages/view/quantitativeAssessment/assessment/assessment",
"style": {
"navigationBarTitleText": "考核评价",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/quantitativeAssessment/casualShot/casualShot",
"style": {
"navigationBarTitleText": "随手拍",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/quantitativeAssessment/distribute/distribute",
"style": {
"navigationBarTitleText": "分配",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/quantitativeAssessment/index/index",
"style": {
"navigationBarTitleText": "量化考核首页",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/quantitativeAssessment/index/details",
"style": {
"navigationBarTitleText": "量化考核详情",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/quantitativeAssessment/quantitativeSummary/quantitativeSummary",
"style": {
"navigationBarTitleText": "量化汇总",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/quantitativeAssessment/index/noticeAnnouncement",
"style": {
"navigationBarTitleText": "通知公告",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/quantitativeAssessment/index/playPage",
"style": {
"navigationBarTitleText": "播放页面",
"enablePullDownRefresh": false
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#fff",
"backgroundColor": "#F8F8F8",
"orientation": "portrait",
"navigationStyle": "custom",
"app-plus": {
"background": "#efeff4",
"titleView": false
}
},
"tabBar": {
"selectedColor": "#447ade",
"color": "#999999",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [
{
"text": "消息",
"pagePath": "pages/base/message/index",
"iconPath": "static/tabBar/x1.png",
"selectedIconPath": "static/tabBar/1.png"
},
{
"text": "自助服务",
"pagePath": "pages/base/service/index",
"iconPath": "static/tabBar/x2.png",
"selectedIconPath": "static/tabBar/2.png"
},
{
"text": "我的",
"pagePath": "pages/base/mine/index",
"iconPath": "static/tabBar/x3.png",
"selectedIconPath": "static/tabBar/3.png"
}
]
}
}

View File

@ -320,6 +320,14 @@ const sections = reactive<Section[]>([
permissionKey: "routine-bjjl", // permissionKey: "routine-bjjl", //
path: "/pages/view/notice/index", path: "/pages/view/notice/index",
}, },
{
id: "r13",
icon: "jltj",
text: "接龙统计",
show: true,
permissionKey: "routine-jltj", //
path: "/pages/view/analysis/jl/index",
},
{ {
id: "hs4", id: "hs4",
icon: "xsda", icon: "xsda",

View File

@ -0,0 +1,290 @@
<!-- src/pages/view/analysis/jl/classDetail.vue -->
<template>
<view class="class-detail-page">
<!-- 顶部标题 -->
<view class="page-header">
<text class="page-title">{{ njmc }} - 班级接龙完成情况</text>
<text class="refresh-btn" @click="refreshData">刷新</text>
</view>
<!-- 班级统计列表 -->
<view class="class-list">
<view
class="class-item"
v-for="classItem in classStats"
:key="classItem.bj_id"
>
<view class="class-header">
<text class="class-name">{{ classItem.bjmc }}</text>
<text class="completion-rate">{{ calculateCompletionRate(classItem) }}%</text>
</view>
<view class="class-stats">
<view class="stat-item">
<text class="stat-label">总人数</text>
<text class="stat-value">{{ classItem.jlts }}</text>
</view>
<view class="stat-item" @click="handlePersonnelClick(classItem, 'yjl')">
<text class="stat-label">已接龙</text>
<text class="stat-value success">{{ classItem.yjl }}</text>
</view>
<view class="stat-item" @click="handlePersonnelClick(classItem, 'wjl')">
<text class="stat-label">未接龙</text>
<text class="stat-value danger">{{ classItem.wjl }}</text>
</view>
<view class="stat-item" @click="handlePersonnelClick(classItem, 'wgz')">
<text class="stat-label">未关注</text>
<text class="stat-value warning">{{ classItem.wgz }}</text>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" v-if="classStats.length === 0">
<text class="empty-text">暂无班级数据</text>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getClassDetailApi, getPersonnelListApi } from '@/api/base/jlApi'
interface ClassStats {
nj_id: string;
njmc: string;
bj_id: string;
bjmc: string;
yjl: number;
wjl: number;
wgz: number;
jlts: number;
}
const jlId = ref('')
const njId = ref('')
const njmc = ref('')
const classStats = ref<ClassStats[]>([])
//
const calculateCompletionRate = (classItem: ClassStats) => {
if (classItem.jlts === 0) return 0
return Math.round((classItem.yjl / classItem.jlts) * 100)
}
//
const refreshData = async () => {
await loadClassDetailData()
}
//
const handlePersonnelClick = async (classItem: ClassStats, type: string) => {
try {
uni.showLoading({ title: '加载中...' })
const result = await getPersonnelListApi({
jlId: jlId.value,
type: type,
njId: njId.value,
bjId: classItem.bj_id
})
if (result) {
//
//
console.log('人员列表数据:', result)
//
let title = ''
switch (type) {
case 'yjl':
title = '已接龙人员'
break
case 'wjl':
title = '未接龙人员'
break
case 'wgz':
title = '未关注人员'
break
default:
title = '人员列表'
}
//
uni.navigateTo({
url: `/pages/view/analysis/jl/personnelList?jlId=${jlId.value}&type=${type}&njId=${njId.value}&bjId=${classItem.bj_id}&njmc=${encodeURIComponent(njmc.value)}&title=${encodeURIComponent(title)}`
})
}
} catch (error) {
console.error('加载人员列表失败:', error)
uni.showToast({
title: '加载失败',
icon: 'error'
})
} finally {
uni.hideLoading()
}
}
//
const loadClassDetailData = async () => {
try {
uni.showLoading({ title: '加载中...' })
const result = await getClassDetailApi({
jlId: jlId.value,
njId: njId.value
})
if (result) {
classStats.value = result
}
} catch (error) {
console.error('加载班级详情数据失败:', error)
uni.showToast({
title: '加载失败',
icon: 'error'
})
} finally {
uni.hideLoading()
}
}
onLoad((options) => {
if (options.jlId && options.njId && options.njmc) {
jlId.value = options.jlId
njId.value = options.njId
njmc.value = decodeURIComponent(options.njmc)
loadClassDetailData()
}
})
</script>
<style scoped lang="scss">
.class-detail-page {
min-height: 100vh;
background-color: #f5f7fa;
padding: 12px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #fff;
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.page-title {
font-size: 16px;
font-weight: 600;
color: #333;
}
.refresh-btn {
font-size: 14px;
color: #1890ff;
padding: 4px 8px;
border: 1px solid #1890ff;
border-radius: 4px;
}
.class-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.class-item {
background-color: #fff;
border-radius: 12px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.class-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid #f0f0f0;
}
.class-name {
font-size: 16px;
font-weight: 600;
color: #333;
}
.completion-rate {
font-size: 14px;
color: #1890ff;
font-weight: bold;
}
.class-stats {
display: flex;
justify-content: space-around;
}
.stat-item {
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
transform: scale(1.05);
}
&:active {
transform: scale(0.95);
}
}
.stat-label {
display: block;
font-size: 12px;
color: #666;
margin-bottom: 4px;
}
.stat-value {
font-size: 16px;
font-weight: bold;
color: #333;
&.success {
color: #52c41a;
}
&.danger {
color: #ff4d4f;
}
&.warning {
color: #faad14;
}
}
.empty-state {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
background-color: #fff;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.empty-text {
font-size: 14px;
color: #999;
}
</style>

View File

@ -0,0 +1,341 @@
<!-- src/pages/view/analysis/jl/index.vue -->
<template>
<view class="jl-list-page">
<!-- 列表内容 -->
<BasicListLayout @register="register" v-model="dataList">
<template v-slot="{ data }">
<view class="jl-card" @click="goToStatistics(data.id)">
<view class="card-header">
<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="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">发布者: {{ 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="flex-row items-center pb-10 pt-5">
<u-button
text="新增接龙"
class="mx-15"
type="primary"
@click="goToPublish"
/>
</view>
</template>
</BasicListLayout>
<!-- 用uniqueList渲染 -->
<template v-for="item in uniqueList" :key="item.id">
<!-- 这里可以加自定义渲染内容做二次验证 -->
</template>
</view>
</template>
<script lang="ts" setup>
import { useLayout } from "@/components/BasicListLayout/hooks/useLayout";
import { mobilejllistApi } from "@/api/base/jlApi";
import { imagUrl } from "@/utils";
import { computed, watch } from "vue";
import { onShow } from "@dcloudio/uni-app";
interface JlItem {
id: string;
jlmc: string; //
jlms: string; //
jlStatus: string; // ABC
jlFbr: string; //
jlFbtime: string; //
jlfm: string; //
njmc: string;
bjmc: string; //
jlkstime: string; //
jljstime: string; //
jsxm?: string;
}
// 使 BasicListLayout
const [register, { reload }] = useLayout({
api: mobilejllistApi, // api使使 query
componentProps: {},
});
//
const dataList = ref<JlItem[]>([]);
const uniqueList = computed(() => {
const map = new Map();
(dataList.value || []).forEach(item => {
if (item && item.id != null) map.set(String(item.id), item);
});
return Array.from(map.values());
});
// dataList
watch(dataList, (val) => {
//
});
//
const goToPublish = () => {
uni.navigateTo({
url: "/pages/view/notice/publish",
});
};
//
const goToStatistics = (jlId: string) => {
uni.navigateTo({
url: `/pages/view/analysis/jl/statistics?jlId=${jlId}`,
});
};
// CSS
const getStatusClass = (status: string) => {
if (status === "A") return "status-published";
if (status === "B") return "status-draft";
return "status-ended";
};
//
const getStatusText = (status: string) => {
if (status === "A") return "待推送";
if (status === "B") return "暂存";
return "已推送";
};
//
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")}`;
};
onShow(() => {
reload();
});
</script>
<style scoped lang="scss">
.jl-list-page {
position: relative;
min-height: 100vh;
background-color: #f5f7fa;
padding: 12px;
box-sizing: border-box;
}
.jl-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;
cursor: pointer;
&:active {
transform: translateY(1px);
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.12);
background-color: #f8f9fa;
}
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 4px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 2px;
}
}
.card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
gap: 12px;
.jl-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;
}
.jl-status {
font-size: 11px;
padding: 4px 8px;
border-radius: 12px;
color: #fff;
white-space: nowrap;
flex-shrink: 0;
font-weight: 500;
letter-spacing: 0.5px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
&.status-published {
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a52 100%);
}
&.status-draft {
background: linear-gradient(135deg, #ffa726 0%, #ff9800 100%);
}
&.status-ended {
background: linear-gradient(135deg, #66bb6a 0%, #4caf50 100%);
}
}
}
.card-body {
margin-bottom: 12px;
.cover-thumbnail {
width: 80px;
height: 60px;
border-radius: 8px;
margin-right: 12px;
float: left;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
object-fit: cover;
}
.jl-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;
}
}
.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;
}
}
}
//
.white-bg-color {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
margin: 16px 12px;
padding: 16px;
box-shadow: 0 4px 16px rgba(102, 126, 234, 0.3);
.flex-row {
justify-content: center;
}
.u-button {
background: rgba(255, 255, 255, 0.9);
color: #667eea;
border: none;
font-weight: 600;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
&:active {
background: rgba(255, 255, 255, 1);
transform: translateY(1px);
}
}
}
//
@media (max-width: 375px) {
.jl-list-page {
padding: 8px;
}
.jl-card {
padding: 12px;
margin-bottom: 8px;
}
.card-header .jl-title {
font-size: 15px;
}
.card-footer .footer-item {
max-width: 150px;
font-size: 12px;
}
}
//
.jl-card {
animation: fadeInUp 0.3s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@ -0,0 +1,437 @@
<!-- src/pages/view/analysis/jl/personnelList.vue -->
<template>
<view class="personnel-list-page">
<!-- 页面头部 -->
<view class="page-header">
<text class="page-title">{{ pageTitle }}</text>
<text class="refresh-btn" @click="refreshData">刷新</text>
</view>
<!-- 筛选信息 -->
<view class="filter-info" v-if="njId && njmc">
<text class="filter-text">{{ njmc }}</text>
</view>
<!-- 人员列表 -->
<view class="personnel-list">
<view
class="personnel-item"
v-for="personnel in personnelList"
:key="personnel.id"
>
<view class="personnel-main-info">
<text class="personnel-name">{{ personnel.xm }}</text>
<text class="personnel-class">{{ personnel.njmc }}{{ personnel.bjmc }}</text>
</view>
<view class="personnel-status">
<view class="status-tag" :class="getStatusClass()">
{{ getStatusText() }}
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" v-if="personnelList.length === 0 && !isLoading">
<text class="empty-text">暂无人员数据</text>
</view>
<!-- 加载状态 -->
<view class="loading-state" v-if="isLoading">
<text class="loading-text">加载中...</text>
</view>
<!-- 信息推送按钮 - 只在未接龙时显示 -->
<view class="push-button-container" v-if="type === 'wjl' && personnelList.length > 0">
<button class="push-button" @click="handlePushMessage">信息推送</button>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getPersonnelListApi, pushMessageApi } from '@/api/base/jlApi'
interface Personnel {
id: string;
xm: string;
njmc: string;
bjmc: string;
jlts: number;
}
const jlId = ref('')
const type = ref('')
const pageTitle = ref('')
const bjId = ref('')
const njId = ref('')
const njmc = ref('')
const personnelList = ref<Personnel[]>([])
const isLoading = ref(false)
//
const getStatusClass = () => {
switch (type.value) {
case 'yjl':
return 'completed'
case 'wjl':
return 'pending'
case 'wgz':
return 'unfollowed'
default:
return ''
}
}
//
const getStatusText = () => {
switch (type.value) {
case 'yjl':
return '已接龙'
case 'wjl':
return '未接龙'
case 'wgz':
return '未关注'
default:
return ''
}
}
//
const refreshData = async () => {
await loadPersonnelData()
}
//
const handlePushMessage = async () => {
try {
uni.showModal({
title: '确认推送',
content: `确定要对 ${personnelList.value.length} 名未接龙人员进行信息推送吗?`,
success: async (res) => {
if (res.confirm) {
await pushMessage()
}
}
})
} catch (error) {
console.error('推送确认失败:', error)
}
}
//
const pushMessage = async () => {
try {
uni.showLoading({ title: '推送中...' })
const params: any = {
jlId: jlId.value
}
//
if (njId.value) {
params.njId = njId.value
}
//
if (bjId.value) {
params.bjId = bjId.value
}
console.log('推送API参数:', params)
const result = await pushMessageApi(params)
if (result && result.resultCode === 1) {
uni.showToast({
title: '推送成功',
icon: 'success'
})
//
setTimeout(() => {
uni.navigateBack({
delta: 1
})
}, 1500)
} else {
uni.showToast({
title: result?.message || '推送失败',
icon: 'error'
})
}
} catch (error) {
console.error('推送失败:', error)
uni.showToast({
title: '推送失败',
icon: 'error'
})
} finally {
uni.hideLoading()
}
}
//
const loadPersonnelData = async () => {
try {
isLoading.value = true
const params: any = {
jlId: jlId.value,
type: type.value
}
//
if (bjId.value) {
params.bjId = bjId.value
}
//
if (njId.value) {
params.njId = njId.value
}
console.log('调用API参数:', params)
const result = await getPersonnelListApi(params)
console.log('API返回结果:', result)
//
if (result) {
if (Array.isArray(result)) {
//
personnelList.value = result
console.log('设置人员列表数据(数组):', personnelList.value)
} else if (result.result && Array.isArray(result.result)) {
// {result: [...]}
personnelList.value = result.result
console.log('设置人员列表数据(对象):', personnelList.value)
} else {
console.log('数据格式不正确:', result)
}
} else {
console.log('没有获取到人员数据')
}
} catch (error) {
console.error('加载人员数据失败:', error)
uni.showToast({
title: '加载失败',
icon: 'error'
})
} finally {
isLoading.value = false
}
}
onLoad((options) => {
console.log('personnelList onLoad options:', options)
if (options.jlId && options.type) {
jlId.value = options.jlId
type.value = options.type
//
if (options.title) {
pageTitle.value = decodeURIComponent(options.title)
} else {
//
switch (options.type) {
case 'yjl':
pageTitle.value = '已接龙人员'
break
case 'wjl':
pageTitle.value = '未接龙人员'
break
case 'wgz':
pageTitle.value = '未关注人员'
break
default:
pageTitle.value = '人员列表'
}
}
//
if (options.bjId) {
bjId.value = options.bjId
}
//
if (options.njId) {
njId.value = options.njId
}
if (options.njmc) {
njmc.value = decodeURIComponent(options.njmc)
}
console.log('设置后的参数:', {
jlId: jlId.value,
type: type.value,
pageTitle: pageTitle.value,
bjId: bjId.value,
njId: njId.value,
njmc: njmc.value
})
loadPersonnelData()
}
})
</script>
<style scoped lang="scss">
.personnel-list-page {
min-height: 100vh;
background-color: #f5f7fa;
padding: 12px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #fff;
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.page-title {
font-size: 16px;
font-weight: 600;
color: #333;
}
.refresh-btn {
font-size: 14px;
color: #1890ff;
padding: 4px 8px;
border: 1px solid #1890ff;
border-radius: 4px;
}
.filter-info {
background-color: #fff;
border-radius: 12px;
padding: 12px 16px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.filter-text {
font-size: 14px;
color: #1890ff;
font-weight: 500;
}
.personnel-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.personnel-item {
background-color: #fff;
border-radius: 12px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: space-between;
align-items: center;
}
.personnel-main-info {
flex: 1;
}
.personnel-name {
font-size: 16px;
font-weight: 600;
color: #333;
display: block;
margin-bottom: 4px;
}
.personnel-class {
font-size: 14px;
color: #666;
}
.personnel-status {
flex-shrink: 0;
}
.status-tag {
display: inline-flex;
align-items: center;
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
&.completed {
background-color: #e1f3d8;
color: #67c23a;
border: 1px solid #b3e19d;
}
&.pending {
background-color: #fef0f0;
color: #f56c6c;
border: 1px solid #fbc4c4;
}
&.unfollowed {
background-color: #fff7e6;
color: #faad14;
border: 1px solid #ffd591;
}
}
.empty-state,
.loading-state {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
background-color: #fff;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.empty-text,
.loading-text {
font-size: 14px;
color: #999;
}
.push-button-container {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
padding: 16px;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
z-index: 100;
}
.push-button {
width: 100%;
height: 44px;
background-color: #1890ff;
color: #fff;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
&:active {
background-color: #096dd9;
}
}
</style>

View File

@ -0,0 +1,410 @@
<!-- src/pages/view/analysis/jl/statistics.vue -->
<template>
<view class="statistics-page">
<!-- 顶部导航 -->
<view class="nav-tabs">
<view
class="nav-tab"
:class="{ active: activeTab === 'completion' }"
@click="switchTab('completion')"
>
完成情况
</view>
<view
class="nav-tab"
:class="{ active: activeTab === 'details' }"
@click="switchTab('details')"
>
接龙详情
</view>
</view>
<!-- 接龙完成情况 -->
<view class="completion-card" v-if="activeTab === 'completion'">
<view class="card-header">
<text class="card-title">接龙完成情况</text>
<text class="refresh-btn" @click="refreshData">刷新</text>
</view>
<!-- 全部年级统计 -->
<view class="total-stats" v-if="totalStats">
<view class="stats-item">
<text class="stats-label">总人数</text>
<text class="stats-value">{{ totalStats.jltsall }}</text>
</view>
<view class="stats-item clickable" @click="goToPersonnelList('yjl', '已接龙人员')">
<text class="stats-label">已接龙</text>
<text class="stats-value success">{{ totalStats.yjl }}</text>
</view>
<view class="stats-item clickable" @click="goToPersonnelList('wjl', '未接龙人员')">
<text class="stats-label">未接龙</text>
<text class="stats-value danger">{{ totalStats.wjl }}</text>
</view>
<view class="stats-item clickable" @click="goToPersonnelList('wgz', '未关注人员')">
<text class="stats-label">未关注</text>
<text class="stats-value warning">{{ totalStats.wgz }}</text>
</view>
</view>
<!-- 各年级统计 -->
<view class="grade-stats" v-for="grade in gradeStats" :key="grade.nj_id">
<view class="grade-header">
<text class="grade-name">{{ grade.njmc }}</text>
<view class="grade-right">
<text class="completion-rate">{{ calculateCompletionRate(grade) }}%</text>
<text class="detail-arrow" @click="goToClassDetail(grade.nj_id, grade.njmc)">></text>
</view>
</view>
<view class="grade-details">
<view class="detail-item">
<text class="detail-label">总人数</text>
<text class="detail-value">{{ grade.jltsall }}</text>
</view>
<view class="detail-item clickable" @click="goToPersonnelList('yjl', '已接龙人员', grade.nj_id, grade.njmc)">
<text class="detail-label">已接龙</text>
<text class="detail-value success">{{ grade.yjl }}</text>
</view>
<view class="detail-item clickable" @click="goToPersonnelList('wjl', '未接龙人员', grade.nj_id, grade.njmc)">
<text class="detail-label">未接龙</text>
<text class="detail-value danger">{{ grade.wjl }}</text>
</view>
<view class="detail-item clickable" @click="goToPersonnelList('wgz', '未关注人员', grade.nj_id, grade.njmc)">
<text class="detail-label">未关注</text>
<text class="detail-value warning">{{ grade.wgz }}</text>
</view>
</view>
</view>
</view>
<!-- 接龙详情 -->
<view class="details-card" v-if="activeTab === 'details'">
<text class="card-title">接龙详情</text>
<!-- 这里可以添加接龙详情内容 -->
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, onMounted, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getJlStatisticsApi } from '@/api/base/jlApi'
interface TotalStats {
yjl: number; //
wjl: number; //
wgz: number; //
jltsall: number; //
}
interface GradeStats {
nj_id: string;
njmc: string;
yjl: number;
wjl: number;
wgz: number;
jltsall: number;
}
const activeTab = ref('completion')
const jlId = ref('')
const totalStats = ref<TotalStats | null>(null)
const gradeStats = ref<GradeStats[]>([])
//
const switchTab = (tab: string) => {
activeTab.value = tab
}
//
const calculateCompletionRate = (grade: GradeStats) => {
if (grade.jltsall === 0) return 0
return Math.round((grade.yjl / grade.jltsall) * 100)
}
//
const refreshData = async () => {
await loadStatisticsData()
}
//
const goToClassDetail = (njId: string, njmc: string) => {
uni.navigateTo({
url: `/pages/view/analysis/jl/classDetail?jlId=${jlId.value}&njId=${njId}&njmc=${encodeURIComponent(njmc)}`
})
}
//
const goToPersonnelList = (type: string, title: string, njId?: string, njmc?: string) => {
let url = `/pages/view/analysis/jl/personnelList?jlId=${jlId.value}&type=${type}&title=${encodeURIComponent(title)}`
//
if (njId && njmc) {
url += `&njId=${njId}&njmc=${encodeURIComponent(njmc)}`
}
uni.navigateTo({ url })
}
//
const loadStatisticsData = async () => {
try {
uni.showLoading({ title: '加载中...' })
// 使all
const result = await getJlStatisticsApi({
jlId: jlId.value,
type: 'all'
})
if (result.totalStats) {
//
totalStats.value = result.totalStats
}
if (result.gradeStats) {
//
gradeStats.value = result.gradeStats
}
} catch (error) {
console.error('加载统计数据失败:', error)
uni.showToast({
title: '加载失败',
icon: 'error'
})
} finally {
uni.hideLoading()
}
}
onLoad((options) => {
if (options.jlId) {
jlId.value = options.jlId
loadStatisticsData()
}
})
</script>
<style scoped lang="scss">
.statistics-page {
min-height: 100vh;
background-color: #f5f7fa;
padding: 12px;
}
.nav-tabs {
display: flex;
background-color: #fff;
border-radius: 12px;
margin-bottom: 12px;
padding: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.nav-tab {
flex: 1;
text-align: center;
padding: 12px 0;
font-size: 16px;
color: #666;
border-radius: 8px;
transition: all 0.3s ease;
&.active {
background-color: #1890ff;
color: #fff;
font-weight: 600;
}
}
.completion-card,
.details-card {
background-color: #fff;
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.card-title {
font-size: 16px;
font-weight: 600;
color: #333;
}
.refresh-btn {
font-size: 14px;
color: #1890ff;
padding: 4px 8px;
border: 1px solid #1890ff;
border-radius: 4px;
}
.total-stats {
display: flex;
justify-content: space-around;
background-color: #f8f9fa;
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
align-items: center;
}
.stats-item {
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
min-height: 60px;
justify-content: center;
&.clickable {
cursor: pointer;
transition: all 0.3s ease;
border-radius: 8px;
padding: 8px;
&:active {
transform: scale(0.95);
background-color: #f0f8ff;
}
}
}
.stats-label {
display: block;
font-size: 12px;
color: #666;
margin-bottom: 4px;
}
.stats-value {
font-size: 20px;
font-weight: bold;
color: #333;
&.success {
color: #52c41a;
}
&.danger {
color: #ff4d4f;
}
&.warning {
color: #faad14;
}
}
.grade-stats {
margin-bottom: 12px;
border: 1px solid #f0f0f0;
border-radius: 8px;
overflow: hidden;
}
.grade-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background-color: #fafafa;
border-bottom: 1px solid #f0f0f0;
}
.grade-right {
display: flex;
align-items: center;
gap: 8px;
}
.grade-name {
font-size: 14px;
font-weight: 600;
color: #333;
}
.completion-rate {
font-size: 14px;
color: #1890ff;
font-weight: bold;
}
.detail-arrow {
font-size: 16px;
color: #999;
font-weight: bold;
cursor: pointer;
transition: color 0.3s ease;
&:hover {
color: #1890ff;
}
}
.grade-details {
display: flex;
justify-content: space-between;
padding: 12px 16px;
align-items: center;
flex-wrap: wrap;
gap: 8px 0;
}
.detail-item {
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
min-height: 50px;
justify-content: center;
flex: 1;
min-width: 0;
&.clickable {
cursor: pointer;
transition: all 0.3s ease;
border-radius: 8px;
padding: 8px;
&:active {
transform: scale(0.95);
background-color: #f0f8ff;
}
}
}
.detail-label {
display: block;
font-size: 12px;
color: #666;
margin-bottom: 4px;
}
.detail-value {
font-size: 16px;
font-weight: bold;
color: #333;
&.success {
color: #52c41a;
}
&.danger {
color: #ff4d4f;
}
}
</style>

View File

@ -129,7 +129,7 @@ const [register, { setValue, getValue }] = useForm({
field: "fileUrl", field: "fileUrl",
label: "上传附件", label: "上传附件",
component: "BasicUpload", component: "BasicUpload",
required: true, required: false,
itemProps: { itemProps: {
labelPosition: "top", labelPosition: "top",
}, },

View File

@ -182,7 +182,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, watch, onMounted } from "vue"; import { ref, computed, watch, onMounted } from "vue";
import { onLoad } from "@dcloudio/uni-app"; import { onLoad } from "@dcloudio/uni-app";
import { getByJlIdApi, jlzxFindByJlParamsApi, findAllNjBjTree } from "@/api/base/server"; import { getByJlIdApi, jlzxFindByJlParamsApi } from "@/api/base/jlApi";
import { findAllNjBjTree } from "@/api/base/server";
import { imagUrl } from "@/utils"; import { imagUrl } from "@/utils";
import { BASE_IMAGE_URL } from "@/config"; import { BASE_IMAGE_URL } from "@/config";
import BasicTree from '@/components/BasicTree/Tree.vue'; import BasicTree from '@/components/BasicTree/Tree.vue';

View File

@ -67,7 +67,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useLayout } from "@/components/BasicListLayout/hooks/useLayout"; import { useLayout } from "@/components/BasicListLayout/hooks/useLayout";
import { mobilejllistApi } from "@/api/base/server"; import { mobilejllistApi } from "@/api/base/jlApi";
import { imagUrl } from "@/utils"; import { imagUrl } from "@/utils";
import { computed, watch } from "vue"; import { computed, watch } from "vue";
import { onShow } from "@dcloudio/uni-app"; import { onShow } from "@dcloudio/uni-app";
@ -136,6 +136,7 @@ const goToPush = (jlId: string) => {
}); });
}; };
// CSS // CSS
const getStatusClass = (status: string) => { const getStatusClass = (status: string) => {
if (status === "A") return "status-published"; if (status === "A") return "status-published";

View File

@ -240,7 +240,8 @@ import CustomUpload from "/src/components/BasicUpload/CustomUpload.vue";
import BasicTree from "@/components/BasicTree/Tree.vue"; import BasicTree from "@/components/BasicTree/Tree.vue";
import { attachmentUpload } from "@/api/system/upload"; import { attachmentUpload } from "@/api/system/upload";
import { imagUrl } from "@/utils"; import { imagUrl } from "@/utils";
import { findAllNjBjTree, mobilejlstudentListApi, jlFindByIdApi, jlSaveApi } from "@/api/base/server"; import { mobilejlstudentListApi, jlFindByIdApi, jlSaveApi } from "@/api/base/jlApi";
import { findAllNjBjTree } from "@/api/base/server";
import { useUserStore } from "@/store/modules/user"; import { useUserStore } from "@/store/modules/user";
interface Attachment { interface Attachment {

View File

@ -52,7 +52,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue"; import { ref } from "vue";
import { onLoad } from "@dcloudio/uni-app"; import { onLoad } from "@dcloudio/uni-app";
import { jlzxFindByJlParamsApi, xxtsSaveByJlzxParamsApi } from "@/api/base/server"; import { jlzxFindByJlParamsApi, xxtsSaveByJlzxParamsApi } from "@/api/base/jlApi";
// //
interface ApiResponse<T = any> { interface ApiResponse<T = any> {

View File

@ -30,8 +30,8 @@
<!-- 第一行上课周期开课年级 --> <!-- 第一行上课周期开课年级 -->
<view class="course-info-row"> <view class="course-info-row">
<view class="course-info-item"> <view class="course-info-item">
<view class="info-label">上课周期</view> <view class="info-label">上课时间</view>
<view class="info-data">{{ xkkc.skzqmc }}</view> <view class="info-data study-time">{{ xkkc.studyTime }}</view>
</view> </view>
<view class="course-info-item"> <view class="course-info-item">
<view class="info-label">开课年级</view> <view class="info-label">开课年级</view>
@ -283,6 +283,7 @@ const loadXcCourseList = async (pbData: any) => {
skzqmc: item.skzqmc || '每周', skzqmc: item.skzqmc || '每周',
skkstime: item.skkstime, skkstime: item.skkstime,
skjstime: item.skjstime, skjstime: item.skjstime,
studyTime: item.studyTime, //
kcdd: item.kcdd || '暂无', kcdd: item.kcdd || '暂无',
njname: item.njname || '暂无', njname: item.njname || '暂无',
hasNum: item.hasNum || 0, hasNum: item.hasNum || 0,
@ -304,6 +305,7 @@ const loadXcCourseList = async (pbData: any) => {
skzqmc: item.skzqmc || '每周', skzqmc: item.skzqmc || '每周',
skkstime: item.skkstime, skkstime: item.skkstime,
skjstime: item.skjstime, skjstime: item.skjstime,
studyTime: item.studyTime, //
kcdd: item.kcdd || '暂无', kcdd: item.kcdd || '暂无',
njname: item.njname || '暂无', njname: item.njname || '暂无',
hasNum: item.hasNum || 0, hasNum: item.hasNum || 0,
@ -722,7 +724,7 @@ onBeforeUnmount(() => {
color: #666; color: #666;
flex: 0 0 70px; flex: 0 0 70px;
font-weight: 500; font-weight: 500;
margin-right: 5px; margin-right: 0px;
} }
.info-data { .info-data {
@ -730,6 +732,13 @@ onBeforeUnmount(() => {
color: #333; color: #333;
font-weight: 400; font-weight: 400;
word-break: break-all; word-break: break-all;
//
&.study-time {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
} }
} }

View File

@ -24,6 +24,21 @@
</view> </view>
</view> </view>
<!-- 巡查时间信息 -->
<view class="inspection-time-info" v-if="xkkc.skkstime && xkkc.skjstime">
<view class="time-item">
<u-icon name="clock" color="#4080ff" size="16"></u-icon>
<text class="time-label">巡查时间</text>
<text class="time-value">{{ xkkc.skkstime }} - {{ xkkc.skjstime }}</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 class="teacher-selection-info"> <view class="teacher-selection-info">
<view class="teacher-item"> <view class="teacher-item">
@ -42,7 +57,7 @@
</view> </view>
<view> <view v-if="canInspect">
<!-- 点名情况 --> <!-- 点名情况 -->
<view class="section mx-15 mb-15"> <view class="section mx-15 mb-15">
<view class="section-title-bar"> <view class="section-title-bar">
@ -214,7 +229,10 @@
</view> </view>
<template #bottom> <template #bottom>
<view class="submit-btn-wrap py-10 px-20 bg-white"> <view
v-if="canInspect"
class="submit-btn-wrap py-10 px-20 bg-white"
>
<button <button
class="submit-btn" class="submit-btn"
:class="{ 'submit-btn-disabled': isSubmitting }" :class="{ 'submit-btn-disabled': isSubmitting }"
@ -330,6 +348,7 @@ const videoList = ref<VideoItem[]>([]);
// //
const inspectionStatusText = ref(""); const inspectionStatusText = ref("");
const canInspect = ref(true); //
// //
const rollCallData = ref({ const rollCallData = ref({
@ -487,36 +506,55 @@ const previewImage = (index: number) => {
}); });
}; };
// // -
const checkInspectionTime = () => { const checkInspectionTime = () => {
const currentTime = now; const skkstime = xkkc.value.skkstime;
let wDay = currentTime.day(); const skjstime = xkkc.value.skjstime;
if (wDay === 0) {
wDay = 7;
}
let mDay = currentTime.date();
let xcFlag = false;
let msg = "";
// //
switch (xkkc.value.skzqlx) { if (!skkstime || !skjstime) {
case "每天": canInspect.value = true;
xcFlag = true; inspectionStatusText.value = "可以巡查";
msg = "可以巡查"; return;
break;
case "每周":
const daysOfWeek = xkkc.value.skzq.split(",").map(Number);
xcFlag = daysOfWeek.includes(wDay);
msg = xcFlag ? "可以巡查" : "没到巡查日期";
break;
case "每月":
const daysOfMonth = xkkc.value.skzq.split(",").map(Number);
xcFlag = daysOfMonth.includes(mDay);
msg = xcFlag ? "可以巡查" : "没到巡查日期";
break;
} }
return { xcFlag, msg }; //
const currentTime = now.format("HH:mm:ss");
//
const currentTimeMinutes = timeToMinutes(currentTime);
const startTimeMinutes = timeToMinutes(skkstime);
const endTimeMinutes = timeToMinutes(skjstime);
//
if (currentTimeMinutes < startTimeMinutes) {
canInspect.value = false;
inspectionStatusText.value = `还未到巡查时间,无法巡查(巡查时间:${skkstime} - ${skjstime}`;
} else if (currentTimeMinutes > endTimeMinutes) {
canInspect.value = false;
inspectionStatusText.value = `巡查时间已结束,无法巡查(巡查时间:${skkstime} - ${skjstime}`;
} else {
canInspect.value = true;
inspectionStatusText.value = `可以巡查(巡查时间:${skkstime} - ${skjstime}`;
}
};
// 便
const timeToMinutes = (timeStr: string) => {
if (!timeStr || !timeStr.includes(':')) {
return 0;
}
const parts = timeStr.split(':');
if (parts.length !== 3) {
return 0;
}
const hours = parseInt(parts[0], 10) || 0;
const minutes = parseInt(parts[1], 10) || 0;
const seconds = parseInt(parts[2], 10) || 0;
return hours * 60 + minutes + seconds / 60;
}; };
// //
@ -531,11 +569,12 @@ const submit = async () => {
return; return;
} }
// //
const { xcFlag, msg } = checkInspectionTime(); checkInspectionTime();
if (!xcFlag) {
if (!canInspect.value) {
uni.showToast({ uni.showToast({
title: msg, title: inspectionStatusText.value,
icon: "none", icon: "none",
duration: 2000, duration: 2000,
}); });
@ -642,6 +681,7 @@ const getVideoUrls = () => {
onMounted(async () => { onMounted(async () => {
await loadRollCallData(); // await loadRollCallData(); //
await loadCheckItems(); // await loadCheckItems(); //
checkInspectionTime(); //
}); });
</script> </script>
@ -696,6 +736,48 @@ onMounted(async () => {
} }
} }
.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;
}
}
.teacher-selection-info { .teacher-selection-info {
margin-top: 15px; margin-top: 15px;
padding: 10px 15px; padding: 10px 15px;

View File

@ -0,0 +1,273 @@
<!-- src/pages/base/message/detail.vue -->
<template>
<view class="message-detail-page">
<view v-if="isLoading" class="loading-indicator">加载中...</view>
<view v-else class="detail-content">
<view class="detail-header">
<view class="title-tag-row">
<text class="detail-title">{{ rw.rwmc }}</text>
<view class="tag" :class="rw.tagType">{{ rw.tagText }}</view>
</view>
<view class="detail-meta">
<text>{{ rw.rwkstime }}</text>
<!-- <text>{{ messageDetail.timeAgo }}</text>-->
</view>
</view>
<view class="detail-body">
<BasicForm :schema="schema" v-model="formData">
</BasicForm>
</view>
<view class="detail-footer" v-if="rwzxqds.length==0">
<button type="primary" class="action-button" @click="saveRwZx">处理</button>
</view>
</view>
<!-- <view v-else class="empty-state">消息详情未找到</view>-->
</view>
</template>
<script lang="ts" setup>
import {ref} from 'vue';
import {onLoad} from '@dcloudio/uni-app';
import {rwFindInfoByRwId, rwflFindRwlxsByRwId, rwzxExecutedInfoByRwIdAndJsApi, rwzxSaveApi} from "@/api/base/server";
import {useForm} from "@/components/BasicForm/hooks/useForm";
import {navigateBack, showToast} from "@/utils/uniapp";
import {useUserStore} from "@/store/modules/user";
interface MessageDetail {
id: string; // Assuming an ID is passed or can be derived
title: string;
desc: string;
date: string;
timeAgo: string;
tagText: string;
tagType: string;
// Add other fields as necessary
}
const formData: any = ref({})
const messageId = ref<string>('');
const messageDetail = ref<MessageDetail | null>({
id: 'todo1',
title: '教务通知 (待办)',
desc: '学校召开期初教学准备会议暨首次教学工作例会. 会议强调了新学期的教学重点和要求,请各位老师认真准备。',
date: '2025-02-17',
timeAgo: '8 mins 前',
tagText: '通知',
tagType: 'notice',
likes: 6,
comments: 12
});
const isLoading = ref(false);
const rwflx: any = ref([])
const rw = ref({})
const schema = reactive<FormsSchema[]>([])
const {getUser} = useUserStore()
async function saveRwZx() {
const result = [];
for (let i = 0; i < rwflx.value.length; i++) {
console.log(44, rwflx.value[i].id, formData.value[rwflx.value[i].id])
if (rwflx.value[i].rwbs && (!formData.value[rwflx.value[i].id] || formData.value[rwflx.value[i].id] == "")) {
showToast("请填写必填项!")
return;
}
result.push({
rwlxId: rwflx.value[i].id,
rwzxqdtx: formData.value[rwflx.value[i].id],
})
}
await rwzxSaveApi({
mobile: getUser.mobile,
rwId: rw.value.id,
rwzxqdDtos: result
})
showToast("操作成功!");
uni.navigateBack({delta: 1})
}
const rwzxqds = ref([])
onLoad(async (options) => {
if (options && options.id) {
const {result} = await rwFindInfoByRwId({
rwId: options.id
});
rwflx.value = result.rwlxes;
rw.value = result;
for (let i = 0; i < rwflx.value.length; i++) {
if (rwflx.value[i].rwfl == "sctp" || rwflx.value[i].rwfl == "scsp" || rwflx.value[i].rwfl == "scwd") {
schema.push({
field: `${rwflx.value[i].id}`,
label: `${rwflx.value[i].rwbt}`,
component: "BasicUpload",
required: rwflx.value[i].rwbs,
componentProps: {}
})
} else if (rwflx.value[i].rwfl == "text") {
schema.push({
field: `${rwflx.value[i].id}`,
label: `${rwflx.value[i].rwbt}`,
component: "BasicInput",
required: rwflx.value[i].rwbs,
itemProps: {
labelPosition: "top",
},
componentProps: {
type: "textarea",
},
})
} else if (rwflx.value[i].rwfl == "dxsx" || rwflx.value[i].rwfl == "dxxz") {
let options = rwflx.value[i].remark.split("");
let range = [];
for (let i = 0; i < options.length; i++) {
range.push({
name: options[i]
});
}
schema.push({
field: `${rwflx.value[i].id}`,
label: `${rwflx.value[i].rwbt}`,
component: "BasicPicker",
required: rwflx.value[i].rwbs,
itemProps: {
labelPosition: "top",
},
componentProps: {
range: range,
rangeKey: "name",
savaKey: "name",
},
})
}
}
const res = await rwzxExecutedInfoByRwIdAndJsApi({
rwId: options.id,
mobile: getUser.mobile
});
if (res && res.result && res.result.length) {
rwzxqds.value = res.result;
const showData = {};
for (let i = 0; i < rwzxqds.value.length; i++) {
showData[rwzxqds.value[i].rwlxId] = rwzxqds.value[i].rwzxqdtx;
}
formData.value = showData;
}
} else {
console.error('Message ID/Data is missing!');
uni.showToast({title: '加载失败,缺少信息', icon: 'none'});
}
});
</script>
<style scoped lang="scss">
.message-detail-page {
background-color: #f4f5f7;
min-height: 100vh;
padding: 15px;
box-sizing: border-box;
}
.loading-indicator,
.empty-state {
text-align: center;
color: #999;
padding: 40px 15px;
font-size: 14px;
}
.detail-content {
background-color: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.detail-header {
border-bottom: 1px solid #f0f0f0;
padding-bottom: 15px;
margin-bottom: 20px;
.title-tag-row {
display: flex;
justify-content: space-between;
align-items: flex-start; // Align items to the top
margin-bottom: 10px;
}
.detail-title {
font-size: 18px;
font-weight: bold;
color: #333;
flex: 1; // Allow title to take available space
margin-right: 10px; // Space between title and tag
line-height: 1.4;
}
.tag { // Reuse tag styles from index page if possible, or define here
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
color: #ffffff;
white-space: nowrap;
flex-shrink: 0; // Prevent tag from shrinking
&.notice {
background-color: #447ade;
}
&.task {
background-color: #19be6b;
}
&.approval {
background-color: #ff9f0a;
}
&.submit {
background-color: #8e8e93;
}
}
.detail-meta {
font-size: 12px;
color: #999;
text {
margin-right: 15px;
}
}
}
.detail-body {
margin-bottom: 40px;
.detail-desc {
font-size: 15px;
color: #555;
line-height: 1.7;
word-break: break-word;
}
}
.detail-footer {
// text-align: center; // Removed center alignment
// Add margin if needed, e.g., margin-top: 20px;
}
// Style for the action button
.action-button {
width: 100%; // Make button full width
height: 44px; // Standard button height
line-height: 44px; // Match height for vertical centering
font-size: 16px; // Slightly larger font
font-weight: 500; // Medium weight
border-radius: 8px; // Consistent border radius
margin-top: 20px; // Add space above the button
// Ensure primary color is applied correctly (uni-app default should work)
// background-color: #447ade;
// color: #ffffff;
}
</style>

273
src/pages/view/rw/index.vue Normal file
View File

@ -0,0 +1,273 @@
<!-- src/pages/base/message/detail.vue -->
<template>
<view class="message-detail-page">
<view v-if="isLoading" class="loading-indicator">加载中...</view>
<view v-else class="detail-content">
<view class="detail-header">
<view class="title-tag-row">
<text class="detail-title">{{ rw.rwmc }}</text>
<view class="tag" :class="rw.tagType">{{ rw.tagText }}</view>
</view>
<view class="detail-meta">
<text>{{ rw.rwkstime }}</text>
<!-- <text>{{ messageDetail.timeAgo }}</text>-->
</view>
</view>
<view class="detail-body">
<BasicForm :schema="schema" v-model="formData">
</BasicForm>
</view>
<view class="detail-footer" v-if="rwzxqds.length==0">
<button type="primary" class="action-button" @click="saveRwZx">处理</button>
</view>
</view>
<!-- <view v-else class="empty-state">消息详情未找到</view>-->
</view>
</template>
<script lang="ts" setup>
import {ref} from 'vue';
import {onLoad} from '@dcloudio/uni-app';
import {rwFindInfoByRwId, rwflFindRwlxsByRwId, rwzxExecutedInfoByRwIdAndJsApi, rwzxSaveApi} from "@/api/base/server";
import {useForm} from "@/components/BasicForm/hooks/useForm";
import {navigateBack, showToast} from "@/utils/uniapp";
import {useUserStore} from "@/store/modules/user";
interface MessageDetail {
id: string; // Assuming an ID is passed or can be derived
title: string;
desc: string;
date: string;
timeAgo: string;
tagText: string;
tagType: string;
// Add other fields as necessary
}
const formData: any = ref({})
const messageId = ref<string>('');
const messageDetail = ref<MessageDetail | null>({
id: 'todo1',
title: '教务通知 (待办)',
desc: '学校召开期初教学准备会议暨首次教学工作例会. 会议强调了新学期的教学重点和要求,请各位老师认真准备。',
date: '2025-02-17',
timeAgo: '8 mins 前',
tagText: '通知',
tagType: 'notice',
likes: 6,
comments: 12
});
const isLoading = ref(false);
const rwflx: any = ref([])
const rw = ref({})
const schema = reactive<FormsSchema[]>([])
const {getUser} = useUserStore()
async function saveRwZx() {
const result = [];
for (let i = 0; i < rwflx.value.length; i++) {
console.log(44, rwflx.value[i].id, formData.value[rwflx.value[i].id])
if (rwflx.value[i].rwbs && (!formData.value[rwflx.value[i].id] || formData.value[rwflx.value[i].id] == "")) {
showToast("请填写必填项!")
return;
}
result.push({
rwlxId: rwflx.value[i].id,
rwzxqdtx: formData.value[rwflx.value[i].id],
})
}
await rwzxSaveApi({
mobile: getUser.mobile,
rwId: rw.value.id,
rwzxqdDtos: result
})
showToast("操作成功!");
uni.navigateBack({delta: 1})
}
const rwzxqds = ref([])
onLoad(async (options) => {
if (options && options.id) {
const {result} = await rwFindInfoByRwId({
rwId: options.id
});
rwflx.value = result.rwlxes;
rw.value = result;
for (let i = 0; i < rwflx.value.length; i++) {
if (rwflx.value[i].rwfl == "sctp" || rwflx.value[i].rwfl == "scsp" || rwflx.value[i].rwfl == "scwd") {
schema.push({
field: `${rwflx.value[i].id}`,
label: `${rwflx.value[i].rwbt}`,
component: "BasicUpload",
required: rwflx.value[i].rwbs,
componentProps: {}
})
} else if (rwflx.value[i].rwfl == "text") {
schema.push({
field: `${rwflx.value[i].id}`,
label: `${rwflx.value[i].rwbt}`,
component: "BasicInput",
required: rwflx.value[i].rwbs,
itemProps: {
labelPosition: "top",
},
componentProps: {
type: "textarea",
},
})
} else if (rwflx.value[i].rwfl == "dxsx" || rwflx.value[i].rwfl == "dxxz") {
let options = rwflx.value[i].remark.split("");
let range = [];
for (let i = 0; i < options.length; i++) {
range.push({
name: options[i]
});
}
schema.push({
field: `${rwflx.value[i].id}`,
label: `${rwflx.value[i].rwbt}`,
component: "BasicPicker",
required: rwflx.value[i].rwbs,
itemProps: {
labelPosition: "top",
},
componentProps: {
range: range,
rangeKey: "name",
savaKey: "name",
},
})
}
}
const res = await rwzxExecutedInfoByRwIdAndJsApi({
rwId: options.id,
mobile: getUser.mobile
});
if (res && res.result && res.result.length) {
rwzxqds.value = res.result;
const showData = {};
for (let i = 0; i < rwzxqds.value.length; i++) {
showData[rwzxqds.value[i].rwlxId] = rwzxqds.value[i].rwzxqdtx;
}
formData.value = showData;
}
} else {
console.error('Message ID/Data is missing!');
uni.showToast({title: '加载失败,缺少信息', icon: 'none'});
}
});
</script>
<style scoped lang="scss">
.message-detail-page {
background-color: #f4f5f7;
min-height: 100vh;
padding: 15px;
box-sizing: border-box;
}
.loading-indicator,
.empty-state {
text-align: center;
color: #999;
padding: 40px 15px;
font-size: 14px;
}
.detail-content {
background-color: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.detail-header {
border-bottom: 1px solid #f0f0f0;
padding-bottom: 15px;
margin-bottom: 20px;
.title-tag-row {
display: flex;
justify-content: space-between;
align-items: flex-start; // Align items to the top
margin-bottom: 10px;
}
.detail-title {
font-size: 18px;
font-weight: bold;
color: #333;
flex: 1; // Allow title to take available space
margin-right: 10px; // Space between title and tag
line-height: 1.4;
}
.tag { // Reuse tag styles from index page if possible, or define here
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
color: #ffffff;
white-space: nowrap;
flex-shrink: 0; // Prevent tag from shrinking
&.notice {
background-color: #447ade;
}
&.task {
background-color: #19be6b;
}
&.approval {
background-color: #ff9f0a;
}
&.submit {
background-color: #8e8e93;
}
}
.detail-meta {
font-size: 12px;
color: #999;
text {
margin-right: 15px;
}
}
}
.detail-body {
margin-bottom: 40px;
.detail-desc {
font-size: 15px;
color: #555;
line-height: 1.7;
word-break: break-word;
}
}
.detail-footer {
// text-align: center; // Removed center alignment
// Add margin if needed, e.g., margin-top: 20px;
}
// Style for the action button
.action-button {
width: 100%; // Make button full width
height: 44px; // Standard button height
line-height: 44px; // Match height for vertical centering
font-size: 16px; // Slightly larger font
font-weight: 500; // Medium weight
border-radius: 8px; // Consistent border radius
margin-top: 20px; // Add space above the button
// Ensure primary color is applied correctly (uni-app default should work)
// background-color: #447ade;
// color: #ffffff;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB