调整教师请假逻辑

This commit is contained in:
ywyonui 2025-08-29 01:27:54 +08:00
parent ef933f954c
commit 68fd30a282
19 changed files with 2065 additions and 1807 deletions

View File

@ -16,6 +16,11 @@ export const jsFindAll = async () => {
return await get("/api/js/findAllBasicInfo"); return await get("/api/js/findAllBasicInfo");
}; };
// 所有教师基础信息Vo版本
export const jsFindAllBasicInfoVo = async () => {
return await get("/api/js/findAllBasicInfoVo");
};
// 所有职务 // 所有职务
export const zwFindAllApi = async () => { export const zwFindAllApi = async () => {
return await get("/api/zw/findAll"); return await get("/api/zw/findAll");

View File

@ -4,26 +4,26 @@
import { get, post } from "@/utils/request"; import { get, post } from "@/utils/request";
/** /**
* *
*/ */
export const jsQjSqApi = async (params: any) => { export const jsQjSqApi = async (params: any) => {
return await post("/api/jsQj/sq", params); return await post("/api/jsQj/sq", params);
}; };
/** /**
* *
*/
export const jsQjSpApi = async (params: any) => {
return await post("/api/jsQj/sp", params);
};
/**
*
*/ */
export const jsQjCxtjApi = async (params: any) => { export const jsQjCxtjApi = async (params: any) => {
return await post("/api/jsQj/cxtj", params); return await post("/api/jsQj/cxtj", params);
}; };
/**
*
*/
export const jsQjSpApi = async (params: any) => {
return await post("/api/jsQj/sp", params);
};
/** /**
* *
*/ */
@ -39,19 +39,12 @@ export const jsQjJwcXtApi = async (params: any) => {
}; };
/** /**
* *
*/ */
export const jsQjDkQrApi = async (params: any) => { export const jsQjDkQrApi = async (params: any) => {
return await post("/api/jsQj/dk/qr", params); return await post("/api/jsQj/dk/qr", params);
}; };
/**
*
*/
export const jsQjZbApi = async (params: any) => {
return await post("/api/jsQj/zb", params);
};
/** /**
* *
*/ */
@ -89,8 +82,39 @@ export const findQjListApi = async (params: any) => {
}; };
/** /**
* * ID获取默认审批人和抄送人
*/ */
export const getQjActivitiHistoryApi = async (params: any) => { export const getApproversByRuleId = async (ruleId: string) => {
return await get("/activiti/history/historicFlow", params); return await get("/api/jsQj/getApproversByRuleId", { ruleId });
};
/**
* ID获取请假详情
*/
export const findQjByIdApi = async (params: { id: string }) => {
return await get("/api/jsQj/findById", params);
};
// 保留旧的接口方法以兼容现有代码,但标记为废弃
/**
* @deprecated 使 jsQjSqApi
*/
export const jsQjSaveOrUpdateApi = async (params: any) => {
return await post("/api/jsQj/sq", params);
};
/**
* @deprecated 使 jsQjSpApi
*/
export const jsQjApproveApi = async (params: any) => {
return await post("/api/jsQj/sp", params);
};
/**
*
* @param ywId ID
* @param ywType
*/
export const getJsQjApprovalProcessApi = (ywId: string, ywType: string = 'JS_QJ') => {
return get("/api/lcglSp/getByYwIdAndYwType", { ywId, ywType });
}; };

64
src/api/base/lcglSpApi.ts Normal file
View File

@ -0,0 +1,64 @@
import { get, post } from "@/utils/request";
// 流程审批相关API接口
/**
* ID和业务类型获取审批流程
* @param ywId ID
* @param ywType
*/
export function getByYwIdAndYwTypeApi(ywId: string, ywType: string) {
return get('/api/lcglSp/getByYwIdAndYwType', { ywId, ywType });
}
/**
* ID
* @param ywId ID
* @param ywType
* @param spType
*/
export function getByYwIdAndYwTypeAndSpTypeApi(ywId: string, ywType: string, spType: string) {
return get('/api/lcglSp/getByYwIdAndYwTypeAndSpType', { ywId, ywType, spType });
}
/**
*
* @param params
*/
export function lcglSpFindPageApi(params: {
page: number;
pageSize: number;
ywId?: string;
ywType?: string;
spType?: string;
approveStatus?: string;
}) {
return get('/api/lcglSp/findPage', params);
}
/**
* /
* @param params
*/
export function lcglSpSaveApi(params: {
id?: string;
ywId: string;
ywType: string;
userId: string;
userName: string;
spType: string;
approveStatus?: string;
approveRemark?: string;
setId?: string;
sort?: number;
}) {
return post('/api/lcglSp/save', params);
}
/**
*
* @param params
*/
export function lcglSpLogicDeleteApi(params: { id: string }) {
return post('/api/lcglSp/logicDelete', params);
}

View File

