first commit
This commit is contained in:
362
src/views/progress/constructionSchedulePlan/index.vue
Normal file
362
src/views/progress/constructionSchedulePlan/index.vue
Normal file
@ -0,0 +1,362 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||||
<el-form-item label="节点名称" prop="nodeName">
|
||||
<el-input v-model="queryParams.nodeName" placeholder="请输入节点名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
|
||||
<el-option v-for="dict in project_construction_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['progress:constructionSchedulePlan:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
<el-table
|
||||
ref="constructionSchedulePlanTableRef"
|
||||
v-loading="loading"
|
||||
:data="constructionSchedulePlanList"
|
||||
row-key="id"
|
||||
:default-expand-all="isExpandAll"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
>
|
||||
<!-- <el-table-column label="序号" type="id" /> -->
|
||||
<el-table-column label="节点名称" prop="nodeName" />
|
||||
<el-table-column label="对应项目结构" align="center" prop="projectStructure" />
|
||||
<el-table-column label="预计开始时间" align="center" prop="planStartDate" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.planStartDate, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="预计结束时间" align="center" prop="planEndDate" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.planEndDate, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际开始时间" align="center" prop="practicalStartDate" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.practicalStartDate, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际结束时间" align="center" prop="practicalEndDate" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.practicalEndDate, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="project_construction_status" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['progress:constructionSchedulePlan:edit']" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="新增" placement="top">
|
||||
<el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['progress:constructionSchedulePlan:add']" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="Delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['progress:constructionSchedulePlan:remove']"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
<!-- 添加或修改施工进度计划对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
|
||||
<el-form ref="constructionSchedulePlanFormRef" :model="form" :rules="rules" label-width="110px">
|
||||
<el-form-item label="父节点" prop="parentId">
|
||||
<el-tree-select
|
||||
v-model="form.parentId"
|
||||
:data="constructionSchedulePlanOptions"
|
||||
:props="{ value: 'id', label: 'nodeName', children: 'children' }"
|
||||
value-key="id"
|
||||
placeholder="请选择父节点"
|
||||
check-strictly
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="节点名称" prop="nodeName">
|
||||
<el-input v-model="form.nodeName" placeholder="请输入节点名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="对应项目结构" prop="projectStructure">
|
||||
<el-input v-model="form.projectStructure" placeholder="请输入对应项目结构" />
|
||||
</el-form-item>
|
||||
<el-form-item label="预计开始时间" prop="planStartDate">
|
||||
<el-date-picker clearable v-model="form.planStartDate" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="选择预计开始时间" />
|
||||
</el-form-item>
|
||||
<el-form-item label="预计结束时间" prop="planEndDate">
|
||||
<el-date-picker clearable v-model="form.planEndDate" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="选择预计结束时间" />
|
||||
</el-form-item>
|
||||
<el-form-item label="实际开始时间" prop="practicalStartDate">
|
||||
<el-date-picker
|
||||
clearable
|
||||
v-model="form.practicalStartDate"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="选择实际开始时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="实际结束时间" prop="practicalEndDate">
|
||||
<el-date-picker
|
||||
clearable
|
||||
v-model="form.practicalEndDate"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="选择实际结束时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="form.status" placeholder="请选择状态">
|
||||
<el-option v-for="dict in project_construction_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="ConstructionSchedulePlan" lang="ts">
|
||||
import {
|
||||
listConstructionSchedulePlan,
|
||||
getConstructionSchedulePlan,
|
||||
delConstructionSchedulePlan,
|
||||
addConstructionSchedulePlan,
|
||||
updateConstructionSchedulePlan
|
||||
} from '@/api/progress/constructionSchedulePlan';
|
||||
import {
|
||||
ConstructionSchedulePlanVO,
|
||||
ConstructionSchedulePlanQuery,
|
||||
ConstructionSchedulePlanForm
|
||||
} from '@/api/progress/constructionSchedulePlan/types';
|
||||
import { useUserStoreHook } from '@/store/modules/user';
|
||||
|
||||
type ConstructionSchedulePlanOption = {
|
||||
id: number;
|
||||
nodeName: string;
|
||||
children?: ConstructionSchedulePlanOption[];
|
||||
};
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const userStore = useUserStoreHook();
|
||||
// 从 store 中获取项目列表和当前选中的项目
|
||||
const currentProject = computed(() => userStore.selectedProject);
|
||||
|
||||
const { project_construction_status } = toRefs<any>(proxy?.useDict('project_construction_status'));
|
||||
|
||||
const constructionSchedulePlanList = ref<ConstructionSchedulePlanVO[]>([]);
|
||||
const constructionSchedulePlanOptions = ref<ConstructionSchedulePlanOption[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const showSearch = ref(true);
|
||||
const isExpandAll = ref(true);
|
||||
const loading = ref(false);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const constructionSchedulePlanFormRef = ref<ElFormInstance>();
|
||||
const constructionSchedulePlanTableRef = ref<ElTableInstance>();
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const initFormData: ConstructionSchedulePlanForm = {
|
||||
id: undefined,
|
||||
projectId: currentProject.value?.id,
|
||||
parentId: undefined,
|
||||
nodeName: undefined,
|
||||
projectStructure: undefined,
|
||||
planStartDate: undefined,
|
||||
planEndDate: undefined,
|
||||
practicalStartDate: undefined,
|
||||
practicalEndDate: undefined,
|
||||
status: undefined,
|
||||
remark: undefined
|
||||
};
|
||||
|
||||
const data = reactive<PageData<ConstructionSchedulePlanForm, ConstructionSchedulePlanQuery>>({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
projectId: currentProject.value?.id,
|
||||
parentId: undefined,
|
||||
nodeName: undefined,
|
||||
status: undefined,
|
||||
params: {}
|
||||
},
|
||||
rules: {
|
||||
id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }],
|
||||
projectId: [{ required: true, message: '项目ID不能为空', trigger: 'blur' }],
|
||||
parentId: [{ required: true, message: '父ID不能为空', trigger: 'blur' }],
|
||||
nodeName: [{ required: true, message: '节点名称不能为空', trigger: 'blur' }],
|
||||
planStartDate: [{ required: true, message: '预计开始时间不能为空', trigger: 'blur' }],
|
||||
planEndDate: [{ required: true, message: '预计结束时间不能为空', trigger: 'blur' }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 查询施工进度计划列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const res = await listConstructionSchedulePlan(queryParams.value);
|
||||
const data = proxy?.handleTree<ConstructionSchedulePlanVO>(res.data, 'id', 'parentId');
|
||||
console.log('🚀 ~ getList ~ data:', data);
|
||||
|
||||
if (data) {
|
||||
constructionSchedulePlanList.value = data;
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 查询施工进度计划下拉树结构 */
|
||||
const getTreeselect = async () => {
|
||||
const res = await listConstructionSchedulePlan();
|
||||
constructionSchedulePlanOptions.value = [];
|
||||
const data: ConstructionSchedulePlanOption = { id: 0, nodeName: '顶级节点', children: [] };
|
||||
data.children = proxy?.handleTree<ConstructionSchedulePlanOption>(res.data, 'id', 'parentId');
|
||||
constructionSchedulePlanOptions.value.push(data);
|
||||
};
|
||||
|
||||
// 取消按钮
|
||||
const cancel = () => {
|
||||
reset();
|
||||
dialog.visible = false;
|
||||
};
|
||||
|
||||
// 表单重置
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData };
|
||||
constructionSchedulePlanFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = (row?: ConstructionSchedulePlanVO) => {
|
||||
reset();
|
||||
getTreeselect();
|
||||
if (row != null && row.id) {
|
||||
form.value.parentId = row.id;
|
||||
} else {
|
||||
form.value.parentId = 0;
|
||||
}
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加施工进度计划';
|
||||
};
|
||||
|
||||
/** 展开/折叠操作 */
|
||||
const handleToggleExpandAll = () => {
|
||||
isExpandAll.value = !isExpandAll.value;
|
||||
toggleExpandAll(constructionSchedulePlanList.value, isExpandAll.value);
|
||||
};
|
||||
|
||||
/** 展开/折叠操作 */
|
||||
const toggleExpandAll = (data: ConstructionSchedulePlanVO[], status: boolean) => {
|
||||
data.forEach((item) => {
|
||||
constructionSchedulePlanTableRef.value?.toggleRowExpansion(item, status);
|
||||
if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
|
||||
});
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row: ConstructionSchedulePlanVO) => {
|
||||
reset();
|
||||
await getTreeselect();
|
||||
if (row != null) {
|
||||
form.value.parentId = row.parentId;
|
||||
}
|
||||
const res = await getConstructionSchedulePlan(row.id);
|
||||
Object.assign(form.value, res.data);
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改施工进度计划';
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
constructionSchedulePlanFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
if (form.value.id) {
|
||||
await updateConstructionSchedulePlan(form.value).finally(() => (buttonLoading.value = false));
|
||||
} else {
|
||||
await addConstructionSchedulePlan(form.value).finally(() => (buttonLoading.value = false));
|
||||
}
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
dialog.visible = false;
|
||||
getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row: ConstructionSchedulePlanVO) => {
|
||||
await proxy?.$modal.confirm('是否确认删除施工进度计划编号为"' + row.id + '"的数据项?');
|
||||
loading.value = true;
|
||||
await delConstructionSchedulePlan(row.id).finally(() => (loading.value = false));
|
||||
await getList();
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
|
||||
//监听项目id刷新数据
|
||||
const listeningProject = watch(
|
||||
() => currentProject.value?.id,
|
||||
(nid, oid) => {
|
||||
queryParams.value.projectId = nid;
|
||||
form.value.projectId = nid;
|
||||
getList();
|
||||
}
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
listeningProject();
|
||||
});
|
||||
</script>
|
||||
286
src/views/progress/plan/component/createDaily.vue
Normal file
286
src/views/progress/plan/component/createDaily.vue
Normal file
@ -0,0 +1,286 @@
|
||||
<template>
|
||||
<div class="daily_paper">
|
||||
<el-dialog
|
||||
v-model="isShowDialog"
|
||||
@close="onCancel"
|
||||
width="1000px"
|
||||
:close-on-click-modal="false"
|
||||
:destroy-on-close="true"
|
||||
:lock-scroll="false"
|
||||
:append-to-body="false"
|
||||
>
|
||||
<template #header>
|
||||
<div style="font-size: 18px">{{ infoDetail.name }} 日报填写</div>
|
||||
</template>
|
||||
<div class="box">
|
||||
<div class="box-left">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="tableData"
|
||||
height="64vh"
|
||||
:cell-style="{ textAlign: 'center' }"
|
||||
:header-cell-style="{ textAlign: 'center' }"
|
||||
border
|
||||
:row-key="tableKey"
|
||||
:expand-row-keys="expandRowKeys"
|
||||
@expand-change="clickOpen"
|
||||
>
|
||||
<el-table-column type="expand">
|
||||
<template #default="props">
|
||||
<div style="margin-left: 45px" m="4">
|
||||
<el-table
|
||||
:border="true"
|
||||
:data="props.row.detailList"
|
||||
:header-cell-style="{ 'text-align': 'center' }"
|
||||
:cell-style="{ 'text-align': 'center' }"
|
||||
highlight-current-row
|
||||
>
|
||||
<el-table-column label="序号" type="index" width="60px" />
|
||||
<el-table-column label="计划日期" prop="date">
|
||||
<template #default="scope">
|
||||
<span class="text-3">{{ scope.row.date }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" prop="planNumber" width="60" />
|
||||
<el-table-column label="完成量" prop="finishedNumber" width="65" />
|
||||
<el-table-column label="操作" class-name="small-padding" width="80px" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="handleDayAdd(scope.row, props.row)"
|
||||
><el-icon><Plus /></el-icon>日报</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="序号" type="index" :index="indexMethod" width="60px" />
|
||||
<el-table-column label="计划数量" prop="planNumber" min-width="100px" />
|
||||
<el-table-column label="开始时间" min-width="100px">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.startDate.split(' ')[0] }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getWorkList"
|
||||
/>
|
||||
</div>
|
||||
<div class="box_right">
|
||||
<div class="box_form" v-if="formDetail.submitTime">
|
||||
<span>{{ dayDetail.date }}</span>
|
||||
<el-form size="large" ref="formRef" :model="formDetail" :rules="rules" label-width="110px">
|
||||
<el-form-item label="实际完成数量" prop="finishedNumber">
|
||||
<el-row>
|
||||
<el-col :span="18">
|
||||
<el-input type="number" :min="0" v-model="formDetail.finishedNumber" placeholder="请输入实际完成数量"> </el-input
|
||||
></el-col>
|
||||
</el-row> </el-form-item
|
||||
></el-form>
|
||||
<div class="footer_box">
|
||||
<el-button @click="onAddSubmit" type="primary" size="large">提 交</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="onCancel" size="large">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { workScheduleList, workScheduleDel, workScheduleSubmit } from '@/api/progress/plan';
|
||||
import { progressPlanDetailForm, workScheduleListQuery } from '@/api/progress/plan/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
|
||||
const menuRef = ref();
|
||||
const formRef = ref();
|
||||
const rowData = ref(null);
|
||||
const expandRowKeys = ref([]);
|
||||
const loading = ref(false);
|
||||
const isShowDialog = ref(false);
|
||||
const tableData = ref([]);
|
||||
const total = ref(0);
|
||||
const oldLength = ref(0);
|
||||
|
||||
const queryParams = reactive<workScheduleListQuery>({
|
||||
pageSize: 10,
|
||||
pageNum: 1,
|
||||
progressCategoryId: ''
|
||||
});
|
||||
|
||||
const infoDetail = reactive({
|
||||
name: ''
|
||||
});
|
||||
|
||||
const formDetail = reactive<progressPlanDetailForm>({
|
||||
id: '',
|
||||
submitTime: '',
|
||||
finishedNumber: 0
|
||||
});
|
||||
|
||||
const dayDetail = reactive({
|
||||
date: '',
|
||||
finishedNumber: 0
|
||||
});
|
||||
|
||||
const rules = {
|
||||
planNum: [{ required: true, message: '实际完成数量不能为空', trigger: 'blur' }]
|
||||
};
|
||||
|
||||
const openDialog = (row: any) => {
|
||||
queryParams.progressCategoryId = row.id;
|
||||
infoDetail.name = row.name;
|
||||
isShowDialog.value = true;
|
||||
rowData.value = row;
|
||||
getWorkList(true);
|
||||
};
|
||||
|
||||
const getWorkList = (bool = false) => {
|
||||
loading.value = true;
|
||||
workScheduleList(queryParams).then((res: any) => {
|
||||
if (res.code === 200) {
|
||||
tableData.value = res.rows.map((item: any, index: number) => ({ ...item, index: index + 1 }));
|
||||
if (bool) oldLength.value = res.rows.length;
|
||||
total.value = res.rows.total;
|
||||
}
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
emit('getProgressList');
|
||||
isShowDialog.value = false;
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const indexMethod = (index: number) => {
|
||||
const currentPage = queryParams.pageNum - 1;
|
||||
return index + 1 + (currentPage > 0 ? currentPage * queryParams.pageSize : 0);
|
||||
};
|
||||
|
||||
const handleDayAdd = (row: any, obj: any) => {
|
||||
formDetail.id = row.id;
|
||||
formDetail.submitTime = row.date;
|
||||
formDetail.finishedNumber = row.finishedNumber;
|
||||
Object.assign(dayDetail, row);
|
||||
};
|
||||
|
||||
const onAddSubmit = () => {
|
||||
dayDetail.finishedNumber = Number(dayDetail.finishedNumber);
|
||||
workScheduleSubmit(formDetail).then((res: any) => {
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('添加成功');
|
||||
dayDetail.finishedNumber = formDetail.finishedNumber;
|
||||
formDetail.submitTime = '';
|
||||
formDetail.finishedNumber = 0;
|
||||
getWorkList();
|
||||
} else {
|
||||
ElMessage.error(res.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const tableKey = (row: any) => row.id;
|
||||
|
||||
const clickOpen = (row: any) => {
|
||||
const idx = expandRowKeys.value.indexOf(row.id);
|
||||
if (idx !== -1) expandRowKeys.value.splice(idx, 1);
|
||||
else expandRowKeys.value.push(row.id);
|
||||
expandRowKeys.value = [...new Set(expandRowKeys.value)];
|
||||
};
|
||||
|
||||
defineExpose({ openDialog });
|
||||
const emit = defineEmits(['getProgressList']);
|
||||
|
||||
let scrollTop = 0;
|
||||
|
||||
watch(
|
||||
() => isShowDialog.value,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
scrollTop = window.scrollY || document.documentElement.scrollTop;
|
||||
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
||||
|
||||
document.body.style.position = 'fixed';
|
||||
document.body.style.top = `-${scrollTop}px`;
|
||||
document.body.style.width = '100%';
|
||||
document.body.style.paddingRight = `${scrollbarWidth}px`; // 👈 补偿滚动条宽度
|
||||
} else {
|
||||
document.body.style.position = '';
|
||||
document.body.style.top = '';
|
||||
document.body.style.width = '';
|
||||
document.body.style.paddingRight = ''; // 👈 恢复
|
||||
|
||||
nextTick(() => {
|
||||
window.scrollTo(0, scrollTop);
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.daily_paper {
|
||||
width: 100%;
|
||||
.box {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
padding: 0 10px;
|
||||
.box-left {
|
||||
width: 46%;
|
||||
float: left;
|
||||
}
|
||||
.box_right {
|
||||
width: 48%;
|
||||
float: left;
|
||||
border: 1px solid #ebeef5;
|
||||
height: 64vh;
|
||||
margin-left: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.box_form {
|
||||
width: 80%;
|
||||
background-color: aliceblue;
|
||||
padding: 10px;
|
||||
> span {
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-size: 20px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.footer_box {
|
||||
margin: 20px auto 0;
|
||||
width: 100%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
padding: 10px 10px !important;
|
||||
}
|
||||
.el-drawer__title {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
583
src/views/progress/plan/component/createDailyRate.vue
Normal file
583
src/views/progress/plan/component/createDailyRate.vue
Normal file
@ -0,0 +1,583 @@
|
||||
<template>
|
||||
<div class="daily-paper-count">
|
||||
<el-dialog
|
||||
v-model="isShowDialog"
|
||||
@close="onCancel"
|
||||
width="70vw"
|
||||
:close-on-click-modal="false"
|
||||
:destroy-on-close="true"
|
||||
:lock-scroll="false"
|
||||
:append-to-body="false"
|
||||
>
|
||||
<template #header>
|
||||
<div v-drag="['.daily-paper-count .el-dialog', '.daily-paper-count .el-dialog__header']" style="font-size: 18px">
|
||||
{{ infoDetail.name }} 日报填写
|
||||
</div>
|
||||
</template>
|
||||
<div class="box">
|
||||
<div class="box-left">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="tableData"
|
||||
height="64vh"
|
||||
:cell-style="{ textAlign: 'center' }"
|
||||
:header-cell-style="{ textAlign: 'center' }"
|
||||
border
|
||||
:row-key="tableKey"
|
||||
:expand-row-keys="expandRowKeys"
|
||||
@expand-change="clickOpen"
|
||||
>
|
||||
<el-table-column type="expand">
|
||||
<template #default="{ row: propsRow }">
|
||||
<div style="margin-left: 45px" m="4">
|
||||
<el-table
|
||||
:border="true"
|
||||
:data="propsRow.detailList"
|
||||
:header-cell-style="{ 'text-align': 'center' }"
|
||||
:cell-style="{ 'text-align': 'center' }"
|
||||
highlight-current-row
|
||||
>
|
||||
<el-table-column label="序号" type="index" width="60px" />
|
||||
<el-table-column label="计划日期" prop="date" />
|
||||
<el-table-column label="数量" prop="planNumber" width="60" />
|
||||
<el-table-column label="完成量" prop="finishedNumber" width="70" />
|
||||
<el-table-column label="AI填报" prop="aiFill" width="70" />
|
||||
<el-table-column label="操作" class-name="small-padding" width="170px" fixed="right">
|
||||
<template #default="{ row: scopeRow, $index }">
|
||||
<el-button type="primary" link @click="handleDayAdd(scopeRow, propsRow)">
|
||||
<el-icon><Plus /></el-icon>日报
|
||||
</el-button>
|
||||
<el-button type="success" link @click="handleView(scopeRow, propsRow)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="序号" type="index" :index="indexMethod" width="60px" />
|
||||
<el-table-column label="计划数量" prop="planNumber" min-width="100px" />
|
||||
<el-table-column label="完成数量" prop="finishedNumber" min-width="100px" />
|
||||
<el-table-column label="延期量" min-width="100px">
|
||||
<template #default="{ row: scopeRow }">
|
||||
<el-tag :type="scopeRow.delayNumber.toString() ? 'success' : 'danger'">
|
||||
{{ scopeRow.delayNumber || 0 }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="AI填报" prop="aiFill" min-width="100px" />
|
||||
<el-table-column label="开始时间" min-width="100px">
|
||||
<template #default="{ row: scopeRow }">
|
||||
<span>{{ scopeRow.startDate.split(' ')[0] }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getWorkList"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
:isSmall="5"
|
||||
class="float-left mt-4.5!"
|
||||
/>
|
||||
</div>
|
||||
<div class="box_right" v-if="showDayWork">
|
||||
<div class="time_submit">
|
||||
<span>{{ formDetail.submitTime }}</span>
|
||||
<el-button type="primary" :disabled="!checkedList.length || flag" @click="onUploadDaily" size="small">
|
||||
<el-icon><Upload /></el-icon>提交日报
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="loading1"
|
||||
height="58vh"
|
||||
:data="detialList"
|
||||
@selection-change="handleSelectionChange"
|
||||
:row-key="(row) => row.id"
|
||||
ref="multipleTableRef"
|
||||
>
|
||||
<el-table-column label="未完成工作" align="center">
|
||||
<el-table-column :reserve-selection="true" type="selection" width="55" align="center" />
|
||||
<el-table-column label="序号" align="center" type="index" width="60px" />
|
||||
<el-table-column label="编号" align="center" prop="name" min-width="100px" />
|
||||
<el-table-column label="状态" align="center" min-width="100px">
|
||||
<template #default="{ row: scopeRow }">
|
||||
<el-tag :type="typeList[scopeRow.status]">
|
||||
{{ filterStatus(Number(scopeRow.status)) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination
|
||||
v-show="1"
|
||||
:total="detailTotal"
|
||||
v-model:page="detailQueryParams.pageNum"
|
||||
v-model:limit="detailQueryParams.pageSize"
|
||||
@pagination="getPvModuleList"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
:isSmall="5"
|
||||
/>
|
||||
</div>
|
||||
<div class="box_right" v-else>
|
||||
<div class="time_submit">
|
||||
<span>{{ formDetail.submitTime }}</span>
|
||||
<el-button type="danger" :disabled="single" @click="handleRemove(null)" size="small">
|
||||
<el-icon><Minus /></el-icon>批量移除
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="loading2"
|
||||
height="58vh"
|
||||
:data="detialWordList"
|
||||
@selection-change="handleSelectionChangeWork"
|
||||
:row-key="(row) => row.id"
|
||||
ref="multipleTableWorkRef"
|
||||
>
|
||||
<el-table-column label="已完成工作" align="center">
|
||||
<el-table-column :reserve-selection="true" type="selection" width="55" align="center" />
|
||||
<el-table-column label="序号" align="center" type="index" width="60px" />
|
||||
<el-table-column label="编号" align="center" prop="name" min-width="100px" />
|
||||
<el-table-column label="填报方式" align="center" prop="status" min-width="100px">
|
||||
<template #default="{ row: scopeRow }">
|
||||
<span v-if="scopeRow.finishType === '1'">手动填报</span>
|
||||
<span v-if="scopeRow.finishType === '2'">AI识别</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" min-width="100px">
|
||||
<template #default="{ row: scopeRow }">
|
||||
<el-button type="danger" link @click="handleRemove(scopeRow)">
|
||||
<el-icon><Minus /></el-icon>移除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination
|
||||
v-show="detailTotalWork > 0"
|
||||
:total="detailTotalWork"
|
||||
v-model:page="detailQueryParamsWork.pageNum"
|
||||
v-model:limit="detailQueryParamsWork.pageSize"
|
||||
@pagination="getPvModuleList"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
:isSmall="5"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="onCancel" size="large">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { workScheduleList, pvModuleList, addDaily, getDailyBook, deleteDaily } from '@/api/progress/plan';
|
||||
import { workScheduleListQuery } from '@/api/progress/plan/types';
|
||||
|
||||
// 响应式状态
|
||||
const state = reactive<{
|
||||
expandRowKeys: string[];
|
||||
loading: boolean;
|
||||
isShowDialog: boolean;
|
||||
queryParams: workScheduleListQuery;
|
||||
infoDetail: { name: string };
|
||||
tableData: any[];
|
||||
total: number;
|
||||
formDetail: {
|
||||
workId: string;
|
||||
id: string;
|
||||
submitTime: string;
|
||||
finishedNumber: number;
|
||||
};
|
||||
detialList: any[];
|
||||
detailTotal: number;
|
||||
detailQueryParams: {
|
||||
pageSize: number;
|
||||
pageNum: number;
|
||||
};
|
||||
loading1: boolean;
|
||||
loading2: boolean;
|
||||
typeList: ('info' | 'warning' | 'success' | 'danger')[];
|
||||
checkedList: number[];
|
||||
updateRow: any | null;
|
||||
showDayWork: boolean;
|
||||
detialWordList: any[];
|
||||
detailTotalWork: number;
|
||||
detailQueryParamsWork: {
|
||||
pageSize: number;
|
||||
pageNum: number;
|
||||
};
|
||||
single: boolean;
|
||||
checkList: number[];
|
||||
flag: boolean;
|
||||
}>({
|
||||
expandRowKeys: [],
|
||||
loading: false,
|
||||
isShowDialog: false,
|
||||
queryParams: {
|
||||
pageSize: 10,
|
||||
pageNum: 1,
|
||||
progressCategoryId: ''
|
||||
},
|
||||
infoDetail: { name: '' },
|
||||
tableData: [],
|
||||
total: 0,
|
||||
formDetail: {
|
||||
workId: '',
|
||||
id: '',
|
||||
submitTime: '选择日期',
|
||||
finishedNumber: 0
|
||||
},
|
||||
detialList: [],
|
||||
detailTotal: 0,
|
||||
detailQueryParams: {
|
||||
pageSize: 10,
|
||||
pageNum: 1
|
||||
},
|
||||
loading1: false,
|
||||
loading2: false,
|
||||
typeList: ['info', 'warning', 'success', 'danger'],
|
||||
checkedList: [],
|
||||
updateRow: null,
|
||||
showDayWork: true,
|
||||
detialWordList: [],
|
||||
detailTotalWork: 0,
|
||||
detailQueryParamsWork: {
|
||||
pageSize: 10,
|
||||
pageNum: 1
|
||||
},
|
||||
single: true,
|
||||
checkList: [],
|
||||
flag: false
|
||||
});
|
||||
|
||||
// 引用refs
|
||||
const multipleTableRef = ref();
|
||||
const multipleTableWorkRef = ref();
|
||||
|
||||
// 状态解包
|
||||
const {
|
||||
expandRowKeys,
|
||||
loading,
|
||||
isShowDialog,
|
||||
queryParams,
|
||||
infoDetail,
|
||||
tableData,
|
||||
total,
|
||||
formDetail,
|
||||
detialList,
|
||||
detailTotal,
|
||||
detailQueryParams,
|
||||
loading1,
|
||||
loading2,
|
||||
typeList,
|
||||
checkedList,
|
||||
updateRow,
|
||||
showDayWork,
|
||||
detialWordList,
|
||||
detailTotalWork,
|
||||
detailQueryParamsWork,
|
||||
single,
|
||||
checkList,
|
||||
flag
|
||||
} = toRefs(state);
|
||||
|
||||
// 状态过滤函数
|
||||
const filterStatus = (val: number): string => {
|
||||
switch (val) {
|
||||
case 0:
|
||||
return '未开始';
|
||||
case 1:
|
||||
return '进行中';
|
||||
case 2:
|
||||
return '已完成';
|
||||
case 3:
|
||||
return '已延期';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (row: any) => {
|
||||
resetForm(false);
|
||||
formDetail.value.submitTime = '';
|
||||
queryParams.value.progressCategoryId = row.id;
|
||||
formDetail.value.workId = row.work_id;
|
||||
infoDetail.value = row;
|
||||
state.isShowDialog = true;
|
||||
getWorkList(true);
|
||||
};
|
||||
|
||||
const resetForm = (bool: boolean) => {
|
||||
showDayWork.value = bool;
|
||||
detialList.value = [];
|
||||
detialWordList.value = [];
|
||||
};
|
||||
|
||||
// 获取未完成数据
|
||||
const getPvModuleList = () => {
|
||||
loading1.value = true;
|
||||
pvModuleList({
|
||||
id: formDetail.value.id,
|
||||
...detailQueryParams.value
|
||||
}).then((res: any) => {
|
||||
loading1.value = false;
|
||||
if (res.code === 200) {
|
||||
detialList.value = res.rows;
|
||||
detailTotal.value = res.total;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 获取计划列表
|
||||
const getWorkList = (bool = false) => {
|
||||
loading.value = true;
|
||||
workScheduleList(queryParams.value).then((res: any) => {
|
||||
if (res.code === 200) {
|
||||
state.tableData = res.rows.map((item: any, i: number) => {
|
||||
item.index = i + 1;
|
||||
item.aiFill = item.detailList?.reduce((sum: number, child: any) => sum + child.aiFill, 0) || 0;
|
||||
return item;
|
||||
});
|
||||
state.total = res.total;
|
||||
}
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
// 关闭弹窗
|
||||
const onCancel = () => {
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
isShowDialog.value = false;
|
||||
emit('getProgressList');
|
||||
};
|
||||
|
||||
// 自定义序号
|
||||
const indexMethod = (index: number): number => {
|
||||
const currentPage = queryParams.value.pageNum - 1;
|
||||
return currentPage > -1 ? index + 1 + currentPage * queryParams.value.pageSize : index + 1;
|
||||
};
|
||||
|
||||
// 日报添加
|
||||
const handleDayAdd = (row: any, obj: any) => {
|
||||
resetForm(true);
|
||||
formDetail.value.id = row.id;
|
||||
formDetail.value.submitTime = row.date;
|
||||
state.updateRow = row;
|
||||
getPvModuleList();
|
||||
};
|
||||
|
||||
const tableKey = (row: any) => row.id;
|
||||
|
||||
// 展开行处理
|
||||
const clickOpen = (row: any) => {
|
||||
const rowId = String(row.id);
|
||||
const index = state.expandRowKeys.indexOf(rowId);
|
||||
index === -1 ? state.expandRowKeys.push(rowId) : state.expandRowKeys.splice(index, 1);
|
||||
state.expandRowKeys = [...new Set(state.expandRowKeys)];
|
||||
};
|
||||
|
||||
// 多选框处理
|
||||
const handleSelectionChange = (selection: any[]) => {
|
||||
state.checkedList = selection.map((item: any) => item.id);
|
||||
};
|
||||
|
||||
// 提交日报
|
||||
const onUploadDaily = () => {
|
||||
if (!checkedList.value.length) {
|
||||
ElMessage.warning('请添加完成的任务编号');
|
||||
return;
|
||||
}
|
||||
|
||||
const obj = {
|
||||
finishedDetailIdList: checkedList.value,
|
||||
id: formDetail.value.id
|
||||
};
|
||||
|
||||
state.flag = true;
|
||||
addDaily(obj).then((res: any) => {
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('添加成功');
|
||||
if (state.updateRow) {
|
||||
state.updateRow.finishedNumber += checkedList.value.length;
|
||||
}
|
||||
checkedList.value = [];
|
||||
multipleTableRef.value?.clearSelection();
|
||||
getPvModuleList();
|
||||
getWorkList();
|
||||
} else {
|
||||
ElMessage.error(res.message);
|
||||
}
|
||||
state.flag = false;
|
||||
});
|
||||
};
|
||||
|
||||
// 查看日报
|
||||
const handleView = (row: any, obj: any) => {
|
||||
resetForm(false);
|
||||
state.updateRow = row;
|
||||
formDetail.value.id = row.id;
|
||||
formDetail.value.submitTime = row.date;
|
||||
getDailyBookList(row.date);
|
||||
|
||||
showDayWork.value = false;
|
||||
};
|
||||
|
||||
// 获取已填日报
|
||||
const getDailyBookList = (doneTime: string) => {
|
||||
detialWordList.value = [];
|
||||
getDailyBook({
|
||||
id: formDetail.value.id,
|
||||
...detailQueryParams.value
|
||||
}).then((res: any) => {
|
||||
if (res.code === 200) {
|
||||
detialWordList.value = res.rows || [];
|
||||
detailTotalWork.value = res.total;
|
||||
} else {
|
||||
ElMessage.error(res.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 选中处理(已完成)
|
||||
const handleSelectionChangeWork = (selection: any[]) => {
|
||||
state.checkList = selection.map((item: any) => item.id);
|
||||
state.single = selection.length === 0;
|
||||
};
|
||||
|
||||
// 移除日报
|
||||
const handleRemove = (row?: any) => {
|
||||
const planID = row ? [row.id] : state.checkList;
|
||||
const obj = {
|
||||
detailIdList: planID,
|
||||
id: formDetail.value.id
|
||||
};
|
||||
|
||||
ElMessageBox.confirm('确认移除该条数据?', '温馨提示', {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
deleteDaily(obj).then((res: any) => {
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('移除成功');
|
||||
if (state.updateRow) {
|
||||
state.updateRow.finishedNumber -= planID.length;
|
||||
}
|
||||
getDailyBookList(formDetail.value.submitTime);
|
||||
getWorkList();
|
||||
} else {
|
||||
ElMessage.error(res.message);
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
// 延期计算
|
||||
// const filterW = (row: any): number => {
|
||||
// const { finishedNumber, planNumber, endAt } = row;
|
||||
// if (!endAt) return 0;
|
||||
|
||||
// const endTime = new Date(endAt).getTime();
|
||||
// const now = new Date().getTime();
|
||||
|
||||
// if (endTime <= now && planNumber > finishedNumber) {
|
||||
// return planNumber - finishedNumber;
|
||||
// }
|
||||
// return 0;
|
||||
// };
|
||||
|
||||
// 暴露给模板的属性和方法
|
||||
defineExpose({
|
||||
openDialog,
|
||||
closeDialog
|
||||
});
|
||||
const emit = defineEmits(['getProgressList']);
|
||||
|
||||
let scrollTop = 0;
|
||||
|
||||
watch(
|
||||
() => isShowDialog.value,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
scrollTop = window.scrollY || document.documentElement.scrollTop;
|
||||
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
||||
|
||||
document.body.style.position = 'fixed';
|
||||
document.body.style.top = `-${scrollTop}px`;
|
||||
document.body.style.width = '100%';
|
||||
document.body.style.paddingRight = `${scrollbarWidth}px`; // 👈 补偿滚动条宽度
|
||||
} else {
|
||||
document.body.style.position = '';
|
||||
document.body.style.top = '';
|
||||
document.body.style.width = '';
|
||||
document.body.style.paddingRight = ''; // 👈 恢复
|
||||
|
||||
nextTick(() => {
|
||||
window.scrollTo(0, scrollTop);
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.daily-paper-count {
|
||||
width: 100%;
|
||||
|
||||
.box {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
padding: 0 10px;
|
||||
|
||||
.box-left {
|
||||
width: 50%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.box_right {
|
||||
width: 46%;
|
||||
float: left;
|
||||
border: 1px solid #ebeef5;
|
||||
height: 64vh;
|
||||
margin-left: 20px;
|
||||
|
||||
.time_submit {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
> span {
|
||||
font-size: 20px;
|
||||
font-family: revert;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
padding: 10px 10px !important;
|
||||
}
|
||||
|
||||
.el-drawer__title {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
319
src/views/progress/plan/component/createPlan.vue
Normal file
319
src/views/progress/plan/component/createPlan.vue
Normal file
@ -0,0 +1,319 @@
|
||||
<template>
|
||||
<div class="new-bar-chart-add">
|
||||
<el-dialog v-model="isShowDialog" width="800px" :close-on-click-modal="false" :destroy-on-close="true">
|
||||
<template #header>
|
||||
<div v-drag="['.new-bar-chart-add .el-dialog', '.new-bar-chart-add .el-dialog__header']" style="font-size: 18px">
|
||||
{{ infoDetail.name }} 计划创建
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="14">
|
||||
<el-form size="large" ref="formRef" :model="formData" :rules="rules" label-width="80px">
|
||||
<el-form-item label="开始时间" prop="start_at">
|
||||
<el-date-picker
|
||||
v-model="formData.start_at"
|
||||
placeholder="选择开始时间"
|
||||
type="date"
|
||||
clearable
|
||||
value-format="YYYY-MM-DD"
|
||||
size="large"
|
||||
:disabled-date="pickerOptionsStart"
|
||||
:disabled="remainingNumAt.leftNum === 0"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="end_at">
|
||||
<el-date-picker
|
||||
v-model="formData.end_at"
|
||||
placeholder="选择结束时间"
|
||||
type="date"
|
||||
clearable
|
||||
value-format="YYYY-MM-DD"
|
||||
size="large"
|
||||
:disabled-date="pickerOptionsEnd"
|
||||
:disabled="remainingNumAt.leftNum === 0"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="计划数量" prop="planNumber">
|
||||
<el-row>
|
||||
<el-col :span="18">
|
||||
<el-input
|
||||
v-model="formData.planNumber"
|
||||
type="number"
|
||||
:min="0"
|
||||
:max="remainingNumAt.leftNum"
|
||||
:disabled="remainingNumAt.leftNum === 0"
|
||||
placeholder="请输入计划数量"
|
||||
>
|
||||
<template #append>
|
||||
<span style="font-size: 12px">最大值:{{ remainingNumAt.leftNum }}</span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-button type="primary" @click="onAverage" :disabled="!formData.planNumber || tableData.length === 0" style="margin-left: 10px"
|
||||
>均值</el-button
|
||||
>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="10">
|
||||
<el-table
|
||||
style="margin-left: 10px; margin-bottom: 10px"
|
||||
height="250"
|
||||
:data="paginatedData"
|
||||
border
|
||||
empty-text="暂无计划"
|
||||
:cell-style="{ textAlign: 'center' }"
|
||||
:header-cell-style="{ textAlign: 'center' }"
|
||||
>
|
||||
<el-table-column type="index" :index="indexMethod" label="序号" width="60" />
|
||||
<el-table-column prop="date" label="日期" width="130" />
|
||||
<el-table-column prop="planNumber" label="数量">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row.planNumber"
|
||||
:value-on-clear="0"
|
||||
:min="0"
|
||||
:max="scope.row.max"
|
||||
@change="onChangeSum"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
style="margin-left: 10px"
|
||||
background
|
||||
layout="total, prev, pager, next"
|
||||
:total="tableData.length"
|
||||
:page-size="pageSize"
|
||||
:current-page="currentPage"
|
||||
@current-change="(val) => (currentPage = val)"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" size="large" @click="onSubmit">确 定</el-button>
|
||||
<el-button size="large" @click="closeDialog">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch, computed } from 'vue';
|
||||
import { ElMessage, FormInstance } from 'element-plus';
|
||||
import { workScheduleAddPlan, lastTime } from '@/api/progress/plan';
|
||||
import { lastTimeVo, ProgressCategoryVO, ProgressPlanForm } from '@/api/progress/plan/types';
|
||||
|
||||
// Form refs
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// State
|
||||
const isShowDialog = ref(false);
|
||||
const currentPage = ref(1);
|
||||
const pageSize = 10;
|
||||
|
||||
const formData = reactive({
|
||||
start_at: '',
|
||||
end_at: '',
|
||||
planNumber: 0
|
||||
});
|
||||
|
||||
const rules = {
|
||||
start_at: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
|
||||
end_at: [{ required: true, message: '结束时间不能为空', trigger: 'blur' }],
|
||||
planNumber: [
|
||||
{
|
||||
trigger: 'input', // 用户输入时触发校验
|
||||
validator: (_rule: any, value: string, callback: any) => {
|
||||
const numberValue = Number(value);
|
||||
console.log(numberValue);
|
||||
|
||||
if (value === '') {
|
||||
callback(new Error('请输入值'));
|
||||
} else if (numberValue > remainingNumAt.leftNum) {
|
||||
callback(new Error(`数字不能大于 ${remainingNumAt.leftNum}`));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
},
|
||||
{ required: true, message: '计划数量不能为空', trigger: 'blur' }
|
||||
]
|
||||
};
|
||||
|
||||
const infoDetail = reactive<ProgressCategoryVO>({} as ProgressCategoryVO);
|
||||
|
||||
const remainingNumAt = reactive<lastTimeVo>({
|
||||
endDate: '',
|
||||
leftNum: 100
|
||||
});
|
||||
|
||||
const tableData = ref<{ date: string; planNumber: number; max: number }[]>([]);
|
||||
|
||||
// Computed paginated data
|
||||
const paginatedData = computed(() => {
|
||||
const start = (currentPage.value - 1) * pageSize;
|
||||
return tableData.value.slice(start, start + pageSize);
|
||||
});
|
||||
|
||||
// Watch date changes
|
||||
watch(
|
||||
() => [formData.start_at, formData.end_at],
|
||||
([start, end]) => {
|
||||
if (start && end) {
|
||||
const dates = generateDateRange(start, end);
|
||||
tableData.value = dates.map((date) => ({
|
||||
date,
|
||||
planNumber: 0,
|
||||
max: remainingNumAt.leftNum
|
||||
}));
|
||||
} else {
|
||||
tableData.value = [];
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Date helpers
|
||||
const generateDateRange = (start: string, end: string): string[] => {
|
||||
const result: string[] = [];
|
||||
const startDate = new Date(start);
|
||||
const endDate = new Date(end);
|
||||
while (startDate <= endDate) {
|
||||
result.push(startDate.toISOString().split('T')[0]);
|
||||
startDate.setDate(startDate.getDate() + 1);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// Picker rules
|
||||
const pickerOptionsStart = (date: Date) => {
|
||||
if (formData.end_at) return date.getTime() > new Date(formData.end_at).getTime();
|
||||
if (remainingNumAt.endDate) return date.getTime() < new Date(remainingNumAt.endDate).getTime();
|
||||
return false;
|
||||
};
|
||||
|
||||
const pickerOptionsEnd = (date: Date) => {
|
||||
if (formData.start_at) return date.getTime() < new Date(formData.start_at).getTime();
|
||||
if (remainingNumAt.endDate) return date.getTime() < new Date(remainingNumAt.endDate).getTime();
|
||||
return false;
|
||||
};
|
||||
|
||||
// Average calculation
|
||||
const onAverage = () => {
|
||||
const total = formData.planNumber;
|
||||
const len = tableData.value.length;
|
||||
const avg = Math.floor(total / len);
|
||||
let remainder = total % len;
|
||||
|
||||
tableData.value.forEach((row) => {
|
||||
row.planNumber = avg + (remainder > 0 ? 1 : 0);
|
||||
remainder--;
|
||||
});
|
||||
};
|
||||
|
||||
// Total sum validation
|
||||
const onChangeSum = () => {
|
||||
let total = tableData.value.reduce((sum, item) => sum + item.planNumber, 0);
|
||||
if (total > remainingNumAt.leftNum) {
|
||||
tableData.value.forEach((item) => {
|
||||
item.max = item.planNumber;
|
||||
});
|
||||
}
|
||||
formData.planNumber = total;
|
||||
};
|
||||
|
||||
// Index method
|
||||
const indexMethod = (index: number) => {
|
||||
return index + 1 + (currentPage.value - 1) * pageSize;
|
||||
};
|
||||
|
||||
// Submit handler
|
||||
const onSubmit = () => {
|
||||
if (!formRef.value) return;
|
||||
formRef.value.validate((valid) => {
|
||||
if (!valid) return;
|
||||
const payload: ProgressPlanForm = {
|
||||
projectId: infoDetail.projectId,
|
||||
planNumber: formData.planNumber,
|
||||
detailList: tableData.value,
|
||||
startDate: formData.start_at,
|
||||
endDate: formData.end_at,
|
||||
progressCategoryId: infoDetail.id,
|
||||
matrixId: infoDetail.matrixId
|
||||
};
|
||||
|
||||
workScheduleAddPlan(payload).then((res: any) => {
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('添加成功');
|
||||
emit('getProgressList');
|
||||
closeDialog();
|
||||
} else {
|
||||
ElMessage.error(res.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Dialog controls
|
||||
const openDialog = (row: typeof infoDetail) => {
|
||||
Object.assign(infoDetail, row);
|
||||
isShowDialog.value = true;
|
||||
resetForm();
|
||||
fetchLastTime(row);
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
isShowDialog.value = false;
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
formData.start_at = '';
|
||||
formData.end_at = '';
|
||||
formData.planNumber = 0;
|
||||
tableData.value = [];
|
||||
};
|
||||
|
||||
const fetchLastTime = (row: typeof infoDetail) => {
|
||||
console.log(row);
|
||||
|
||||
lastTime(row.id).then((res: any) => {
|
||||
if (res.code === 200) {
|
||||
Object.assign(remainingNumAt, res.data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Export function if needed externally
|
||||
defineExpose({ openDialog });
|
||||
const emit = defineEmits(['getProgressList']);
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.new-bar-chart-add {
|
||||
.el-form-item--large .el-form-item__content {
|
||||
line-height: 32px !important;
|
||||
}
|
||||
.el-input-number--small {
|
||||
width: 90px;
|
||||
}
|
||||
.el-input-group__append {
|
||||
padding: 0 10px;
|
||||
}
|
||||
.el-form {
|
||||
background: aliceblue;
|
||||
padding: 60px 10px;
|
||||
}
|
||||
.el-row {
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
439
src/views/progress/plan/index.vue
Normal file
439
src/views/progress/plan/index.vue
Normal file
@ -0,0 +1,439 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||||
<el-form-item label="请选择方阵:" prop="pid" label-width="100">
|
||||
<!-- <el-input v-model="queryParams.pid" placeholder="请选择" clearable /> -->
|
||||
<el-cascader
|
||||
:options="matrixOptions"
|
||||
placeholder="请选择"
|
||||
@change="handleChange"
|
||||
:props="{ value: 'matrixId', label: 'name' }"
|
||||
v-model="queryParams.matrixId"
|
||||
clearable
|
||||
/>
|
||||
<!-- <el-select v-model="matrixValue" placeholder="请选择" @change="handleChange" clearable>
|
||||
<el-option v-for="item in matrixOptions" :key="item.id" :label="item.matrixName" :value="item.id" />
|
||||
</el-select> -->
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Download" @click="handleQuery">导出周报</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<el-card shadow="never">
|
||||
<!-- <template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['progress:progressCategory:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template> -->
|
||||
<el-table
|
||||
ref="progressCategoryTableRef"
|
||||
v-loading="loading"
|
||||
:data="progressCategoryList"
|
||||
row-key="id"
|
||||
:default-expand-all="isExpandAll"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
border
|
||||
>
|
||||
<el-table-column label="" width="50" type="expand">
|
||||
<template #header>
|
||||
<el-icon
|
||||
class="cursor-pointer text-4! transform-rotate-z--90 transition-all-300"
|
||||
:class="!isExpandAll ? 'transform-rotate-z--90' : 'transform-rotate-z-90'"
|
||||
@click="handleToggleExpandAll"
|
||||
><Expand
|
||||
/></el-icon>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-card class="pl-25" shadow="hover">
|
||||
<el-table :data="scope.row.children" border>
|
||||
<el-table-column label="名称" align="center" prop="name" width="170">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip :content="row.remark" placement="top" effect="dark" v-if="row.remark">
|
||||
<span class="flex items-center justify-center"
|
||||
><i class="iconfont icon-wenhao mr-0.5 text-3.5! text-#999"></i>{{ row.name }}</span
|
||||
>
|
||||
</el-tooltip>
|
||||
<span v-else>{{ row.name }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="progress_status" :value="row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否延期" align="center" prop="isDelay" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.isDelay == '1' ? 'danger' : 'primary'">{{ row.isDelay == '1' ? '是' : '否' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="计量方式" align="center" prop="unitType" width="100">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="progress_unit_type" :value="row.unitType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总量" align="center" prop="total" width="100" />
|
||||
<el-table-column label="总进度" align="center" prop="projectId">
|
||||
<template #default="{ row }">
|
||||
<el-progress :text-inside="true" :stroke-width="20" :percentage="row.completedPercentage" status="success" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="计划总量" align="center" prop="planTotal" width="100" />
|
||||
<el-table-column label="计划中" align="center" prop="projectId">
|
||||
<template #default="{ row }">
|
||||
<el-progress :text-inside="true" :stroke-width="20" :percentage="row.planTotalPercentage" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="对比" align="center" prop="unitType" width="100">
|
||||
<template #default="{ row }">
|
||||
{{ row.completed + '/' + row.total }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200">
|
||||
<template #default="scope">
|
||||
<!-- <el-button
|
||||
type="warning"
|
||||
icon="Download"
|
||||
link
|
||||
size="small"
|
||||
@click="openDialog(scope.row, 'importDataStatus', '上传数据')"
|
||||
v-hasPermi="['progress:progressCategory:add']"
|
||||
>
|
||||
导入数据
|
||||
</el-button> -->
|
||||
<el-button
|
||||
type="warning"
|
||||
icon="Download"
|
||||
link
|
||||
size="small"
|
||||
v-if="scope.row.name === '光伏板'"
|
||||
@click="openDialog(scope.row, 'importTableStatus', '上传表格')"
|
||||
v-hasPermi="['progress:progressCategory:add']"
|
||||
>
|
||||
导入表格
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
icon="Plus"
|
||||
link
|
||||
size="small"
|
||||
@click="planRef.openDialog(scope.row)"
|
||||
v-hasPermi="['progress:progressCategory:add']"
|
||||
>
|
||||
计划
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="Plus"
|
||||
link
|
||||
size="small"
|
||||
@click="handleDayAdd(scope.row)"
|
||||
v-hasPermi="['progress:progressCategory:add']"
|
||||
>
|
||||
日报
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="名称" align="center" prop="name" width="150" />
|
||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="progress_status" :value="row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总进度" align="center" prop="projectId">
|
||||
<template #default="{ row }">
|
||||
<el-progress :text-inside="true" :stroke-width="20" :percentage="row.completedPercentage" status="success" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
<!-- 导入数据对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.importDataStatus" width="500px" append-to-body>
|
||||
<file-upload class="pl-20 pt" v-model="dialog.file" :limit="20" :file-size="50" :file-type="['shp', 'shx', 'dbf']" />
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel('importDataStatus')">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<!-- 导入表格对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.importTableStatus" width="500px" append-to-body>
|
||||
<file-upload class="pl-20 pt" v-model="dialog.file" :limit="20" :file-size="50" :file-type="['xls', 'xlsx']" />
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel('importTableStatus')">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<!-- 创建计划对话框 -->
|
||||
<CreatePlan ref="planRef" @getProgressList="getList"></CreatePlan>
|
||||
<CreateDaily ref="dailyRef" @getProgressList="getList"></CreateDaily>
|
||||
<CreateDailyRate ref="dailyRateRef" @getProgressList="getList"></CreateDailyRate>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="ProgressCategory" lang="ts">
|
||||
import {
|
||||
listProgressCategory,
|
||||
getProgressCategory,
|
||||
delProgressCategory,
|
||||
addProgressCategory,
|
||||
updateProgressCategory,
|
||||
getProjectSquare
|
||||
} from '@/api/progress/plan/index';
|
||||
import { ProgressCategoryVO, ProgressCategoryQuery, ProgressCategoryForm } from '@/api/progress/plan/types';
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const { progress_unit_type, progress_status } = toRefs<any>(proxy?.useDict('progress_unit_type', 'progress_status'));
|
||||
|
||||
import CreatePlan from './component/createPlan.vue';
|
||||
import CreateDaily from './component/createDaily.vue';
|
||||
import CreateDailyRate from './component/createDailyRate.vue';
|
||||
type ProgressCategoryOption = {
|
||||
id: number;
|
||||
name: string;
|
||||
children?: ProgressCategoryOption[];
|
||||
};
|
||||
import { useUserStoreHook } from '@/store/modules/user';
|
||||
// 获取用户 store
|
||||
const userStore = useUserStoreHook();
|
||||
// 从 store 中获取项目列表和当前选中的项目
|
||||
const currentProject = computed(() => userStore.selectedProject);
|
||||
const progressCategoryList = ref<ProgressCategoryVO[]>([]);
|
||||
const progressCategoryOptions = ref<ProgressCategoryOption[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const showSearch = ref(true);
|
||||
const isExpandAll = ref(true);
|
||||
const loading = ref(false);
|
||||
const planRef = ref<InstanceType<typeof CreatePlan>>();
|
||||
const dailyRef = ref<InstanceType<typeof CreateDaily>>();
|
||||
const dailyRateRef = ref<InstanceType<typeof CreateDailyRate>>();
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const progressCategoryFormRef = ref<ElFormInstance>();
|
||||
const progressCategoryTableRef = ref<ElTableInstance>();
|
||||
const matrixOptions = ref([]);
|
||||
const dialog = reactive<any>({
|
||||
dailyStatus: false,
|
||||
planStatus: false,
|
||||
importDataStatus: false,
|
||||
importTableStatus: false,
|
||||
title: '',
|
||||
file: ''
|
||||
});
|
||||
const matrixValue = ref<number | undefined>(matrixOptions.value.length > 0 ? matrixOptions.value[0].id : undefined);
|
||||
const initFormData: ProgressCategoryForm = {
|
||||
id: undefined,
|
||||
pid: undefined,
|
||||
name: undefined,
|
||||
matrixId: undefined,
|
||||
unitType: undefined,
|
||||
projectId: currentProject.value?.id
|
||||
};
|
||||
|
||||
const data = reactive<PageData<ProgressCategoryForm, ProgressCategoryQuery>>({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
pid: undefined,
|
||||
name: undefined,
|
||||
unitType: undefined,
|
||||
projectId: currentProject.value?.id,
|
||||
matrixId: undefined,
|
||||
params: {}
|
||||
},
|
||||
rules: {
|
||||
id: [{ required: true, message: '主键id不能为空', trigger: 'blur' }],
|
||||
pid: [{ required: true, message: '父类别id不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '类别名称不能为空', trigger: 'blur' }],
|
||||
unitType: [{ required: true, message: '计量方式不能为空', trigger: 'change' }],
|
||||
projectId: [{ required: true, message: '项目id不能为空', trigger: 'blur' }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 查询进度类别列表 */
|
||||
const getList = async () => {
|
||||
if (!queryParams.value.matrixId) {
|
||||
const res = await getProjectSquare(currentProject.value?.id);
|
||||
if (!res.data || res.data.length === 0) {
|
||||
proxy?.$modal.msgWarning('当前项目下没有方阵,请先创建方阵');
|
||||
} else {
|
||||
let matrixList = res.data.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
matrixId: item.projectId
|
||||
};
|
||||
});
|
||||
try {
|
||||
if (!matrixValue.value) matrixValue.value = matrixList[0].id;
|
||||
matrixOptions.value = matrixList;
|
||||
queryParams.value.matrixId = matrixList[0].children[0].matrixId;
|
||||
} catch (error) {
|
||||
proxy?.$modal.msgError('获取方阵失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await listProgressCategory(queryParams.value);
|
||||
const data = proxy?.handleTree<ProgressCategoryVO>(res.data, 'id', 'parentId');
|
||||
if (data) {
|
||||
progressCategoryList.value = data;
|
||||
}
|
||||
} finally {
|
||||
// 不管成功或失败,最后都设置为 false
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 查询进度类别下拉树结构 */
|
||||
const getTreeselect = async () => {
|
||||
const res = await listProgressCategory();
|
||||
progressCategoryOptions.value = [];
|
||||
const data: ProgressCategoryOption = { id: 0, name: '顶级节点', children: [] };
|
||||
data.children = proxy?.handleTree<ProgressCategoryOption>(res.data, 'id', 'pid');
|
||||
progressCategoryOptions.value.push(data);
|
||||
};
|
||||
|
||||
// 取消按钮
|
||||
const cancel = (visible) => {
|
||||
reset();
|
||||
dialog[visible] = false;
|
||||
};
|
||||
|
||||
// 表单重置
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData };
|
||||
progressCategoryFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
//切换项目重置方阵
|
||||
const resetMatrix = () => {
|
||||
matrixValue.value = undefined;
|
||||
queryParams.value.matrixId = undefined;
|
||||
matrixOptions.value = [];
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 级联选择器改变事件 */
|
||||
const handleChange = (value: number) => {
|
||||
queryParams.value.matrixId = value[1];
|
||||
|
||||
getList();
|
||||
};
|
||||
|
||||
const handleDayAdd = (row: ProgressCategoryVO) => {
|
||||
if (row.unitType === '2') {
|
||||
dailyRef.value.openDialog(row);
|
||||
} else {
|
||||
dailyRateRef.value.openDialog(row);
|
||||
}
|
||||
};
|
||||
|
||||
/** 导入按钮操作 */
|
||||
const openDialog = (row: ProgressCategoryVO, status: string, title: string) => {
|
||||
reset();
|
||||
// getTreeselect();
|
||||
if (row != null && row.id) {
|
||||
form.value.pid = row.id;
|
||||
} else {
|
||||
form.value.pid = 0;
|
||||
}
|
||||
dialog[status] = true;
|
||||
dialog.title = title;
|
||||
};
|
||||
|
||||
/** 展开/折叠操作 */
|
||||
const handleToggleExpandAll = () => {
|
||||
isExpandAll.value = !isExpandAll.value;
|
||||
toggleExpandAll(progressCategoryList.value, isExpandAll.value);
|
||||
};
|
||||
|
||||
/** 展开/折叠操作 */
|
||||
const toggleExpandAll = (data: ProgressCategoryVO[], status: boolean) => {
|
||||
data.forEach((item) => {
|
||||
progressCategoryTableRef.value?.toggleRowExpansion(item, status);
|
||||
if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
|
||||
});
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row: ProgressCategoryVO) => {
|
||||
reset();
|
||||
await getTreeselect();
|
||||
if (row != null) {
|
||||
form.value.pid = row.pid;
|
||||
}
|
||||
const res = await getProgressCategory(row.id);
|
||||
Object.assign(form.value, res.data);
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改进度类别';
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
progressCategoryFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
if (form.value.id) {
|
||||
await updateProgressCategory(form.value).finally(() => (buttonLoading.value = false));
|
||||
} else {
|
||||
await addProgressCategory(form.value).finally(() => (buttonLoading.value = false));
|
||||
}
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
dialog.visible = false;
|
||||
getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row: ProgressCategoryVO) => {
|
||||
await proxy?.$modal.confirm('是否确认删除进度类别编号为"' + row.id + '"的数据项?');
|
||||
loading.value = true;
|
||||
await delProgressCategory(row.id).finally(() => (loading.value = false));
|
||||
await getList();
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
|
||||
//监听项目id刷新数据
|
||||
const listeningProject = watch(
|
||||
() => currentProject.value?.id,
|
||||
(nid, oid) => {
|
||||
queryParams.value.projectId = nid;
|
||||
form.value.projectId = nid;
|
||||
resetMatrix();
|
||||
getList();
|
||||
}
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
listeningProject();
|
||||
});
|
||||
</script>
|
||||
382
src/views/progress/progressCategory/index.vue
Normal file
382
src/views/progress/progressCategory/index.vue
Normal file
@ -0,0 +1,382 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||||
<el-form-item label="请选择方阵:" prop="pid" label-width="100">
|
||||
<!-- <el-input v-model="queryParams.pid" placeholder="请选择" clearable /> -->
|
||||
<el-cascader
|
||||
:options="matrixOptions"
|
||||
placeholder="请选择"
|
||||
@change="handleChange"
|
||||
:props="{ value: 'matrixId', label: 'name' }"
|
||||
v-model="queryParams.matrixId"
|
||||
clearable
|
||||
/>
|
||||
<!-- <el-select v-model="matrixValue" placeholder="请选择" @change="handleChange" clearable>
|
||||
<el-option v-for="item in matrixOptions" :key="item.id" :label="item.matrixName" :value="item.id" />
|
||||
</el-select> -->
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
<el-table
|
||||
ref="progressCategoryTableRef"
|
||||
v-loading="loading"
|
||||
:data="progressCategoryList"
|
||||
row-key="id"
|
||||
:default-expand-all="isExpandAll"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
>
|
||||
<!-- <el-table-column label="父类别id" prop="parentId" /> -->
|
||||
<el-table-column label="类别名称" prop="name" />
|
||||
<el-table-column label="计量方式" align="center" prop="unitType">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="progress_unit_type" :value="row.unitType" v-if="row.parentId != 0" />
|
||||
<span v-else></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="计量单位" align="center" prop="unit">
|
||||
<template #default="{ row }">
|
||||
{{ row.parentId == 0 ? '' : row.unit }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="综合单价" align="center" prop="unitPrice">
|
||||
<template #default="{ row }">
|
||||
{{ row.parentId == 0 ? '' : row.unitPrice }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="产值金额" align="center" prop="outputValue">
|
||||
<template #default="{ row }">
|
||||
{{ row.parentId == 0 ? '' : row.outputValue }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总数量/百分比" align="center" prop="total">
|
||||
<template #default="{ row }">
|
||||
{{ row.parentId == 0 ? '' : row.total }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<div v-if="scope.row.parentId">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['progress:progressCategory:edit']">
|
||||
修改
|
||||
</el-button>
|
||||
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['progress:progressCategory:remove']">
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
<!-- 添加或修改分项工程单价对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
|
||||
<el-form ref="progressCategoryFormRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="类别名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入类别名称" :disabled="form.id != null" />
|
||||
</el-form-item>
|
||||
<el-form-item label="计量单位" prop="unit">
|
||||
<el-input v-model="form.unit" placeholder="请输入计量单位" />
|
||||
</el-form-item>
|
||||
<el-form-item label="数量" prop="total" v-if="isDisabled">
|
||||
<el-input v-model="form.total" placeholder="请输入数量" />
|
||||
</el-form-item>
|
||||
<el-form-item label="综合单价" prop="unitPrice">
|
||||
<el-input v-model="form.unitPrice" placeholder="请输入综合单价" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- <el-form-item label="总数量/百分比" prop="total">
|
||||
<el-input v-model="form.total" placeholder="请输入总数量/百分比" />
|
||||
</el-form-item> -->
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="ProgressCategory" lang="ts">
|
||||
import { getProjectSquare } from '@/api/progress/plan';
|
||||
import {
|
||||
listProgressCategory,
|
||||
getProgressCategory,
|
||||
delProgressCategory,
|
||||
addProgressCategory,
|
||||
updateProgressCategory
|
||||
} from '@/api/progress/progressCategory';
|
||||
import { ProgressCategoryVO, ProgressCategoryQuery, ProgressCategoryForm } from '@/api/progress/progressCategory/types';
|
||||
import { useUserStoreHook } from '@/store/modules/user';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { progress_unit_type, progress_status } = toRefs<any>(proxy?.useDict('progress_unit_type', 'progress_status'));
|
||||
|
||||
// 获取用户 store
|
||||
const userStore = useUserStoreHook();
|
||||
// 从 store 中获取项目列表和当前选中的项目
|
||||
const currentProject = computed(() => userStore.selectedProject);
|
||||
type ProgressCategoryOption = {
|
||||
id: number;
|
||||
name: string;
|
||||
children?: ProgressCategoryOption[];
|
||||
};
|
||||
|
||||
const matrixOptions = ref([]);
|
||||
|
||||
const progressCategoryList = ref<ProgressCategoryVO[]>([]);
|
||||
const progressCategoryOptions = ref<ProgressCategoryOption[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const showSearch = ref(true);
|
||||
const isExpandAll = ref(true);
|
||||
const loading = ref(false);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const progressCategoryFormRef = ref<ElFormInstance>();
|
||||
const progressCategoryTableRef = ref<ElTableInstance>();
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const initFormData: ProgressCategoryForm = {
|
||||
id: undefined,
|
||||
parentId: undefined,
|
||||
projectId: currentProject.value?.id,
|
||||
matrixId: undefined,
|
||||
matrixName: undefined,
|
||||
name: undefined,
|
||||
unitType: undefined,
|
||||
unit: undefined,
|
||||
unitPrice: undefined,
|
||||
outputValue: undefined,
|
||||
total: undefined,
|
||||
completed: undefined,
|
||||
planTotal: undefined,
|
||||
isDelay: undefined,
|
||||
workType: undefined,
|
||||
status: undefined,
|
||||
remark: undefined
|
||||
};
|
||||
|
||||
const data = reactive<PageData<ProgressCategoryForm, ProgressCategoryQuery>>({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
parentId: undefined,
|
||||
projectId: currentProject.value?.id,
|
||||
matrixId: undefined,
|
||||
matrixName: undefined,
|
||||
name: undefined,
|
||||
unitType: undefined,
|
||||
unit: undefined,
|
||||
unitPrice: undefined,
|
||||
outputValue: undefined,
|
||||
total: undefined,
|
||||
completed: undefined,
|
||||
planTotal: undefined,
|
||||
isDelay: undefined,
|
||||
workType: undefined,
|
||||
status: undefined,
|
||||
params: {}
|
||||
},
|
||||
rules: {
|
||||
id: [{ required: true, message: '主键id不能为空', trigger: 'blur' }],
|
||||
parentId: [{ required: true, message: '父类别id不能为空', trigger: 'blur' }],
|
||||
projectId: [{ required: true, message: '项目id不能为空', trigger: 'blur' }],
|
||||
matrixId: [{ required: true, message: '方阵id不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '类别名称不能为空', trigger: 'blur' }],
|
||||
unitType: [{ required: true, message: '计量方式不能为空', trigger: 'change' }],
|
||||
unitPrice: [{ required: true, message: '综合单价不能为空', trigger: 'blur' }],
|
||||
outputValue: [{ required: true, message: '产值金额不能为空', trigger: 'blur' }],
|
||||
isDelay: [{ required: true, message: '是否超期不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '完成状态不能为空', trigger: 'change' }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 查询分项工程单价列表 */
|
||||
const getList = async () => {
|
||||
if (!queryParams.value.matrixId) {
|
||||
const res = await getProjectSquare(currentProject.value?.id);
|
||||
if (res.data.length === 0) {
|
||||
proxy?.$modal.msgWarning('当前项目下没有方阵,请先创建方阵');
|
||||
} else {
|
||||
let matrixList = res.data.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
matrixId: item.projectId
|
||||
};
|
||||
});
|
||||
if (!matrixValue.value) matrixValue.value = matrixList[0].id;
|
||||
matrixOptions.value = matrixList;
|
||||
queryParams.value.matrixId = matrixList[0].children[0].matrixId;
|
||||
}
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await listProgressCategory(queryParams.value);
|
||||
const data = proxy?.handleTree<ProgressCategoryVO>(res.data, 'id', 'parentId');
|
||||
if (data) {
|
||||
progressCategoryList.value = data;
|
||||
loading.value = false;
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 查询分项工程单价下拉树结构 */
|
||||
const getTreeselect = async () => {
|
||||
const res = await listProgressCategory(queryParams.value);
|
||||
progressCategoryOptions.value = [];
|
||||
const data: ProgressCategoryOption = { id: 0, name: '顶级节点', children: [] };
|
||||
data.children = proxy?.handleTree<ProgressCategoryOption>(res.data, 'id', 'parentId');
|
||||
progressCategoryOptions.value.push(data);
|
||||
};
|
||||
|
||||
// 取消按钮
|
||||
const cancel = () => {
|
||||
reset();
|
||||
dialog.visible = false;
|
||||
};
|
||||
|
||||
// 表单重置
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData };
|
||||
isDisabled.value = false;
|
||||
progressCategoryFormRef.value?.resetFields();
|
||||
};
|
||||
/** 级联选择器改变事件 */
|
||||
const handleChange = (value: number) => {
|
||||
queryParams.value.matrixId = value[1];
|
||||
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = (row?: ProgressCategoryVO) => {
|
||||
reset();
|
||||
getTreeselect();
|
||||
if (row != null && row.id) {
|
||||
form.value.parentId = row.id;
|
||||
} else {
|
||||
form.value.parentId = 0;
|
||||
}
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加分项工程单价';
|
||||
};
|
||||
|
||||
/** 展开/折叠操作 */
|
||||
const handleToggleExpandAll = () => {
|
||||
isExpandAll.value = !isExpandAll.value;
|
||||
toggleExpandAll(progressCategoryList.value, isExpandAll.value);
|
||||
};
|
||||
|
||||
/** 展开/折叠操作 */
|
||||
const toggleExpandAll = (data: ProgressCategoryVO[], status: boolean) => {
|
||||
data.forEach((item) => {
|
||||
progressCategoryTableRef.value?.toggleRowExpansion(item, status);
|
||||
if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
|
||||
});
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const isDisabled = ref<boolean>(false);
|
||||
const handleUpdate = async (row: ProgressCategoryVO) => {
|
||||
reset();
|
||||
if (!row.unitType) {
|
||||
isDisabled.value = true;
|
||||
}
|
||||
await getTreeselect();
|
||||
if (row != null) {
|
||||
form.value.parentId = row.parentId;
|
||||
}
|
||||
const res = await getProgressCategory(row.id);
|
||||
Object.assign(form.value, res.data);
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改分项工程单价';
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
progressCategoryFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
if (form.value.id) {
|
||||
await updateProgressCategory(form.value).finally(() => (buttonLoading.value = false));
|
||||
} else {
|
||||
await addProgressCategory(form.value).finally(() => (buttonLoading.value = false));
|
||||
}
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
dialog.visible = false;
|
||||
getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
//切换项目重置方阵
|
||||
const matrixValue = ref<number | undefined>(matrixOptions.value.length > 0 ? matrixOptions.value[0].id : undefined);
|
||||
const resetMatrix = () => {
|
||||
matrixValue.value = undefined;
|
||||
queryParams.value.matrixId = undefined;
|
||||
matrixOptions.value = [];
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row: ProgressCategoryVO) => {
|
||||
await proxy?.$modal.confirm('是否确认删除分项工程单价编号为"' + row.id + '"的数据项?');
|
||||
loading.value = true;
|
||||
await delProgressCategory(row.id).finally(() => (loading.value = false));
|
||||
await getList();
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
//监听项目id刷新数据
|
||||
const listeningProject = watch(
|
||||
() => currentProject.value?.id,
|
||||
(nid, oid) => {
|
||||
queryParams.value.projectId = nid;
|
||||
form.value.projectId = nid;
|
||||
resetMatrix();
|
||||
getList();
|
||||
}
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
listeningProject();
|
||||
});
|
||||
</script>
|
||||
310
src/views/progress/progressCategorySystemTemplate/index.vue
Normal file
310
src/views/progress/progressCategorySystemTemplate/index.vue
Normal file
@ -0,0 +1,310 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||||
<el-form-item label="类别名称" prop="name">
|
||||
<el-input v-model="queryParams.name" placeholder="请输入类别名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="施工类型" prop="constructionType">
|
||||
<el-select v-model="queryParams.constructionType" placeholder="请选择施工类型" clearable @change="getList">
|
||||
<el-option v-for="dict in project_category_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['progress:progressCategoryTemplate:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
<el-table
|
||||
ref="progressCategoryTemplateTableRef"
|
||||
v-loading="loading"
|
||||
:data="progressCategoryTemplateList"
|
||||
row-key="id"
|
||||
:default-expand-all="isExpandAll"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
>
|
||||
<el-table-column label="类别名称" prop="name" />
|
||||
<el-table-column label="计量方式" align="center" prop="unitType" width="100">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="progress_unit_type" :value="row.unitType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="关联数据" align="center" prop="workType">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="progress_work_type" :value="row.workType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['progress:progressCategoryTemplate:edit']" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="新增" placement="top">
|
||||
<el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['progress:progressCategoryTemplate:add']" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="Delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['progress:progressCategoryTemplate:remove']"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
<!-- 添加或修改进度类别模版对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
|
||||
<el-form ref="progressCategoryTemplateFormRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="父类别id" prop="parentId">
|
||||
<el-tree-select
|
||||
v-model="form.parentId"
|
||||
:data="progressCategoryTemplateOptions"
|
||||
:props="{ value: 'id', label: 'name', children: 'children' }"
|
||||
value-key="id"
|
||||
placeholder="请选择父类别id"
|
||||
check-strictly
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="施工类型" prop="constructionType">
|
||||
<el-select v-model="form.constructionType" placeholder="请选择施工类型" disabled>
|
||||
<el-option v-for="dict in project_category_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="计量方式" prop="unitType">
|
||||
<el-select v-model="form.unitType" placeholder="请选择关联数据">
|
||||
<el-option v-for="dict in progress_unit_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="类别名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入类别名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="关联数据" prop="workType">
|
||||
<el-select v-model="form.workType" placeholder="请选择关联数据">
|
||||
<el-option v-for="dict in progress_work_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="ProgressCategoryTemplate" lang="ts">
|
||||
import {
|
||||
addProgressCategoryTemplate,
|
||||
delProgressCategoryTemplate,
|
||||
getProgressCategoryTemplate,
|
||||
listProgressCategoryTemplate,
|
||||
updateProgressCategoryTemplate
|
||||
} from '@/api/progress/progressCategoryTemplate';
|
||||
import {
|
||||
ProgressCategoryTemplateForm,
|
||||
ProgressCategoryTemplateQuery,
|
||||
ProgressCategoryTemplateVO
|
||||
} from '@/api/progress/progressCategoryTemplate/types';
|
||||
|
||||
type ProgressCategoryTemplateOption = {
|
||||
id: number;
|
||||
name: string;
|
||||
children?: ProgressCategoryTemplateOption[];
|
||||
};
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const { progress_work_type, progress_unit_type, project_category_type } = toRefs<any>(
|
||||
proxy?.useDict('progress_work_type', 'progress_unit_type', 'project_category_type')
|
||||
);
|
||||
const progressCategoryTemplateList = ref<ProgressCategoryTemplateVO[]>([]);
|
||||
const progressCategoryTemplateOptions = ref<ProgressCategoryTemplateOption[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const showSearch = ref(true);
|
||||
const isExpandAll = ref(true);
|
||||
const loading = ref(false);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const progressCategoryTemplateFormRef = ref<ElFormInstance>();
|
||||
const progressCategoryTemplateTableRef = ref<ElTableInstance>();
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const initFormData: ProgressCategoryTemplateForm = {
|
||||
id: undefined,
|
||||
parentId: undefined,
|
||||
name: undefined,
|
||||
unitType: undefined,
|
||||
workType: undefined,
|
||||
constructionType: undefined,
|
||||
projectId: 0,
|
||||
remark: undefined
|
||||
};
|
||||
|
||||
const data = reactive<PageData<ProgressCategoryTemplateForm, ProgressCategoryTemplateQuery>>({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
parentId: undefined,
|
||||
name: undefined,
|
||||
unitType: undefined,
|
||||
workType: undefined,
|
||||
constructionType: '1',
|
||||
projectId: 0,
|
||||
params: {}
|
||||
},
|
||||
rules: {
|
||||
id: [{ required: true, message: '主键id不能为空', trigger: 'blur' }],
|
||||
parentId: [{ required: true, message: '父类别id不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '类别名称不能为空', trigger: 'blur' }],
|
||||
unitType: [{ required: true, message: '计量方式不能为空', trigger: 'change' }],
|
||||
projectId: [{ required: true, message: '项目id不能为空', trigger: 'blur' }],
|
||||
constructionType: [{ required: true, message: '施工类型不能为空', trigger: 'change' }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 查询进度类别模版列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const res = await listProgressCategoryTemplate(queryParams.value);
|
||||
const data = proxy?.handleTree<ProgressCategoryTemplateVO>(res.data, 'id', 'parentId');
|
||||
if (data) {
|
||||
progressCategoryTemplateList.value = data;
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 查询进度类别模版下拉树结构 */
|
||||
const getTreeselect = async () => {
|
||||
const res = await listProgressCategoryTemplate();
|
||||
progressCategoryTemplateOptions.value = [];
|
||||
const data: ProgressCategoryTemplateOption = { id: 0, name: '顶级节点', children: [] };
|
||||
data.children = proxy?.handleTree<ProgressCategoryTemplateOption>(res.data, 'id', 'parentId');
|
||||
progressCategoryTemplateOptions.value.push(data);
|
||||
};
|
||||
|
||||
// 取消按钮
|
||||
const cancel = () => {
|
||||
reset();
|
||||
dialog.visible = false;
|
||||
};
|
||||
|
||||
// 表单重置
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData };
|
||||
progressCategoryTemplateFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = (row?: ProgressCategoryTemplateVO) => {
|
||||
reset();
|
||||
form.value.constructionType = queryParams.value.constructionType;
|
||||
getTreeselect();
|
||||
if (row != null && row.id) {
|
||||
form.value.parentId = row.id;
|
||||
} else {
|
||||
form.value.parentId = 0;
|
||||
}
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加进度类别模版';
|
||||
};
|
||||
|
||||
/** 展开/折叠操作 */
|
||||
const handleToggleExpandAll = () => {
|
||||
isExpandAll.value = !isExpandAll.value;
|
||||
toggleExpandAll(progressCategoryTemplateList.value, isExpandAll.value);
|
||||
};
|
||||
|
||||
/** 展开/折叠操作 */
|
||||
const toggleExpandAll = (data: ProgressCategoryTemplateVO[], status: boolean) => {
|
||||
data.forEach((item) => {
|
||||
progressCategoryTemplateTableRef.value?.toggleRowExpansion(item, status);
|
||||
if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
|
||||
});
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row: ProgressCategoryTemplateVO) => {
|
||||
reset();
|
||||
await getTreeselect();
|
||||
if (row != null) {
|
||||
form.value.parentId = row.parentId;
|
||||
}
|
||||
const res = await getProgressCategoryTemplate(row.id);
|
||||
Object.assign(form.value, res.data);
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改进度类别模版';
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
progressCategoryTemplateFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
if (form.value.id) {
|
||||
await updateProgressCategoryTemplate(form.value).finally(() => (buttonLoading.value = false));
|
||||
} else {
|
||||
await addProgressCategoryTemplate(form.value).finally(() => (buttonLoading.value = false));
|
||||
}
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
dialog.visible = false;
|
||||
getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row: ProgressCategoryTemplateVO) => {
|
||||
await proxy?.$modal.confirm('是否确认删除进度类别模版编号为"' + row.id + '"的数据项?');
|
||||
loading.value = true;
|
||||
await delProgressCategoryTemplate(row.id).finally(() => (loading.value = false));
|
||||
await getList();
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
321
src/views/progress/progressCategoryTemplate/index.vue
Normal file
321
src/views/progress/progressCategoryTemplate/index.vue
Normal file
@ -0,0 +1,321 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||||
<el-form-item label="选择项目" prop="projectId" width="100">
|
||||
<el-select v-model="queryParams.projectId" @change="getList">
|
||||
<el-option v-for="item in projectSon" :label="item.projectName" :value="item.id" :key="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="类别名称" prop="name">
|
||||
<el-input v-model="queryParams.name" placeholder="请输入类别名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['progress:progressCategoryTemplate:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
<el-table
|
||||
ref="progressCategoryTemplateTableRef"
|
||||
v-loading="loading"
|
||||
:data="progressCategoryTemplateList"
|
||||
row-key="id"
|
||||
:default-expand-all="isExpandAll"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
>
|
||||
<el-table-column label="类别名称" prop="name" />
|
||||
<el-table-column label="计量方式" align="center" prop="unitType" width="100">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="progress_unit_type" :value="row.unitType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="关联数据" align="center" prop="workType">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="progress_work_type" :value="row.workType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['progress:progressCategoryTemplate:edit']" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="新增" placement="top">
|
||||
<el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['progress:progressCategoryTemplate:add']" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="Delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['progress:progressCategoryTemplate:remove']"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
<!-- 添加或修改进度类别模版对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
|
||||
<el-form ref="progressCategoryTemplateFormRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="父类别id" prop="parentId">
|
||||
<el-tree-select
|
||||
v-model="form.parentId"
|
||||
:data="progressCategoryTemplateOptions"
|
||||
:props="{ value: 'id', label: 'name', children: 'children' }"
|
||||
value-key="id"
|
||||
placeholder="请选择父类别id"
|
||||
check-strictly
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="计量方式" prop="unitType">
|
||||
<el-select v-model="form.unitType" placeholder="请选择关联数据">
|
||||
<el-option v-for="dict in progress_unit_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="类别名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入类别名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="关联数据" prop="workType">
|
||||
<el-select v-model="form.workType" placeholder="请选择关联数据">
|
||||
<el-option v-for="dict in progress_work_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="ProgressCategoryTemplate" lang="ts">
|
||||
import {
|
||||
addProgressCategoryTemplate,
|
||||
delProgressCategoryTemplate,
|
||||
getProgressCategoryTemplate,
|
||||
listProgressCategoryTemplate,
|
||||
updateProgressCategoryTemplate
|
||||
} from '@/api/progress/progressCategoryTemplate';
|
||||
import {
|
||||
ProgressCategoryTemplateForm,
|
||||
ProgressCategoryTemplateQuery,
|
||||
ProgressCategoryTemplateVO
|
||||
} from '@/api/progress/progressCategoryTemplate/types';
|
||||
import { useUserStoreHook } from '@/store/modules/user';
|
||||
import { getChildProject } from '@/api/project/project';
|
||||
|
||||
type ProgressCategoryTemplateOption = {
|
||||
id: number;
|
||||
name: string;
|
||||
children?: ProgressCategoryTemplateOption[];
|
||||
};
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const { progress_work_type, progress_unit_type, project_category_type } = toRefs<any>(
|
||||
proxy?.useDict('progress_work_type', 'progress_unit_type', 'project_category_type')
|
||||
);
|
||||
// 获取用户 store
|
||||
const userStore = useUserStoreHook();
|
||||
// 从 store 中获取项目列表和当前选中的项目
|
||||
const currentProject = computed(() => userStore.selectedProject);
|
||||
const progressCategoryTemplateList = ref<ProgressCategoryTemplateVO[]>([]);
|
||||
const progressCategoryTemplateOptions = ref<ProgressCategoryTemplateOption[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const showSearch = ref(true);
|
||||
const isExpandAll = ref(true);
|
||||
const loading = ref(false);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const progressCategoryTemplateFormRef = ref<ElFormInstance>();
|
||||
const progressCategoryTemplateTableRef = ref<ElTableInstance>();
|
||||
const projectSon = ref<any>([]);
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const initFormData: ProgressCategoryTemplateForm = {
|
||||
id: undefined,
|
||||
parentId: undefined,
|
||||
name: undefined,
|
||||
unitType: undefined,
|
||||
workType: undefined,
|
||||
projectId: undefined,
|
||||
constructionType: undefined,
|
||||
remark: undefined
|
||||
};
|
||||
|
||||
const data = reactive<PageData<ProgressCategoryTemplateForm, ProgressCategoryTemplateQuery>>({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
parentId: undefined,
|
||||
name: undefined,
|
||||
unitType: undefined,
|
||||
workType: undefined,
|
||||
projectId: undefined,
|
||||
constructionType: undefined,
|
||||
params: {}
|
||||
},
|
||||
rules: {
|
||||
id: [{ required: true, message: '主键id不能为空', trigger: 'blur' }],
|
||||
parentId: [{ required: true, message: '父类别id不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '类别名称不能为空', trigger: 'blur' }],
|
||||
unitType: [{ required: true, message: '计量方式不能为空', trigger: 'change' }],
|
||||
projectId: [{ required: true, message: '项目id不能为空', trigger: 'blur' }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 查询进度类别模版列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const res = await listProgressCategoryTemplate(queryParams.value);
|
||||
const data = proxy?.handleTree<ProgressCategoryTemplateVO>(res.data, 'id', 'parentId');
|
||||
if (data) {
|
||||
progressCategoryTemplateList.value = data;
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 查询进度类别模版下拉树结构 */
|
||||
const getTreeselect = async () => {
|
||||
const res = await listProgressCategoryTemplate();
|
||||
progressCategoryTemplateOptions.value = [];
|
||||
const data: ProgressCategoryTemplateOption = { id: 0, name: '顶级节点', children: [] };
|
||||
data.children = proxy?.handleTree<ProgressCategoryTemplateOption>(res.data, 'id', 'parentId');
|
||||
progressCategoryTemplateOptions.value.push(data);
|
||||
};
|
||||
|
||||
// 取消按钮
|
||||
const cancel = () => {
|
||||
reset();
|
||||
dialog.visible = false;
|
||||
};
|
||||
|
||||
// 表单重置
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData };
|
||||
progressCategoryTemplateFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
queryParams.value.projectId = projectSon.value[0]?.id;
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = (row?: ProgressCategoryTemplateVO) => {
|
||||
reset();
|
||||
getTreeselect();
|
||||
if (row != null && row.id) {
|
||||
form.value.parentId = row.id;
|
||||
} else {
|
||||
form.value.parentId = 0;
|
||||
}
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加进度类别模版';
|
||||
};
|
||||
|
||||
/** 展开/折叠操作 */
|
||||
const handleToggleExpandAll = () => {
|
||||
isExpandAll.value = !isExpandAll.value;
|
||||
toggleExpandAll(progressCategoryTemplateList.value, isExpandAll.value);
|
||||
};
|
||||
|
||||
/** 展开/折叠操作 */
|
||||
const toggleExpandAll = (data: ProgressCategoryTemplateVO[], status: boolean) => {
|
||||
data.forEach((item) => {
|
||||
progressCategoryTemplateTableRef.value?.toggleRowExpansion(item, status);
|
||||
if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
|
||||
});
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row: ProgressCategoryTemplateVO) => {
|
||||
reset();
|
||||
await getTreeselect();
|
||||
if (row != null) {
|
||||
form.value.parentId = row.parentId;
|
||||
}
|
||||
const res = await getProgressCategoryTemplate(row.id);
|
||||
Object.assign(form.value, res.data);
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改进度类别模版';
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
progressCategoryTemplateFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
form.value.projectId = queryParams.value.projectId;
|
||||
if (form.value.id) {
|
||||
await updateProgressCategoryTemplate(form.value).finally(() => (buttonLoading.value = false));
|
||||
} else {
|
||||
await addProgressCategoryTemplate(form.value).finally(() => (buttonLoading.value = false));
|
||||
}
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
dialog.visible = false;
|
||||
getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row: ProgressCategoryTemplateVO) => {
|
||||
await proxy?.$modal.confirm('是否确认删除进度类别模版编号为"' + row.id + '"的数据项?');
|
||||
loading.value = true;
|
||||
await delProgressCategoryTemplate(row.id).finally(() => (loading.value = false));
|
||||
await getList();
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取子项目列表
|
||||
*/
|
||||
const getSubProjectList = async () => {
|
||||
const res = await getChildProject(currentProject.value?.id);
|
||||
projectSon.value = res.data;
|
||||
queryParams.value.projectId = projectSon.value[0]?.id;
|
||||
getList();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getSubProjectList();
|
||||
});
|
||||
</script>
|
||||
826
src/views/progress/progressPaper/index.vue
Normal file
826
src/views/progress/progressPaper/index.vue
Normal file
@ -0,0 +1,826 @@
|
||||
<template>
|
||||
<div v-loading="geoTiffLoading" class="flex h100vh">
|
||||
<div class="header flex justify-between">
|
||||
<div class="tips flex items-center justify-between">
|
||||
<div class="dot1">未提交</div>
|
||||
<div class="dot2">已提交</div>
|
||||
<div class="dot3">已选择,待提交</div>
|
||||
<el-tooltip class="box-item" effect="dark" content="右键拖动生成套索区域选中/取消图形" placement="bottom">
|
||||
<i class="iconfont icon-wenhao"></i>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-form :model="queryParams" ref="form" label-width="80px" inline class="flex items-center">
|
||||
<el-form-item label="请选择项目:" prop="pid" label-width="100" style="margin-bottom: 0; color: #fff">
|
||||
<el-select v-model="selectedProjectId" placeholder="请选择" @change="handleSelect" clearable>
|
||||
<el-option v-for="item in ProjectList" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="请选择方阵:" prop="pid" label-width="100" style="margin-bottom: 0">
|
||||
<!-- <el-select v-model="matrixValue" placeholder="请选择" @change="handleChange" clearable>
|
||||
<el-option v-for="item in matrixOptions" :key="item.id" :label="item.matrixName" :value="item.id" />
|
||||
</el-select> -->
|
||||
<el-cascader
|
||||
:options="matrixOptions"
|
||||
placeholder="请选择"
|
||||
@change="handleChange"
|
||||
:props="{ value: 'matrixId', label: 'name' }"
|
||||
v-model="queryParams.matrixId"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="ol-map" id="olMap"></div>
|
||||
<div class="aside">
|
||||
<el-tree
|
||||
style="max-width: 600px"
|
||||
:data="progressCategoryList"
|
||||
ref="treeRef"
|
||||
@check-change="handleCheckChange"
|
||||
:props="treeProps"
|
||||
:load="loadNode"
|
||||
node-key="id"
|
||||
lazy
|
||||
:render-content="renderContent"
|
||||
check-strictly
|
||||
:expand-on-click-node="false"
|
||||
/>
|
||||
</div>
|
||||
<div class="submit">
|
||||
<el-button type="primary" size="default" @click="submit" :loading="loading">提交</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Map from 'ol/Map'; // OpenLayers的主要类,用于创建和管理地图
|
||||
import View from 'ol/View'; // OpenLayers的视图类,定义地图的视图属性
|
||||
import { Tile as TileLayer, WebGLTile } from 'ol/layer'; // OpenLayers的瓦片图层类
|
||||
import { Raster, XYZ } from 'ol/source'; // OpenLayers的瓦片数据源,包括XYZ格式和OpenStreetMap专用的数据源
|
||||
import { defaults as defaultControls, defaults, FullScreen, MousePosition, ScaleLine } from 'ol/control';
|
||||
import { fromLonLat, toLonLat, transform, transformExtent } from 'ol/proj';
|
||||
import { useUserStoreHook } from '@/store/modules/user';
|
||||
import { getProjectSquare, listProgressCategory, addDaily, workScheduleListPosition } from '@/api/progress/plan';
|
||||
import { ProgressCategoryVO, progressPlanDetailForm } from '@/api/progress/plan/types';
|
||||
import { Circle, Fill, Stroke, Style, Text } from 'ol/style';
|
||||
import { defaults as defaultInteractions, DragPan } from 'ol/interaction';
|
||||
import Feature from 'ol/Feature';
|
||||
import { Point, Polygon } from 'ol/geom';
|
||||
import VectorSource from 'ol/source/Vector';
|
||||
import VectorLayer from 'ol/layer/Vector';
|
||||
import Node from 'element-plus/es/components/tree/src/model/node.mjs';
|
||||
import { ElCheckbox } from 'element-plus';
|
||||
import { LassoSelector } from '@/utils/lassoSelect';
|
||||
import { FeatureCollection, Geometry } from 'geojson';
|
||||
import { MapViewFitter } from '@/utils/setMapCenter';
|
||||
import PointerInteraction from 'ol/interaction/Pointer';
|
||||
import { Coordinate } from 'ol/coordinate';
|
||||
import GeoTIFF, { fromBlob, fromUrl, fromArrayBuffer, Pool } from 'geotiff';
|
||||
import GeoTIFFSource from 'ol/source/GeoTIFF';
|
||||
import ImageLayer from 'ol/layer/Image';
|
||||
import Static from 'ol/source/ImageStatic';
|
||||
import proj4 from 'proj4';
|
||||
import { register } from 'ol/proj/proj4';
|
||||
import gcoord from 'gcoord';
|
||||
import { createXYZ } from 'ol/tilegrid';
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const orthophoto = '@/assets/images/orthophoto.tif';
|
||||
const selector = ref<LassoSelector | null>(null);
|
||||
// 获取用户 store
|
||||
const userStore = useUserStoreHook();
|
||||
// 从 store 中获取项目列表和当前选中的项目
|
||||
const currentProject = computed(() => userStore.selectedProject);
|
||||
const ProjectList = computed(() => userStore.projects);
|
||||
const selectedProjectId = ref(userStore.selectedProject?.id || '');
|
||||
const treeRef = ref();
|
||||
const queryParams = ref({
|
||||
pid: undefined,
|
||||
name: undefined,
|
||||
unitType: undefined,
|
||||
projectId: currentProject.value?.id,
|
||||
matrixId: undefined,
|
||||
params: {}
|
||||
});
|
||||
const submitForm = ref<progressPlanDetailForm>({
|
||||
id: '',
|
||||
finishedDetailIdList: [] as string[]
|
||||
});
|
||||
const loading = ref(false);
|
||||
const geoTiffLoading = ref(false);
|
||||
const matrixOptions = ref([]);
|
||||
const matrixValue = ref<number | undefined>(matrixOptions.value.length > 0 ? matrixOptions.value[0].id : undefined);
|
||||
const progressCategoryList = ref<ProgressCategoryVO[]>([]);
|
||||
const treeProps = {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
isLeaf: 'leaf',
|
||||
hasChildren: 'hasChildren', // 重要
|
||||
disabled: 'disabled'
|
||||
};
|
||||
|
||||
//切换项目
|
||||
const handleSelect = (projectId: string) => {
|
||||
const selectedProject = ProjectList.value.find((p) => p.id === projectId);
|
||||
if (selectedProject) {
|
||||
userStore.setSelectedProject(selectedProject);
|
||||
|
||||
resetMatrix();
|
||||
getList();
|
||||
}
|
||||
};
|
||||
|
||||
/** 进度类别树选中事件 */
|
||||
const handleCheckChange = (data: any, checked: boolean, indeterminate: boolean) => {
|
||||
const node: Node | undefined = treeRef.value?.getNode(data.id);
|
||||
|
||||
if (node.level === 2) {
|
||||
//二级节点 展开显示图层 收起删除
|
||||
const children = node.childNodes.map((child) => child.data);
|
||||
children.forEach((child) => {
|
||||
child.disabled = !checked;
|
||||
});
|
||||
// 让 el-tree 刷新显示
|
||||
treeRef.value.updateKeyChildren(data.id, children);
|
||||
if (checked) {
|
||||
addPointToMap(data.threeChildren); // 添加点到地图
|
||||
clearLevel3Checked(node.level); // 清除三级节点的选中状态
|
||||
treeRef.value.setChecked(data.id, true, false);
|
||||
} else {
|
||||
data.threeChildren.forEach((child: any) => {
|
||||
const feature = featureMap[child.id];
|
||||
if (feature && sharedSource.hasFeature(feature)) {
|
||||
sharedSource.removeFeature(feature);
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
// if (!checked) return (submitForm.value.id = ''); // 只处理第三级节点的选中事件
|
||||
if (!checked) {
|
||||
// ✅ 只在取消的是当前 id 时清空
|
||||
if (submitForm.value.id === data.id) {
|
||||
submitForm.value.id = '';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const parent = node.parent;
|
||||
if (!parent) return;
|
||||
//消除所有节点的选中状态
|
||||
clearLevel3Checked(node.level); // 清除三级节点的选中状态
|
||||
// 设置当前点击项为选中
|
||||
treeRef.value.setChecked(data.id, true, false);
|
||||
submitForm.value.id = data.id; // 设置提交表单的id
|
||||
console.log('submitForm', submitForm.value);
|
||||
};
|
||||
|
||||
//清除某一级节点所有选中状态
|
||||
const clearLevel3Checked = (level) => {
|
||||
const rootNodes = treeRef.value.store.root.childNodes;
|
||||
|
||||
const clearChecked = (nodes: any[]) => {
|
||||
nodes.forEach((node) => {
|
||||
if (node.level === level) {
|
||||
treeRef.value.setChecked(node.key, false, false);
|
||||
} else if (node.childNodes?.length) {
|
||||
clearChecked(node.childNodes);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
clearChecked(rootNodes);
|
||||
};
|
||||
|
||||
/** 关闭节点事件 */
|
||||
// const closeNode = (node: any) => {
|
||||
// // 清除子节点
|
||||
// if (node.pid) {
|
||||
// // node.threeChildren.forEach((child: any) => {
|
||||
// // const feature = featureMap[child.id];
|
||||
// // if (feature && sharedSource.hasFeature(feature)) {
|
||||
// // sharedSource.removeFeature(feature);
|
||||
// // }
|
||||
// // });
|
||||
// }
|
||||
// };
|
||||
|
||||
// /** 打开节点事件 */
|
||||
// const openNode = (node: any) => {
|
||||
// // 清除子节点
|
||||
// if (!node.pid) return;
|
||||
// // addPointToMap(node.threeChildren); // 添加点到地图
|
||||
// };
|
||||
|
||||
//懒加载子节点
|
||||
const loadNode = async (node: any, resolve: (data: any[]) => void) => {
|
||||
if (node.level !== 2) {
|
||||
// 只对二级节点加载子节点
|
||||
resolve(node.data.children || []);
|
||||
return;
|
||||
}
|
||||
|
||||
const secondLevelNodeId = node.data.id;
|
||||
const res = await workScheduleListPosition(secondLevelNodeId); // 替换成你的 API
|
||||
|
||||
const children = res.data.detailList || [];
|
||||
|
||||
if (children.length === 0) {
|
||||
proxy?.$modal.msgWarning(`节点 "${node.data.name}" 为空`);
|
||||
resolve([]);
|
||||
}
|
||||
|
||||
// 标记子节点为叶子节点
|
||||
const threeLeafList = children.map((detail) => {
|
||||
return {
|
||||
...detail,
|
||||
name: detail.date, // 设置为叶子节点
|
||||
leaf: true, // 标记为叶子节点
|
||||
disabled: true
|
||||
};
|
||||
});
|
||||
progressCategoryList.value.forEach((item, i) => {
|
||||
let indexNum = item.children.findIndex((item) => item.id === secondLevelNodeId);
|
||||
if (indexNum !== -1) {
|
||||
item.children[indexNum].threeChildren = res.data.facilityList; // 将子节点添加到当前节点的threeChildren属性中
|
||||
// item.children[indexNum].detailChildren = children; // 将子节点添加到当前节点的threeChildren属性中
|
||||
}
|
||||
});
|
||||
|
||||
resolve(threeLeafList);
|
||||
};
|
||||
|
||||
/** 提交按钮点击事件 */
|
||||
const submit = () => {
|
||||
console.log('sunbmitForm', submitForm.value);
|
||||
const { finishedDetailIdList, id } = submitForm.value;
|
||||
if (!id || finishedDetailIdList.length === 0) return proxy?.$modal.msgWarning('请选择图层以及日期');
|
||||
|
||||
loading.value = true;
|
||||
addDaily(submitForm.value)
|
||||
.then(() => {
|
||||
proxy?.$modal.msgSuccess('提交成功');
|
||||
const scale = Math.max(map.getView().getZoom() / 10, 1); // 获取当前缩放比例
|
||||
sharedSource.getFeatures().forEach((feature) => {
|
||||
if (feature.get('highlighted')) {
|
||||
feature.setStyle(successStyle(feature.get('name'), scale)); // 转为成功样式
|
||||
feature.set('highlighted', false); // 重置高亮状态
|
||||
feature.set('status', '2'); // 设置为完成状态
|
||||
}
|
||||
});
|
||||
resetTreeAndMap();
|
||||
})
|
||||
.catch((error) => {
|
||||
proxy?.$modal.msgError(`提交失败: ${error.message}`);
|
||||
});
|
||||
};
|
||||
|
||||
//重置树形结构选中以及图层高亮
|
||||
const resetTreeAndMap = () => {
|
||||
// 重置树形结构选中状态
|
||||
clearLevel3Checked(3);
|
||||
//取消加载状态
|
||||
loading.value = false;
|
||||
// 清除地图上的所有高亮
|
||||
const scale = Math.max(map.getView().getZoom() / 10, 1); // 获取当前缩放比例
|
||||
sharedSource.getFeatures().forEach((feature) => {
|
||||
if (feature.get('highlighted')) {
|
||||
feature.setStyle(defaultStyle(feature.get('name'), scale)); // 恢复默认样式
|
||||
feature.set('highlighted', false); // 重置高亮状态
|
||||
}
|
||||
});
|
||||
// 清空已完成列表
|
||||
submitForm.value.finishedDetailIdList = [];
|
||||
};
|
||||
|
||||
/** 方阵选择器改变事件 */
|
||||
const handleChange = (value: number) => {
|
||||
queryParams.value.matrixId = value[1];
|
||||
|
||||
getList();
|
||||
};
|
||||
|
||||
//限定部分节点能选择
|
||||
const renderContent = (context, { node, data }) => {
|
||||
if (node.level === 3) {
|
||||
return h('span', { class: 'custom-tree-node' }, [
|
||||
h(ElCheckbox, {
|
||||
modelValue: node.checked,
|
||||
'onUpdate:modelValue': (val) => node.setChecked(val),
|
||||
style: 'margin-right: 8px;',
|
||||
disabled: data.disabled
|
||||
}),
|
||||
h('span', [node.label])
|
||||
]);
|
||||
}
|
||||
if (node.level === 2) {
|
||||
return h('span', { class: 'custom-tree-node' }, [
|
||||
h(ElCheckbox, {
|
||||
modelValue: node.checked,
|
||||
'onUpdate:modelValue': (val) => node.setChecked(val),
|
||||
style: 'margin-right: 8px;',
|
||||
disabled: !data.threeChildren || data.threeChildren.length == 0
|
||||
}),
|
||||
h('span', { onDblclick: () => handlePosition(data, node) }, node.label)
|
||||
]);
|
||||
} else {
|
||||
return h('span', node.label);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePosition = (data: any, node: any) => {
|
||||
if (!data.threeChildren) return;
|
||||
|
||||
const fitter = new MapViewFitter(map); // 传入你的 OpenLayers 地图实例
|
||||
const features: GeoJSON.Feature[] = data.threeChildren.map((item) => {
|
||||
if ('type' in item && item.type === 'Feature') {
|
||||
return item as GeoJSON.Feature;
|
||||
}
|
||||
const raw = item;
|
||||
|
||||
let coordinates: any;
|
||||
if (raw.type === 'Polygon') {
|
||||
coordinates = [(raw.positions as [string, string][]).map(([lng, lat]) => [parseFloat(lng), parseFloat(lat)])];
|
||||
} else if (raw.type === 'Point') {
|
||||
const [lng, lat] = raw.positions as [string, string];
|
||||
coordinates = [parseFloat(lng), parseFloat(lat)];
|
||||
} else {
|
||||
throw new Error(`Unsupported geometry type: ${raw.type}`);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: raw.type,
|
||||
coordinates
|
||||
},
|
||||
properties: {
|
||||
id: raw.id,
|
||||
name: raw.name,
|
||||
status: raw.status,
|
||||
finishDate: raw.finishDate
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
if (features?.length) {
|
||||
const featureCollection: FeatureCollection<Geometry> = {
|
||||
type: 'FeatureCollection',
|
||||
features
|
||||
};
|
||||
|
||||
fitter.fit(featureCollection);
|
||||
}
|
||||
};
|
||||
|
||||
//切换项目重置方阵
|
||||
const resetMatrix = () => {
|
||||
matrixValue.value = undefined;
|
||||
queryParams.value.matrixId = undefined;
|
||||
matrixOptions.value = [];
|
||||
};
|
||||
|
||||
/** 查询进度类别列表 */
|
||||
const getList = async () => {
|
||||
if (!queryParams.value.matrixId) {
|
||||
const res = await getProjectSquare(currentProject.value?.id);
|
||||
if (res.data.length === 0) {
|
||||
proxy?.$modal.msgWarning('当前项目下没有方阵,请先创建方阵');
|
||||
} else {
|
||||
let matrixList = res.data.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
matrixId: item.projectId
|
||||
};
|
||||
});
|
||||
if (!matrixValue.value) matrixValue.value = matrixList[0].id;
|
||||
matrixOptions.value = matrixList;
|
||||
queryParams.value.matrixId = matrixList[0].children[0].matrixId;
|
||||
}
|
||||
}
|
||||
loading.value = true;
|
||||
queryParams.value.projectId = currentProject.value?.id;
|
||||
const res = await listProgressCategory(queryParams.value);
|
||||
const data = proxy?.handleTree<ProgressCategoryVO>(res.data, 'id', 'pid');
|
||||
if (data) {
|
||||
progressCategoryList.value = data;
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
let map: any = null;
|
||||
const centerPosition = ref(fromLonLat([107.12932403398425, 23.805564054229908]));
|
||||
const initOLMap = () => {
|
||||
map = new Map({
|
||||
// 设置地图容器的ID
|
||||
target: 'olMap',
|
||||
// 定义地图的图层列表,用于显示特定的地理信息。
|
||||
layers: [
|
||||
// 高德地图
|
||||
// TileLayer表示一个瓦片图层,它由一系列瓦片(通常是图片)组成,用于在地图上显示地理数据。
|
||||
|
||||
new TileLayer({
|
||||
source: new XYZ({
|
||||
url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
|
||||
maxZoom: 18
|
||||
})
|
||||
}),
|
||||
new TileLayer({
|
||||
// 设置图层的数据源为XYZ类型。XYZ是一个通用的瓦片图层源,它允许你通过URL模板来获取瓦片
|
||||
source: new XYZ({
|
||||
url: 'http://192.168.110.2:8000/api/projects/3/tasks/c2e3227f-343f-48b1-88c0-1432d6eab33f/orthophoto/tiles/{z}/{x}/{y}'
|
||||
})
|
||||
})
|
||||
],
|
||||
// 设置地图的视图参数
|
||||
// View表示地图的视图,它定义了地图的中心点、缩放级别、旋转角度等参数。
|
||||
view: new View({
|
||||
// fromLonLat是一个函数,用于将经纬度坐标转换为地图的坐标系统。
|
||||
center: centerPosition.value, //地图中心点
|
||||
zoom: 15, // 缩放级别
|
||||
minZoom: 0, // 最小缩放级别
|
||||
// maxZoom: 19, // 最大缩放级别
|
||||
constrainResolution: true // 因为存在非整数的缩放级别,所以设置该参数为true来让每次缩放结束后自动缩放到距离最近的一个整数级别,这个必须要设置,当缩放在非整数级别时地图会糊
|
||||
// projection: 'EPSG:4326' // 投影坐标系,默认是3857
|
||||
}),
|
||||
|
||||
//加载控件到地图容器中
|
||||
controls: defaultControls({
|
||||
zoom: false,
|
||||
rotate: false,
|
||||
attribution: false
|
||||
}).extend([new ScaleLine()]),
|
||||
interactions: defaultInteractions({
|
||||
doubleClickZoom: false // 禁用双击缩放
|
||||
})
|
||||
});
|
||||
map.on('click', (e: any) => {
|
||||
const zoom = map.getView().getZoom();
|
||||
const scale = Math.max(zoom / 10, 1); // 缩放比例,根据需要调整公式
|
||||
map.forEachFeatureAtPixel(e.pixel, (feature: Feature) => {
|
||||
toggleFeatureHighlight(feature);
|
||||
});
|
||||
});
|
||||
map.getView().on('change:resolution', () => {
|
||||
const zoom = map.getView().getZoom();
|
||||
const scale = Math.max(zoom / 10, 1); // 缩放比例,根据需要调整公式
|
||||
|
||||
sharedSource.getFeatures().forEach((feature) => {
|
||||
const style = feature.getStyle();
|
||||
|
||||
if (style instanceof Style && style.getText()) {
|
||||
style.getText().setScale(scale);
|
||||
feature.setStyle(style); // 重新应用样式
|
||||
}
|
||||
});
|
||||
});
|
||||
map.on('moveend', (e: any) => {
|
||||
// console.log('地图移动', e);
|
||||
// 获取当前缩放级别
|
||||
var zoomLevel = map.getView().getZoom();
|
||||
console.log('当前缩放级别:', zoomLevel);
|
||||
});
|
||||
|
||||
// 4. 添加 pointermove 鼠标悬停事件
|
||||
let lastFeature: Feature | null = null;
|
||||
|
||||
map.on('pointermove', (evt) => {
|
||||
map.getTargetElement().style.cursor = '';
|
||||
|
||||
const feature = map.forEachFeatureAtPixel(evt.pixel, (f) => f);
|
||||
const zoom = map.getView().getZoom();
|
||||
const scale = Math.max(zoom / 10, 1);
|
||||
|
||||
// 👉 若当前划入的 feature 是不允许 hover 的,直接跳过处理
|
||||
const currStatus = feature?.get('status');
|
||||
const currHighlighted = feature?.get('highlighted');
|
||||
if (feature && (currStatus === '2' || currHighlighted === true || feature.getGeometry()?.getType() !== 'Polygon')) {
|
||||
return; // ❌ 不执行 hover 效果,也不更新 lastFeature
|
||||
}
|
||||
|
||||
// ✅ 若进入了新的可 hover feature
|
||||
if (feature && feature !== lastFeature) {
|
||||
if (lastFeature) {
|
||||
const lastStatus = lastFeature.get('status');
|
||||
const lastHighlighted = lastFeature.get('highlighted');
|
||||
|
||||
if (lastStatus === '2') {
|
||||
lastFeature.setStyle(successStyle(lastFeature.get('name'), scale));
|
||||
} else if (lastHighlighted === true) {
|
||||
lastFeature.setStyle(highlightStyle(lastFeature.get('name'), scale));
|
||||
} else {
|
||||
lastFeature.setStyle(defaultStyle(lastFeature.get('name'), scale));
|
||||
}
|
||||
}
|
||||
|
||||
feature.setStyle(hoverStyle(feature.get('name'), scale));
|
||||
map.getTargetElement().style.cursor = 'pointer';
|
||||
lastFeature = feature;
|
||||
} else if (!feature && lastFeature) {
|
||||
// ✅ 鼠标移出所有图形时恢复
|
||||
const lastStatus = lastFeature.get('status');
|
||||
const lastHighlighted = lastFeature.get('highlighted');
|
||||
|
||||
if (lastStatus === '2') {
|
||||
lastFeature.setStyle(successStyle(lastFeature.get('name'), scale));
|
||||
} else if (lastHighlighted === true) {
|
||||
lastFeature.setStyle(highlightStyle(lastFeature.get('name'), scale));
|
||||
} else {
|
||||
lastFeature.setStyle(defaultStyle(lastFeature.get('name'), scale));
|
||||
}
|
||||
|
||||
lastFeature = null;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const highlightStyle = (name, scale) => {
|
||||
return new Style({
|
||||
stroke: new Stroke({
|
||||
color: 'orange',
|
||||
width: 4
|
||||
}),
|
||||
fill: new Fill({
|
||||
color: 'rgba(255,165,0,0.3)' // 半透明橙色
|
||||
}),
|
||||
text: new Text({
|
||||
font: '10px Microsoft YaHei',
|
||||
text: name,
|
||||
placement: 'line', // 👈 关键属性
|
||||
offsetX: 50, // 向右偏移 10 像素
|
||||
offsetY: 20, // 向下偏移 5 像素
|
||||
scale,
|
||||
fill: new Fill({ color: '#FFFFFF' })
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
const defaultStyle = (name, scale) => {
|
||||
return new Style({
|
||||
stroke: new Stroke({
|
||||
color: '#003366',
|
||||
width: 2
|
||||
}),
|
||||
text: new Text({
|
||||
font: '10px Microsoft YaHei',
|
||||
text: name,
|
||||
scale,
|
||||
placement: 'line', // 👈 关键属性
|
||||
offsetX: 50, // 向右偏移 10 像素
|
||||
offsetY: 20, // 向下偏移 5 像素
|
||||
fill: new Fill({ color: '#FFFFFF ' })
|
||||
}),
|
||||
fill: new Fill({ color: 'skyblue' })
|
||||
});
|
||||
};
|
||||
|
||||
const successStyle = (name, scale) => {
|
||||
return new Style({
|
||||
stroke: new Stroke({
|
||||
color: '#2E7D32 ',
|
||||
width: 2
|
||||
}),
|
||||
text: new Text({
|
||||
font: '10px Microsoft YaHei',
|
||||
text: name,
|
||||
scale,
|
||||
placement: 'line', // 👈 关键属性
|
||||
offsetX: 50, // 向右偏移 10 像素
|
||||
offsetY: 20, // 向下偏移 5 像素
|
||||
fill: new Fill({ color: '#FFFFFF ' })
|
||||
}),
|
||||
fill: new Fill({ color: '#7bdd63 ' })
|
||||
});
|
||||
};
|
||||
|
||||
const hoverStyle = (name, scale) => {
|
||||
return new Style({
|
||||
stroke: new Stroke({
|
||||
color: 'orange',
|
||||
width: 2
|
||||
}),
|
||||
fill: new Fill({
|
||||
color: 'rgba(255,165,0,0.3)' // 半透明橙色
|
||||
}),
|
||||
text: new Text({
|
||||
font: '10px Microsoft YaHei',
|
||||
text: name,
|
||||
scale,
|
||||
placement: 'line', // 👈 关键属性
|
||||
offsetX: 50, // 向右偏移 10 像素
|
||||
offsetY: 20, // 向下偏移 5 像素
|
||||
fill: new Fill({ color: '#FFFFFF ' })
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建图层
|
||||
* @param {*} pointObj 坐标数组
|
||||
* @param {*} type 类型
|
||||
* @param {*} id 唯一id
|
||||
* @param {*} name 名称
|
||||
* */
|
||||
// 共享 source 和图层(全局一次性创建)
|
||||
const sharedSource = new VectorSource();
|
||||
const sharedLayer = new VectorLayer({
|
||||
source: sharedSource,
|
||||
renderMode: 'image' // 提高渲染性能
|
||||
} as any);
|
||||
// id => Feature 映射表
|
||||
const featureMap: Record<string, Feature> = {};
|
||||
const creatPoint = (pointObj: Array<any>, type: string, id: string, name?: string, status?: string) => {
|
||||
let geometry;
|
||||
|
||||
if (type === 'Point') {
|
||||
geometry = new Point(fromLonLat(pointObj));
|
||||
} else if (type === 'Polygon') {
|
||||
const coords = pointObj.map((arr: any) => fromLonLat(arr));
|
||||
geometry = new Polygon([coords]);
|
||||
}
|
||||
|
||||
const feature = new Feature({ geometry });
|
||||
const zoom = map.getView().getZoom();
|
||||
const scale = Math.max(zoom / 30, 1); // 缩放比例,根据需要调整公式
|
||||
const pointStyle = new Style({
|
||||
image: new Circle({
|
||||
radius: 2,
|
||||
fill: new Fill({ color: 'red' })
|
||||
}),
|
||||
text: new Text({
|
||||
font: '12px Microsoft YaHei',
|
||||
text: name,
|
||||
scale,
|
||||
fill: new Fill({ color: '#7bdd63' })
|
||||
})
|
||||
});
|
||||
|
||||
const polygonStyle = status == '2' ? successStyle(name, scale) : defaultStyle(name || '', scale);
|
||||
|
||||
feature.setStyle(type === 'Point' ? pointStyle : polygonStyle);
|
||||
feature.set('name', name || ''); // 设置名称
|
||||
feature.set('status', status || ''); // 设置完成状态 2为完成 其他为未完成
|
||||
feature.set('id', id || '');
|
||||
// 缓存 feature(用于后续判断)
|
||||
featureMap[id] = feature;
|
||||
};
|
||||
|
||||
// 添加点到地图
|
||||
const addPointToMap = (features: Array<any>) => {
|
||||
features.forEach((item: any) => {
|
||||
const fid = item.id;
|
||||
|
||||
// 没创建过就先创建
|
||||
if (!featureMap[fid]) {
|
||||
creatPoint(item.positions, item.type, fid, item.name, item.status);
|
||||
}
|
||||
|
||||
// 添加到共享 source 中(避免重复添加)
|
||||
const feature = featureMap[fid];
|
||||
if (!sharedSource.hasFeature(feature)) {
|
||||
sharedSource.addFeature(feature);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
//选中几何图形
|
||||
const toggleFeatureHighlight = (feature: Feature) => {
|
||||
const zoom = map.getView().getZoom();
|
||||
const scale = Math.max(zoom / 10, 1);
|
||||
if (feature.get('status') === '2') return;
|
||||
if (feature.getGeometry()?.getType() !== 'Polygon') return;
|
||||
|
||||
const isHighlighted = feature.get('highlighted') === true;
|
||||
|
||||
if (isHighlighted) {
|
||||
// 如果是反选或 toggle 模式,允许取消
|
||||
feature.setStyle(defaultStyle(feature.get('name'), scale));
|
||||
feature.set('highlighted', false);
|
||||
const id = feature.get('id');
|
||||
const idx = submitForm.value.finishedDetailIdList.indexOf(id);
|
||||
if (idx > -1) submitForm.value.finishedDetailIdList.splice(idx, 1);
|
||||
} else {
|
||||
feature.setStyle(highlightStyle(feature.get('name'), scale));
|
||||
feature.set('highlighted', true);
|
||||
const id = feature.get('id');
|
||||
if (!submitForm.value.finishedDetailIdList.includes(id)) {
|
||||
submitForm.value.finishedDetailIdList.push(id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
// 地图初始化
|
||||
initOLMap();
|
||||
map.addLayer(sharedLayer);
|
||||
selector.value = new LassoSelector(map, sharedSource, (features, isInvert = false) => {
|
||||
features.forEach((feature) => {
|
||||
console.log(feature.get('status'));
|
||||
|
||||
toggleFeatureHighlight(feature);
|
||||
});
|
||||
});
|
||||
|
||||
// enableMiddleMousePan(map);
|
||||
getList();
|
||||
creatPoint(fromLonLat([107.13149145799198, 23.804125705140834]), 'Point', '1', '测试点1', '1');
|
||||
});
|
||||
|
||||
function enableMiddleMousePan(map: Map) {
|
||||
// 先移除默认的 DragPan(通常响应左键)
|
||||
const interactions = map.getInteractions();
|
||||
interactions.forEach((interaction) => {
|
||||
if (interaction instanceof DragPan) {
|
||||
map.removeInteraction(interaction);
|
||||
}
|
||||
});
|
||||
|
||||
// 添加只响应中键的 DragPan
|
||||
const middleButtonDragPan = new DragPan({
|
||||
condition: (event) => {
|
||||
// 只允许中键 (mouse button 1) 拖动
|
||||
return event.originalEvent instanceof MouseEvent && event.originalEvent.button === 1;
|
||||
}
|
||||
});
|
||||
|
||||
map.addInteraction(middleButtonDragPan);
|
||||
|
||||
// 禁用中键点击默认滚动行为(浏览器可能会出现滚动箭头)
|
||||
// map.getViewport().addEventListener('mousedown', (e) => {
|
||||
// if (e.button === 1) e.preventDefault();
|
||||
// });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ol-map {
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
.header {
|
||||
height: 70px;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
background: rgba(255, 255, 255, 0.2); /* 半透明白色 */
|
||||
backdrop-filter: blur(10px); /* 背景模糊 */
|
||||
-webkit-backdrop-filter: blur(10px); /* 兼容 Safari */
|
||||
}
|
||||
.aside {
|
||||
position: absolute;
|
||||
top: 10%;
|
||||
left: 30px;
|
||||
width: 300px;
|
||||
height: 70vh;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
z-index: 3;
|
||||
overflow: auto;
|
||||
}
|
||||
.submit {
|
||||
position: absolute;
|
||||
bottom: 70px;
|
||||
right: 70px;
|
||||
z-index: 3;
|
||||
}
|
||||
.custom-tree-node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.tips {
|
||||
margin: 0 15px;
|
||||
position: relative;
|
||||
font-size: 18px;
|
||||
color: #fff;
|
||||
> div {
|
||||
margin: 0 25px;
|
||||
position: relative;
|
||||
font-size: 12px;
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
display: inline-block;
|
||||
left: -15px;
|
||||
top: 30%;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
.dot1 {
|
||||
&::before {
|
||||
background-color: #1d6fe9;
|
||||
}
|
||||
}
|
||||
.dot2 {
|
||||
&::before {
|
||||
background-color: #67c23a;
|
||||
}
|
||||
}
|
||||
.dot3 {
|
||||
&::before {
|
||||
background-color: #ff8d1a;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user