调整请假

This commit is contained in:
ywyonui 2025-07-06 21:49:44 +08:00
parent 7bb1548115
commit b1cf1b9a6f
8 changed files with 792 additions and 185 deletions

View File

@ -121,3 +121,26 @@ export const jzXkTkjApi = async (params: any) => {
export const jzXkJfCxjApi = async (params: any) => { export const jzXkJfCxjApi = async (params: any) => {
return await post("/mobile/jz/xk/jfcx", params); return await post("/mobile/jz/xk/jfcx", params);
}; };
/**
*
*/
export const jzAddXsQjApi = async (params: any) => {
return await post("/api/xsQj/save", params);
};
/**
*
*/
export const jzXsQjListApi = async (params: any) => {
return await get("/api/xsQj/findPage", params);
};
/**
*
*/
export const jzXsQjActivitiHistoryApi = async (params: any) => {
return await get("/api/activiti/history/historicFlow", params);
};

View File

@ -19,7 +19,7 @@
</view> </view>
</template> </template>
<view class="p-15"> <view class="p-15">
<view v-for="(item,index) in dataList" :key="item.id||index"> <view v-for="(item,index) in dataList" :key="item.id || index">
<slot :data="item" :index="index" :list="dataList"/> <slot :data="item" :index="index" :list="dataList"/>
</view> </view>
</view> </view>
@ -32,7 +32,7 @@
import {reactive} from "vue"; import {reactive} from "vue";
import type {LayoutOptions, PagingRefInterface} from "@/components/BasicListLayout/type/useLayout"; import type {LayoutOptions, PagingRefInterface} from "@/components/BasicListLayout/type/useLayout";
const dataList = ref([]) const dataList = ref<any>([])
const fixed = ref(false) const fixed = ref(false)
const pagingRef = ref<PagingRefInterface | null>(null) const pagingRef = ref<PagingRefInterface | null>(null)

View File

@ -48,36 +48,6 @@ let pageParams = ref({
xsId: getCurXs.id xsId: getCurXs.id
}) })
//
const mockLeaveData = [
{
id: "ks001",
applicantName: "施瑞辰",
reason: "家里有事要处理",
startTime: "2025-02-17 08:00",
endTime: "2025-02-17 18:00",
},
{
id: "ks002",
applicantName: "李晓明",
reason: "身体不适,需要就医",
startTime: "2025-02-18 09:00",
endTime: "2025-02-18 17:00",
},
//
];
// API API
const testList = async (param?: any): Promise<{ message: string, resultCode: number, rows: any[] }> => {
console.log("Simulating API call for ks list with params:", param);
//
return new Promise((resolve) => {
setTimeout(() => {
resolve({ message: "测试", resultCode: 1, rows: mockLeaveData });
}, 500);
});
};
const [register, { reload }] = useLayout({ const [register, { reload }] = useLayout({
api: xsKsccApi, api: xsKsccApi,
componentProps: {}, componentProps: {},
@ -92,9 +62,6 @@ const viewDetails = (item: any | null) => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.ks-application-page {
}
.ks-card { .ks-card {
background-color: #ffffff; background-color: #ffffff;
border-radius: 8px; border-radius: 8px;

View File

@ -0,0 +1,200 @@
<template>
<BasicLayout :fixed="false">
<view class="p-15">
<BasicForm @register="register" />
</view>
<template #bottom>
<view class="white-bg-color py-5">
<view class="flex-row items-center pb-10 pt-5">
<u-button
text="取消"
class="ml-15 mr-7"
:plain="true"
@click="navigateBack"
/>
<u-button
text="提交"
class="mr-15 mr-7"
type="primary"
@click="submit"
/>
</view>
</view>
</template>
</BasicLayout>
</template>
<script setup lang="ts">
import { navigateBack } from "@/utils/uniapp";
import { useForm } from "@/components/BasicForm/hooks/useForm";
import { dicApi } from "@/api/system/dic";
import { jzAddXsQjApi } from "@/api/base/server";
import { showToast } from "@/utils/uniapp";
import dayjs from "dayjs";
import { useUserStore } from "@/store/modules/user";
const { getCurXs, getUser } = useUserStore();
//
const props = withDefaults(defineProps<{
data: any
}>(), {
data: () => ({
id: "",
qjlx: "事假",
qjkssj: "2025-07-07 09:00:00",
qjjssj: "2025-07-08 10:00:00",
qjsc: "25小时",
qjsy: "我有事情",
sflx: 1
})
});
let formData = ref<any>({});
const [register, { getValue, setValue }] = useForm({
schema: [
{
field: "qjlx",
label: "请假类型",
required: true,
component: "BasicPicker",
componentProps: {
api: dicApi,
param: { pid: 1007011432 },
rangeKey: "dictionaryValue",
savaKey: "dictionaryCode",
},
},
{ interval: true },
{
field: "qjkstime",
label: "开始时间",
component: "BasicDateTimes",
required: true,
componentProps: {
type: 'datetime',
change: (e: string) => changeKsTime(e)
},
},
{
field: "qjjstime",
label: "结束时间",
component: "BasicDateTimes",
required: true,
componentProps: {
type: 'datetime',
change: (e: string) => changeJsTime(e)
},
},
{
field: "qjsc",
label: "请假时长",
component: "BasicInput",
componentProps: {
disabled: true,
placeholder: "请输入选择开始时间和结束时间"
},
},
{ interval: true },
{
field: "qjsy",
label: "请假事由",
component: "BasicInput",
required: true,
itemProps: {
labelPosition: "top",
},
componentProps: {
type: "textarea",
},
},
{
field: "sflx",
label: "是否离校",
component: "BasicCheckbox",
required: true,
itemProps: {
labelPosition: "top",
},
componentProps: {
data: [
{ value: 0, text: "否" },
{ value: 1, text: "是" },
],
},
},
// {
// field: "qjtp",
// label: "",
// component: "BasicUpload",
// required: true,
// itemProps: {
// labelPosition: "top",
// },
// componentProps: {},
// },
],
});
setValue(props.data)
const changeKsTime = (selectedTime?: string) => {
if (!selectedTime) {
return;
}
formData.value.qjkstime = selectedTime;
validateTime();
};
const changeJsTime = (selectedTime?: string) => {
if (!selectedTime) {
return;
}
formData.value.qjjstime = selectedTime;
validateTime();
};
const validateTime = () => {
const data = formData.value;
if (!data.qjkstime || !data.qjjstime) {
return false;
}
// 使dayjs
const ksTime = dayjs(data.qjkstime).valueOf();
const jsTime = dayjs(data.qjjstime).valueOf();
if (ksTime > jsTime) {
uni.showToast({
title: "请假开始时间不能大于请假结束时间!",
icon: "none",
});
return false;
}
//
data.qjsc = Math.round((jsTime - ksTime) / (1000 * 60 * 60)) + "小时";
setValue({ qjsc: data.qjsc });
return true;
}
const submit = () => {
const fd = getValue();
if (!validateTime()) {
return;
}
const params = { ...formData, ...fd };
if (props.data && props.data.id) {
params.id = props.data.id;
} else {
params.id = null;
params.xsId = getCurXs.id; // ID
params.jsId = getCurXs.bzrId; // ID
params.xqId = getCurXs.xqId; // ID
params.jzId = getUser.jzId; // ID
}
params.flag = 1;
jzAddXsQjApi(params).then(() => {
showToast({ title: "提交成功", icon: "success" });
navigateBack();
});
};
</script>

View File

@ -0,0 +1,147 @@
<template>
<view class="leave-list">
<BasicListLayout @register="register" style="position: absolute;">
<template v-slot="{ data, index }">
<view class="leave-card" @click="goToDetail(data)">
<view class="card-header">
<text class="applicant-name">{{ data.applicantName }}的请假申请</text>
</view>
<view class="card-body">
<view class="info-row">
<text class="label">请假事由:</text>
<text class="value">{{ data.reason }}</text>
</view>
<view class="info-row">
<text class="label">开始时间:</text>
<text class="value">{{ data.startTime }}</text>
</view>
<view class="info-row">
<text class="label">结束时间:</text>
<text class="value">{{ data.endTime }}</text>
</view>
</view>
<view class="card-footer">
<text>查看详情</text>
<text class="arrow">
<uni-icons type="arrowright" size="16" color="#ccc"></uni-icons>
</text>
</view>
</view>
</template>
</BasicListLayout>
</view>
</template>
<script setup lang="ts">
import { useLayout } from "@/components/BasicListLayout/hooks/useLayout";
import { jzXsQjListApi } from "@/api/base/server";
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
const { getCurXs } = useUserStore();
const { setData } = useDataStore();
let pageParams = ref({
rows: 10,
xsId: getCurXs.id
})
const [register, { reload }] = useLayout({
api: jzXsQjListApi,
componentProps: {},
param: pageParams.value
});
//
const goToDetail = (item: any | null) => {
setData(item);
let url = '/pages/base/leave-request/leaveApplication'; // 使
if (item && item.id) {
console.log('View details for:', item);
url += `?id=${item.id}`; // ID
} else {
console.log('Navigating to create new leave application.');
// ID
}
uni.navigateTo({ url });
};
</script>
<style lang="scss" scoped>
.leave-list {
display: flex;
width: 100%;
height: 100%;
flex: 1 0 1px;
position: relative;
.leave-card {
background-color: #ffffff;
border-radius: 8px;
margin-bottom: 15px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
overflow: hidden;
}
.card-header {
padding: 12px 15px;
border-bottom: 1px solid #f0f0f0;
.applicant-name {
font-size: 16px;
font-weight: bold;
color: #333;
}
}
.card-body {
padding: 15px;
.info-row {
display: flex;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
.label {
font-size: 14px;
color: #666;
width: 70px;
flex-shrink: 0;
margin-right: 8px;
}
.value {
font-size: 14px;
color: #333;
flex: 1;
}
}
}
.card-footer {
padding: 12px 15px;
border-top: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
color: #888;
cursor: pointer;
.arrow {
font-size: 16px;
color: #ccc;
}
}
::v-deep .zp-loading-fixed {
position: absolute;
}
::v-deep .d-load-main {
position: absolute;
}
}
</style>

View File

@ -1,166 +1,51 @@
<template> <template>
<view class="leave-application-page"> <view class="leave-page">
<BasicListLayout @register="register"> <!-- 选项卡 -->
<template v-slot="{ data, index }"> <BasicTabs class="leave-tabs"
<view class="leave-card" @click="viewDetails(data)"> ref="tabsRef" :list="tabList" bar-width="60px" scroll-count="4"
<view class="card-header"> :current="curTabIndex" @change="switchTab"
<text class="applicant-name" />
>{{ data.applicantName }}的请假申请</text <view class="leave-edit" v-if="curTabIndex === 0">
> <XsQjEdit />
</view> </view>
<view class="card-body"> <view class="leave-list" v-else>
<view class="info-row"> <XsQjList />
<text class="label">请假事由:</text> </view>
<text class="value">{{ data.reason }}</text>
</view>
<view class="info-row">
<text class="label">开始时间:</text>
<text class="value">{{ data.startTime }}</text>
</view>
<view class="info-row">
<text class="label">结束时间:</text>
<text class="value">{{ data.endTime }}</text>
</view>
</view>
<view class="card-footer">
<text>查看详情</text>
<text class="arrow">
<uni-icons type="arrowright" size="16" color="#ccc"></uni-icons>
</text>
</view>
</view>
</template>
<template #bottom>
<view class="button" @click="viewDetails(null)"> <!-- Pass null for new application -->
<text>新增请假</text>
</view>
</template>
</BasicListLayout>
</view> </view>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import XsQjEdit from "./components/xsQjEdit.vue"
import { useLayout } from "@/components/BasicListLayout/hooks/useLayout"; import XsQjList from "./components/xsQjList.vue"
// const tabList = ref([
const mockLeaveData = [ { name: "请假申请", id: "leave-edit" },
{ { name: "请假记录", id: "leave-list" },
id: "leave001", ]);
applicantName: "施瑞辰",
reason: "家里有事要处理",
startTime: "2025-02-17 08:00",
endTime: "2025-02-17 18:00",
},
{
id: "leave002",
applicantName: "李晓明",
reason: "身体不适,需要就医",
startTime: "2025-02-18 09:00",
endTime: "2025-02-18 17:00",
},
//
];
// API API const curTabIndex = ref(0);
const testList = async (param?: any): Promise<{ message: string, resultCode: number, rows: any[] }> => {
console.log("Simulating API call for leave list with params:", param);
//
return new Promise((resolve) => {
setTimeout(() => {
resolve({ message: "测试", resultCode: 1, rows: mockLeaveData });
}, 500);
});
};
const [register, { reload }] = useLayout({ const switchTab = (index : number) => {
api: testList, curTabIndex.value = index;
componentProps: {}, }
});
//
const viewDetails = (item: any | null) => {
let url = '/pages/base/leave-request/leaveApplication'; // 使
if (item && item.id) {
console.log('View details for:', item);
url += `?id=${item.id}`; // ID
} else {
console.log('Navigating to create new leave application.');
// ID
}
uni.navigateTo({ url });
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.leave-application-page {
}
.leave-card { .leave-page {
background-color: #ffffff; flex: 1 0 1px;
border-radius: 8px;
margin-bottom: 15px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
overflow: hidden;
}
.card-header {
padding: 12px 15px;
border-bottom: 1px solid #f0f0f0;
.applicant-name {
font-size: 16px;
font-weight: bold;
color: #333;
}
}
.card-body {
padding: 15px;
.info-row {
display: flex;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
.label {
font-size: 14px;
color: #666;
width: 70px;
flex-shrink: 0;
margin-right: 8px;
}
.value {
font-size: 14px;
color: #333;
flex: 1;
}
}
}
.card-footer {
padding: 12px 15px;
border-top: 1px solid #f0f0f0;
display: flex; display: flex;
justify-content: space-between; flex-direction: column;
align-items: center; height: 100vh;
font-size: 14px; .leave-tabs {
color: #888; flex: 0 0 45px;
cursor: pointer; }
.leave-edit,
.arrow { .leave-list {
font-size: 16px; flex: 1 0 1px;
color: #ccc; position: relative;
} }
} }
// ( BasicListLayout bottom )
.button {
padding: 10px 15px;
background-color: #447ade;
color: white;
text-align: center;
border-radius: 5px;
margin: 10px 15px; //
font-size: 16px;
}
</style> </style>

View File

@ -0,0 +1,385 @@
<template>
<BasicLayout>
<view class="course-detail">
<!-- 课程信息卡片 -->
<view class="info-card">
<view class="card-title">课程信息</view>
<view class="divider"></view>
<view class="course-info">
<image
class="course-image"
:src="courseDetail.xkkcImg"
mode="aspectFill"
></image>
<view class="course-content">
<view class="course-name">{{ courseDetail.title }}</view>
<view class="course-teacher"
>开课老师{{ courseDetail.teacher }}</view
>
<view class="course-location"
>上课地点{{ courseDetail.location }}</view
>
<view class="course-time"
>上课时间{{ courseDetail.studyTime }}</view
>
<view class="course-price">
金额<text class="price-value">¥{{ courseDetail.price }}</text>
</view>
</view>
</view>
</view>
<!-- 老师信息卡片 -->
<view class="info-card">
<view class="card-title">老师信息</view>
<view class="divider"></view>
<view class="teacher-info">
<!-- 教师头部信息区域 -->
<view class="teacher-header">
<image
class="teacher-avatar"
:src="teacherInfo.avatar"
mode="aspectFill"
></image>
<view class="teacher-basic-info">
<view class="teacher-name">{{ teacherInfo.name }}</view>
<view class="teacher-title">课程教师</view>
</view>
</view>
<!-- 教师简介区域 -->
<view class="teacher-intro-section">
<view class="intro-title">教师简介</view>
<view class="teacher-intro">
<text>{{ teacherInfo.introduction }}</text>
</view>
</view>
</view>
</view>
<!-- 教学理念 -->
<view class="info-card">
<view class="card-title">教学理念</view>
<view class="divider"></view>
<view class="content-section">
<text>{{ teachingPhilosophy }}</text>
</view>
</view>
<!-- 教学计划 -->
<view class="info-card">
<view class="card-title">教学计划</view>
<view class="divider"></view>
<view class="content-section">
<template v-if="teachingPlan && teachingPlan.length > 0">
<view
v-for="(phase, index) in teachingPlan"
:key="index"
class="teaching-phase"
>
<text>{{ phase }}</text>
</view>
</template>
<template v-else>
<view class="empty-data">
<u-icon name="info-circle" color="#C8C9CC" size="18"></u-icon>
<text>暂无教学计划</text>
</view>
</template>
</view>
</view>
</view>
<template #bottom>
<view class="white-bg-color py-5">
<view class="flex-row items-center pb-10 pt-5">
<u-button
text="返回"
class="ml-15 mr-7"
:plain="true"
@click="navigateBack"
/>
</view>
</view>
</template>
</BasicLayout>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from "vue";
import { navigateBack } from "@/utils/uniapp";
import { useDataStore } from "@/store/modules/data";
import { storeToRefs } from "pinia";
import { imagUrl } from "@/utils";
import { kcjhFindKcjhByKcIdApi } from "@/api/base/server";
//
interface CourseData {
id?: string;
kcmc?: string;
jsName?: string;
kcdd?: string;
kcje?: number;
studyTime?: string;
kcjsms?: string;
jxll?: string;
remark?: string;
xkkcImg?: string;
[key: string]: any; //
}
const useData = useDataStore();
const { kcData } = storeToRefs(useData);
// -
const teachingPlan = ref<string[]>([]);
const courseData = kcData.value as CourseData;
if (courseData && courseData.id) {
kcjhFindKcjhByKcIdApi({
xkkcId: courseData.id,
}).then((res) => {
if (res.resultCode == 1) {
teachingPlan.value = res.result.map(
(item: any) => item.jhjd + ":" + item.jhms
);
}
});
}
//
const courseDetail = computed(() => {
const data = (kcData.value as CourseData) || {};
return {
id: data.id || "",
title: data.kcmc || "暂无课程名称",
teacher: data.jsName || "暂无教师信息",
location: data.kcdd || "暂无地点信息",
price: data.kcje || 0,
studyTime: data.studyTime || "暂无上课时间",
xkkcImg: data.xkkcImg || "/static/images/robot-course.jpg", //
};
});
//
const teacherInfo = computed(() => {
const data = (kcData.value as CourseData) || {};
return {
name: data.jsName || "暂无教师信息",
avatar: imagUrl(data.jstx), //
introduction: data.kcjsms || "暂无教师介绍",
};
});
//
const teachingPhilosophy = computed(() => {
const data = (kcData.value as CourseData) || {};
return data.jxll || "暂无教学理念信息";
});
</script>
<style lang="scss" scoped>
.course-detail {
background-color: #f5f7fa;
}
.nav-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px;
height: 44px;
background-color: #2879ff;
.nav-left {
width: 40px;
height: 40px;
display: flex;
align-items: center;
}
.nav-title {
font-size: 18px;
font-weight: 500;
color: #fff;
}
.nav-right {
width: 40px;
}
}
.info-card {
margin: 15px;
background-color: #fff;
border-radius: 8px;
padding: 15px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
.card-title {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
}
.divider {
height: 1px;
background-color: #eee;
margin-bottom: 15px;
}
}
.course-info {
display: flex;
.course-image {
width: 120px;
height: 138px;
border-radius: 8px;
margin-right: 15px;
}
.course-content {
flex: 1;
.course-name {
font-size: 18px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
}
.course-teacher,
.course-location {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.course-time {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.course-price {
font-size: 14px;
color: #666;
.price-value {
color: #ff6b00;
font-weight: bold;
}
}
}
}
.teacher-info {
display: flex;
flex-direction: column;
.teacher-header {
display: flex;
align-items: center;
margin-bottom: 15px;
.teacher-avatar {
width: 60px;
height: 60px;
border-radius: 50%;
margin-right: 15px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
border: 2px solid #fff;
}
.teacher-basic-info {
.teacher-name {
font-size: 18px;
font-weight: 500;
color: #333;
margin-bottom: 4px;
}
.teacher-title {
font-size: 14px;
color: #666;
background-color: #f4f7fc;
padding: 2px 8px;
border-radius: 4px;
display: inline-block;
}
}
}
.teacher-intro-section {
width: 100%;
.intro-title {
font-size: 15px;
font-weight: 500;
color: #555;
margin-bottom: 8px;
padding-left: 8px;
border-left: 3px solid #2879ff;
}
.teacher-intro {
font-size: 14px;
color: #666;
line-height: 1.6;
background-color: #f8f9fc;
padding: 12px;
border-radius: 8px;
text-align: justify;
}
}
}
.content-section {
font-size: 14px;
color: #666;
line-height: 1.6;
.teaching-phase {
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
}
.empty-data {
display: flex;
align-items: center;
justify-content: center;
padding: 20px 0;
color: #909399;
font-size: 14px;
text {
margin-left: 8px;
}
}
}
.bottom-action {
padding: 15px;
margin-top: 20px;
.back-btn {
width: 100%;
height: 44px;
line-height: 44px;
background-color: #fff;
color: #333;
border-radius: 22px;
font-size: 16px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
}
}
</style>

View File

@ -220,7 +220,7 @@ async function afterRead(event: any, index: number) {
const tempFilePath = event.tempFilePaths[0]; const tempFilePath = event.tempFilePaths[0];
showLoading({ title: "上传中" }); showLoading({ title: "上传中" });
try { try {
const res = await attachmentUpload(tempFilePath); const res = await attachmentUpload(tempFilePath) as { result: Array<{ filePath: string }> };
const result = res.result; const result = res.result;
if (result && result.length > 0 && result[0].filePath) { if (result && result.length > 0 && result[0].filePath) {
students.value[index].xstx = result[0].filePath; students.value[index].xstx = result[0].filePath;