@ -1,12 +1,18 @@
<template> <template>
<view class="basic-js-picker"> <view class="js-picker">
<view class="js-selected" @click="showPicker"> <!-- 自定义触发器slot -->
<view v-if="customTrigger" @click="showPicker">
<slot name="trigger" :selectedList="selectedList" :getShowSelectedName="getShowSelectedName"></slot>
</view>
<!-- 默认触发器 -->
<view v-else class="js-selected" @click="showPicker">
<view class="js-selected-name"> <view class="js-selected-name">
<text class="data" v-if="selectedList && selectedList.length">{{ getShowSelectedName() }}</text> <text class="data" v-if="selectedList && selectedList.length">{{ getShowSelectedName() }}</text>
<text class="data" style="color: #999;" v-else>{{ placeholder }}</text> <text class="data" style="color: #999;" v-else>{{ placeholder }}</text>
</view> </view>
<uni-icons type="arrowright" size="16" color="#999"></uni-icons> <uni-icons type="arrowright" size="16" color="#999"></uni-icons>
</view> </view>
<u-popup :show="showPopup" @close="showPopup=false"> <u-popup :show="showPopup" @close="showPopup=false">
<view class="js-picker-popup"> <view class="js-picker-popup">
<view class="js-picker-header"> <view class="js-picker-header">
@ -14,7 +20,7 @@
<view class="js-picker-title">{{ title }}</view> <view class="js-picker-title">{{ title }}</view>
<view class="js-ok-btn" @click="handleOk">确定</view> <view class="js-ok-btn" @click="handleOk">确定</view>
</view> </view>
<view class="js-picker-search" v-if="showSearch"> <view class="js-picker-search">
<BasicSearch @change="handleSearch" :showAction="false" height="36" :placeholder="searchPlaceholder"/> <BasicSearch @change="handleSearch" :showAction="false" height="36" :placeholder="searchPlaceholder"/>
</view> </view>
<view class="js-list"> <view class="js-list">
@ -32,13 +38,13 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, nextTick } from 'vue';
import { useCommonStore } from "@/store/modules/common"; import { useCommonStore } from "@/store/modules/common";
const { getAllJs } = useCommonStore(); const { getAllJsBasicInfoVo } = useCommonStore();
// //
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
modelValue?: any, defualtValue?: any,
defaultValue?: any,
parentData?: any, parentData?: any,
multiple?: boolean, multiple?: boolean,
// id // id
@ -47,23 +53,23 @@ const props = withDefaults(defineProps<{
title?: string, title?: string,
placeholder?: string, placeholder?: string,
searchPlaceholder?: string, searchPlaceholder?: string,
showSearch?: boolean //
customTrigger?: boolean
}>(), { }>(), {
modelValue: null, defualtValue: null,
defaultValue: null,
parentData: null, parentData: null,
multiple: false, multiple: false,
excludeIds: [], excludeIds: [],
title: '请选择教师', title: '请选择教师',
placeholder: '请选择师', placeholder: '请选择师',
searchPlaceholder: '输入教师名称查询', searchPlaceholder: '输入教师名称查询',
showSearch: true customTrigger: false
}); });
// emit // emit
const emit = defineEmits(['change', 'update:modelValue']) const emit = defineEmits(['change'])
const targetScrollTop = ref(0); // scroll-top const targetScrollTop = ref(0); // scroll-top
const showPopup = ref(false); const showPopup = ref(false);
const jsListAll = ref<any>([]); const jsListAll = ref<any>([]);
@ -72,22 +78,8 @@ let searchKey = "";
const selectedList = ref<any>([]); const selectedList = ref<any>([]);
// v-model
const currentValue = computed({
get() {
return props.modelValue;
},
set(value) {
emit('update:modelValue', value);
}
});
//
const internalValue = ref(props.modelValue || props.defaultValue);
const getShowSelectedName = () => { const getShowSelectedName = () => {
const names = selectedList.value.map((item: any) => item.label).join(","); return selectedList.value.map((item: any) => item.label).join(",");
return names;
}; };
const handleSearch = (value: any) => { const handleSearch = (value: any) => {
@ -99,19 +91,24 @@ const handleSearch = (value: any) => {
const handleSelect = (item: any) => { const handleSelect = (item: any) => {
if (props.multiple) { if (props.multiple) {
// //
const index = selectedList.value.findIndex((selected: any) => selected.value === item.value); const isSelected = selectedList.value.some((selected: any) => selected.value === item.value);
if (index > -1) { if (isSelected) {
// //
selectedList.value.splice(index, 1); const index = selectedList.value.findIndex((selected: any) => selected.value === item.value);
if (index > -1) {
selectedList.value.splice(index, 1);
}
item.selected = false; item.selected = false;
} else { } else {
// //
selectedList.value.push(item); selectedList.value.push(item);
item.selected = true; item.selected = true;
} }
} else { } else {
// //
jsList.value.forEach((listItem: any) => listItem.selected = false); for (const listItem of jsList.value) {
listItem.selected = false;
}
selectedList.value = [item]; selectedList.value = [item];
item.selected = true; item.selected = true;
} }
@ -123,217 +120,132 @@ const handleCancel = () => {
const handleOk = () => { const handleOk = () => {
showPopup.value = false; showPopup.value = false;
let result;
if (props.multiple) { if (props.multiple) {
result = selectedList.value; emit("change", selectedList.value, props.parentData);
} else { } else {
result = selectedList.value[0] || null; emit("change", selectedList.value[0] || null, props.parentData);
} }
}
// v-model
currentValue.value = result;
// change
emit("change", result, props.parentData);
};
const showPicker = () => { const showPicker = () => {
showPopup.value = true; rebuildJsList();
nextTick(() => {
showPopup.value = true;
});
}; };
const rebuildJsList = () => { const rebuildJsList = () => {
jsList.value = []; jsList.value = [];
jsListAll.value.forEach((item: any) => { if (!jsListAll.value || !Array.isArray(jsListAll.value)) {
if (props.excludeIds && props.excludeIds.length && props.excludeIds.includes(item.id)) {
return;
}
// jsxm
if (!searchKey || item.jsxm.includes(searchKey)) {
jsList.value.push({
label: item.jsxm,
value: item.id,
headPic: item.headPic,
selected: false
});
}
});
//
restoreSelection();
};
const restoreSelection = () => {
if (!selectedList.value.length) {
return; return;
} }
// selectedList // 使for...of
jsList.value.forEach((item: any) => { for (const item of jsListAll.value) {
const isSelected = selectedList.value.some((selected: any) => selected.value === item.value); // item
item.selected = isSelected; if (!item || !item.id || !item.jsxm) {
}); continue;
}
//
if (props.excludeIds && Array.isArray(props.excludeIds) && props.excludeIds.includes(item.id)) {
continue;
}
// jsxm
if (!searchKey || item.jsxm.includes(searchKey)) {
jsList.value.push({
...item,
label: item.jsxm,
value: item.id,
selected: false
});
}
}
}; };
// //
defineExpose({ defineExpose({
showPicker, showPicker
open: showPicker,
close: () => showPopup.value = false
}); });
onMounted(async () => { onMounted(async () => {
const res = await getAllJs() try {
jsListAll.value = res.result || []; const res = await getAllJsBasicInfoVo()
if (res && res.result && Array.isArray(res.result)) {
rebuildJsList(); jsListAll.value = res.result;
// tickjsList // rebuildJsList
await nextTick(); await new Promise<void>((resolve) => {
rebuildJsList();
// // 使nextTickDOM
const initialValue = internalValue.value || props.defaultValue; nextTick(() => {
resolve();
if (initialValue) { });
setInitialValue(initialValue); });
}
}); //
if (props.defualtValue) {
// if (props.multiple) {
const setInitialValue = (value: any) => { if (Array.isArray(props.defualtValue) && props.defualtValue.length > 0) {
if (!jsList.value.length) { selectedList.value = [];
return; // 使for...of
} for (const item of jsList.value) {
if (props.defualtValue.includes(item.value)) {
if (props.multiple) { item.selected = true;
if (!Array.isArray(value) || !value.length) { selectedList.value.push(item);
return; } else {
} item.selected = false;
}
selectedList.value = []; }
}
// } else {
jsList.value.forEach((item: any) => { // 使for...of
item.selected = false; for (const item of jsList.value) {
}); if (item.value === props.defualtValue) {
item.selected = true;
// selectedList.value = [item];
let processedValue = value; } else {
item.selected = false;
// {0: {value: '1001', label: ''}, 1: {value: '1002', label: ''}} }
if (value[0] && typeof value[0] === 'object' && (value[0].value || value[0].id)) { }
processedValue = value.map((item: any) => item.value || item.id);
}
//
selectedList.value = [];
//
processedValue.forEach((id: any) => {
const foundItem = jsList.value.find((item: any) => item.value === id || item.id === id);
if (foundItem) {
foundItem.selected = true;
selectedList.value.push(foundItem);
}
});
//
jsList.value.forEach((item: any) => {
if (!processedValue.includes(item.value) && !processedValue.includes(item.id)) {
item.selected = false;
}
});
} else {
//
//
jsList.value.forEach((item: any) => {
item.selected = false;
});
//
let processedValue = value;
if (value && typeof value === 'object' && (value.value || value.id)) {
processedValue = value.value || value.id;
}
// valuevalueid
const targetItem = jsList.value.find((item: any) => item.value === processedValue || item.id === processedValue);
if (targetItem) {
targetItem.selected = true;
selectedList.value = [targetItem];
}
}
// selectedListjsListAll
if (selectedList.value.length === 0 && value && Array.isArray(value)) {
selectedList.value = [];
//
let processedValue = value;
if (value[0] && typeof value[0] === 'object' && (value[0].value || value[0].id)) {
processedValue = value.map((item: any) => item.value || item.id);
}
processedValue.forEach((id: any) => {
const foundItem = jsListAll.value.find((item: any) => item.id === id);
if (foundItem) {
const itemForList = {
label: foundItem.jsxm,
value: foundItem.id,
headPic: foundItem.headPic,
selected: true
};
selectedList.value.push(itemForList);
// jsList
const jsListItem = jsList.value.find((item: any) => item.value === foundItem.id);
if (jsListItem) {
jsListItem.selected = true;
} }
} }
}); } else {
} console.warn('JsPicker: 获取教师数据失败或数据格式不正确');
};
// modelValue
watch(() => props.modelValue, (newValue) => {
if (newValue !== internalValue.value) {
internalValue.value = newValue;
// jsListjsList
if (jsList.value.length > 0) {
setInitialValue(newValue);
} }
} catch (error) {
console.error('JsPicker初始化失败:', error);
} }
}, { immediate: true }); });
// defaultValue
watch(() => props.defaultValue, (newValue) => {
if (newValue && jsList.value.length > 0) {
setInitialValue(newValue);
}
}, { immediate: false });
// jsList jsList
watch(() => jsList.value, (newJsList) => {
if (newJsList.length > 0) {
const initialValue = internalValue.value || props.defaultValue;
if (initialValue) {
setInitialValue(initialValue);
}
}
}, { immediate: false });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.basic-js-picker { .js-picker {
flex: 1; flex: 1;
.js-selected { .js-selected {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: space-between;
padding: 20rpx 30rpx;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-radius: 12rpx;
border: 1rpx solid #e9ecef;
transition: all 0.3s ease;
&:active {
transform: translateY(2rpx);
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
.js-selected-name { .js-selected-name {
flex: 1; flex: 1;
.data {
font-size: 30rpx;
color: #333;
font-weight: 500;
}
} }
} }
.js-picker-popup { .js-picker-popup {
@ -384,4 +296,4 @@ watch(() => jsList.value, (newJsList) => {
} }
} }
} }
</style> </style>

View File

@ -0,0 +1,477 @@
<template>
<view class="spr-list-container">
<!-- 审批人列表 -->
<view class="section">
<view class="section-header">
<text class="section-title">审批人</text>
<!-- 审批人选择器 -->
<BasicJsPicker
ref="approverPickerRef"
:customTrigger="true"
:multiple="true"
:excludeIds="getExcludeApproverIds()"
:defualtValue="[]"
title="选择审批人"
placeholder="请选择审批人"
searchPlaceholder="搜索审批人"
@change="handleApproverChange"
>
<template #trigger>
<view class="add-btn">
<text class="add-icon">+</text>
<text class="add-text">添加审批人</text>
</view>
</template>
</BasicJsPicker>
</view>
<view class="list-content">
<view v-for="(spr, index) in approvers" :key="spr.id" class="list-item">
<view class="item-info">
<text class="item-name">{{ spr.jsxm }}</text>
</view>
<view class="item-actions">
<text class="delete-btn" @click="removeApprover(index)">删除</text>
</view>
</view>
<view v-if="approvers.length === 0" class="empty-tip">
<text>暂无审批人请添加</text>
</view>
</view>
</view>
<!-- 抄送人列表 -->
<view class="section">
<view class="section-header">
<text class="section-title">抄送人</text>
<!-- 抄送人选择器 -->
<BasicJsPicker
ref="ccPickerRef"
:customTrigger="true"
:multiple="true"
:excludeIds="getExcludeCcIds()"
:defualtValue="[]"
title="选择抄送人"
placeholder="请选择抄送人"
searchPlaceholder="搜索抄送人"
@change="handleCcChange"
>
<template #trigger>
<view class="add-btn">
<text class="add-icon">+</text>
<text class="add-text">添加抄送人</text>
</view>
</template>
</BasicJsPicker>
</view>
<view class="list-content">
<view v-for="(csr, index) in ccList" :key="csr.id" class="list-item">
<view class="item-info">
<text class="item-name">{{ csr.jsxm }}</text>
</view>
<view class="item-actions">
<text class="delete-btn" @click="removeCc(index)">删除</text>
</view>
</view>
<view v-if="ccList.length === 0" class="empty-tip">
<text>暂无抄送人请添加</text>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted } from "vue";
import { getApproversByRuleId } from "@/api/base/jsQjApi";
import { getByYwIdAndYwTypeApi } from "@/api/base/lcglSpApi";
import { useCommonStore } from "@/store/modules/common";
import BasicJsPicker from "@/components/BasicJsPicker/Picker.vue";
// uni
declare const uni: any;
// JsVo
interface JsInfo {
id: string;
jsxm: string;
lxdh?: string;
sfzh?: string;
headPic?: string;
userId?: number;
deptId?: string;
}
//
const normalizeTeacherData = (item: any): JsInfo => {
return {
id: item.id || item.value,
jsxm: item.jsxm || item.label,
lxdh: item.lxdh,
sfzh: item.sfzh,
headPic: item.headPic,
userId: item.userId,
deptId: item.deptId,
};
};
//
const props = withDefaults(
defineProps<{
defaultValue?: any;
ruleId?: string;
qjId?: string; // ID
}>(),
{
defaultValue: () => ({
sprList: [],
csrList: [],
}),
ruleId: "",
qjId: "",
}
);
// emit
const emit = defineEmits(["change"]);
// - 使defaultValue
const approvers = ref<JsInfo[]>([]);
const ccList = ref<JsInfo[]>([]);
//
const approverPickerRef = ref();
const ccPickerRef = ref();
// common store
const commonStore = useCommonStore();
//
const initData = () => {
if (props.defaultValue) {
if (
props.defaultValue.sprList &&
Array.isArray(props.defaultValue.sprList)
) {
// 使
approvers.value = props.defaultValue.sprList.map(normalizeTeacherData);
}
if (
props.defaultValue.csrList &&
Array.isArray(props.defaultValue.csrList)
) {
// 使
ccList.value = props.defaultValue.csrList.map(normalizeTeacherData);
}
}
};
//
watch(
() => props.defaultValue,
() => {
initData();
},
{ immediate: true, deep: true }
);
//
const loadDefaultApprovers = async () => {
try {
// IDLcglSp
if (props.qjId) {
await loadExistingApprovers();
} else if (props.ruleId) {
// LcglSet
await loadDefaultFromLcglSet();
}
} catch (error) {
console.error("获取默认审批人失败:", error);
uni.showToast({
title: "获取默认审批人失败",
icon: "none",
});
}
};
// LcglSp使
const loadExistingApprovers = async () => {
try {
const response = await getByYwIdAndYwTypeApi(props.qjId, "JS_QJ");
if (
response &&
response.resultCode === 1 &&
response.result &&
Array.isArray(response.result)
) {
const spList = response.result;
//
let approverList: any = [];
let ccListData: any = [];
// 使for...of
for (const item of spList) {
if (item && item.userId && item.userName) {
if (item.spType === "SP") {
approverList.push(item);
} else if (item.spType === "CC") {
ccListData.push(item);
}
}
}
let changeFlag = false;
// 使
if (approvers.value.length === 0 && approverList.length > 0) {
approvers.value = approverList.map(normalizeTeacherData);
changeFlag = true;
}
if (ccList.value.length === 0 && ccListData.length > 0) {
ccList.value = ccListData.map(normalizeTeacherData);
changeFlag = true;
}
if (changeFlag) {
notifyChange();
}
}
} catch (error) {
console.error("获取已有审批人失败:", error);
}
};
// LcglSet
const loadDefaultFromLcglSet = async () => {
try {
const response = await getApproversByRuleId(props.ruleId);
if (response && response.resultCode === 1 && response.result) {
const data = response.result;
let changeFlag = false;
// 使
if (
approvers.value.length === 0 &&
data.approvers &&
Array.isArray(data.approvers)
) {
approvers.value = data.approvers.map(normalizeTeacherData);
changeFlag = true;
}
if (
ccList.value.length === 0 &&
data.ccList &&
Array.isArray(data.ccList)
) {
ccList.value = data.ccList.map(normalizeTeacherData);
changeFlag = true;
}
if (changeFlag) {
notifyChange();
}
}
} catch (error) {
console.error("获取默认审批人失败:", error);
}
};
//
const notifyChange = () => {
emit("change", {
sprList: approvers.value,
csrList: ccList.value,
});
};
//
const handleApproverChange = (selectedTeachers: any[]) => {
if (Array.isArray(selectedTeachers)) {
// 使
const normalizedTeachers = selectedTeachers.map(normalizeTeacherData);
approvers.value.push(...normalizedTeachers);
notifyChange();
uni.showToast({
title: "审批人设置成功",
icon: "success",
});
}
};
//
const handleCcChange = (selectedTeachers: any[]) => {
if (Array.isArray(selectedTeachers)) {
// 使
const normalizedTeachers = selectedTeachers.map(normalizeTeacherData);
ccList.value.push(...normalizedTeachers);
notifyChange();
uni.showToast({
title: "抄送人设置成功",
icon: "success",
});
}
};
//
const removeApprover = (index: number) => {
uni.showModal({
title: "确认删除",
content: `确定要删除审批人"${approvers.value[index].jsxm}"吗?`,
success: (res: any) => {
if (res.confirm) {
approvers.value.splice(index, 1);
notifyChange();
uni.showToast({
title: "删除成功",
icon: "success",
});
}
},
});
};
//
const removeCc = (index: number) => {
uni.showModal({
title: "确认删除",
content: `确定要删除抄送人"${ccList.value[index].jsxm}"吗?`,
success: (res: any) => {
if (res.confirm) {
ccList.value.splice(index, 1);
notifyChange();
uni.showToast({
title: "删除成功",
icon: "success",
});
}
},
});
};
// ID
const getExcludeApproverIds = () => {
return approvers.value.map((item) => item.id).filter((id) => id);
};
// ID
const getExcludeCcIds = () => {
return ccList.value.map((item) => item.id).filter((id) => id);
};
//
onMounted(async () => {
try {
initData();
await loadDefaultApprovers();
//
await commonStore.getAllJsBasicInfoVo();
} catch (error) {
console.error("SprList组件初始化失败:", error);
}
});
</script>
<style lang="scss" scoped>
.spr-list-container {
padding: 20rpx;
.section {
margin-bottom: 40rpx;
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.add-btn {
display: flex;
align-items: center;
justify-content: flex-end;
padding: 12rpx 24rpx;
background: #ffffff;
border-radius: 8rpx;
transition: all 0.3s ease;
&:active {
background: #f5f5f5;
}
.add-icon {
color: #1890ff;
font-size: 28rpx;
margin-right: 8rpx;
font-weight: bold;
}
.add-text {
color: #1890ff;
font-size: 28rpx;
font-weight: 500;
}
}
}
.list-content {
.list-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-radius: 12rpx;
margin-bottom: 16rpx;
border: 1rpx solid #e9ecef;
transition: all 0.3s ease;
&:hover {
transform: translateY(-2rpx);
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
}
.item-info {
flex: 1;
.item-name {
font-size: 30rpx;
color: #333;
font-weight: 500;
display: block;
}
}
.item-actions {
.delete-btn {
color: #ff6b6b;
font-size: 28rpx;
padding: 8rpx 16rpx;
background: rgba(255, 107, 107, 0.1);
border-radius: 6rpx;
transition: all 0.3s ease;
&:active {
background: rgba(255, 107, 107, 0.2);
}
}
}
}
.empty-tip {
text-align: center;
padding: 60rpx 0;
color: #999;
font-size: 28rpx;
background: #f8f9fa;
border-radius: 12rpx;
border: 2rpx dashed #dee2e6;
}
}
}
}
</style>

View File

@ -1,217 +0,0 @@
<template>
<view class="js-picker">
<view class="js-selected" @click="showPicker">
<view class="js-selected-name">
<text class="data" v-if="selectedList && selectedList.length">{{ getShowSelectedName() }}</text>
<text class="data" style="color: #999;" v-else>请选择老师</text>
</view>
<uni-icons type="arrowright" size="16" color="#999"></uni-icons>
</view>
<u-popup :show="showPopup" @close="showPopup=false">
<view class="js-picker-popup">
<view class="js-picker-header">
<view class="js-cancel-btn" @click="handleCancel">取消</view>
<view class="js-picker-title">请选择教师</view>
<view class="js-ok-btn" @click="handleOk">确定</view>
</view>
<view class="js-picker-search">
<BasicSearch @change="handleSearch" :showAction="false" height="36" :placeholder="'输入教师名称查询'"/>
</view>
<view class="js-list">
<scroll-view scroll-y style="max-height: 60vh" :scroll-top="targetScrollTop">
<view class="js-item" v-for="(item, index) in jsList" :key="index"
:class="{ selected: item.selected }" @click="handleSelect(item)">
<view class="js-name">{{ item.label }}</view>
<BasicIcon type="checkmarkempty" v-if="item.selected" color="#333" />
</view>
</scroll-view>
</view>
</view>
</u-popup>
</view>
</template>
<script lang="ts" setup>
import { useCommonStore } from "@/store/modules/common";
const { getAllJs } = useCommonStore();
//
const props = withDefaults(defineProps<{
defualtValue?: any,
parentData?: any,
multiple?: boolean,
// id
excludeIds?: any
}>(), {
defualtValue: null,
parentData: null,
multiple: false,
excludeIds: []
});
// emit
const emit = defineEmits(['change'])
const targetScrollTop = ref(0); // scroll-top
const showPopup = ref(false);
const jsListAll = ref<any>([]);
const jsList = ref<any>([]);
let searchKey = "";
const selectedList = ref<any>([]);
const getShowSelectedName = () => {
return selectedList.value.map((item: any) => item.label).join(",");
};
const handleSearch = (value: any) => {
searchKey = value;
rebuildJsList();
return
};
const handleSelect = (item: any) => {
if (props.multiple) {
selectedList.value.push(item);
} else {
jsList.value.map((item: any) => item.selected = false);
selectedList.value = [item];
}
item.selected = true;
}
const handleCancel = () => {
showPopup.value = false;
}
const handleOk = () => {
showPopup.value = false;
if (props.multiple) {
emit("change", selectedList.value, props.parentData);
} else {
emit("change", selectedList.value[0], props.parentData);
}
}
const showPicker = () => {
showPopup.value = true;
};
const rebuildJsList = () => {
jsList.value = [];
jsListAll.value.map((item: any) => {
if (props.excludeIds && props.excludeIds.length && props.excludeIds.includes(item.id)) {
return;
}
// jsxm
if ( !searchKey || item.jsxm.includes(searchKey)) {
jsList.value.push({
label: item.jsxm,
value: item.id,
headPic: item.headPic
});
}
});
};
//
defineExpose({
showPicker
});
onMounted(async () => {
const res = await getAllJs()
jsListAll.value = res.result || [];
rebuildJsList();
if (!props.defualtValue) {
return;
}
if (props.multiple) {
if (!props.defualtValue.length) {
return;
}
selectedList.value = [];
for (let i = 0; i < jsListAll.value.length; i++) {
const item = jsListAll.value[i];
if (props.defualtValue.indexOf(item.value) > -1) {
item.selected = true;
selectedList.value.push(item);
} else {
item.selected = false;
}
}
} else {
for (let i = 0; i < jsList.value.length; i++) {
const item = jsList.value[i];
if (item.value === props.defualtValue) {
item.selected = true;
selectedList.value.push(item);
} else {
item.selected = false;
}
}
}
});
</script>
<style lang="scss" scoped>
.js-picker {
flex: 1;
.js-selected {
display: flex;
align-items: center;
justify-content: center;
.js-selected-name {
flex: 1;
}
}
.js-picker-popup {
min-height: 50vh;
max-height: 90vh;
padding: 10px;
display: flex;
flex-direction: column;
.js-picker-header {
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
.js-cancel-btn {
flex: 0 0 50;
padding: 5px 10px;
}
.js-ok-btn {
flex: 0 0 50;
padding: 5px 10px;
color: #3c9cff;
}
.js-picker-title {
flex: 1 0 1px;
text-align: center;
font-size: 20px;
}
}
.js-picker-search {
margin: 10px 0;
}
.js-list {
flex: 1 0 1px;
.js-item {
display: flex;
padding: 10px 15px;
border-bottom: 1px solid #eee;
&:last-child {
border-bottom: none;
}
&.selected {
background-color: beige;
}
.js-name {
flex: 1;
}
}
}
}
}
</style>

View File

@ -81,7 +81,7 @@
<view class="dk-title pl-15 pr-15"> <view class="dk-title pl-15 pr-15">
<BasicTitle line title="请假流程" :isBorder="false" /> <BasicTitle line title="请假流程" :isBorder="false" />
</view> </view>
<ProgressList :procInstId="qjData.procInstId" /> <ProgressList :qjId="qjData.id" />
</view> </view>
</view> </view>
</template> </template>

View File

@ -1,348 +1,143 @@
<template> <template>
<view class="back-f8f8f8"> <view class="js-qj-dk-edit">
<view class="flex-row items-center justify-between py-15 global-bg-color"> <view class="dk-header">
<view class="dk-title"> <text class="dk-title">代课教师</text>
<BasicTitle line title="代课明细" :isBorder="false" /> <BasicJsPicker
</view> :customTrigger="true"
<view @click="getPkkbList"> :defualtValue="[]"
<BasicIcon type="refreshempty" size="25" /> @change="handleDkJsChange"
</view> >
<template #trigger>
<button class="add-btn" type="primary" size="mini">添加代课教师</button>
</template>
</BasicJsPicker>
</view> </view>
<view class="dk-tabs mb-10" v-if="dkList && dkList.length">
<BasicTabs <view class="dk-list">
class="type-tabs" <view
ref="tabsRef" v-for="(item, index) in dkList"
:list="tabList" :key="item.id"
bar-width="60px" class="dk-item"
scroll-count="4" >
:current="curTabIndex" <view class="dk-info">
@change="switchTab" <text class="js-name">{{ item.jsxm }}</text>
/> <text class="course-info">{{ item.kcmc }} - {{ item.kcsj }}</text>
<view class="dk-card" v-if="curTabIndex === 0">
<view class="card-body">
<view class="info-row">
<text class="label">代课老师:</text>
<view class="value">
<JsPicker
@change="changeJsByTy"
:parent-data="tyDk"
:defualtValue="tyDk.dkJsId"
:multiple="false"
:excludeIds="excludeIds"
/>
</view>
</view>
</view> </view>
</view>
<view v-if="curTabIndex === 1"> <view class="dk-actions">
<view v-for="(item, index) in kmDkList" :key="index"> <button
<view class="dk-card" style="margin: 0"> class="remove-btn"
<view class="card-body"> size="mini"
<view class="info-row"> @click="removeDkJs(index)"
<text class="label">排课名称:</text> >
<text class="value">{{ item.pkMc }}</text> 删除
</view> </button>
<view class="info-row">
<text class="label">代课老师:</text>
<view class="value">
<JsPicker
@change="changeJsByKm"
:parent-data="item"
:defualtValue="item.dkJsId"
:multiple="false"
:excludeIds="excludeIds"
/>
</view>
</view>
</view>
</view>
</view> </view>
</view> </view>
</view> </view>
<view v-if="dkList.length > 0">
<view v-for="(item, index) in dkList" :key="index">
<view class="dk-card">
<view class="card-header">
<text class="applicant-name"
>{{ item.dktime }}{{ wdNameList[item.xq - 1] }}{{
item.jcmc
}}</text
>
</view>
<view class="card-body">
<view class="info-row">
<text class="label">排课名称:</text>
<text class="value">{{ item.pkMc }}</text>
</view>
<view class="info-row">
<text class="label">上课时间:</text>
<text class="value">{{ item.startTime }}-{{ item.endTime }}</text>
</view>
<view class="info-row">
<text class="label">代课老师:</text>
<view class="value" v-if="curTabIndex === 2">
<JsPicker
@change="changeJs"
:parent-data="item"
:defualtValue="item.dkJsId"
:multiple="false"
:excludeIds="excludeIds"
/>
</view>
<view v-else>{{ item.dkJsName }}</view>
</view>
</view>
</view>
</view>
</view>
<view v-else class="p-15 flex-row-center color-9 font-13 white-bg-color"
>暂无数据</view
>
</view> </view>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { getPkkbByJsRangeTimeApi } from "@/api/base/jsQjApi"; import { ref, watch } from 'vue'
import JsPicker from "@/pages/components/JsPicker/index.vue"; import BasicJsPicker from '@/components/BasicJsPicker/Picker.vue'
import { useUserStore } from "@/store/modules/user";
const { getJs } = useUserStore();
// // Props
const props = withDefaults( const props = defineProps({
defineProps<{ modelValue: {
data: any; type: Array,
}>(), default: () => []
{
data: () => ({
jsId: "",
qjkstime: "", //
qjjstime: "", //
}),
} }
); })
const wdNameList = ref<string[]>([ // Emits
"周一", const emit = defineEmits(['update:modelValue'])
"周二",
"周三",
"周四",
"周五",
"周六",
"周日",
]);
const jsTypeMc: any = { //
ZAM: "早自习", const dkList = ref<any[]>([])
AM: "上午",
PM: "下午",
};
// //
const excludeIds = ref<any>([]); const handleDkJsChange = (selectedItems: any[]) => {
console.log(selectedItems)
if (props.data && props.data.jsId && props.data.jsId.length) { if (Array.isArray(selectedItems)) {
excludeIds.value.push(props.data.jsId); dkList.value = selectedItems
} else { emit('update:modelValue', selectedItems)
excludeIds.value.push(getJs.id); }
} }
const tyDk = ref<any>({}); //
const removeDkJs = (index: number) => {
dkList.value.splice(index, 1)
emit('update:modelValue', dkList.value)
}
const dkList = ref<any>([]); // modelValue
watch(() => props.modelValue, (newVal) => {
const kmDkList = ref<any>([]); if (Array.isArray(newVal)) {
dkList.value = newVal
const tabList = ref([
{ name: "统一设置", id: "tab-ty" },
{ name: "按排课设置", id: "tab-pk" },
{ name: "按节次设置", id: "tab-jc" },
]);
const curTabIndex = ref(0);
const switchTab = (index: number) => {
curTabIndex.value = index;
};
const getPkkbList = async () => {
const res = await getPkkbByJsRangeTimeApi({
jsId: props.data.jsId,
startTime: props.data.qjkstime,
endTime: props.data.qjjstime,
});
//
const srcData: any = {};
dkList.value.map((item: any) => {
const key = item.dktime + item.jcType + item.jc;
srcData[key] = {
dkJsId: item.dkJsId,
dkJsName: item.dkJsName,
};
});
const kmMap: any = {};
dkList.value = res.result.map((item: any) => {
item.dktime = item.kbtime.split(" ")[0];
item.njbjmx = item.bc + item.bjmc;
item.jcmc = jsTypeMc[item.jcType] + "第" + item.jc + "节";
const key = item.dktime + item.jcType + item.jc;
const src = srcData[key];
if (src) {
item.dkJsId = src.dkJsId;
item.dkJsName = src.dkJsName;
} else {
item.dkJsId = "";
item.dkJsName = "";
}
kmMap[item.pkId] = kmMap[item.pkId] || {
pkMc: item.pkMc,
pkId: item.pkId,
};
return item;
});
// kmMapvalue
kmDkList.value = Object.values(kmMap);
};
const changeJsByTy = (selected: any, item: any) => {
item.dkJsId = selected.value;
item.dkJsName = selected.label;
const newList = dkList.value.map((dk: any) => {
return {
...dk,
dkJsId: item.dkJsId,
dkJsName: item.dkJsName,
};
});
dkList.value = newList;
const newKmList = kmDkList.value.map((km: any) => {
return {
...km,
dkJsId: item.dkJsId,
dkJsName: item.dkJsName,
};
});
kmDkList.value = newKmList;
};
const changeJsByKm = (selected: any, item: any) => {
item.dkJsId = selected.value;
item.dkJsName = selected.label;
//
const newList = dkList.value.map((dk: any) => {
if (dk.pkId === item.pkId) {
return { ...dk, dkJsId: item.dkJsId, dkJsName: item.dkJsName };
}
return dk;
});
dkList.value = newList;
};
const changeJs = (selected: any, item: any) => {
item.dkJsId = selected.value;
item.dkJsName = selected.label;
};
const validate = async () => {
//
if (!dkList.value || !dkList.value.length) {
await getPkkbList();
} }
const list = dkList.value; }, { deep: true, immediate: true })
// true
if (!list || !list.length) {
return true;
}
//
for (let i = 0; i < list.length; i++) {
const item = list[i];
if (!item.dkJsId) {
return false;
}
}
return true;
};
// ref
const getDkList = () => {
return dkList.value;
};
//
defineExpose({
getPkkbList,
validate,
getDkList,
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.dk-tabs { .js-qj-dk-edit {
flex: 1 0 1px; .dk-header {
}
.dk-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; display: flex;
margin-bottom: 10px; justify-content: space-between;
align-items: center;
&:last-child { margin-bottom: 20rpx;
margin-bottom: 0;
} .dk-title {
font-size: 28rpx;
.label { font-weight: bold;
font-size: 14px;
color: #666;
width: 70px;
flex-shrink: 0;
margin-right: 8px;
}
.value {
font-size: 14px;
color: #333; color: #333;
flex: 1; }
.add-btn {
background: #007aff;
color: #fff;
border: none;
border-radius: 6rpx;
}
}
.dk-list {
.dk-item {
display: flex; display: flex;
.data { justify-content: space-between;
align-items: center;
padding: 20rpx;
background: #f8f9fa;
border-radius: 8rpx;
margin-bottom: 16rpx;
.dk-info {
flex: 1; flex: 1;
.js-name {
display: block;
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 8rpx;
}
.course-info {
display: block;
font-size: 24rpx;
color: #666;
}
}
.dk-actions {
.remove-btn {
background: #ff3b30;
color: #fff;
border: none;
border-radius: 6rpx;
}
} }
} }
} }
} }
</style>
.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;
}
}
</style>

View File

@ -1,251 +1,233 @@
<template> <template>
<BasicLayout :fixed="false"> <view class="js-qj-edit">
<view class="p-15"> <uni-forms ref="formRef" :model="formData" :rules="formRules">
<BasicForm @register="register"> <!-- 基本信息 -->
<template #dkmx> <view class="form-section">
<JsQjDkEdit :data="formData" ref="dkRef" v-if="formData.dkfs === 0" /> <view class="section-title">基本信息</view>
</template> <uni-forms-item label="请假类型" name="qjlx">
</BasicForm> <uni-data-select
</view> v-model="formData.qjlx"
<template #bottom> :localdata="qjlxOptions"
<view class="white-bg-color py-5"> placeholder="请选择请假类型"
<view class="flex-row items-center pb-10 pt-5"> />
<u-button text="取消" class="ml-15 mr-7" :plain="true" @click="navigateBack" /> </uni-forms-item>
<u-button text="提交" class="mr-15 mr-7" type="primary" @click="submit" />
<uni-forms-item label="请假开始时间" name="qjkstime">
<uni-datetime-picker
v-model="formData.qjkstime"
type="datetime"
placeholder="请选择开始时间"
/>
</uni-forms-item>
<uni-forms-item label="请假结束时间" name="qjjstime">
<uni-datetime-picker
v-model="formData.qjjstime"
type="datetime"
placeholder="请选择结束时间"
/>
</uni-forms-item>
<uni-forms-item label="请假原因" name="qjyy">
<uni-easyinput
v-model="formData.qjyy"
type="textarea"
placeholder="请输入请假原因"
/>
</uni-forms-item>
</view>
<!-- 代课信息 -->
<view class="form-section">
<view class="section-title">代课信息</view>
<uni-forms-item label="代课方式" name="dkfs">
<uni-data-select
v-model="formData.dkfs"
:localdata="dkfsOptions"
placeholder="请选择代课方式"
@change="handleDkfsChange"
/>
</uni-forms-item>
<view v-if="formData.dkfs === '0'">
<JsQjDkEdit v-model="formData.dkList" />
</view> </view>
</view> </view>
</template>
</BasicLayout> <!-- 审批信息 -->
<view class="form-section">
<view class="section-title">审批信息</view>
<BasicSpCsMgr
:qjId="qjId"
v-model:approvers="formData.sprList"
v-model:ccPersons="formData.csrList"
/>
</view>
<!-- 操作按钮 -->
<view class="form-actions">
<button type="primary" @click="handleSubmit">提交申请</button>
<button @click="handleSave">保存草稿</button>
</view>
</uni-forms>
</view>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import JsQjDkEdit from "./jsQjDkEdit.vue" import { ref, reactive, onMounted, watch } from 'vue'
import { navigateBack } from "@/utils/uniapp"; import { useCommonStore } from '@/store/modules/common'
import { useForm } from "@/components/BasicForm/hooks/useForm"; import BasicSpCsMgr from '@/components/BasicSpCsMgr/index.vue'
import { jsQjSqApi, jsQjCxtjApi } from "@/api/base/jsQjApi"; import JsQjDkEdit from './jsQjDkEdit.vue'
import { showToast } from "@/utils/uniapp"; import { findQjByIdApi, jsQjSqApi } from '@/api/base/jsQjApi'
import dayjs from "dayjs";
import { useUserStore } from "@/store/modules/user";
import { useDicStore } from "@/store/modules/dic";
const { getJs, getUser } = useUserStore();
const { findByPid } = useDicStore();
// // Props
const props = withDefaults(defineProps<{ const props = defineProps({
data?: any qjId: {
}>(), { type: String,
data: () => ({ default: ''
id: "", }
qjlx: "", })
qjkstime: "",
qjjstime: "",
qjsc: "",
qjsy: "",
dkfs: 0,
})
});
const dkRef = ref<any>(null); // Emits
const emit = defineEmits(['submitSuccess'])
let formData = ref<any>({ // Store
...props.data, const commonStore = useCommonStore()
jsId: getJs.id,
});
if (typeof props.data.dkfs === "string") { //
nextTick(() => { const formRef = ref(null)
formData.value.dkfs = parseInt(props.data.dkfs); const formData = reactive({
}) qjlx: '',
qjkstime: '',
qjjstime: '',
qjyy: '',
dkfs: '',
dkList: [],
sprList: [],
csrList: []
})
//
const formRules = {
qjlx: {
rules: [{ required: true, errorMessage: '请选择请假类型' }]
},
qjkstime: {
rules: [{ required: true, errorMessage: '请选择开始时间' }]
},
qjjstime: {
rules: [{ required: true, errorMessage: '请选择结束时间' }]
},
qjyy: {
rules: [{ required: true, errorMessage: '请输入请假原因' }]
},
dkfs: {
rules: [{ required: true, errorMessage: '请选择代课方式' }]
}
} }
const [register, { setValue, getValue }] = useForm({ //
schema: [ const qjlxOptions = [
{ { value: '病假', text: '病假' },
field: "qjlx", { value: '事假', text: '事假' },
label: "请假类型", { value: '其他', text: '其他' }
required: true, ]
component: "BasicPicker",
componentProps: {
api: findByPid,
param: { pid: 1007011432 },
rangeKey: "dictionaryValue",
savaKey: "dictionaryCode",
},
},
{
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: "请输入选择开始时间和结束时间"
},
},
{
field: "qjsy",
label: "请假事由",
component: "BasicInput",
required: true,
itemProps: {
labelPosition: "top",
},
componentProps: {
type: "textarea",
},
},
// {
// field: "qjtp",
// label: "",
// component: "BasicUpload",
// required: true,
// itemProps: {
// labelPosition: "top",
// },
// componentProps: {},
// },
{ interval: true },
{
field: "dkfs",
label: "代课方式",
component: "BasicCheckbox",
required: true,
itemProps: {
labelPosition: "top",
},
componentProps: {
data: [
{ value: 0, text: "自行协调" },
{ value: 1, text: "教科处协调" },
{ value: 2, text: "无须代课" },
],
change: (value: any) => {
formData.value.dkfs = value;
updateDk();
},
},
},
{ colSlot: "dkmx" },
],
});
const changeKsTime = (selectedTime?: string) => { const dkfsOptions = [
if (!selectedTime) { { value: '0', text: '自行协调' },
return; { value: '1', text: '教务处协调' },
} { value: '2', text: '无需代课' }
formData.value.qjkstime = selectedTime; ]
validateTime();
};
const changeJsTime = (selectedTime?: string) => { //
if (!selectedTime) { const handleDkfsChange = (value: string) => {
return; if (value !== '0') {
formData.dkList = []
} }
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 });
updateDk();
return true;
} }
const updateDk = () => { //
if (formData.value.dkfs === 0) { const loadQjDetail = async () => {
nextTick(() => { if (!props.qjId) return
dkRef.value.getPkkbList();
}) try {
} const res = await findQjByIdApi(props.qjId)
}; if (res && res.result) {
Object.assign(formData, res.result)
//
setValue(props.data)
updateDk();
const submit = async () => {
const fd = await getValue();
if (!validateTime()) {
return;
}
const params = { ...fd };
if (fd.dkfs === 0) {
const flag = await dkRef.value.validate();
if (!flag) {
uni.showToast({
title: "请选择代课教师",
icon: "none",
});
return;
}
let dkList = dkRef.value.getDkList() || [];
if (dkList.length) {
params.dkList = dkList.map((item: any) => {
const newItem = {...item};
newItem.jsId = item.dkJsId;
newItem.jsName = item.dkJsName;
newItem.pkkbId = item.id;
newItem.dktime = item.dktime + " 00:00:00";
newItem.id = "";
newItem.qjId = "";
return newItem;
});
} else {
params.dkList = [];
} }
} catch (error) {
console.error('加载请假详情失败:', error)
commonStore.showToast('加载详情失败')
} }
let submitApi = jsQjSqApi; }
if (props.data && props.data.id) {
params.id = props.data.id;
submitApi = jsQjCxtjApi
} else {
params.id = null;
params.jsId = getJs.id;
params.jsName = getJs.jsxm;
}
uni.showLoading({ title: "提交中..." });
await submitApi(params).then(() => {
showToast({ title: "提交成功", icon: "success" });
uni.reLaunch({
url: "/pages/base/service/index"
});
});
uni.hideLoading();
};
//
const handleSubmit = async () => {
try {
await formRef.value.validate()
const submitData = {
...formData,
jsId: commonStore.userInfo.id
}
await jsQjSqApi(submitData)
commonStore.showToast('提交成功')
emit('submitSuccess')
} catch (error) {
console.error('提交失败:', error)
commonStore.showToast('提交失败')
}
}
//
const handleSave = async () => {
try {
// 稿
commonStore.showToast('保存成功')
} catch (error) {
console.error('保存失败:', error)
commonStore.showToast('保存失败')
}
}
// qjId
watch(() => props.qjId, (newVal) => {
if (newVal) {
loadQjDetail()
}
})
onMounted(() => {
if (props.qjId) {
loadQjDetail()
}
})
</script> </script>
<style lang="scss" scoped>
.js-qj-edit {
padding: 20rpx;
.form-section {
margin-bottom: 30rpx;
.section-title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 20rpx;
color: #333;
}
}
.form-actions {
display: flex;
gap: 20rpx;
margin-top: 40rpx;
button {
flex: 1;
}
}
}
</style>

