|
@@ -0,0 +1,659 @@
|
|
|
|
|
+<script lang="ts" setup>
|
|
|
|
|
+import {
|
|
|
|
|
+ IotProjectTaskPlanApi,
|
|
|
|
|
+ type IotProjectTaskPlanCreateData,
|
|
|
|
|
+ type IotProjectTaskPlanExtProperty,
|
|
|
|
|
+ type IotProjectTaskPlanPageParams,
|
|
|
|
|
+ type IotProjectTaskPlanPageVO,
|
|
|
|
|
+ type IotProjectTaskPlanUpdateData
|
|
|
|
|
+} from '@/api/pms/iotprojecttaskplan'
|
|
|
|
|
+import * as DeptApi from '@/api/system/dept'
|
|
|
|
|
+import { useUserStore } from '@/store/modules/user'
|
|
|
|
|
+import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
|
|
|
|
|
+import { rangeShortcuts } from '@/utils/formatTime'
|
|
|
|
|
+import { defaultProps, handleTree } from '@/utils/tree'
|
|
|
|
|
+import { useDebounceFn } from '@vueuse/core'
|
|
|
|
|
+import dayjs from 'dayjs'
|
|
|
|
|
+import type { FormInstance, FormRules } from 'element-plus'
|
|
|
|
|
+
|
|
|
|
|
+defineOptions({ name: 'PlanWorkFill' })
|
|
|
|
|
+
|
|
|
|
|
+const TOP_DEPT_ID = 156
|
|
|
|
|
+const deptId = useUserStore().getUser.deptId
|
|
|
|
|
+const message = useMessage()
|
|
|
|
|
+
|
|
|
|
|
+const createDefaultQuery = (): IotProjectTaskPlanPageParams => ({
|
|
|
|
|
+ pageNo: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ deptId,
|
|
|
|
|
+ startTime: [...rangeShortcuts[8].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))]
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const query = ref<IotProjectTaskPlanPageParams>(createDefaultQuery())
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+const list = ref<IotProjectTaskPlanPageVO[]>([])
|
|
|
|
|
+const total = ref(0)
|
|
|
|
|
+
|
|
|
|
|
+type PlanField = 'planGasInjection' | 'planLayers' | 'planWellTrips' | 'planFootage'
|
|
|
|
|
+
|
|
|
|
|
+interface BusinessPlanConfig {
|
|
|
|
|
+ field: PlanField
|
|
|
|
|
+ unit: string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface DeptOption {
|
|
|
|
|
+ id: number
|
|
|
|
|
+ name: string
|
|
|
|
|
+ parentId?: number
|
|
|
|
|
+ type?: string
|
|
|
|
|
+ disabled?: boolean
|
|
|
|
|
+ children?: DeptOption[]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface CreatePlanForm {
|
|
|
|
|
+ id?: number
|
|
|
|
|
+ deptId?: number
|
|
|
|
|
+ companyId?: number
|
|
|
|
|
+ startTime?: number | string
|
|
|
|
|
+ business: string
|
|
|
|
|
+ workloadUnit: string
|
|
|
|
|
+ planWorkload?: number
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type FormMode = 'create' | 'edit' | 'view'
|
|
|
|
|
+
|
|
|
|
|
+const BUSINESS_PLAN_CONFIG: Record<string, BusinessPlanConfig> = {
|
|
|
|
|
+ 注气: { field: 'planGasInjection', unit: '万方' },
|
|
|
|
|
+ 压裂: { field: 'planLayers', unit: '层' },
|
|
|
|
|
+ 连油: { field: 'planWellTrips', unit: '井' },
|
|
|
|
|
+ 钻井: { field: 'planFootage', unit: '米' },
|
|
|
|
|
+ 修井: { field: 'planWellTrips', unit: '井' },
|
|
|
|
|
+ 井下: { field: 'planWellTrips', unit: '井' }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const businessOptions = getStrDictOptions(DICT_TYPE.RQ_IOT_WORKLOAD_PLAN_BIZ)
|
|
|
|
|
+const deptTreeProps = { ...defaultProps, disabled: 'disabled' }
|
|
|
|
|
+
|
|
|
|
|
+const getBusinessPlanConfig = (business: string) => {
|
|
|
|
|
+ const businessValue = String(business || '').trim()
|
|
|
|
|
+ const businessLabel = businessOptions.find((option) => option.value === businessValue)?.label
|
|
|
|
|
+ return BUSINESS_PLAN_CONFIG[businessValue] || BUSINESS_PLAN_CONFIG[businessLabel || '']
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const getPlanWorkload = (row: IotProjectTaskPlanPageVO) => {
|
|
|
|
|
+ const config = getBusinessPlanConfig(row.business)
|
|
|
|
|
+ if (!config) return '-'
|
|
|
|
|
+
|
|
|
|
|
+ const value = row[config.field]
|
|
|
|
|
+ return value === null || value === undefined || value === '' ? '-' : value
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const createDialogVisible = ref(false)
|
|
|
|
|
+const createLoading = ref(false)
|
|
|
|
|
+const formMode = ref<FormMode>('create')
|
|
|
|
|
+const deptOptionsLoading = ref(false)
|
|
|
|
|
+const extPropertiesLoading = ref(false)
|
|
|
|
|
+const deptOptions = ref<DeptOption[]>([])
|
|
|
|
|
+const flatDeptOptions = ref<DeptOption[]>([])
|
|
|
|
|
+const planExtProperties = ref<IotProjectTaskPlanExtProperty[]>([])
|
|
|
|
|
+const createFormRef = ref<FormInstance>()
|
|
|
|
|
+
|
|
|
|
|
+const isReadonly = computed(() => formMode.value === 'view')
|
|
|
|
|
+const dialogTitle = computed(() => {
|
|
|
|
|
+ if (formMode.value === 'edit') return '编辑计划工作'
|
|
|
|
|
+ if (formMode.value === 'view') return '查看计划工作'
|
|
|
|
|
+ return '新增计划工作'
|
|
|
|
|
+})
|
|
|
|
|
+const dialogDescription = computed(() =>
|
|
|
|
|
+ isReadonly.value ? '查看部门项目业务计划工作量详情' : '维护部门各月份的项目业务计划工作量'
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+const selectableBusinessOptions = computed(() => {
|
|
|
|
|
+ const identifiers = new Set(planExtProperties.value.map((property) => property.identifier))
|
|
|
|
|
+
|
|
|
|
|
+ return businessOptions.filter((option) => {
|
|
|
|
|
+ const config = getBusinessPlanConfig(option.value)
|
|
|
|
|
+ return config ? identifiers.has(config.field) : false
|
|
|
|
|
+ })
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const createInitialForm = (): CreatePlanForm => ({
|
|
|
|
|
+ id: undefined,
|
|
|
|
|
+ deptId: undefined,
|
|
|
|
|
+ companyId: undefined,
|
|
|
|
|
+ startTime: dayjs().startOf('month').valueOf(),
|
|
|
|
|
+ business: '',
|
|
|
|
|
+ workloadUnit: '',
|
|
|
|
|
+ planWorkload: undefined
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const createFormData = ref<CreatePlanForm>(createInitialForm())
|
|
|
|
|
+const createFormRules: FormRules = {
|
|
|
|
|
+ deptId: [{ required: true, message: '请选择部门', trigger: 'change' }],
|
|
|
|
|
+ startTime: [{ required: true, message: '请选择时间', trigger: 'change' }],
|
|
|
|
|
+ business: [{ required: true, message: '请选择项目业务', trigger: 'change' }],
|
|
|
|
|
+ planWorkload: [{ required: true, message: '请输入计划工作量', trigger: 'blur' }]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const loadDeptOptions = async () => {
|
|
|
|
|
+ if (deptOptions.value.length) return
|
|
|
|
|
+
|
|
|
|
|
+ deptOptionsLoading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const data = (await DeptApi.specifiedSimpleDepts(TOP_DEPT_ID)) as DeptOption[]
|
|
|
|
|
+ flatDeptOptions.value = data.map((dept) => ({
|
|
|
|
|
+ ...dept,
|
|
|
|
|
+ type: String(dept.type || ''),
|
|
|
|
|
+ disabled: String(dept.type || '') !== '3'
|
|
|
|
|
+ }))
|
|
|
|
|
+ deptOptions.value = handleTree(flatDeptOptions.value)
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ deptOptionsLoading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const findCompanyId = (selectedDeptId: number) => {
|
|
|
|
|
+ const deptMap = new Map(flatDeptOptions.value.map((dept) => [dept.id, dept]))
|
|
|
|
|
+ const visitedIds = new Set<number>()
|
|
|
|
|
+ let currentDept = deptMap.get(selectedDeptId)
|
|
|
|
|
+
|
|
|
|
|
+ while (currentDept && !visitedIds.has(currentDept.id)) {
|
|
|
|
|
+ visitedIds.add(currentDept.id)
|
|
|
|
|
+ if (String(currentDept.type) === '1') return currentDept.id
|
|
|
|
|
+ currentDept = currentDept.parentId ? deptMap.get(currentDept.parentId) : undefined
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return undefined
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const loadPlanExtProperties = async (selectedDeptId?: number) => {
|
|
|
|
|
+ planExtProperties.value = []
|
|
|
|
|
+ if (!selectedDeptId) return
|
|
|
|
|
+
|
|
|
|
|
+ extPropertiesLoading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const data = await IotProjectTaskPlanApi.getPlanExtProperties(selectedDeptId)
|
|
|
|
|
+ planExtProperties.value = Array.isArray(data) ? data : []
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ extPropertiesLoading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleDeptChange = async (selectedDeptId?: number) => {
|
|
|
|
|
+ createFormData.value.companyId = selectedDeptId ? findCompanyId(selectedDeptId) : undefined
|
|
|
|
|
+ createFormData.value.business = ''
|
|
|
|
|
+ createFormData.value.workloadUnit = ''
|
|
|
|
|
+ createFormData.value.planWorkload = undefined
|
|
|
|
|
+ await loadPlanExtProperties(selectedDeptId)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const normalizeStartTime = (value: unknown) => {
|
|
|
|
|
+ const timestamp = Number(value)
|
|
|
|
|
+ if (Number.isFinite(timestamp) && timestamp > 0) return timestamp
|
|
|
|
|
+
|
|
|
|
|
+ const parsedTimestamp = dayjs(value as string).valueOf()
|
|
|
|
|
+ return Number.isFinite(parsedTimestamp) ? parsedTimestamp : undefined
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleOpenForm = async (mode: FormMode, id?: number) => {
|
|
|
|
|
+ formMode.value = mode
|
|
|
|
|
+ createFormData.value = createInitialForm()
|
|
|
|
|
+ createDialogVisible.value = true
|
|
|
|
|
+ createLoading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ await loadDeptOptions()
|
|
|
|
|
+
|
|
|
|
|
+ if (id) {
|
|
|
|
|
+ const data = await IotProjectTaskPlanApi.get(id)
|
|
|
|
|
+ const business = String(data.business || '')
|
|
|
|
|
+ const config = getBusinessPlanConfig(business)
|
|
|
|
|
+ const workloadValue = config ? data[config.field] : undefined
|
|
|
|
|
+
|
|
|
|
|
+ createFormData.value = {
|
|
|
|
|
+ id: data.id,
|
|
|
|
|
+ deptId: data.deptId,
|
|
|
|
|
+ companyId: data.companyId || findCompanyId(data.deptId),
|
|
|
|
|
+ startTime: normalizeStartTime(data.startTime),
|
|
|
|
|
+ business,
|
|
|
|
|
+ workloadUnit: data.workloadUnit || config?.unit || '',
|
|
|
|
|
+ planWorkload:
|
|
|
|
|
+ workloadValue === null || workloadValue === undefined ? undefined : Number(workloadValue)
|
|
|
|
|
+ }
|
|
|
|
|
+ await loadPlanExtProperties(data.deptId)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await nextTick()
|
|
|
|
|
+ createFormRef.value?.clearValidate()
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ createLoading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleOpenCreate = () => handleOpenForm('create')
|
|
|
|
|
+
|
|
|
|
|
+const handleBusinessChange = () => {
|
|
|
|
|
+ const config = getBusinessPlanConfig(createFormData.value.business)
|
|
|
|
|
+ createFormData.value.workloadUnit = config?.unit || ''
|
|
|
|
|
+ createFormData.value.planWorkload = undefined
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const submitCreate = async () => {
|
|
|
|
|
+ if (isReadonly.value) return
|
|
|
|
|
+ await createFormRef.value?.validate()
|
|
|
|
|
+
|
|
|
|
|
+ const {
|
|
|
|
|
+ deptId: formDeptId,
|
|
|
|
|
+ companyId,
|
|
|
|
|
+ startTime,
|
|
|
|
|
+ business,
|
|
|
|
|
+ workloadUnit,
|
|
|
|
|
+ planWorkload
|
|
|
|
|
+ } = createFormData.value
|
|
|
|
|
+ const config = getBusinessPlanConfig(business)
|
|
|
|
|
+
|
|
|
|
|
+ if (
|
|
|
|
|
+ !formDeptId ||
|
|
|
|
|
+ !companyId ||
|
|
|
|
|
+ !startTime ||
|
|
|
|
|
+ !workloadUnit ||
|
|
|
|
|
+ planWorkload === undefined ||
|
|
|
|
|
+ !config
|
|
|
|
|
+ ) {
|
|
|
|
|
+ message.error('表单数据不完整或项目业务未配置计划工作量字段')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const data: IotProjectTaskPlanCreateData = {
|
|
|
|
|
+ deptId: formDeptId,
|
|
|
|
|
+ companyId,
|
|
|
|
|
+ startTime: dayjs(Number(startTime)).startOf('month').valueOf(),
|
|
|
|
|
+ business,
|
|
|
|
|
+ workloadUnit
|
|
|
|
|
+ }
|
|
|
|
|
+ data[config.field] = planWorkload
|
|
|
|
|
+
|
|
|
|
|
+ createLoading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (formMode.value === 'edit') {
|
|
|
|
|
+ if (!createFormData.value.id) return
|
|
|
|
|
+ await IotProjectTaskPlanApi.update({
|
|
|
|
|
+ ...data,
|
|
|
|
|
+ id: createFormData.value.id
|
|
|
|
|
+ } as IotProjectTaskPlanUpdateData)
|
|
|
|
|
+ message.success('编辑成功')
|
|
|
|
|
+ } else {
|
|
|
|
|
+ await IotProjectTaskPlanApi.create(data)
|
|
|
|
|
+ message.success('新增成功')
|
|
|
|
|
+ }
|
|
|
|
|
+ createDialogVisible.value = false
|
|
|
|
|
+ handleQuery()
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ createLoading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const loadPage = useDebounceFn(async () => {
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const data = await IotProjectTaskPlanApi.getPage(query.value)
|
|
|
|
|
+ console.log('计划工作分页接口数据:', data)
|
|
|
|
|
+ list.value = Array.isArray(data?.list) ? data.list : []
|
|
|
|
|
+ total.value = Number(data?.total || 0)
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}, 200)
|
|
|
|
|
+
|
|
|
|
|
+const handleQuery = () => {
|
|
|
|
|
+ query.value.pageNo = 1
|
|
|
|
|
+ loadPage()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleSizeChange = (pageSize: number) => {
|
|
|
|
|
+ query.value.pageSize = pageSize
|
|
|
|
|
+ handleQuery()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleCurrentChange = (pageNo: number) => {
|
|
|
|
|
+ query.value.pageNo = pageNo
|
|
|
|
|
+ loadPage()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const resetQuery = () => {
|
|
|
|
|
+ query.value = createDefaultQuery()
|
|
|
|
|
+ handleQuery()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+watch(
|
|
|
|
|
+ [() => query.value.deptId, () => query.value.startTime],
|
|
|
|
|
+ () => {
|
|
|
|
|
+ handleQuery()
|
|
|
|
|
+ },
|
|
|
|
|
+ { immediate: true }
|
|
|
|
|
+)
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="grid grid-cols-[auto_minmax(0,1fr)] grid-rows-[62px_minmax(0,1fr)] gap-4 w-full min-w-0 overflow-hidden h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]">
|
|
|
|
|
+ <DeptTreeSelect
|
|
|
|
|
+ :top-id="TOP_DEPT_ID"
|
|
|
|
|
+ :deptId="deptId"
|
|
|
|
|
+ v-model="query.deptId"
|
|
|
|
|
+ :show-title="false"
|
|
|
|
|
+ class="row-span-2" />
|
|
|
|
|
+
|
|
|
|
|
+ <el-form
|
|
|
|
|
+ size="default"
|
|
|
|
|
+ class="plan-query-form bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 flex items-center justify-between min-w-0 overflow-hidden">
|
|
|
|
|
+ <el-form-item label="时间范围">
|
|
|
|
|
+ <el-date-picker
|
|
|
|
|
+ v-model="query.startTime"
|
|
|
|
|
+ value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
+ type="daterange"
|
|
|
|
|
+ start-placeholder="开始日期"
|
|
|
|
|
+ end-placeholder="结束日期"
|
|
|
|
|
+ :shortcuts="rangeShortcuts"
|
|
|
|
|
+ :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
|
|
|
|
+ class="!w-260px" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" plain @click="handleOpenCreate">
|
|
|
|
|
+ <Icon icon="ep:plus" class="mr-5px" />新增
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button type="primary" :loading="loading" @click="handleQuery">
|
|
|
|
|
+ <Icon icon="ep:search" class="mr-5px" />搜索
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button @click="resetQuery"> <Icon icon="ep:refresh" class="mr-5px" />重置 </el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow min-h-0 min-w-0 w-full max-w-full p-4 overflow-hidden flex flex-col">
|
|
|
|
|
+ <div class="relative flex-1 min-h-0 min-w-0 overflow-hidden">
|
|
|
|
|
+ <el-auto-resizer class="absolute inset-0">
|
|
|
|
|
+ <template #default="{ width, height }">
|
|
|
|
|
+ <ZmTable
|
|
|
|
|
+ :data="list"
|
|
|
|
|
+ :loading="loading"
|
|
|
|
|
+ :width="width"
|
|
|
|
|
+ :height="height"
|
|
|
|
|
+ :max-height="height"
|
|
|
|
|
+ empty-text="暂无数据"
|
|
|
|
|
+ show-border>
|
|
|
|
|
+ <ZmTableColumn prop="deptName" label="部门名称" fixed="left" show-overflow-tooltip />
|
|
|
|
|
+ <ZmTableColumn prop="business" label="项目业务" />
|
|
|
|
|
+ <ZmTableColumn prop="yearMonth" label="时间" />
|
|
|
|
|
+ <ZmTableColumn label="计划工作量" cover-formatter :real-value="getPlanWorkload" />
|
|
|
|
|
+ <ZmTableColumn prop="workloadUnit" label="工作量单位" />
|
|
|
|
|
+ <ZmTableColumn label="操作" :width="140" fixed="right" action>
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ size="default"
|
|
|
|
|
+ type="success"
|
|
|
|
|
+ link
|
|
|
|
|
+ @click="handleOpenForm('view', row.id)">
|
|
|
|
|
+ 查看
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ size="default"
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ link
|
|
|
|
|
+ @click="handleOpenForm('edit', row.id)">
|
|
|
|
|
+ 编辑
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </ZmTableColumn>
|
|
|
|
|
+ </ZmTable>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-auto-resizer>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="h-8 mt-2 flex items-center justify-end">
|
|
|
|
|
+ <el-pagination
|
|
|
|
|
+ size="default"
|
|
|
|
|
+ v-show="total > 0"
|
|
|
|
|
+ :current-page="query.pageNo"
|
|
|
|
|
+ :page-size="query.pageSize"
|
|
|
|
|
+ :page-sizes="[10, 20, 30, 50, 100]"
|
|
|
|
|
+ :total="total"
|
|
|
|
|
+ background
|
|
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
+ @size-change="handleSizeChange"
|
|
|
|
|
+ @current-change="handleCurrentChange" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <Dialog v-model="createDialogVisible" :title="dialogTitle" width="780px" class="plan-work-dialog">
|
|
|
|
|
+ <template #title>
|
|
|
|
|
+ <div class="dialog-title">
|
|
|
|
|
+ <div class="dialog-title__icon">
|
|
|
|
|
+ <Icon icon="ep:calendar" :size="22" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <div class="dialog-title__text">{{ dialogTitle }}</div>
|
|
|
|
|
+ <div class="dialog-title__desc">{{ dialogDescription }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-loading="createLoading" class="plan-form-body">
|
|
|
|
|
+ <section class="plan-form-card">
|
|
|
|
|
+ <div class="plan-form-card__header">
|
|
|
|
|
+ <div class="plan-form-card__title">
|
|
|
|
|
+ <span class="plan-form-card__marker"></span>
|
|
|
|
|
+ <span>计划信息</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form
|
|
|
|
|
+ ref="createFormRef"
|
|
|
|
|
+ :model="createFormData"
|
|
|
|
|
+ :rules="createFormRules"
|
|
|
|
|
+ :disabled="isReadonly"
|
|
|
|
|
+ size="default"
|
|
|
|
|
+ label-position="top"
|
|
|
|
|
+ class="plan-create-form">
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :xs="24" :sm="12">
|
|
|
|
|
+ <el-form-item label="选择部门" prop="deptId">
|
|
|
|
|
+ <el-tree-select
|
|
|
|
|
+ v-model="createFormData.deptId"
|
|
|
|
|
+ :data="deptOptions"
|
|
|
|
|
+ :props="deptTreeProps"
|
|
|
|
|
+ :loading="deptOptionsLoading"
|
|
|
|
|
+ :disabled="isReadonly"
|
|
|
|
|
+ node-key="id"
|
|
|
|
|
+ check-strictly
|
|
|
|
|
+ default-expand-all
|
|
|
|
|
+ filterable
|
|
|
|
|
+ clearable
|
|
|
|
|
+ placeholder="请选择部门"
|
|
|
|
|
+ class="w-full"
|
|
|
|
|
+ @change="handleDeptChange" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+
|
|
|
|
|
+ <el-col :xs="24" :sm="12">
|
|
|
|
|
+ <el-form-item label="选择时间" prop="startTime">
|
|
|
|
|
+ <el-date-picker
|
|
|
|
|
+ v-model="createFormData.startTime"
|
|
|
|
|
+ type="month"
|
|
|
|
|
+ value-format="x"
|
|
|
|
|
+ format="YYYY年MM月"
|
|
|
|
|
+ :disabled="isReadonly"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ placeholder="请选择年月"
|
|
|
|
|
+ class="!w-full" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+
|
|
|
|
|
+ <el-col :xs="24" :sm="12">
|
|
|
|
|
+ <el-form-item label="项目业务" prop="business">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="createFormData.business"
|
|
|
|
|
+ :placeholder="createFormData.deptId ? '请选择项目业务' : '请先选择部门'"
|
|
|
|
|
+ :loading="extPropertiesLoading"
|
|
|
|
|
+ :disabled="isReadonly || !createFormData.deptId"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ class="w-full"
|
|
|
|
|
+ @change="handleBusinessChange">
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="option in selectableBusinessOptions"
|
|
|
|
|
+ :key="option.value"
|
|
|
|
|
+ :label="option.label"
|
|
|
|
|
+ :value="option.value" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+
|
|
|
|
|
+ <el-col :xs="24" :sm="12">
|
|
|
|
|
+ <el-form-item label="工作量单位">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="createFormData.workloadUnit"
|
|
|
|
|
+ readonly
|
|
|
|
|
+ placeholder="选择项目业务后自动填入" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+
|
|
|
|
|
+ <el-col :xs="24">
|
|
|
|
|
+ <el-form-item label="计划工作量" prop="planWorkload">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ align="left"
|
|
|
|
|
+ v-model="createFormData.planWorkload"
|
|
|
|
|
+ :min="0"
|
|
|
|
|
+ :controls="false"
|
|
|
|
|
+ :disabled="isReadonly || !createFormData.business"
|
|
|
|
|
+ :placeholder="createFormData.business ? '请输入计划工作量' : '请先选择项目业务'"
|
|
|
|
|
+ class="!w-full" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </section>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <div class="dialog-footer">
|
|
|
|
|
+ <el-button size="default" :disabled="createLoading" @click="createDialogVisible = false">
|
|
|
|
|
+ {{ isReadonly ? '关 闭' : '取 消' }}
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-if="!isReadonly"
|
|
|
|
|
+ size="default"
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ :loading="createLoading"
|
|
|
|
|
+ @click="submitCreate">
|
|
|
|
|
+ 确 定
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </Dialog>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.plan-query-form :deep(.el-form-item) {
|
|
|
|
|
+ margin-bottom: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.dialog-title {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.dialog-title__icon {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ width: 40px;
|
|
|
|
|
+ height: 40px;
|
|
|
|
|
+ color: var(--el-color-primary);
|
|
|
|
|
+ background: rgb(64 158 255 / 12%);
|
|
|
|
|
+ border: 1px solid rgb(64 158 255 / 18%);
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.dialog-title__text {
|
|
|
|
|
+ font-size: 17px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ line-height: 22px;
|
|
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.dialog-title__desc {
|
|
|
|
|
+ margin-top: 3px;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ line-height: 18px;
|
|
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.plan-form-body {
|
|
|
|
|
+ padding: 4px 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.plan-form-card {
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ background: var(--el-bg-color);
|
|
|
|
|
+ border: 1px solid var(--el-border-color-lighter);
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ box-shadow: 0 8px 24px rgb(31 45 61 / 5%);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.plan-form-card__header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ min-height: 52px;
|
|
|
|
|
+ padding: 0 18px;
|
|
|
|
|
+ background: linear-gradient(90deg, rgb(64 158 255 / 8%) 0%, rgb(255 255 255 / 0%) 72%);
|
|
|
|
|
+ border-bottom: 1px solid var(--el-border-color-lighter);
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.plan-form-card__title {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.plan-form-card__marker {
|
|
|
|
|
+ width: 4px;
|
|
|
|
|
+ height: 17px;
|
|
|
|
|
+ background: var(--el-color-primary);
|
|
|
|
|
+ border-radius: 2px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.plan-create-form {
|
|
|
|
|
+ padding: 20px 22px 4px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.plan-create-form :deep(.el-form-item) {
|
|
|
|
|
+ margin-bottom: 18px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.plan-create-form :deep(.el-form-item__label) {
|
|
|
|
|
+ margin-bottom: 6px;
|
|
|
|
|
+ line-height: 18px;
|
|
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.dialog-footer {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@media (width <= 768px) {
|
|
|
|
|
+ .dialog-title__desc {
|
|
|
|
|
+ display: none;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .plan-create-form {
|
|
|
|
|
+ padding: 16px 16px 0;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|