公文流转

This commit is contained in:
hebo 2025-08-24 21:46:29 +08:00
parent 9e2d89e911
commit 7f3c9705cc
12 changed files with 2078 additions and 724 deletions

View File

@ -3,166 +3,115 @@ import { get, post } from "@/utils/request";
// 公文相关API接口
/**
*
* @param params
*
*/
export function getGwListApi(params: {
page: number;
pageSize: number;
status?: string;
keyword?: string;
gwType?: string;
urgencyLevel?: string;
startTime?: string;
endTime?: string;
}) {
return get('/api/gw/list', params);
export function gwFindPageApi(params: any) {
return get('/api/gw/findPage', params);
}
/**
*
* @param id ID
* ID查询公文详情
*/
export function getGwDetailApi(id: string) {
return get(`/api/gw/detail/${id}`);
export function gwFindByIdApi(params: any) {
return get('/api/gw/findById', params);
}
/**
*
* @param data
* /
*/
export function createGwApi(data: {
title: string;
gwType: string;
urgencyLevel: string;
remark?: string;
files: any[];
approvers: any[];
ccUsers: any[];
}) {
return post('/api/gw/create', data);
export function gwSaveApi(params: any) {
return post('/api/gw/save', params);
}
/**
*
* @param id ID
* @param data
*
*/
export function updateGwApi(id: string, data: any) {
return post(`/api/gw/update/${id}`, data);
}
export function gwLogicDeleteApi(params: any) {
// 将参数拼接到URL中确保后端能正确接收
const queryString = Object.keys(params)
.map(key => `${key}=${encodeURIComponent(params[key])}`)
.join('&');
/**
*
* @param id ID
*/
export function deleteGwApi(id: string) {
return post(`/api/gw/delete/${id}`, { id });
// 尝试POST请求如果不行可以改为GET请求
return post(`/api/gw/logicDelete?${queryString}`, params);
// 如果POST请求有问题可以尝试GET请求
// return get(`/api/gw/logicDelete?${queryString}`);
}
/**
* 稿
* @param data 稿
*/
export function saveDraftApi(data: any) {
return post('/api/gw/draft', data);
export function gwDraftApi(params: any) {
return post('/api/gw/draft', params);
}
/**
*
* @param data
*/
export function submitGwApi(data: any) {
return post('/api/gw/submit', data);
export function gwSubmitApi(params: any) {
return post('/api/gw/submit', params);
}
/**
*
* @param data
*
*/
export function saveChangesApi(data: {
gwId: string;
approvers: any[];
ccUsers: any[];
operationLogs: any[];
}) {
return post('/api/gw/changes', data);
export function gwApproveApi(params: any) {
return post('/api/gw/approve', params);
}
/**
*
* @param keyword
*
*/
export function searchUsersApi(keyword: string) {
return get('/api/user/search', { keyword });
export function gwCcConfirmApi(params: any) {
return post('/api/gw/cc-confirm', params);
}
/**
*
* @param gwId ID
*/
export function getApproversApi(gwId: string) {
return get(`/api/gw/approvers/${gwId}`);
export function gwApproversApi(params: any) {
return get('/api/gw/approvers', params);
}
/**
*
* @param gwId ID
*/
export function getCCUsersApi(gwId: string) {
return get(`/api/gw/cc-users/${gwId}`);
export function gwCcUsersApi(params: any) {
return get('/api/gw/cc-users', params);
}
/**
*
* @param gwId ID
*/
export function getOperationLogsApi(gwId: string) {
return get(`/api/gw/logs/${gwId}`);
}
/**
*
* @param file
*/
export function uploadFileApi(file: File) {
const formData = new FormData();
formData.append("file", file);
return post('/api/file/upload', formData);
}
/**
*
* @param fileId ID
*/
export function deleteFileApi(fileId: string) {
return post(`/api/file/delete/${fileId}`, { fileId });
export function gwLogsApi(params: any) {
return get('/api/gw/logs', params);
}
/**
*
*/
export function getGwStatsApi() {
return get('/api/gw/stats');
export function gwStatsApi(params: any) {
return get('/api/gw/stats', params);
}
/**
*
* @param data
*
*/
export function approveGwApi(data: {
gwId: string;
action: "approve" | "reject";
remark?: string;
}) {
return post('/api/gw/approve', data);
export function fileUploadApi(params: any) {
return post('/api/file/upload', params);
}
/**
*
* @param gwId ID
*
*/
export function confirmCCApi(gwId: string) {
return post(`/api/gw/cc-confirm/${gwId}`);
export function fileDeleteApi(params: any) {
return post('/api/file/delete', params);
}
/**
*
*/
export function userSearchApi(params: any) {
return get('/api/user/search', params);
}

View File

@ -0,0 +1,112 @@
import { get, post } from "@/utils/request";
// 流程管理设置相关API接口
/**
*
* @param params
*/
export function lcglSetFindPageApi(params: {
page: number;
pageSize: number;
ruleId?: string;
ruleName?: string;
status?: string;
startTime?: string;
endTime?: string;
}) {
return get('/api/lcglSet/findPage', params);
}
/**
* /
* @param params
*/
export function lcglSetSaveApi(params: {
id?: string;
ruleId: string;
ruleName: string;
spId?: string;
ccId?: string;
status?: string;
remark?: string;
}) {
return post('/api/lcglSet/save', params);
}
/**
*
* @param params
*/
export function lcglSetLogicDeleteApi(params: { id: string }) {
return post('/api/lcglSet/logicDelete', params);
}
/**
* ID查询流程设置详情
* @param params
*/
export function lcglSetFindByIdApi(params: { id: string }) {
return get('/api/lcglSet/findById', params);
}
/**
*
*/
export function lcglSetFindAllApi() {
return get('/api/lcglSet/findAll');
}
/**
* ID查询流程设置
* @param params
*/
export function lcglSetFindByRuleIdApi(params: { ruleId: string }) {
return get('/api/lcglSet/findByRuleId', params);
}
/**
*
* @param params
*/
export function lcglSetUpdateStatusApi(params: {
id: string;
status: string;
}) {
return post('/api/lcglSet/updateStatus', params);
}
/**
*
* @param params
*/
export function lcglSetCopyApi(params: {
id: string;
newRuleId: string;
newRuleName: string;
}) {
return post('/api/lcglSet/copy', params);
}
/**
*
* @param params
*/
export function lcglSetExportApi(params: {
ids?: string[];
ruleId?: string;
status?: string;
}) {
return post('/api/lcglSet/export', params);
}
/**
*
* @param params
*/
export function lcglSetImportApi(params: {
file: File;
overwrite?: boolean;
}) {
return post('/api/lcglSet/import', params);
}

View File

@ -0,0 +1,91 @@
<template>
<view class="py-7">
<BasicJsPicker
ref="pickerRef"
v-model="pickerValue"
v-bind="attrs.componentProps"
@change="handleChange"
/>
</view>
</template>
<script setup lang="ts">
/**
* FormBasicJsPicker 教师选择器表单组件
* 用于在 BasicForm 中使用 BasicJsPicker 组件
*/
import BasicJsPicker from "@/components/BasicJsPicker/Picker.vue";
const pickerRef = ref<any>(null);
const props = defineProps(["modelValue"]);
const emit = defineEmits(["update:modelValue"]);
const attrs: any = useAttrs();
//
const pickerValue = computed({
get() {
return props.modelValue;
},
set(value) {
emit("update:modelValue", value);
},
});
// displayValue open BasicJsPicker
//
const handleChange = (value: any) => {
// multiple
if (attrs.componentProps.multiple) {
//
pickerValue.value = value;
} else {
// null
pickerValue.value = value;
}
};
//
defineExpose({
open: () => pickerRef.value?.open(),
close: () => pickerRef.value?.close()
});
</script>
<style lang="scss" scoped>
.py-7 {
padding: 7rpx 0;
}
.wh-full {
width: 100%;
}
.flex-row {
display: flex;
flex-direction: row;
}
.items-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.font-13 {
font-size: 13rpx;
}
.text-ellipsis-1 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.color-9 {
color: #999;
}
</style>

View File

@ -8,6 +8,7 @@
<FormBasicCode v-bind="attrs" v-if="isShow('BasicCode')" v-model="newValue"/>
<FormBasicSwitch v-bind="attrs" v-if="isShow('BasicSwitch')" v-model="newValue"/>
<FormBasicPicker v-bind="attrs" v-if="isShow('BasicPicker')" v-model="newValue"/>
<FormBasicJsPicker v-bind="attrs" v-if="isShow('BasicJsPicker')" v-model="newValue"/>
<FormBasicNumberBox v-bind="attrs" v-if="isShow('BasicNumberBox')" v-model="newValue"/>
<FormBasicSelectBox v-bind="attrs" v-if="isShow('BasicSelectBox')" v-model="newValue"/>
<FormBasicLocation v-bind="attrs" v-if="isShow('BasicLocation')" v-model="newValue"/>

View File

@ -17,6 +17,7 @@ type Component =
| 'BasicDateTime'
| 'BasicInput'
| 'BasicPicker'
| 'BasicJsPicker'
| 'BasicRate'
| 'BasicSwitch'
| 'BasicUpload'

View File

@ -0,0 +1,387 @@
<template>
<view class="basic-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>{{ placeholder }}</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">{{ title }}</view>
<view class="js-ok-btn" @click="handleOk">确定</view>
</view>
<view class="js-picker-search" v-if="showSearch">
<BasicSearch @change="handleSearch" :showAction="false" height="36" :placeholder="searchPlaceholder"/>
</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<{
modelValue?: any,
defaultValue?: any,
parentData?: any,
multiple?: boolean,
// id
excludeIds?: any,
//
title?: string,
placeholder?: string,
searchPlaceholder?: string,
showSearch?: boolean
}>(), {
modelValue: null,
defaultValue: null,
parentData: null,
multiple: false,
excludeIds: [],
title: '请选择教师',
placeholder: '请选择老师',
searchPlaceholder: '输入教师名称查询',
showSearch: true
});
// emit
const emit = defineEmits(['change', 'update:modelValue'])
const targetScrollTop = ref(0); // scroll-top
const showPopup = ref(false);
const jsListAll = ref<any>([]);
const jsList = ref<any>([]);
let searchKey = "";
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 names = selectedList.value.map((item: any) => item.label).join(",");
return names;
};
const handleSearch = (value: any) => {
searchKey = value;
rebuildJsList();
return
};
const handleSelect = (item: any) => {
if (props.multiple) {
//
const index = selectedList.value.findIndex((selected: any) => selected.value === item.value);
if (index > -1) {
//
selectedList.value.splice(index, 1);
item.selected = false;
} else {
//
selectedList.value.push(item);
item.selected = true;
}
} else {
//
jsList.value.forEach((listItem: any) => listItem.selected = false);
selectedList.value = [item];
item.selected = true;
}
}
const handleCancel = () => {
showPopup.value = false;
}
const handleOk = () => {
showPopup.value = false;
let result;
if (props.multiple) {
result = selectedList.value;
} else {
result = selectedList.value[0] || null;
}
// v-model
currentValue.value = result;
// change
emit("change", result, props.parentData);
};
const showPicker = () => {
showPopup.value = true;
};
const rebuildJsList = () => {
jsList.value = [];
jsListAll.value.forEach((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,
selected: false
});
}
});
//
restoreSelection();
};
const restoreSelection = () => {
if (!selectedList.value.length) {
return;
}
// selectedList
jsList.value.forEach((item: any) => {
const isSelected = selectedList.value.some((selected: any) => selected.value === item.value);
item.selected = isSelected;
});
};
//
defineExpose({
showPicker,
open: showPicker,
close: () => showPopup.value = false
});
onMounted(async () => {
const res = await getAllJs()
jsListAll.value = res.result || [];
rebuildJsList();
// tickjsList
await nextTick();
//
const initialValue = internalValue.value || props.defaultValue;
if (initialValue) {
setInitialValue(initialValue);
}
});
//
const setInitialValue = (value: any) => {
if (!jsList.value.length) {
return;
}
if (props.multiple) {
if (!Array.isArray(value) || !value.length) {
return;
}
selectedList.value = [];
//
jsList.value.forEach((item: any) => {
item.selected = false;
});
//
let processedValue = value;
// {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;
}
}
});
}
};
// modelValue
watch(() => props.modelValue, (newValue) => {
if (newValue !== internalValue.value) {
internalValue.value = newValue;
// jsListjsList
if (jsList.value.length > 0) {
setInitialValue(newValue);
}
}
}, { 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>
<style lang="scss" scoped>
.basic-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

@ -35,7 +35,14 @@ function deletePic(event: any) {
}
}
const props = defineProps(['modelValue'])
// beforeUpload prop
const props = defineProps({
modelValue: String,
beforeUpload: {
type: Function,
default: null
}
})
watchEffect(() => {
if (props.modelValue) {
@ -67,6 +74,12 @@ async function afterRead(event: any) {
})
for (let i = 0; i < lists.length; i++) {
if (props.beforeUpload && !props.beforeUpload(lists[i])) {
// beforeUpload false
data.fileList.splice(fileListLen + i, 1)
continue
}
showLoading({title: '上传中'})
const res:any = await attachmentUpload(lists[i].url)
const result = res.result

View File

@ -179,14 +179,7 @@
}
},
{
"path": "pages/view/routine/GongWenLiuZhuan/index",
"style": {
"navigationBarTitleText": "公文流转",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/GongWenLiuZhuan/detail",
"path": "pages/view/routine/gwlz/gwDetail",
"style": {
"navigationBarTitleText": "公文详情",
"enablePullDownRefresh": false
@ -195,21 +188,21 @@
{
"path": "pages/view/routine/gwlz/gwAdd",
"style": {
"navigationBarTitleText": "公文列表",
"navigationBarTitleText": "公文调整",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/gwlz/index",
"style": {
"navigationBarTitleText": "公文接收",
"navigationBarTitleText": "公文列表",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/gwlz/gwFlow",
"style": {
"navigationBarTitleText": "公文流转",
"navigationBarTitleText": "公文审批",
"enablePullDownRefresh": false
}
},

View File

@ -344,6 +344,7 @@ const sections = reactive<Section[]>([
permissionKey: "routine-gwlz", //
path: "/pages/view/routine/gwlz/index",
},
{
id: "hr3",
icon: "gz",

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,210 @@
<template>
<BasicLayout>
<view class="px-15 pb-15">
<BasicForm @register="register" :readonly="true"> </BasicForm>
</view>
</BasicLayout>
</template>
<script setup lang="ts">
import { navigateTo } from "@/utils/uniapp";
import { useForm } from "@/components/BasicForm/hooks/useForm";
import { findDicTreeByPidApi } from "@/api/system/dic";
import { gwFindByIdApi } from "@/api/routine/gw";
import { useDicStore } from "@/store/modules/dic";
import { ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
const { findByPid } = useDicStore();
// ID
const gwId = ref<string>('');
//
const [register, { setValue, setSchema }] = useForm({
schema: [
{
title: "公文信息",
},
{
field: "title",
label: "公文标题",
component: "BasicInput",
componentProps: {
placeholder: "公文标题",
readonly: true,
},
},
{
field: "docType",
label: "公文类型",
component: "BasicPicker",
componentProps: {
api: findByPid,
param: { pid: "2146632731" }, //
rangeKey: "dictionaryValue",
savaKey: "dictionaryCode",
placeholder: "公文类型",
readonly: true,
},
},
{
field: "urgencyLevel",
label: "紧急程度",
component: "BasicPicker",
componentProps: {
api: findByPid,
param: { pid: "2146632735" }, //
rangeKey: "dictionaryValue",
savaKey: "dictionaryCode",
placeholder: "紧急程度",
readonly: true,
},
},
{
field: "fileUrl",
label: "附件",
component: "BasicUpload",
componentProps: {
multiple: false,
accept: "*/*",
maxCount: 1,
disabled: true,
limit: 1,
readonly: true,
},
},
{
field: "remark",
label: "备注",
component: "BasicInput",
componentProps: {
placeholder: "备注信息",
type: "textarea",
rows: 3,
readonly: true,
},
},
{
title: "审批设置",
},
{
field: "spId",
label: "审批人",
component: "BasicJsPicker",
componentProps: {
multiple: true,
placeholder: "审批人",
title: "审批人",
searchPlaceholder: "输入教师姓名搜索",
readonly: true,
},
},
{
field: "ccId",
label: "抄送人",
component: "BasicJsPicker",
componentProps: {
multiple: true,
placeholder: "抄送人",
title: "抄送人",
searchPlaceholder: "输入教师姓名搜索",
readonly: true,
},
},
{
field: "tjrName",
label: "提交人",
component: "BasicInput",
componentProps: {
placeholder: "提交人",
readonly: true,
},
},
{
field: "tjrtime",
label: "提交时间",
component: "BasicInput",
componentProps: {
placeholder: "提交时间",
readonly: true,
},
},
{
field: "gwStatus",
label: "公文状态",
component: "BasicInput",
componentProps: {
placeholder: "公文状态",
readonly: true,
},
},
],
});
// - 使onLoad
onLoad((options: any) => {
console.log('页面加载,路由参数:', options);
if (options && options.id) {
gwId.value = options.id;
getGwDetail();
} else {
console.error('未获取到公文ID参数');
uni.showToast({
title: "参数错误",
icon: "error",
});
//
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
});
//
const getGwDetail = async () => {
try {
const response = await gwFindByIdApi({ id: gwId.value });
if (response && response.result) {
const gwData = response.result;
//
await setValue({
title: gwData.title || '',
docType: gwData.docType || '',
urgencyLevel: gwData.urgencyLevel || '',
fileUrl: gwData.fileUrl || '',
fileName: gwData.fileName || '',
fileFormat: gwData.fileFormat || '',
remark: gwData.remark || '',
spId: gwData.spId ? gwData.spId.split(',').filter((id: string) => id.trim()) : [],
ccId: gwData.ccId ? gwData.ccId.split(',').filter((id: string) => id.trim()) : [],
tjrName: gwData.tjrxm || gwData.tjrName || '',
tjrtime: gwData.tjrtime || '',
gwStatus: getStatusText(gwData.gwStatus || ''),
});
}
} catch (error) {
console.error("获取公文详情失败:", error);
uni.showToast({
title: "获取详情失败",
icon: "error",
});
}
};
//
const getStatusText = (status: string) => {
const statusMap: Record<string, string> = {
'A': "暂存",
'B': "提交",
};
return statusMap[status] || "未知";
};
</script>
<style lang="scss" scoped>
//
</style>

File diff suppressed because it is too large Load Diff