View File

@ -1,63 +1,163 @@
<template> <template>
<view class="approval-progress"> <view class="approval-progress">
<view class="progress-title"> <view class="progress-title">
<!-- <image src="/static/icons/approval.png" class="title-icon"></image> --> <text class="applicant-name">审批进度</text>
<text class="applicant-name">请假流程进度</text>
</view> </view>
<view class="divider"></view> <view class="divider"></view>
<view class="progress-list"> <view class="progress-list">
<view class="progress-item" v-for="(task, index) in taskList" :key="index"> <view class="progress-item" v-for="(approver, index) in approvalList" :key="index">
<view class="progress-item-row"> <view class="progress-item-row">
<view class="item-avatar"> <view class="item-avatar">
<image <image
:src="task.avatar || '/static/base/home/user-5-line.png'" :src="approver.avatar || '/static/base/home/user-5-line.png'"
class="w-full h-full" class="w-full h-full"
></image> ></image>
</view> </view>
<view class="item-middle"> <view class="item-middle">
<text class="item-name">{{ task.name }}</text> <text class="item-name">{{ approver.userName }}</text>
<text class="item-detail">{{ task.assignee || '' }}</text> <text class="item-detail">{{ getSpTypeText(approver.spType) }}</text>
</view> </view>
<view class="item-right"> <view class="item-right">
<text class="item-time" v-if="task.endTime">{{ task.endTime }}</text> <text class="item-time" v-if="approver.approveTime">{{ formatTime(approver.approveTime) }}</text>
<text class="item-status" v-else>待处理</text> <text class="item-status" :class="getStatusClass(approver.approveStatus)">
{{ getStatusText(approver.approveStatus) }}
</text>
</view> </view>
</view> </view>
<view class="progress-item-line" v-if="index < taskList.length - 1"></view> <view class="progress-item-line" v-if="index < approvalList.length - 1"></view>
</view> </view>
</view> </view>
</view> </view>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { getQjActivitiHistoryApi } from "@/api/base/jsQjApi"; import dayjs from "dayjs";
import { getJsQjApprovalProcessApi } from "@/api/base/jsQjApi";
// //
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
procInstId: string qjId: string
}>(), { }>(), {
procInstId: '' qjId: ''
}); });
// //
const taskList = ref<any[]>([]); const approvalList = ref<any[]>([]);
const loadList = async (procInstId: string) => { //
const res = await getQjActivitiHistoryApi({ processInstanceId: procInstId }); const loadApprovalProcess = async () => {
const list = res.result || []; if (!props.qjId) return;
// list
taskList.value = list.reverse(); try {
} // API
const res = await getJsQjApprovalProcessApi(props.qjId, 'JS_QJ');
if (res.resultCode === 1 && res.result) {
// sort
approvalList.value = res.result.map((item: any) => ({
userName: item.userName || getDefaultUserName(item.spType),
spType: item.spType,
approveStatus: item.approveStatus,
approveTime: item.approveTime,
approveRemark: item.approveRemark,
avatar: item.avatar || '/static/base/home/user-5-line.png'
}));
} else {
loadMockData();
}
} catch (error) {
console.error('获取审批流程失败:', error);
// API使
loadMockData();
}
};
watch(() => props.procInstId, (newVal, oldVal) => { //
// const getDefaultUserName = (spType: string) => {
console.log('procInstId changed:', newVal, oldVal); switch (spType) {
case 'SQ': return '教师';
case 'SP': return '审批人';
case 'CC': return '抄送人';
case 'DK': return '代课老师';
case 'JWC': return '教科处';
default: return '未知';
}
};
//
const loadMockData = () => {
const mockData = [
{
userName: '教师',
spType: 'SQ',
approveStatus: 'approved',
approveTime: new Date(),
approveRemark: '申请人提交',
avatar: '/static/base/home/user-5-line.png'
},
{
userName: '审批人',
spType: 'SP',
approveStatus: 'pending',
approveTime: null,
approveRemark: '待审批',
avatar: '/static/base/home/user-5-line.png'
}
];
approvalList.value = mockData;
};
//
const getSpTypeText = (spType: string) => {
switch (spType) {
case 'SQ': return '申请人';
case 'SP': return '审批人';
case 'CC': return '抄送人';
case 'DK': return '代课老师';
case 'JWC': return '教科处';
default: return '';
}
};
//
const getStatusText = (status: string) => {
switch (status) {
case 'apply': return '已申请';
case 'pending': return '待处理';
case 'approved': return '已同意';
case 'rejected': return '已拒绝';
case 'cc_sent': return '已抄送';
default: return '未知';
}
};
//
const getStatusClass = (status: string) => {
switch (status) {
case 'pending': return 'status-pending';
case 'approved': return 'status-approved';
case 'rejected': return 'status-rejected';
case 'cc_sent': return 'status-cc';
default: return '';
}
};
//
const formatTime = (time: string | Date) => {
if (!time) return '';
return dayjs(time).format('YYYY-MM-DD HH:mm');
};
// qjId
watch(() => props.qjId, (newVal) => {
if (newVal) { if (newVal) {
loadList(newVal); loadApprovalProcess();
} }
}); });
if (props.procInstId) { //
loadList(props.procInstId); if (props.qjId) {
loadApprovalProcess();
} }
</script> </script>
@ -75,12 +175,6 @@ if (props.procInstId) {
color: #333; color: #333;
margin-bottom: 10px; margin-bottom: 10px;
.title-icon {
width: 24px;
height: 24px;
margin-right: 8px;
}
.applicant-name { .applicant-name {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
@ -99,51 +193,88 @@ if (props.procInstId) {
flex-direction: column; flex-direction: column;
.progress-item { .progress-item {
.progress-item-row { position: relative;
.progress-item-row {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 10px 0;
.item-avatar { .item-avatar {
flex: 0 0 40px; width: 40px;
height: 40px; height: 40px;
border-radius: 50%;
overflow: hidden;
margin-right: 12px;
flex-shrink: 0;
} }
.item-middle { .item-middle {
margin-left: 16px; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.item-name {
font-size: 14px;
color: #333;
font-weight: 500;
margin-bottom: 4px;
}
.item-detail {
font-size: 12px;
color: #999;
}
} }
.item-right { .item-right {
flex: 1 0 1px;
display: flex; display: flex;
flex-direction: column;
align-items: flex-end; align-items: flex-end;
.item-time {
font-size: 12px;
color: #999;
margin-bottom: 4px;
}
.item-status {
font-size: 12px;
padding: 2px 8px;
border-radius: 10px;
&.status-pending {
background-color: #fff7e6;
color: #fa8c16;
}
&.status-approved {
background-color: #f6ffed;
color: #52c41a;
}
&.status-rejected {
background-color: #fff2f0;
color: #ff4d4f;
}
&.status-cc {
background-color: #f0f5ff;
color: #1890ff;
}
}
} }
} }
.progress-item-line { .progress-item-line {
width: 1px; height: 20px;
height: 30px; width: 2px;
background-color: #999; background-color: #e8e8e8;
margin: 10px 20px; margin-left: 19px;
margin-top: -10px;
margin-bottom: -10px;
} }
} }
.item-name {
font-size: 16px;
font-weight: bold;
}
.item-detail {
font-size: 14px;
color: #666;
}
.item-time,
.item-status {
font-size: 14px;
color: #999;
margin-left: auto;
}
} }
} }
</style> </style>

View File

@ -1,81 +1,76 @@
<template> <template>
<view class="leave-page"> <BasicLayout>
<JsQjEdit :data="qjData" v-if="qjData.id" /> <view class="p-15">
</view> <view class="info-card">
<view class="card-header">
<text class="card-title">重新提交请假申请</text>
</view>
<view class="card-content">
<text class="info-text">您的请假申请已被驳回请修改后重新提交</text>
</view>
</view>
<!-- 使用请假申请组件 -->
<JsQjEdit :data="qjData" />
</view>
</BasicLayout>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { findQjById } from "@/api/base/jsQjApi"; import { findQjById } from "@/api/base/jsQjApi";
import { xxtsFindByIdApi } from "@/api/base/server";
import { useDataStore } from "@/store/modules/data";
import { useUserStore } from "@/store/modules/user";
import { onLoad } from "@dcloudio/uni-app"; import { onLoad } from "@dcloudio/uni-app";
import { ref, computed, nextTick } from "vue"; import { ref } from "vue";
import JsQjEdit from "./components/jsQjEdit.vue"; import JsQjEdit from "./components/jsQjEdit.vue";
const { getJs, loginByOpenId } = useUserStore(); const qjId = ref<string>();
const { setData, getData, setXxts, getXxts } = useDataStore(); const qjData = ref<any>({});
const dbFlag = ref(false); //
const loadQjData = async () => {
const qjData = computed(() => getData); try {
const result = await findQjById({ id: qjId.value });
onLoad(async (data: any) => { if (result.code === 1) {
// qjData.value = result.data;
if (data && data.from && data.from == "db") {
dbFlag.value = true;
//
const isLoggedIn = await loginByOpenId(data.openId);
if (!isLoggedIn) {
console.log("用户未登录,跳过处理");
return;
} }
} catch (error) {
try { console.error('获取请假信息失败:', error);
// urlidXxts }
const xxtsRes = await xxtsFindByIdApi({ id: data.id }); };
if (xxtsRes && xxtsRes.result) {
const xxts = xxtsRes.result; onLoad((options) => {
if (options.qjId) {
// qjId.value = options.qjId;
if (xxts.dbZt === "B") { loadQjData();
setData({ id: xxts.xxzbId }); // 使ID
let url = "/pages/view/hr/jsQj/detail";
uni.navigateTo({ url });
return;
}
setXxts(xxts);
// ID
const res = await findQjById({ id: xxts.xxzbId });
nextTick(() => {
setData(res.result);
});
}
} catch (error) {
console.error("获取待办信息失败", error);
// Xxts退
const xxtsData = getXxts();
if (xxtsData && xxtsData.dbZt === "B") {
setXxts(xxtsData);
let url = "/pages/view/hr/jsQj/detail";
uni.navigateTo({ url });
return;
}
const res = await findQjById({ id: data.id });
nextTick(() => {
setData(res.result);
});
}
} else {
dbFlag.value = false;
} }
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.leave-page { .info-card {
height: 100vh; background: white;
border-radius: 8px;
margin-bottom: 15px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.card-header {
padding: 15px;
border-bottom: 1px solid #eee;
.card-title {
font-size: 16px;
font-weight: bold;
color: #333;
}
}
.card-content {
padding: 15px;
.info-text {
font-size: 14px;
color: #666;
line-height: 1.5;
}
} }
</style> </style>

View File

@ -1,321 +1,250 @@
<template> <template>
<BasicLayout> <BasicLayout>
<!-- 选项卡 --> <view class="p-15">
<BasicTabs
class="leave-tabs"
ref="tabsRef"
:list="tabList"
bar-width="60px"
scroll-count="4"
:current="curTabIndex"
@change="switchTab"
/>
<view v-if="curTabIndex === 0">
<!-- 请假信息卡片 --> <!-- 请假信息卡片 -->
<view class="info-card" v-for="(item, index) in dkList" :key="index"> <view class="info-card">
<view class="card-header"> <view class="card-header">
<text class="applicant-name" <text class="applicant-name">教师{{ qjData.jsName }}的请假申请</text>
>{{ item.dktime }}{{ item.xqLabel }}{{ item.jcmc }}</text
>
</view> </view>
<view class="divider"></view> <view class="card-content">
<view class="card-body">
<view class="info-row">
<text class="label">排课名称:</text>
<text class="value">{{ item.pkName }}</text>
</view>
<view class="info-row">
<text class="label">上课时间:</text>
<text class="value">{{ item.startTime }}-{{ item.endTime }}</text>
</view>
<view class="info-row"> <view class="info-row">
<text class="label">请假老师:</text> <text class="label">请假老师:</text>
<view class="value">{{ qjData.jsName }}</view> <text class="value">{{ qjData.jsName }}</text>
</view> </view>
<view class="info-row">
<text class="label">请假类型:</text>
<text class="value">{{ qjData.qjlx }}</text>
</view>
<view class="info-row">
<text class="label">请假时间:</text>
<text class="value">{{ qjData.qjkstime }} {{ qjData.qjjstime }}</text>
</view>
<view class="info-row">
<text class="label">请假时长:</text>
<text class="value">{{ qjData.qjsc }}</text>
</view>
<view class="info-row">
<text class="label">请假事由:</text>
<text class="value">{{ qjData.qjsy }}</text>
</view>
</view>
</view>
<!-- 代课信息 -->
<view class="dk-info-card">
<view class="card-header">
<text class="card-title">代课信息</text>
</view>
<view class="card-content">
<view class="info-row">
<text class="label">代课时间:</text>
<text class="value">{{ dkData.dktime }}</text>
</view>
<view class="info-row">
<text class="label">课程名称:</text>
<text class="value">{{ dkData.kcmc }}</text>
</view>
<view class="info-row">
<text class="label">班级:</text>
<text class="value">{{ dkData.bjmc }}</text>
</view>
</view>
</view>
<!-- 确认操作 -->
<view class="action-section">
<view class="action-title">确认代课</view>
<view class="action-buttons">
<u-button
text="拒绝代课"
type="error"
@click="handleReject"
class="action-btn"
/>
<u-button
text="确认代课"
type="primary"
@click="handleConfirm"
class="action-btn"
/>
</view> </view>
</view> </view>
</view> </view>
<JsQjDetail
v-show="curTabIndex === 1"
:qjId="qjId"
:dbFlag="dbFlag"
v-if="qjId && qjId.length"
@loadQjData="loadQjData"
@loadDkList="loadDkList"
/>
<!-- 驳回弹窗 -->
<u-popup
:show="dlgFlag"
mode="center"
:closeOnClickOverlay="false"
@close="closeDlg"
>
<view class="popup-content">
<view class="popup-title">驳回原因</view>
<u-input
v-model="rejectReason"
type="textarea"
placeholder="请填写驳回原因"
:autoHeight="true"
maxlength="200"
/>
<view class="popup-actions flex-row justify-end mt-4">
<u-button class="mr-2" @click="closeDlg">取消</u-button>
<u-button type="primary" @click="handleReject">确定</u-button>
</view>
</view>
</u-popup>
<template #bottom>
<view class="white-bg-color py-5">
<view class="divider"></view>
<view class="flex-row items-center pb-10 pt-5">
<u-button
text="驳回"
class="ml-15 mr-7"
:plain="true"
@click="showDlg"
/>
<u-button
text="同意"
class="mr-15 mr-7"
type="primary"
@click="submit"
/>
</view>
</view>
</template>
</BasicLayout> </BasicLayout>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { jsQjDkQrApi } from "@/api/base/jsQjApi"; import { jsQjDkQrApi } from "@/api/base/jsQjApi";
import { xxtsFindByIdApi } from "@/api/base/server"; import { findQjById, findDkByIdApi } from "@/api/base/jsQjApi";
import { useDataStore } from "@/store/modules/data";
import { useUserStore } from "@/store/modules/user"; import { useUserStore } from "@/store/modules/user";
import { navigateBack } from "@/utils/uniapp"; import { navigateBack } from "@/utils/uniapp";
import { onLoad } from "@dcloudio/uni-app"; import { onLoad } from "@dcloudio/uni-app";
import { ref } from "vue"; import { ref } from "vue";
import JsQjDetail from "./components/jsQjDetail.vue";
const { getJs, loginByOpenId } = useUserStore();
const { setData, getData, setXxts, getXxts } = useDataStore();
const dkList = ref<any>([]); const { getJs } = useUserStore();
const qjData = ref<any>({});
const dbFlag = ref(false);
const qjId = ref<string>(); const qjId = ref<string>();
const dkId = ref<string>(); const dkId = ref<string>();
const qjData = ref<any>({});
const dkData = ref<any>({});
const dlgFlag = ref(false); //
const rejectReason = ref(""); const loadQjData = async () => {
try {
const tabList = ref([ const result = await findQjById({ id: qjId.value });
{ name: "当前代课", id: "dk-info" }, if (result.code === 1) {
{ name: "请假信息", id: "qj-info" }, qjData.value = result.data;
]);
const curTabIndex = ref(1);
const switchTab = (index: number) => {
curTabIndex.value = index;
};
const loadQjData = (data: any) => {
qjData.value = data;
};
const loadDkList = (data: any) => {
// dbGlId
if (!getXxts.xxglId) {
getXxts.xxglId = "";
}
const idArr = getXxts.xxglId.split(",");
dkList.value = [];
data.map((dk: any) => {
if (idArr.includes(String(dk.id))) {
dkList.value.push(dk);
} }
}); } catch (error) {
switchTab(0); console.error('获取请假信息失败:', error);
}
}; };
const submit = async () => { //
const loadDkData = async () => {
try {
const result = await findDkByIdApi({ id: dkId.value });
if (result.code === 1) {
dkData.value = result.data;
}
} catch (error) {
console.error('获取代课信息失败:', error);
}
};
//
const handleConfirm = async () => {
const params = { const params = {
qjId: qjId.value, qjId: qjId.value,
jsId: getJs.id, jsId: getJs.id,
qrStatus: 2, qrStatus: "1", // 1
qrYj: "同意", remark: "确认代课"
}; };
uni.showLoading({ title: "确认代课中..." });
await jsQjDkQrApi(params); uni.showLoading({ title: "确认中..." });
uni.hideLoading();
navigateBack();
};
const showDlg = () => {
dlgFlag.value = true;
};
const closeDlg = () => {
dlgFlag.value = false;
};
//
const handleReject = async () => {
if (!rejectReason.value.trim()) {
uni.showToast({ title: "请填写驳回意见", icon: "none" });
return;
}
const params: any = {
qjId: qjId.value,
jsId: getJs.id,
qrStatus: 1, // 1
qrYj: rejectReason.value,
};
uni.showLoading({ title: "正在驳回..." });
try { try {
await jsQjDkQrApi(params); await jsQjDkQrApi(params);
uni.hideLoading(); uni.showToast({ title: "确认成功", icon: "success" });
uni.showToast({ title: "已驳回", icon: "success" }); navigateBack();
closeDlg(); } catch (error) {
setTimeout(() => { uni.showToast({ title: "确认失败", icon: "error" });
navigateBack();
}, 500);
} catch (e) {
uni.hideLoading();
} }
uni.hideLoading();
}; };
onLoad(async (data: any) => { //
// const handleReject = async () => {
if (data && data.from && data.from == "db") { uni.showModal({
dbFlag.value = true; title: "确认拒绝",
content: "确定要拒绝代课吗?",
// success: async (res) => {
const isLoggedIn = await loginByOpenId(data.openId); if (res.confirm) {
if (!isLoggedIn) { const params = {
console.log("用户未登录,跳过处理"); qjId: qjId.value,
return; jsId: getJs.id,
} qrStatus: "2", // 2
remark: "拒绝代课"
try { };
// urlidXxts
const xxtsRes = await xxtsFindByIdApi({ id: data.id });
if (xxtsRes && xxtsRes.result) {
const xxts = xxtsRes.result;
// uni.showLoading({ title: "处理中..." });
if (xxts.dbZt === "B") { try {
setData({ id: xxts.xxzbId }); await jsQjDkQrApi(params);
let url = "/pages/view/hr/jsQj/detail"; uni.showToast({ title: "已拒绝", icon: "success" });
uni.navigateTo({ url }); navigateBack();
return; } catch (error) {
uni.showToast({ title: "操作失败", icon: "error" });
} }
setXxts(xxts); uni.hideLoading();
// 使IDID
qjId.value = xxts.xxzbId;
dkId.value = xxts.xxglId;
}
} catch (error) {
console.error("获取待办信息失败", error);
// Xxts退
qjId.value = data.id;
dkId.value = data.dkId;
const xxtsData = getXxts();
if (xxtsData && xxtsData.dbZt === "B") {
setData({ id: data.id });
let url = "/pages/view/hr/jsQj/detail";
uni.navigateTo({ url });
return;
} }
} }
} else { });
qjId.value = getData.id; };
dbFlag.value = false;
onLoad((options) => {
if (options.qjId) {
qjId.value = options.qjId;
}
if (options.dkId) {
dkId.value = options.dkId;
}
//
if (qjId.value) {
loadQjData();
}
if (dkId.value) {
loadDkData();
} }
}); });
</script> </script>
<style scoped> <style lang="scss" scoped>
.popup-content { .info-card,
width: 80vw; .dk-info-card {
background: #fff; background: white;
border-radius: 12px;
padding: 24px 16px 16px 16px;
}
.popup-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 12px;
}
.popup-actions {
margin-top: 16px;
}
.info-card {
margin: 15px;
background-color: #fff;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); margin-bottom: 15px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.card-header { .card-header {
padding: 15px;
border-bottom: 1px solid #eee;
.applicant-name,
.card-title {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
color: #333; color: #333;
padding: 15px;
.applicant-name {
font-size: 16px;
font-weight: bold;
color: #333;
}
} }
}
.divider { .card-content {
height: 1px; padding: 15px;
background-color: #eee; }
.info-row {
display: flex;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
} }
.label {
width: 80px;
font-size: 14px;
color: #666;
flex-shrink: 0;
}
.value {
flex: 1;
font-size: 14px;
color: #333;
}
}
.card-body { .action-section {
padding: 15px; background: white;
border-radius: 8px;
.info-row { padding: 15px;
display: flex; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: 10px;
.action-title {
.label { font-size: 16px;
font-size: 14px; font-weight: bold;
color: #999; color: #333;
width: 70px; margin-bottom: 15px;
flex-shrink: 0; text-align: center;
margin-right: 8px; }
}
.action-buttons {
.value { display: flex;
font-size: 14px; gap: 15px;
color: #333;
flex: 1; .action-btn {
} flex: 1;
}
.info-column {
display: flex;
flex-direction: column;
.label {
font-size: 14px;
color: #999;
flex-shrink: 0;
margin-right: 8px;
width: 100%;
margin-bottom: 5px;
}
.value {
font-size: 14px;
color: #333;
flex: 1;
}
} }
} }
} }

View File

@ -1,51 +1,79 @@
<template> <template>
<view class="leave-page"> <view class="leave-page">
<!-- 选项卡 --> <!-- 选项卡 -->
<BasicTabs class="leave-tabs" <BasicTabs
ref="tabsRef" :list="tabList" bar-width="60px" scroll-count="4" class="leave-tabs"
:current="curTabIndex" @change="switchTab" ref="tabsRef"
/> :list="tabList"
bar-width="60px"
scroll-count="4"
:current="curTabIndex"
@change="switchTab"
/>
<view class="leave-edit" v-if="curTabIndex === 0"> <view class="leave-edit" v-if="curTabIndex === 0">
<JsQjEdit /> <JsQjEdit />
</view> </view>
<view class="leave-list" v-else> <view class="leave-list" v-else>
<JsQjList /> <JsQjList />
</view> </view>
</view> </view>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'
import JsQjEdit from "./components/jsQjEdit.vue" import JsQjEdit from "./components/jsQjEdit.vue"
import JsQjList from "./components/jsQjList.vue" import JsQjList from "./components/jsQjList.vue"
//
const tabList = ref([ const tabList = ref([
{ name: "请假申请", id: "leave-edit" }, { name: "请假申请", id: "leave-edit" },
{ name: "请假记录", id: "leave-list" }, { name: "请假记录", id: "leave-list" },
]); ])
const curTabIndex = ref(0); const curTabIndex = ref(0)
const switchTab = (index : number) => { //
curTabIndex.value = index; const switchTab = (index: number) => {
curTabIndex.value = index
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.leave-page { .leave-page {
flex: 1 0 1px; flex: 1 0 1px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100vh; height: 100vh;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
.leave-tabs { .leave-tabs {
flex: 0 0 45px; flex: 0 0 45px;
background: #fff;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
z-index: 10;
} }
.leave-edit, .leave-edit,
.leave-list { .leave-list {
flex: 1 0 1px; flex: 1 0 1px;
position: relative; position: relative;
overflow-y: auto;
}
.leave-edit {
background: #fff;
margin: 20rpx;
border-radius: 16rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
}
.leave-list {
background: #fff;
margin: 20rpx;
border-radius: 16rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
} }
} }
</style>
</style>

View File

@ -3,10 +3,10 @@
<JsQjDetail :qjId="qjId" :dbFlag="dbFlag" v-if="qjId && qjId.length" /> <JsQjDetail :qjId="qjId" :dbFlag="dbFlag" v-if="qjId && qjId.length" />
<!-- 驳回弹窗 --> <!-- 驳回弹窗 -->
<u-popup <u-popup
:show="bhDlgFlag" :show="dlgFlag"
mode="center" mode="center"
:closeOnClickOverlay="false" :closeOnClickOverlay="false"
@close="closeBhDlg" @close="closeDlg"
> >
<view class="popup-content"> <view class="popup-content">
<view class="popup-title">驳回原因</view> <view class="popup-title">驳回原因</view>
@ -18,32 +18,11 @@
maxlength="200" maxlength="200"
/> />
<view class="popup-actions flex-row justify-end mt-4"> <view class="popup-actions flex-row justify-end mt-4">
<u-button class="mr-2" @click="closeBhDlg">取消</u-button> <u-button class="mr-2" @click="closeDlg">取消</u-button>
<u-button type="primary" @click="handleReject">确定</u-button> <u-button type="primary" @click="handleReject">确定</u-button>
</view> </view>
</view> </view>
</u-popup> </u-popup>
<!-- 转办弹窗 -->
<u-popup
:show="zbDlgFlag"
mode="center"
:closeOnClickOverlay="false"
@close="closeZbDlg"
>
<view class="popup-content">
<view class="popup-title">转到教师</view>
<JsPicker
@change="changeZbJs"
:multiple="false"
:excludeIds="excludeIds"
/>
<!-- 这里可以扩展选择转办对象等内容 -->
<view class="popup-actions flex-row justify-end mt-4">
<u-button class="mr-2" @click="closeZbDlg">取消</u-button>
<u-button type="primary" @click="handleTransfer">确定</u-button>
</view>
</view>
</u-popup>
<template #bottom> <template #bottom>
<view class="white-bg-color py-5"> <view class="white-bg-color py-5">
<view class="divider"></view> <view class="divider"></view>
@ -52,9 +31,8 @@
text="驳回" text="驳回"
class="ml-15 mr-7" class="ml-15 mr-7"
:plain="true" :plain="true"
@click="showBhDlg" @click="showDlg"
/> />
<u-button text="转办" class="mr-7" :plain="true" @click="showZbDlg" />
<u-button <u-button
text="同意" text="同意"
class="mr-15 mr-7" class="mr-15 mr-7"
@ -68,63 +46,39 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { jsQjJwcQrApi, jsQjZbApi } from "@/api/base/jsQjApi"; import { jsQjJwcQrApi } from "@/api/base/jsQjApi";
import { xxtsFindByIdApi } from "@/api/base/server";
import JsPicker from "@/pages/components/JsPicker/index.vue";
import { useDataStore } from "@/store/modules/data";
import { useUserStore } from "@/store/modules/user"; import { useUserStore } from "@/store/modules/user";
import { navigateBack } from "@/utils/uniapp"; import { navigateBack } from "@/utils/uniapp";
import { onLoad } from "@dcloudio/uni-app"; import { onLoad } from "@dcloudio/uni-app";
import { ref } from "vue"; import { ref } from "vue";
import JsQjDetail from "./components/jsQjDetail.vue"; import JsQjDetail from "./components/jsQjDetail.vue";
const { getJs, loginByOpenId } = useUserStore();
const { setData, getData, setXxts, getXxts } = useDataStore(); const { getJs } = useUserStore();
const dbFlag = ref(false); const dbFlag = ref(false);
const qjId = ref<string>(); const qjId = ref<string>();
const dlgFlag = ref(false);
//
const bhDlgFlag = ref(false);
const rejectReason = ref(""); const rejectReason = ref("");
//
const zbDlgFlag = ref(false);
const excludeIds = ref<string[]>([getJs.id]);
const zbJs = ref<any>({});
//
const submit = async () => { const submit = async () => {
const params = { const params = {
qjId: qjId.value, qjId: qjId.value,
jsId: getJs.id, jsId: getJs.id,
qrStatus: 2, spStatus: "approved",
qrYj: "同意", spRemark: "同意",
}; };
uni.showLoading({ title: "审批中..." }); uni.showLoading({ title: "确认中..." });
await jsQjJwcQrApi(params); await jsQjJwcQrApi(params);
uni.hideLoading(); uni.hideLoading();
navigateBack(); navigateBack();
}; };
// / const showDlg = () => {
const showBhDlg = () => { dlgFlag.value = true;
bhDlgFlag.value = true;
};
const closeBhDlg = () => {
bhDlgFlag.value = false;
}; };
// / const closeDlg = () => {
const showZbDlg = () => { dlgFlag.value = false;
zbDlgFlag.value = true;
};
const closeZbDlg = () => {
zbDlgFlag.value = false;
};
const changeZbJs = (selected: any) => {
zbJs.value = selected;
}; };
// //
@ -133,115 +87,51 @@ const handleReject = async () => {
uni.showToast({ title: "请填写驳回意见", icon: "none" }); uni.showToast({ title: "请填写驳回意见", icon: "none" });
return; return;
} }
const params: any = { const params = {
qjId: qjId.value, qjId: qjId.value,
jsId: getJs.id, jsId: getJs.id,
spStatus: 1, // 1 spStatus: "rejected",
spYj: rejectReason.value, spRemark: rejectReason.value,
}; };
uni.showLoading({ title: "正在驳回..." }); uni.showLoading({ title: "正在驳回..." });
try { await jsQjJwcQrApi(params);
await jsQjJwcQrApi(params); uni.hideLoading();
uni.hideLoading(); closeDlg();
uni.showToast({ title: "已驳回", icon: "success" }); navigateBack();
closeBhDlg();
setTimeout(() => {
navigateBack();
}, 500);
} catch (e) {
uni.hideLoading();
}
}; };
// onLoad((options) => {
const handleTransfer = async () => { if (options.qjId) {
if (!zbJs.value || !zbJs.value.value) { qjId.value = options.qjId;
uni.showToast({ title: "请填写转办教师", icon: "none" });
return;
} }
const params: any = { if (options.dbFlag) {
qjId: qjId.value, dbFlag.value = options.dbFlag === "true";
jsId: getJs.id,
dbId: getXxts.id,
zbJsId: zbJs.value.value,
zbJsxm: zbJs.value.label,
};
uni.showLoading({ title: "正在转办..." });
try {
await jsQjZbApi(params);
uni.hideLoading();
uni.showToast({ title: "已转办", icon: "success" });
closeBhDlg();
setTimeout(() => {
navigateBack();
}, 500);
} catch (e) {
uni.hideLoading();
}
};
onLoad(async (data: any) => {
//
if (data && data.from && data.from == "db") {
dbFlag.value = true;
//
const isLoggedIn = await loginByOpenId(data.openId);
if (!isLoggedIn) {
console.log("用户未登录,跳过处理");
return;
}
try {
// urlidXxts
const xxtsRes = await xxtsFindByIdApi({ id: data.id });
if (xxtsRes && xxtsRes.result) {
const xxts = xxtsRes.result;
//
if (xxts.dbZt === "B") {
setData({ id: xxts.xxzbId });
let url = "/pages/view/hr/jsQj/detail";
uni.navigateTo({ url });
return;
}
setXxts(xxts);
// 使ID
qjId.value = xxts.xxzbId;
}
} catch (error) {
console.error("获取待办信息失败", error);
// Xxts退
qjId.value = data.id;
const xxtsData = getXxts();
if (xxtsData && xxtsData.dbZt === "B") {
setData({ id: data.id });
let url = "/pages/view/hr/jsQj/detail";
uni.navigateTo({ url });
return;
}
}
} else {
qjId.value = getData.id;
dbFlag.value = false;
} }
}); });
</script> </script>
<style scoped> <style lang="scss" scoped>
.popup-content { .popup-content {
width: 80vw; background-color: white;
background: #fff; border-radius: 8px;
border-radius: 12px; padding: 20px;
padding: 24px 16px 16px 16px; width: 300px;
} }
.popup-title { .popup-title {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
margin-bottom: 12px; margin-bottom: 15px;
text-align: center;
} }
.popup-actions { .popup-actions {
margin-top: 16px; margin-top: 20px;
}
.divider {
height: 1px;
background-color: #eee;
margin: 0 15px;
} }
</style> </style>

View File

@ -1,194 +1,366 @@
<template> <template>
<BasicLayout> <BasicLayout>
<!-- 选项卡 --> <view class="p-15">
<BasicTabs <!-- 请假信息卡片 -->
class="leave-tabs" <view class="info-card">
ref="tabsRef" <view class="card-header">
:list="tabList" <text class="applicant-name">教师{{ qjData.jsName }}的请假申请</text>
bar-width="60px" </view>
scroll-count="4" <view class="card-content">
:current="curTabIndex" <view class="info-row">
@change="switchTab" <text class="label">请假类型:</text>
/> <text class="value">{{ qjData.qjlx }}</text>
<view class="pl-15 pr-15" v-show="curTabIndex === 0"> </view>
<JsQjDkEdit <view class="info-row">
:data="formData" <text class="label">请假时间:</text>
ref="dkRef" <text class="value">{{ qjData.qjkstime }} {{ qjData.qjjstime }}</text>
v-if="formData && formData.dkfs === '1'" </view>
/> <view class="info-row">
</view> <text class="label">请假时长:</text>
<JsQjDetail <text class="value">{{ qjData.qjsc }}</text>
v-show="curTabIndex === 1" </view>
:qjId="qjId" <view class="info-row">
:dbFlag="dbFlag" <text class="label">请假事由:</text>
v-if="qjId && qjId.length" <text class="value">{{ qjData.qjsy }}</text>
@loadQjData="loadQjData" </view>
/>
<template #bottom>
<view class="white-bg-color py-5">
<view class="divider"></view>
<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>
</view> </view>
</template>
<!-- 代课安排 -->
<view class="dk-arrange-card">
<view class="card-header">
<text class="card-title">代课安排</text>
</view>
<view class="card-content">
<view class="dk-list">
<view
v-for="(dk, index) in dkList"
:key="index"
class="dk-item"
>
<view class="dk-header">
<text class="dk-title">代课{{ index + 1 }}</text>
<u-button
text="删除"
size="mini"
type="error"
@click="removeDk(index)"
/>
</view>
<view class="dk-content">
<view class="info-row">
<text class="label">代课教师:</text>
<text class="value">{{ dk.jsName }}</text>
</view>
<view class="info-row">
<text class="label">代课时间:</text>
<text class="value">{{ dk.dktime }}</text>
</view>
<view class="info-row">
<text class="label">课程名称:</text>
<text class="value">{{ dk.kcmc }}</text>
</view>
<view class="info-row">
<text class="label">班级:</text>
<text class="value">{{ dk.bjmc }}</text>
</view>
</view>
</view>
</view>
<view class="add-dk-section">
<u-button
text="添加代课安排"
type="primary"
@click="showDkSelector"
class="add-btn"
/>
</view>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-section">
<u-button
text="提交协调结果"
type="primary"
@click="handleSubmit"
class="submit-btn"
/>
</view>
</view>
<!-- 代课教师选择弹窗 -->
<u-popup
:show="showSelector"
mode="bottom"
height="70%"
@close="closeSelector"
>
<view class="selector-container">
<view class="selector-header">
<text class="selector-title">选择代课教师</text>
<u-button text="确定" type="primary" @click="confirmSelection" />
</view>
<view class="selector-content">
<u-checkbox-group v-model="selectedDkIds">
<view
v-for="dk in availableDkList"
:key="dk.id"
class="dk-option"
>
<u-checkbox
:value="dk.id"
:label="`${dk.jsName} - ${dk.kcmc} - ${dk.bjmc}`"
/>
</view>
</u-checkbox-group>
</view>
</view>
</u-popup>
</BasicLayout> </BasicLayout>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { jsQjJwcXtApi } from "@/api/base/jsQjApi"; import { jsQjJwcXtApi } from "@/api/base/jsQjApi";
import { xxtsFindByIdApi } from "@/api/base/server"; import { findQjById, getPkkbByJsRangeTimeApi } from "@/api/base/jsQjApi";
import { useDataStore } from "@/store/modules/data";
import { useUserStore } from "@/store/modules/user"; import { useUserStore } from "@/store/modules/user";
import { navigateBack, showToast } from "@/utils/uniapp"; import { navigateBack } from "@/utils/uniapp";
import { onLoad } from "@dcloudio/uni-app"; import { onLoad } from "@dcloudio/uni-app";
import { ref, nextTick } from "vue"; import { ref } from "vue";
import JsQjDetail from "./components/jsQjDetail.vue";
import JsQjDkEdit from "./components/jsQjDkEdit.vue";
const { getJs, loginByOpenId } = useUserStore();
const { setData, getData, setXxts, getXxts } = useDataStore();
const dkRef = ref<any>(null); const { getJs } = useUserStore();
const formData = ref<any>({});
const dbFlag = ref(false);
const qjId = ref<string>(); const qjId = ref<string>();
const qjData = ref<any>({});
const dkList = ref<any[]>([]);
const availableDkList = ref<any[]>([]);
const selectedDkIds = ref<string[]>([]);
const showSelector = ref(false);
const tabList = ref([ //
{ name: "代课协调", id: "dk-edit" }, const loadQjData = async () => {
{ name: "请假信息", id: "qj-info" }, try {
]); const result = await findQjById({ id: qjId.value });
if (result.code === 1) {
const curTabIndex = ref(1); qjData.value = result.data;
let initDkTabFlag = false; //
await loadAvailableDkList();
const switchTab = (index: number) => { }
curTabIndex.value = index; } catch (error) {
if (index === 0 && !initDkTabFlag) { console.error('获取请假信息失败:', error);
nextTick(() => {
dkRef.value.getPkkbList();
initDkTabFlag = true;
});
} }
}; };
const loadQjData = (data: any) => { //
formData.value = data; const loadAvailableDkList = async () => {
switchTab(0); try {
const params = {
jsId: qjData.value.jsId,
startTime: qjData.value.qjkstime,
endTime: qjData.value.qjjstime
};
const result = await getPkkbByJsRangeTimeApi(params);
if (result.code === 1) {
availableDkList.value = result.data || [];
}
} catch (error) {
console.error('获取可用代课安排失败:', error);
}
}; };
const submit = async () => { //
const params = { const showDkSelector = () => {
qjId: qjId.value, selectedDkIds.value = [];
jsId: getJs.id, showSelector.value = true;
dkList: [], };
};
const flag = await dkRef.value.validate(); //
if (!flag) { const closeSelector = () => {
showSelector.value = false;
selectedDkIds.value = [];
};
//
const confirmSelection = () => {
const selectedDks = availableDkList.value.filter(dk =>
selectedDkIds.value.includes(dk.id)
);
//
selectedDks.forEach(dk => {
const dkItem = {
jsId: dk.jsId,
jsName: dk.jsName,
dktime: dk.dktime,
kcmc: dk.kcmc,
bjmc: dk.bjmc,
pkkbId: dk.id
};
dkList.value.push(dkItem);
});
closeSelector();
};
//
const removeDk = (index: number) => {
dkList.value.splice(index, 1);
};
//
const handleSubmit = async () => {
if (dkList.value.length === 0) {
uni.showToast({ uni.showToast({
title: "请选择代课教师", title: "请至少添加一个代课安排",
icon: "none", icon: "none"
}); });
return; return;
} }
let dkList = dkRef.value.getDkList() || [];
if (dkList.length) { const params = {
params.dkList = dkList.map((item: any) => { qjId: qjId.value,
const newItem = { ...item }; jsId: getJs.id,
newItem.jsId = item.dkJsId; dkList: dkList.value
newItem.jsName = item.dkJsName; };
newItem.pkkbId = item.id;
newItem.dktime = item.dktime + " 00:00:00";
newItem.id = "";
newItem.qjId = "";
return newItem;
});
}
uni.showLoading({ title: "提交中..." }); uni.showLoading({ title: "提交中..." });
await jsQjJwcXtApi(params).then(() => { try {
showToast({ title: "提交成功", icon: "success" }); await jsQjJwcXtApi(params);
uni.reLaunch({ uni.showToast({ title: "协调成功", icon: "success" });
url: "/pages/base/service/index", navigateBack();
}); } catch (error) {
}); uni.showToast({ title: "协调失败", icon: "error" });
}
uni.hideLoading(); uni.hideLoading();
}; };
onLoad(async (data: any) => { onLoad((options) => {
// if (options.qjId) {
if (data && data.from && data.from == "db") { qjId.value = options.qjId;
dbFlag.value = true; loadQjData();
//
const isLoggedIn = await loginByOpenId(data.openId);
if (!isLoggedIn) {
console.log("用户未登录,跳过处理");
return;
}
try {
// urlidXxts
const xxtsRes = await xxtsFindByIdApi({ id: data.id });
if (xxtsRes && xxtsRes.result) {
const xxts = xxtsRes.result;
//
if (xxts.dbZt === "B") {
setData({ id: xxts.xxzbId });
let url = "/pages/view/hr/jsQj/detail";
uni.navigateTo({ url });
return;
}
setXxts(xxts);
// 使ID
qjId.value = xxts.xxzbId;
}
} catch (error) {
console.error("获取待办信息失败", error);
// Xxts退
qjId.value = data.id;
const xxtsData = getXxts();
if (xxtsData && xxtsData.dbZt === "B") {
setData({ id: data.id });
let url = "/pages/view/hr/jsQj/detail";
uni.navigateTo({ url });
return;
}
}
} else {
qjId.value = getData.id;
dbFlag.value = false;
} }
}); });
</script> </script>
<style scoped> <style lang="scss" scoped>
.popup-content { .info-card,
width: 80vw; .dk-arrange-card {
background: #fff; background: white;
border-radius: 12px; border-radius: 8px;
padding: 24px 16px 16px 16px; margin-bottom: 15px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
} }
.popup-title {
font-size: 16px; .card-header {
font-weight: bold; padding: 15px;
margin-bottom: 12px; border-bottom: 1px solid #eee;
.applicant-name,
.card-title {
font-size: 16px;
font-weight: bold;
color: #333;
}
} }
.popup-actions {
margin-top: 16px; .card-content {
padding: 15px;
}
.info-row {
display: flex;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
.label {
width: 80px;
font-size: 14px;
color: #666;
flex-shrink: 0;
}
.value {
flex: 1;
font-size: 14px;
color: #333;
}
}
.dk-list {
.dk-item {
border: 1px solid #eee;
border-radius: 6px;
margin-bottom: 10px;
.dk-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
background-color: #f5f5f5;
border-bottom: 1px solid #eee;
.dk-title {
font-size: 14px;
font-weight: bold;
color: #333;
}
}
.dk-content {
padding: 10px;
}
}
}
.add-dk-section {
margin-top: 15px;
text-align: center;
.add-btn {
width: 100%;
}
}
.submit-section {
margin-top: 20px;
.submit-btn {
width: 100%;
}
}
.selector-container {
height: 100%;
display: flex;
flex-direction: column;
.selector-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border-bottom: 1px solid #eee;
.selector-title {
font-size: 16px;
font-weight: bold;
}
}
.selector-content {
flex: 1;
padding: 15px;
overflow-y: auto;
.dk-option {
padding: 10px 0;
border-bottom: 1px solid #f0f0f0;
}
}
} }
</style> </style>

View File

@ -1,40 +1,36 @@
<template> <template>
<BasicLayout> <BasicLayout>
<JsQjDetail :qjId="qjId" :dbFlag="dbFlag" v-if="qjId && qjId.length" /> <view class="qj-detail">
<!-- 驳回弹窗 --> <!-- 使用公共组件展示请假详情 -->
<u-popup <JsQjDetail
:show="dlgFlag" :qjId="qjId"
mode="center" :dbFlag="dbFlag"
:closeOnClickOverlay="false" @loadQjData="handleQjDataLoaded"
@close="closeDlg" @loadDkList="handleDkListLoaded"
> />
<view class="popup-content">
<view class="popup-title">驳回原因</view> <!-- 审批意见卡片 -->
<u-input <view class="info-card">
v-model="rejectReason" <view class="card-header">
type="textarea" <text class="applicant-name">审批意见</text>
placeholder="请填写驳回原因" </view>
:autoHeight="true" <view class="divider"></view>
maxlength="200" <view class="card-body">
/> <BasicForm @register="register" />
<view class="popup-actions flex-row justify-end mt-4">
<u-button class="mr-2" @click="closeDlg">取消</u-button>
<u-button type="primary" @click="handleReject">确定</u-button>
</view> </view>
</view> </view>
</u-popup> </view>
<template #bottom> <template #bottom>
<view class="white-bg-color py-5"> <view class="white-bg-color py-5">
<view class="divider"></view>
<view class="flex-row items-center pb-10 pt-5"> <view class="flex-row items-center pb-10 pt-5">
<u-button <u-button
text="驳回" text="取消"
class="ml-15 mr-7" class="ml-15 mr-7"
:plain="true" :plain="true"
@click="showDlg" @click="navigateBack"
/> />
<u-button <u-button
text="同意" text="提交"
class="mr-15 mr-7" class="mr-15 mr-7"
type="primary" type="primary"
@click="submit" @click="submit"
@ -46,72 +42,114 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { jsQjSpApi } from "@/api/base/jsQjApi";
import { xxtsFindByIdApi } from "@/api/base/server";
import { useDataStore } from "@/store/modules/data";
import { useUserStore } from "@/store/modules/user";
import { navigateBack } from "@/utils/uniapp";
import { onLoad } from "@dcloudio/uni-app"; import { onLoad } from "@dcloudio/uni-app";
import { ref } from "vue"; import { useForm } from "@/components/BasicForm/hooks/useForm";
import { navigateBack } from "@/utils/uniapp";
import { jsQjSpApi } from "@/api/base/jsQjApi";
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
import { ref, computed } from "vue";
import { xxtsFindByIdApi } from "@/api/base/server";
import JsQjDetail from "./components/jsQjDetail.vue"; import JsQjDetail from "./components/jsQjDetail.vue";
const { getJs, loginByOpenId } = useUserStore(); const { getJs, loginByOpenId } = useUserStore();
const { setData, getData, setXxts, getXxts } = useDataStore(); const { getData, setXxts, setData, getXxts } = useDataStore();
const dbFlag = ref(false); const dbFlag = ref(false);
const qjId = ref('');
const qjId = ref<string>(); //
const qjData = computed(() => getData || {});
const dlgFlag = ref(false); const [register, { getValue }] = useForm({
const rejectReason = ref(""); schema: [
{
field: "spStatus",
label: "审批意见",
component: "BasicCheckbox",
required: true,
itemProps: {
labelPosition: "top",
},
componentProps: {
data: [
{ value: 'approved', text: "同意" },
{ value: 'rejected', text: "拒绝" },
],
},
},
{
field: "spYj",
label: "审批说明",
component: "BasicInput",
required: true,
itemProps: {
labelPosition: "top",
},
componentProps: {
type: "textarea",
placeholder: "请输入审批说明",
},
},
],
});
//
const qjData = computed(() => getData || {});
const handleQjDataLoaded = (data: any) => {
setData(data);
};
const handleDkListLoaded = (list: any[]) => {
// JsQjDetail
};
const submit = async () => { const submit = async () => {
const params = {
qjId: qjId.value,
jsId: getJs.id,
spStatus: 2,
spYj: "同意",
};
uni.showLoading({ title: "审批中..." });
await jsQjSpApi(params);
uni.hideLoading();
navigateBack();
};
const showDlg = () => {
dlgFlag.value = true;
};
const closeDlg = () => {
dlgFlag.value = false;
};
//
const handleReject = async () => {
if (!rejectReason.value.trim()) {
uni.showToast({ title: "请填写驳回意见", icon: "none" });
return;
}
const params: any = {
qjId: qjId.value,
jsId: getJs.id,
spStatus: 1, // 1
spYj: rejectReason.value,
};
uni.showLoading({ title: "正在驳回..." });
try { try {
await jsQjSpApi(params); const formData = await getValue();
if (!formData.spStatus || !formData.spYj) {
uni.showToast({
title: '请填写完整的审批信息',
icon: 'none'
});
return;
}
const params = {
qjId: qjData.value.id,
jsId: getJs.id,
spStatus: formData.spStatus,
spYj: formData.spYj,
};
uni.showLoading({
title: "提交中...",
});
const res = await jsQjSpApi(params);
uni.hideLoading(); uni.hideLoading();
uni.showToast({ title: "已驳回", icon: "success" }); uni.showToast({
closeDlg(); title: '审批提交成功',
icon: 'success'
});
setTimeout(() => { setTimeout(() => {
navigateBack(); navigateBack();
}, 500); }, 1500);
} catch (e) {
} catch (error) {
uni.hideLoading(); uni.hideLoading();
uni.showToast({
title: '提交失败,请重试',
icon: 'none'
});
console.error('审批提交失败:', error);
} }
}; };
onLoad(async (data: any) => { onLoad(async (data?: any) => {
// //
if (data && data.from && data.from == "db") { if (data && data.from && data.from == "db") {
dbFlag.value = true; dbFlag.value = true;
@ -128,7 +166,6 @@ onLoad(async (data: any) => {
const xxtsRes = await xxtsFindByIdApi({ id: data.id }); const xxtsRes = await xxtsFindByIdApi({ id: data.id });
if (xxtsRes && xxtsRes.result) { if (xxtsRes && xxtsRes.result) {
const xxts = xxtsRes.result; const xxts = xxtsRes.result;
// //
if (xxts.dbZt === "B") { if (xxts.dbZt === "B") {
setData({ id: xxts.xxzbId }); setData({ id: xxts.xxzbId });
@ -137,42 +174,61 @@ onLoad(async (data: any) => {
return; return;
} }
setXxts(xxts); setXxts(xxts);
// 使ID
qjId.value = xxts.xxzbId; qjId.value = xxts.xxzbId;
} }
} catch (error) { } catch (error) {
console.error("获取待办信息失败", error); console.error("获取待办信息失败", error);
// Xxts退 // Xxts退
qjId.value = data.id; const xxtsData = getXxts;
const xxtsData = getXxts();
if (xxtsData && xxtsData.dbZt === "B") { if (xxtsData && xxtsData.dbZt === "B") {
setData({ id: data.id }); setData({ id: data.id });
let url = "/pages/view/hr/jsQj/detail"; let url = "/pages/view/hr/jsQj/detail";
uni.navigateTo({ url }); uni.navigateTo({ url });
return; return;
} }
qjId.value = data.id;
} }
} else { } else {
qjId.value = getData.id;
dbFlag.value = false; dbFlag.value = false;
//
qjId.value = getData.id || '';
} }
}); });
</script> </script>
<style scoped> <style lang="scss" scoped>
.popup-content { .qj-detail {
width: 80vw; background-color: #f5f7fa;
background: #fff;
border-radius: 12px;
padding: 24px 16px 16px 16px;
} }
.popup-title {
font-size: 16px; .info-card {
font-weight: bold; margin: 15px;
margin-bottom: 12px; background-color: #fff;
} border-radius: 8px;
.popup-actions { padding: 15px;
margin-top: 16px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
.card-header {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
.applicant-name {
font-size: 16px;
font-weight: bold;
color: #333;
}
}
.divider {
height: 1px;
background-color: #eee;
margin-bottom: 15px;
}
.card-body {
padding: 15px;
}
} }
</style> </style>

View File

@ -15,7 +15,7 @@
<!-- 陪餐教师选择 --> <!-- 陪餐教师选择 -->
<view class="section" v-if="curBj"> <view class="section" v-if="curBj">
<view class="section-title">陪餐教师</view> <view class="section-title">陪餐教师</view>
<JsPicker <BasicJsPicker
:multiple="true" :multiple="true"
@change="jsXz" @change="jsXz"
placeholder="请选择陪餐教师" placeholder="请选择陪餐教师"
@ -252,7 +252,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import NjBjPicker from '@/pages/components/NjBjPicker/index.vue' import NjBjPicker from '@/pages/components/NjBjPicker/index.vue'
import JsPicker from '@/pages/components/JsPicker/index.vue' import BasicJsPicker from '@/components/BasicJsPicker/Picker.vue'
import DmPsComponent from '@/pages/components/dmPs/index.vue' import DmPsComponent from '@/pages/components/dmPs/index.vue'
import { getClassStudentDmDataApi, submitJcDmDataApi } from '@/api/base/jcApi' import { getClassStudentDmDataApi, submitJcDmDataApi } from '@/api/base/jcApi'
import { imagUrl } from "@/utils"; import { imagUrl } from "@/utils";

View File

@ -987,12 +987,12 @@ const ensureTeacherDataCached = async () => {
if (!hasTeacherData) { if (!hasTeacherData) {
console.log('localStorage中没有教师数据开始获取并缓存'); console.log('localStorage中没有教师数据开始获取并缓存');
// getAllJs // getAllJsBasicInfoVo
const { useCommonStore } = await import('@/store/modules/common'); const { useCommonStore } = await import('@/store/modules/common');
const commonStore = useCommonStore(); const commonStore = useCommonStore();
try { try {
const result = await commonStore.getAllJs(); const result = await commonStore.getAllJsBasicInfoVo();
console.log('成功获取教师数据并写入缓存:', result); console.log('成功获取教师数据并写入缓存:', result);
} catch (error) { } catch (error) {
console.error('获取教师数据失败:', error); console.error('获取教师数据失败:', error);

View File

@ -1,6 +1,7 @@
import { import {
bjFindByNjId, bjFindByNjId,
jsFindAll, jsFindAll,
jsFindAllBasicInfoVo,
njFindAll, njFindAll,
zwFindAllApi, zwFindAllApi,
zwGetListByLxApi, zwGetListByLxApi,
@ -50,6 +51,20 @@ export const useCommonStore = defineStore({
} }
return Promise.resolve(this.data.allJs); return Promise.resolve(this.data.allJs);
}, },
// 所有教师基础信息Vo版本
async getAllJsBasicInfoVo(): Promise<any> {
// if (!this.data.allJsBasicInfoVo) {
this.data.allJsBasicInfoVo = await jsFindAllBasicInfoVo();
// }
return Promise.resolve(this.data.allJsBasicInfoVo);
},
// 获取教师列表Vo版本用于审批人/抄送人选择)
async getJsList(): Promise<any> {
if (!this.data.jsList) {
this.data.jsList = await jsFindAllBasicInfoVo();
}
return Promise.resolve(this.data.jsList);
},
// 所有职务 // 所有职务
async getAllZw(): Promise<any> { async getAllZw(): Promise<any> {
if (!this.data.allZw) { if (!this.data.allZw) {