Browse Source

Merge branch 'feature/dailyreport'

Zimo 1 week ago
parent
commit
f5106b4fd8

+ 307 - 168
src/views/pms/device/CustomerList.vue

@@ -1,176 +1,315 @@
 <template>
-  <Dialog v-model="dialogVisible" :title="dialogTitle" style="width: 1100px;min-height: 800px">
-  <ContentWrap>
-    <el-form
-      class="-mb-15px"
-      :model="queryParams"
-      ref="queryFormRef"
-      :inline="true"
-      label-width="68px"
-    >
-      <el-form-item label-width="90px" label="客商名称" prop="name" >
-        <el-input
-          @keyup.enter="handleQuery"
-          v-model="queryParams.name"
-          placeholder="请输入名称"
-          class="!w-240px"
-        />
-      </el-form-item>
-      <el-form-item label-width="90px" label="客商编号" prop="code" >
-        <el-input
-          @keyup.enter="handleQuery"
-          v-model="queryParams.code"
-          placeholder="请输入名称"
-          class="!w-240px"
-        />
-      </el-form-item>
-      <el-form-item>
-        <el-button @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>
-  </ContentWrap>
-  <ContentWrap>
-    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @row-click="handleRowClick">
-      <el-table-column width="60" label="选择">
-        <template #default="{ row }">
-          <el-radio
-            :model-value="selectedRow?.id"
-            :label="row.id"
-            @click.stop="selectRow(row)"
-            class="no-label-radio"
-          />
-        </template>
-      </el-table-column>
-      <el-table-column label="客商名称" align="center" prop="name" />
-      <el-table-column label="客商编号" align="center" prop="code" />
-      <el-table-column label="客商分类" align="center" prop="classification" >
-        <template #default="scope">
-          <dict-tag :type="DICT_TYPE.SUPPLIER_TYPE" :value="scope.row.classification" />
-        </template>
-      </el-table-column>
-      <el-table-column label="状态" align="center" prop="status">
-        <template #default="scope">
-          <dict-tag :type="DICT_TYPE.SUPPLIER_STATUS" :value="scope.row.status" />
-        </template>
-      </el-table-column>
-      <el-table-column
-        label="创建时间"
-        align="center"
-        prop="createTime"
-        width="180"
-        :formatter="dateFormatter"
-      />
-<!--      <el-table-column label="操作" align="center">-->
-<!--        <template #default="scope">-->
-<!--          <el-button-->
-<!--            link-->
-<!--            type="primary"-->
-<!--            @click="choose(scope.row)"-->
-<!--          >-->
-<!--            选择-->
-<!--          </el-button>-->
-
-<!--        </template>-->
-<!--      </el-table-column>-->
-    </el-table>
-    <!-- 分页 -->
-    <Pagination
-      :total="total"
-      v-model:page="queryParams.pageNo"
-      v-model:limit="queryParams.pageSize"
-      @pagination="getList"
-    />
-  </ContentWrap>
-    </Dialog>
+  <Dialog
+    v-model="dialogVisible"
+    width="min(1280px, calc(100vw - 32px))"
+    class="customer-select-dialog">
+    <template #title>
+      <div class="dialog-title">
+        <div class="dialog-title__icon"><Icon icon="ep:office-building" /></div>
+        <div>
+          <div class="dialog-title__text">选择客户</div>
+          <div class="dialog-title__subtitle">查询并选择需要关联的客户信息</div>
+        </div>
+      </div>
+    </template>
+
+    <div class="customer-select-content">
+      <el-form
+        ref="queryFormRef"
+        :model="queryParams"
+        label-width="76px"
+        size="default"
+        class="customer-query-form">
+        <div class="query-fields">
+          <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="code">
+            <el-input
+              v-model="queryParams.code"
+              placeholder="请输入客商编号"
+              clearable
+              @keyup.enter="handleQuery" />
+          </el-form-item>
+          <el-form-item class="query-actions">
+            <el-button size="default" type="primary" @click="handleQuery">
+              <Icon icon="ep:search" class="mr-5px" />搜索
+            </el-button>
+            <el-button size="default" @click="resetQuery">
+              <Icon icon="ep:refresh" class="mr-5px" />重置
+            </el-button>
+          </el-form-item>
+        </div>
+      </el-form>
+
+      <section class="customer-table-section">
+        <div class="section-header">
+          <div class="section-title">客户列表</div>
+          <span class="section-total">共 {{ total }} 条</span>
+        </div>
+        <div class="table-container">
+          <ZmTable
+            :loading="loading"
+            :data="list"
+            height="360px"
+            show-border
+            @row-click="handleRowClick">
+            <ZmTableColumn width="70" label="选择" hide-in-column-settings>
+              <template #default="{ row }">
+                <el-radio
+                  :model-value="selectedRow?.id"
+                  :value="row.id"
+                  class="no-label-radio"
+                  @click.stop="selectRow(row)" />
+              </template>
+            </ZmTableColumn>
+            <ZmTableColumn label="客商名称" prop="name" />
+            <ZmTableColumn label="客商编号" prop="code" />
+            <ZmTableColumn label="客商分类" prop="classification">
+              <template #default="scope">
+                <dict-tag :type="DICT_TYPE.SUPPLIER_TYPE" :value="scope.row.classification" />
+              </template>
+            </ZmTableColumn>
+            <ZmTableColumn label="状态" prop="status">
+              <template #default="scope">
+                <dict-tag :type="DICT_TYPE.SUPPLIER_STATUS" :value="scope.row.status" />
+              </template>
+            </ZmTableColumn>
+            <ZmTableColumn label="创建时间" prop="createTime" :formatter="dateFormatter" />
+          </ZmTable>
+        </div>
+        <Pagination
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          :total="total"
+          @pagination="getList" />
+      </section>
+    </div>
+
+    <template #footer>
+      <el-button size="default" @click="dialogVisible = false">关闭</el-button>
+    </template>
+  </Dialog>
 </template>
 
 <script setup lang="ts">
-  import {DictDataVO} from "@/api/system/dict/dict.data";
-  import {DICT_TYPE, getIntDictOptions} from "@/utils/dict";
-  import {dateFormatter} from "@/utils/formatTime";
-  import {Api, SupplierVO} from '@/api/supplier/base'
-  import { propTypes } from '@/utils/propTypes'
-  const emit = defineEmits(['choose']) // 定义 success 事件,用于操作成功后的回调
-  const dialogVisible = ref(false) // 弹窗的是否展示
-  const dialogTitle = ref('') // 弹窗的是否展示
-  const customerType = ref('') // 弹窗的是否展示
-  const loading = ref(true) // 列表的加载中
-  const queryFormRef = ref() // 搜索的表单
-  const list = ref<SupplierVO[]>([]) // 列表的数据
-  const total = ref(0) // 列表的总页数
-  // const props = defineProps({
-  //   type: propTypes.bool.def(), // 是否作为详情组件
-  // })
-  const selectedRow = ref(null);
-
-  // 处理单选逻辑
-  const selectRow = (row) => {
-    selectedRow.value = selectedRow.value?.id === row.id ? null : row;
-    emit('choose', row)
-    dialogVisible.value = false
-  };
-
-  // 点击整行选中
-  const handleRowClick = (row) => {
-    selectRow(row);
-  };
-  const queryParams = reactive({
-    pageNo: 1,
-    pageSize: 10,
-    customerNature: undefined,
-    code: undefined,
-    name: undefined,
-    classification: undefined,
-    type: undefined,
-    nature: undefined,
-    creditCode: undefined,
-    tin: undefined,
-    corporation: undefined,
-    incorporationDate: [],
-    address: undefined,
-    bizScope: undefined,
-    registeredCapital: undefined,
-    annualTurnove: undefined,
-    size: undefined,
-    status: undefined,
-    remark: undefined,
-    createTime: [],
-    customerNature: ''
-  })
-  const open = async (type: string) => {
-    dialogVisible.value = true
-    queryParams.name = undefined
-    selectedRow.value = null
-    await getList();
-  }
-  defineExpose({ open }) // 提供 open 方法,用于打开弹窗
-  const getList = async () => {
-    loading.value = true
-    try {
-      console.log(JSON.stringify(queryParams))
-      const data = await Api.getPage(queryParams)
-      list.value = data.list
-      total.value = data.total
-    } finally {
-      loading.value = false
-    }
-  }
-  /** 搜索按钮操作 */
-  const handleQuery = () => {
-    queryParams.pageNo = 1
-    getList()
-  }
-  const choose = (row: DictDataVO) => {
-    emit("choose", row);
-    dialogVisible.value = false;
-  }
-  /** 重置按钮操作 */
-  const resetQuery = () => {
-    queryFormRef.value.resetFields()
-    handleQuery()
+import { DICT_TYPE } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
+import { Api, SupplierVO } from '@/api/supplier/base'
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
+
+const { ZmTable, ZmTableColumn } = useTableComponents()
+const emit = defineEmits(['choose']) // 定义 success 事件,用于操作成功后的回调
+const dialogVisible = ref(false) // 弹窗的是否展示
+const loading = ref(true) // 列表的加载中
+const queryFormRef = ref() // 搜索的表单
+const list = ref<SupplierVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+// const props = defineProps({
+//   type: propTypes.bool.def(), // 是否作为详情组件
+// })
+const selectedRow = ref<SupplierVO | null>(null)
+
+// 处理单选逻辑
+const selectRow = (row) => {
+  selectedRow.value = selectedRow.value?.id === row.id ? null : row
+  emit('choose', row)
+  dialogVisible.value = false
+}
+
+// 点击整行选中
+const handleRowClick = (row) => {
+  selectRow(row)
+}
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  customerNature: undefined,
+  code: undefined,
+  name: undefined,
+  classification: undefined,
+  type: undefined,
+  nature: undefined,
+  creditCode: undefined,
+  tin: undefined,
+  corporation: undefined,
+  incorporationDate: [],
+  address: undefined,
+  bizScope: undefined,
+  registeredCapital: undefined,
+  annualTurnove: undefined,
+  size: undefined,
+  status: undefined,
+  remark: undefined,
+  createTime: [],
+  customerNature: ''
+})
+const open = async () => {
+  dialogVisible.value = true
+  queryParams.name = undefined
+  selectedRow.value = null
+  await getList()
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+const getList = async () => {
+  loading.value = true
+  try {
+    console.log(JSON.stringify(queryParams))
+    const data = await Api.getPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
   }
+}
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
 </script>
-<style scoped lang="scss">
+<style scoped>
+.dialog-title {
+  display: flex;
+  align-items: center;
+  min-width: 0;
+}
+
+.dialog-title__icon {
+  display: inline-flex;
+  flex: 0 0 auto;
+  align-items: center;
+  justify-content: center;
+  width: 38px;
+  height: 38px;
+  margin-right: 12px;
+  color: var(--el-color-primary);
+  background: var(--el-color-primary-light-9);
+  border: 1px solid var(--el-color-primary-light-7);
+  border-radius: 6px;
+}
+
+.dialog-title__text {
+  font-size: 16px;
+  font-weight: 600;
+  line-height: 22px;
+  color: var(--el-text-color-primary);
+}
+
+.dialog-title__subtitle {
+  margin-top: 1px;
+  font-size: 12px;
+  line-height: 18px;
+  color: var(--el-text-color-secondary);
+}
+
+.customer-select-content {
+  display: flex;
+  flex-direction: column;
+  gap: 14px;
+}
+
+.customer-query-form {
+  padding: 14px 16px;
+  background: var(--el-fill-color-extra-light);
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 6px;
+}
+
+.query-fields {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(280px, 1fr)) auto;
+  gap: 12px 24px;
+  align-items: center;
+}
 
+.customer-query-form :deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+.query-actions {
+  min-width: max-content;
+}
+
+.query-actions :deep(.el-form-item__content) {
+  gap: 10px;
+  flex-wrap: nowrap;
+  margin-left: 0 !important;
+}
+
+.query-actions :deep(.el-button) {
+  margin-left: 0;
+}
+
+.customer-table-section {
+  min-width: 0;
+  overflow: hidden;
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 6px;
+}
+
+.section-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  min-height: 44px;
+  padding: 0 14px;
+  border-bottom: 1px solid var(--el-border-color-lighter);
+}
+
+.section-title {
+  position: relative;
+  padding-left: 11px;
+  font-size: 14px;
+  font-weight: 600;
+  color: var(--el-text-color-primary);
+}
+
+.section-title::before {
+  position: absolute;
+  top: 2px;
+  bottom: 2px;
+  left: 0;
+  width: 3px;
+  background: var(--el-color-primary);
+  border-radius: 2px;
+  content: '';
+}
+
+.section-total {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+}
+
+.table-container {
+  min-width: 0;
+  padding: 12px 12px 0;
+}
+
+.customer-table-section :deep(.pagination-container) {
+  padding: 12px;
+}
+
+.no-label-radio :deep(.el-radio__label) {
+  display: none;
+}
+
+@media (width <= 768px) {
+  .dialog-title__subtitle {
+    display: none;
+  }
+
+  .query-fields {
+    grid-template-columns: minmax(0, 1fr);
+  }
+}
 </style>

+ 521 - 257
src/views/pms/iotprojectinfo/IotProjectInfoForm.vue

@@ -1,247 +1,272 @@
 <template>
-  <ContentWrap v-loading="formLoading">
+  <div class="project-form-page">
+    <ContentWrap class="page-header">
+      <div class="page-header__main">
+        <div class="page-header__icon">
+          <Icon :icon="formType === 'create' ? 'ep:document-add' : 'ep:edit-pen'" />
+        </div>
+        <div>
+          <h2 class="page-header__title">
+            {{ formType === 'create' ? '新增项目信息' : '编辑项目信息' }}
+          </h2>
+          <p class="page-header__subtitle">维护客户、合同、归属部门及项目配置信息</p>
+        </div>
+      </div>
+      <el-button size="default" @click="close">
+        <Icon icon="ep:arrow-left" class="mr-5px" />返回
+      </el-button>
+    </ContentWrap>
+
     <el-form
       ref="formRef"
+      v-loading="formLoading"
       :model="formData"
       :rules="formRules"
-      label-width="120px"
-      v-loading="formLoading"
-    >
-      <el-row>
-        <el-col :span="12">
-          <el-form-item label="客户名称" prop="manufacturerId">
-            <el-select
-              clearable
-              @clear="zzClear"
-              v-model="formData.manufactureName"
-              :placeholder="t('deviceForm.mfgHolder')"
-              @click="openCustomerZz"
-            />
-          </el-form-item>
-        </el-col>
-        <el-col :span="12">
-          <el-form-item :label="t('project.payment')" prop="payment">
-            <el-select v-model="formData.payment" placeholder="请选择" clearable>
-              <el-option
-                v-for="dict in getStrDictOptions(DICT_TYPE.PMS_PROJECT_SETTLEMENT)"
-                :key="dict.id"
-                :label="dict.label"
-                :value="dict.value"
-              />
-            </el-select>
-          </el-form-item>
-        </el-col>
-      </el-row>
-
-      <el-row>
-        <el-col :span="12">
-          <el-form-item label="合同名称" prop="contractName">
-            <el-input v-model="formData.contractName" placeholder="请输入合同名称" />
-          </el-form-item>
-        </el-col>
-        <el-col :span="12">
-          <el-form-item label="合同编号" prop="contractCode">
-            <el-input v-model="formData.contractCode" placeholder="请输入合同编号" />
-          </el-form-item>
-        </el-col>
-      </el-row>
-      <el-row>
-        <el-col :span="12">
-          <el-form-item label="开始时间" prop="startTime">
-            <el-date-picker
-              style="width: 150%"
-              v-model="formData.startTime"
-              type="date"
-              value-format="x"
-              placeholder="选择开始时间"
-            />
-          </el-form-item>
-        </el-col>
-        <el-col :span="12">
-          <el-form-item label="完成时间" prop="endTime">
-            <el-date-picker
-              style="width: 150%"
-              v-model="formData.endTime"
-              type="date"
-              value-format="x"
-              placeholder="选择完成时间"
-            />
-          </el-form-item>
-        </el-col>
-      </el-row>
-
-      <el-row>
-        <el-col :span="12">
-          <el-form-item :label="t('iotDevice.company')" prop="deptId">
-            <el-tree-select
-              :disabled="isDeptDisabled"
-              v-model="formData.deptId"
-              :data="deptList"
-              :props="defaultProps"
-              check-strictly
-              node-key="id"
-              filterable
-              placeholder="请选择所属公司"
-            />
-          </el-form-item>
-        </el-col>
-        <el-col :span="12">
-          <el-form-item label="项目部" prop="projectDeptId">
-            <el-select
-              v-model="formData.projectDeptId"
-              :options="deptOptions"
-              filterable
-              placeholder="请选择项目部"
-              clearable
-            />
-          </el-form-item>
-        </el-col>
-      </el-row>
-
-      <el-row>
-        <el-col :span="12">
-          <el-form-item label="责任人" prop="responsiblePerson">
+      label-width="108px"
+      size="default"
+      class="project-form">
+      <ContentWrap class="project-form-card">
+        <div class="form-card-header">
+          <span class="form-card-icon form-card-icon--blue">
+            <Icon icon="ep:document" />
+          </span>
+          <div>
+            <h3>合同信息</h3>
+            <p>客户、合同及项目周期信息</p>
+          </div>
+        </div>
+        <div class="form-card-body">
+          <el-row :gutter="24">
+            <el-col :xs="24" :md="12">
+              <el-form-item label="客户名称" prop="manufacturerId">
+                <el-select
+                  v-model="formData.manufactureName"
+                  clearable
+                  :placeholder="t('deviceForm.mfgHolder')"
+                  @clear="zzClear"
+                  @click="openCustomerZz" />
+              </el-form-item>
+            </el-col>
+            <el-col :xs="24" :md="12">
+              <el-form-item :label="t('project.payment')" prop="payment">
+                <el-select v-model="formData.payment" placeholder="请选择" clearable>
+                  <el-option
+                    v-for="dict in getStrDictOptions(DICT_TYPE.PMS_PROJECT_SETTLEMENT)"
+                    :key="dict.id"
+                    :label="dict.label"
+                    :value="dict.value" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :xs="24" :md="12">
+              <el-form-item label="合同名称" prop="contractName">
+                <el-input v-model="formData.contractName" placeholder="请输入合同名称" />
+              </el-form-item>
+            </el-col>
+            <el-col :xs="24" :md="12">
+              <el-form-item label="合同编号" prop="contractCode">
+                <el-input v-model="formData.contractCode" placeholder="请输入合同编号" />
+              </el-form-item>
+            </el-col>
+            <el-col :xs="24" :md="12">
+              <el-form-item label="开始时间" prop="startTime">
+                <el-date-picker
+                  v-model="formData.startTime"
+                  type="date"
+                  value-format="x"
+                  placeholder="选择开始时间" />
+              </el-form-item>
+            </el-col>
+            <el-col :xs="24" :md="12">
+              <el-form-item label="完成时间" prop="endTime">
+                <el-date-picker
+                  v-model="formData.endTime"
+                  type="date"
+                  value-format="x"
+                  placeholder="选择完成时间" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </div>
+      </ContentWrap>
+
+      <ContentWrap class="project-form-card">
+        <div class="form-card-header">
+          <span class="form-card-icon form-card-icon--green">
+            <Icon icon="ep:setting" />
+          </span>
+          <div>
+            <h3>项目配置</h3>
+            <p>归属部门、责任人与项目属性</p>
+          </div>
+        </div>
+        <div class="form-card-body">
+          <el-row :gutter="24">
+            <el-col :xs="24" :md="12">
+              <el-form-item :label="t('iotDevice.company')" prop="deptId">
+                <el-tree-select
+                  v-model="formData.deptId"
+                  :disabled="isDeptDisabled"
+                  :data="deptList"
+                  :props="defaultProps"
+                  check-strictly
+                  node-key="id"
+                  filterable
+                  placeholder="请选择所属公司" />
+              </el-form-item>
+            </el-col>
+            <el-col :xs="24" :md="12">
+              <el-form-item label="项目部" prop="projectDeptId">
+                <el-select
+                  v-model="formData.projectDeptId"
+                  :options="deptOptions"
+                  :loading="deptLoading"
+                  filterable
+                  placeholder="请选择项目部"
+                  clearable />
+              </el-form-item>
+            </el-col>
+            <el-col :xs="24" :md="12">
+              <el-form-item label="责任人" prop="responsiblePerson">
+                <div class="responsible-person-control">
+                  <el-tooltip
+                    v-if="selectedUserNames"
+                    effect="dark"
+                    placement="top"
+                    :content="getFullSelectedUserNames()">
+                    <span class="user-names">{{ selectedUserNames }}</span>
+                  </el-tooltip>
+                  <span v-else class="user-names user-names--empty">暂未选择责任人</span>
+                  <el-button
+                    type="primary"
+                    plain
+                    size="default"
+                    :disabled="!formData.deptId"
+                    @click="openUserDialog">
+                    <Icon icon="ep:user" class="mr-5px" />选择
+                  </el-button>
+                </div>
+                <div v-if="!formData.deptId" class="el-form-item__error">请先选择所属公司</div>
+              </el-form-item>
+            </el-col>
+            <el-col :xs="24" :md="12">
+              <el-form-item :label="t('project.overseaFlag')" prop="overseas">
+                <el-select v-model="formData.overseas" placeholder="请选择" clearable>
+                  <el-option
+                    v-for="dict in getIntDictOptions(DICT_TYPE.PMS_PROJECT_OVERSEA_FLAG)"
+                    :key="dict.id"
+                    :label="dict.label"
+                    :value="dict.value" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :xs="24" :md="12">
+              <el-form-item :label="t('project.overseaArea')" prop="overseasArea">
+                <el-select v-model="formData.overseasArea" placeholder="请选择" clearable>
+                  <el-option
+                    v-for="dict in getStrDictOptions(DICT_TYPE.PMS_PROJECT_AREA)"
+                    :key="dict.id"
+                    :label="dict.label"
+                    :value="dict.value" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :xs="24" :md="12">
+              <el-form-item :label="t('project.workArea')" prop="location">
+                <el-autocomplete
+                  ref="workAreaAutocomplete"
+                  v-model="formData.location"
+                  :fetch-suggestions="querySearch"
+                  :trigger-on-focus="true"
+                  :disabled="!workAreaOptions.length"
+                  placeholder="请输入施工区域"
+                  popper-class="work-area-autocomplete"
+                  @focus="handleWorkAreaFocus"
+                  @select="handleSelect">
+                  <template #prefix>
+                    <el-icon v-if="loadingWorkAreaOptions" class="is-loading">
+                      <Loading />
+                    </el-icon>
+                  </template>
+                </el-autocomplete>
+                <div v-if="!workAreaOptions.length" class="el-form-item__error">
+                  暂无可用施工区域选项,请手动输入
+                </div>
+              </el-form-item>
+            </el-col>
+            <el-col :xs="24" :md="12">
+              <el-form-item label="虚拟项目" prop="contractSubject">
+                <el-switch
+                  v-model="formData.contractSubject"
+                  active-value="Y"
+                  inactive-value="N"
+                  active-text="是"
+                  inactive-text="否" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="24">
+              <el-form-item label="备注" prop="remark">
+                <el-input
+                  v-model="formData.remark"
+                  placeholder="请输入备注"
+                  type="textarea"
+                  :rows="3" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </div>
+      </ContentWrap>
+    </el-form>
+
+    <CustomerList ref="customerZzFormRef" @choose="customerZzChoose" />
+
+    <el-dialog
+      v-model="userDialogVisible"
+      width="920px"
+      :before-close="handleUserDialogClose"
+      class="user-select-dialog">
+      <template #header>
+        <div class="dialog-title">
+          <div class="dialog-title__icon"><Icon icon="ep:user" /></div>
+          <div>
+            <div class="dialog-title__text">选择责任人</div>
+            <div class="dialog-title__subtitle">从当前公司人员中选择项目责任人</div>
+          </div>
+        </div>
+      </template>
+      <div class="transfer-container">
+        <el-transfer
+          v-model="selectedUserIds"
+          :data="userList"
+          :titles="['可选人员', '已选人员']"
+          :props="{ key: 'id', label: 'nickname' }"
+          filterable
+          class="transfer-component">
+          <template #default="{ option }">
             <el-tooltip
-              v-if="selectedUserNames"
               effect="dark"
               placement="top"
-              :content="getFullSelectedUserNames()"
-            >
-              <span class="user-names">
-                {{ selectedUserNames }}
+              :content="`${option.nickname} - ${option.deptName || '未分配部门'}`">
+              <span class="transfer-option-text">
+                {{ option.nickname }} - {{ option.deptName || '未分配部门' }}
               </span>
             </el-tooltip>
-            <el-button
-              type="primary"
-              size="small"
-              @click="openUserDialog"
-              style="margin-left: 10px"
-              :disabled="!formData.deptId"
-            >
-              选择责任人
-            </el-button>
-            <div v-if="!formData.deptId" class="el-form-item__error">请先选择所属公司</div>
-          </el-form-item>
-        </el-col>
-        <el-col :span="12">
-          <el-form-item :label="t('project.overseaFlag')" prop="overseas">
-            <el-select v-model="formData.overseas" placeholder="请选择" clearable>
-              <el-option
-                v-for="dict in getIntDictOptions(DICT_TYPE.PMS_PROJECT_OVERSEA_FLAG)"
-                :key="dict.id"
-                :label="dict.label"
-                :value="dict.value"
-              />
-            </el-select>
-          </el-form-item>
-        </el-col>
-      </el-row>
-
-      <el-row>
-        <el-col :span="12">
-          <el-form-item :label="t('project.overseaArea')" prop="overseasArea">
-            <el-select v-model="formData.overseasArea" placeholder="请选择" clearable>
-              <el-option
-                v-for="dict in getStrDictOptions(DICT_TYPE.PMS_PROJECT_AREA)"
-                :key="dict.id"
-                :label="dict.label"
-                :value="dict.value"
-              />
-            </el-select>
-          </el-form-item>
-        </el-col>
-        <el-col :span="12">
-          <el-form-item :label="t('project.workArea')" prop="location">
-            <el-autocomplete
-              ref="workAreaAutocomplete"
-              v-model="formData.location"
-              :fetch-suggestions="querySearch"
-              :trigger-on-focus="true"
-              placeholder="请输入施工区域"
-              @focus="handleWorkAreaFocus"
-              @select="handleSelect"
-              :disabled="!workAreaOptions.length"
-              popper-class="work-area-autocomplete"
-            >
-              <template #prefix>
-                <el-icon v-if="loadingWorkAreaOptions" class="is-loading">
-                  <Loading />
-                </el-icon>
-              </template>
-            </el-autocomplete>
-            <div v-if="!workAreaOptions.length" class="el-form-item__error">
-              暂无可用施工区域选项,请手动输入
-            </div>
-          </el-form-item>
-        </el-col>
-      </el-row>
-      <el-row>
-        <el-col :span="12">
-          <el-form-item label="虚拟项目" prop="contractSubject">
-            <el-switch
-              v-model="formData.contractSubject"
-              active-value="Y"
-              inactive-value="N"
-              active-text="是"
-              inactive-text="否"
-            />
-          </el-form-item>
-        </el-col>
-        <el-col :span="12">
-          <el-form-item label="备注" prop="remark">
-            <el-input v-model="formData.remark" placeholder="请输入备注" type="textarea" />
-          </el-form-item>
-        </el-col>
-      </el-row>
-    </el-form>
-  </ContentWrap>
-
-  <CustomerList ref="customerZzFormRef" @choose="customerZzChoose" />
-
-  <!-- 责任人选择对话框 -->
-  <el-dialog
-    v-model="userDialogVisible"
-    title="选择责任人"
-    width="1000px"
-    :before-close="handleUserDialogClose"
-    class="user-select-dialog"
-  >
-    <div class="transfer-container">
-      <el-transfer
-        v-model="selectedUserIds"
-        :data="userList"
-        :titles="['可选人员', '已选人员']"
-        :props="{ key: 'id', label: 'nickname' }"
-        filterable
-        class="transfer-component"
-      >
-        <template #default="{ option }">
-          <el-tooltip
-            effect="dark"
-            placement="top"
-            :content="`${option.nickname} - ${option.deptName || '未分配部门'}`"
-          >
-            <span class="transfer-option-text">
-              {{ option.nickname }} - {{ option.deptName || '未分配部门' }}
-            </span>
-          </el-tooltip>
-        </template>
-      </el-transfer>
+          </template>
+        </el-transfer>
+      </div>
+      <template #footer>
+        <el-button size="default" @click="handleUserDialogClose">取消</el-button>
+        <el-button size="default" type="primary" @click="confirmUserSelection">确定</el-button>
+      </template>
+    </el-dialog>
+
+    <div class="form-actions">
+      <el-button size="default" type="primary" :loading="formLoading" @click="submitForm">
+        <Icon icon="ep:check" class="mr-5px" />保存
+      </el-button>
+      <el-button size="default" @click="close">取消</el-button>
     </div>
-    <template #footer>
-      <span class="dialog-footer">
-        <el-button @click="handleUserDialogClose">取消</el-button>
-        <el-button type="primary" @click="confirmUserSelection">确定</el-button>
-      </span>
-    </template>
-  </el-dialog>
-
-  <ContentWrap>
-    <el-form style="float: right">
-      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
-      <el-button @click="close">取 消</el-button>
-    </el-form>
-  </ContentWrap>
+  </div>
 </template>
 <script setup lang="ts">
 import { IotProjectInfoApi, IotProjectInfoVO } from '@/api/pms/iotprojectinfo'
@@ -916,59 +941,298 @@ const handleSelect = (item: any) => {
 </script>
 
 <style scoped>
+.project-form-page {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  min-height: calc(
+    100vh - 20px - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height)
+  );
+}
+
+.page-header {
+  margin-bottom: 0;
+}
+
+.page-header :deep(.el-card__body) {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  min-height: 64px;
+  padding: 12px 16px;
+}
+
+.page-header__main,
+.dialog-title {
+  display: flex;
+  align-items: center;
+  min-width: 0;
+}
+
+.page-header__icon,
+.dialog-title__icon {
+  display: inline-flex;
+  flex: 0 0 auto;
+  align-items: center;
+  justify-content: center;
+  width: 38px;
+  height: 38px;
+  margin-right: 12px;
+  color: var(--el-color-primary);
+  background: var(--el-color-primary-light-9);
+  border: 1px solid var(--el-color-primary-light-7);
+  border-radius: 6px;
+}
+
+.page-header__title {
+  margin: 0;
+  font-size: 16px;
+  font-weight: 600;
+  line-height: 24px;
+  letter-spacing: 0;
+  color: var(--el-text-color-primary);
+}
+
+.page-header__subtitle,
+.dialog-title__subtitle {
+  margin: 2px 0 0;
+  font-size: 12px;
+  line-height: 18px;
+  color: var(--el-text-color-secondary);
+}
+
+.project-form {
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+  gap: 12px;
+}
+
+.project-form-card {
+  margin-bottom: 0;
+}
+
+.project-form-card :deep(.el-card__body) {
+  padding: 0;
+}
+
+.form-card-header {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  min-height: 64px;
+  padding: 14px 18px;
+  background: linear-gradient(90deg, #f7faff 0%, var(--el-bg-color) 78%);
+  border-bottom: 1px solid var(--el-border-color-lighter);
+}
+
+.form-card-header h3 {
+  margin: 0;
+  font-size: 15px;
+  font-weight: 700;
+  line-height: 22px;
+  color: var(--el-text-color-primary);
+}
+
+.form-card-header p {
+  margin: 2px 0 0;
+  font-size: 12px;
+  line-height: 18px;
+  color: var(--el-text-color-secondary);
+}
+
+.form-card-icon {
+  display: inline-flex;
+  flex: 0 0 auto;
+  align-items: center;
+  justify-content: center;
+  width: 36px;
+  height: 36px;
+  font-size: 18px;
+  border-radius: 9px;
+}
+
+.form-card-icon--blue {
+  color: #3478f6;
+  background: #eaf2ff;
+}
+
+.form-card-icon--green {
+  color: #20a36a;
+  background: #e7f8f0;
+}
+
+.form-card-body {
+  padding: 18px 20px 0;
+}
+
+.project-form :deep(.el-select),
+.project-form :deep(.el-tree-select),
+.project-form :deep(.el-date-editor),
+.project-form :deep(.el-autocomplete) {
+  width: 100%;
+}
+
+.project-form :deep(.el-form-item) {
+  margin-bottom: 18px;
+}
+
+.project-form :deep(.el-form-item__label) {
+  white-space: nowrap;
+}
+
+.responsible-person-control {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  width: 100%;
+  min-width: 0;
+}
+
 .user-names {
-  display: inline-block;
-  max-height: 32px;
-  min-width: 200px;
-  padding: 0 10px;
-  background-color: #f5f7fa;
-  border: 1px solid #dcdfe6;
+  display: block;
+  height: 32px;
+  min-width: 0;
+  padding: 0 11px;
+  overflow: hidden;
+  line-height: 30px;
+  color: var(--el-text-color-regular);
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  border: 1px solid var(--el-border-color);
   border-radius: 4px;
+  flex: 1;
+}
+
+.user-names--empty {
+  color: var(--el-text-color-placeholder);
+}
+
+.form-actions {
+  position: sticky;
+  bottom: 0;
+  z-index: 10;
+  display: flex;
+  flex-direction: row-reverse;
+  gap: 10px;
+  align-items: center;
+  min-height: 58px;
+  padding: 10px 16px;
+  background: var(--el-bg-color);
+  border-top: 1px solid var(--el-border-color-lighter);
+  box-shadow: 0 -4px 12px rgb(0 0 0 / 4%);
+}
+
+.form-actions :deep(.el-button) {
+  margin-left: 0;
+}
+
+.dialog-title__text {
+  font-size: 16px;
+  font-weight: 600;
+  line-height: 24px;
+  color: var(--el-text-color-primary);
+}
+
+.user-select-dialog :deep(.el-dialog__header) {
+  padding: 16px 20px;
+  margin-right: 0;
+  border-bottom: 1px solid var(--el-border-color-lighter);
+}
+
+.user-select-dialog :deep(.el-dialog__body) {
+  padding: 18px 20px;
+}
+
+.user-select-dialog :deep(.el-dialog__footer) {
+  padding: 12px 20px;
+  border-top: 1px solid var(--el-border-color-lighter);
 }
 
 .transfer-container {
-  padding: 0;
-  text-align: center;
+  width: 100%;
 }
 
-/* 3. 直接调整 el-transfer 左右窗口的宽度 */
 :deep(.el-transfer-panel) {
-  width: 40% !important; /* 强制设置左右面板宽度,确保两侧面板对等 */
+  flex: 1;
+  width: auto;
 }
 
 .transfer-component {
+  display: flex;
+  align-items: center;
   width: 100%;
-  min-width: 600px;
+  min-width: 0;
+}
+
+.transfer-component :deep(.el-transfer__buttons) {
+  flex: 0 0 auto;
+  padding: 0 16px;
 }
 
 :deep(.el-transfer-panel__item) {
-  display: flex !important;
-  height: 32px !important;
-  padding: 0 8px !important;
-  margin: 0 !important;
+  display: flex;
+  align-items: center;
+  height: 32px;
+  padding: 0 8px;
+  margin: 0;
   overflow: hidden;
-  line-height: 32px !important;
+  line-height: 32px;
   text-overflow: ellipsis;
   white-space: nowrap;
-  align-items: center !important;
 }
 
 .transfer-option-text {
   display: inline-block;
   max-width: 100%;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
 }
 
 :deep(.el-transfer-panel__list) {
-  width: 100% !important; /* 使面板内部内容宽度占满 */
+  width: 100%;
+}
+
+:global(.work-area-autocomplete .el-autocomplete-suggestion__list) {
+  max-height: 300px;
+  overflow-y: auto;
 }
 
-/* 添加全局样式,用于自定义下拉框 */
-.work-area-autocomplete .el-autocomplete-suggestion__list {
-  max-height: 300px; /* 设置最大高度 */
-  overflow-y: auto; /* 添加垂直滚动条 */
+:global(.work-area-autocomplete .el-autocomplete-suggestion__wrap) {
+  max-height: 300px;
 }
 
-.work-area-autocomplete .el-autocomplete-suggestion__wrap {
-  max-height: 300px; /* 设置最大高度 */
+@media (width <= 768px) {
+  .page-header :deep(.el-card__body) {
+    align-items: flex-start;
+  }
+
+  .page-header__subtitle {
+    display: none;
+  }
+
+  .form-card-body {
+    padding-right: 12px;
+    padding-left: 12px;
+  }
+
+  .user-select-dialog :deep(.el-dialog) {
+    width: calc(100% - 24px) !important;
+  }
+
+  .transfer-component {
+    display: block;
+  }
+
+  .transfer-component :deep(.el-transfer-panel) {
+    width: 100%;
+  }
+
+  .transfer-component :deep(.el-transfer__buttons) {
+    display: flex;
+    justify-content: center;
+    padding: 12px 0;
+  }
 }
 </style>

+ 32 - 51
src/views/pms/iotprojectinfo/index.vue

@@ -64,23 +64,23 @@
             :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
             class="query-control query-control--date" />
         </el-form-item>
-        <el-form-item class="query-actions">
-          <el-button type="primary" @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-button
-            type="primary"
-            plain
-            @click="openForm('create', undefined)"
-            v-hasPermi="['rq:iot-project-info:create']">
-            <Icon icon="ep:plus" class="mr-5px" />新增
-          </el-button>
-          <el-button type="success" plain :loading="exportLoading" @click="handleExport">
-            <Icon icon="ep:download" class="mr-5px" />导出
-          </el-button>
-        </el-form-item>
       </div>
+      <el-form-item class="query-actions">
+        <el-button type="primary" @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-button
+          type="primary"
+          plain
+          @click="openForm('create', undefined)"
+          v-hasPermi="['rq:iot-project-info:create']">
+          <Icon icon="ep:plus" class="mr-5px" />新增
+        </el-button>
+        <el-button type="success" plain :loading="exportLoading" @click="handleExport">
+          <Icon icon="ep:download" class="mr-5px" />导出
+        </el-button>
+      </el-form-item>
     </el-form>
 
     <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg flex flex-col p-4 min-w-0 min-h-0">
@@ -899,17 +899,9 @@ watch(
 </script>
 
 <style scoped>
-@media (width <= 1500px) {
+@media (width <= 1600px) {
   .iot-project-info-query .query-row {
-    gap: 12px 18px;
-  }
-
-  .iot-project-info-query .query-control {
-    width: 210px;
-  }
-
-  .iot-project-info-query .query-control--date {
-    width: 280px;
+    grid-template-columns: repeat(3, minmax(240px, 1fr));
   }
 }
 
@@ -922,12 +914,8 @@ watch(
     );
   }
 
-  .iot-project-info-query .query-actions {
-    width: 100%;
-  }
-
-  .iot-project-info-query .query-actions :deep(.el-form-item__content) {
-    justify-content: flex-start;
+  .iot-project-info-query .query-row {
+    grid-template-columns: repeat(2, minmax(240px, 1fr));
   }
 }
 
@@ -936,15 +924,8 @@ watch(
     padding: 12px;
   }
 
-  .iot-project-info-query .query-row,
-  .iot-project-info-query .query-row :deep(.el-form-item),
-  .iot-project-info-query .query-actions {
-    width: 100%;
-  }
-
-  .iot-project-info-query .query-control,
-  .iot-project-info-query .query-control--date {
-    width: 100%;
+  .iot-project-info-query .query-row {
+    grid-template-columns: minmax(0, 1fr);
   }
 
   .iot-project-info-query .query-actions :deep(.el-form-item__content) {
@@ -961,26 +942,26 @@ watch(
 }
 
 .iot-project-info-query {
-  display: block;
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
 }
 
 .query-row {
-  display: flex;
-  flex-wrap: wrap;
-  align-items: center;
-  gap: 12px 24px;
+  display: grid;
+  grid-template-columns: repeat(4, minmax(220px, 1fr)) minmax(320px, 1.4fr);
+  gap: 12px 20px;
   min-width: 0;
 }
 
 .query-actions {
-  margin-left: auto;
-  flex: 0 0 auto;
+  width: 100%;
 }
 
 .query-actions :deep(.el-form-item__content) {
   display: flex;
   flex-wrap: wrap;
-  justify-content: flex-end;
+  justify-content: flex-start;
   gap: 8px 10px;
 }
 
@@ -989,11 +970,11 @@ watch(
 }
 
 .query-control {
-  width: 210px;
+  width: 100%;
 }
 
 .query-control--date {
-  width: 300px;
+  width: 100%;
 }
 
 :deep(.el-form-item) {

File diff suppressed because it is too large
+ 776 - 707
src/views/pms/iotprojecttask/IotProjectTaskForm.vue


+ 249 - 68
src/views/pms/iotprojecttask/index.vue

@@ -531,21 +531,45 @@ onMounted(async () => {
 
   <el-dialog
     v-model="planDialogVisible"
-    :title="`${currentRow?.contractName || ''} - ${currentRow?.wellName || ''} - 任务计划`"
-    width="80%">
-    <div class="mb-15px">
-      <el-button type="primary" @click="addNewRow">
-        <Icon icon="ep:plus" class="mr-5px" />新增行
-      </el-button>
-    </div>
+    width="min(1440px, calc(100vw - 40px))"
+    class="plan-dialog">
+    <template #header>
+      <div class="plan-dialog-title">
+        <span class="plan-dialog-title__icon">
+          <Icon icon="ep:calendar" />
+        </span>
+        <div class="plan-dialog-title__content">
+          <h3>任务计划</h3>
+          <p>
+            {{ currentRow?.contractName || '未关联合同' }}
+            <span class="plan-dialog-title__separator">·</span>
+            {{ currentRow?.wellName || '未填写井号' }}
+          </p>
+        </div>
+      </div>
+    </template>
 
-    <el-table :data="planList" border stripe>
-      <el-table-column label="序号" width="60" align="center">
-        <template #default="scope">
-          {{ scope.$index + 1 }}
-        </template>
-      </el-table-column>
-      <!-- <el-table-column label="施工状态" min-width="200">
+    <div class="plan-dialog-content">
+      <div class="plan-toolbar">
+        <div>
+          <div class="plan-toolbar__title">计划明细</div>
+          <div class="plan-toolbar__subtitle">维护任务开始时间、结束时间及工作量</div>
+        </div>
+        <div class="plan-toolbar__actions">
+          <span class="plan-count">共 {{ planList.length }} 条</span>
+          <el-button size="default" type="primary" @click="addNewRow">
+            <Icon icon="ep:plus" class="mr-5px" />新增计划
+          </el-button>
+        </div>
+      </div>
+
+      <el-table :data="planList" border stripe max-height="50vh" class="plan-table">
+        <el-table-column label="序号" width="60" align="center">
+          <template #default="scope">
+            {{ scope.$index + 1 }}
+          </template>
+        </el-table-column>
+        <!-- <el-table-column label="施工状态" min-width="200">
         <template #default="scope">
           <el-select
             v-model="scope.row.status"
@@ -561,63 +585,70 @@ onMounted(async () => {
           </el-select>
         </template>
       </el-table-column> -->
-      <el-table-column label="开始时间" min-width="200">
-        <template #default="scope">
-          <el-date-picker
-            v-model="scope.row.startTime"
-            type="datetime"
-            placeholder="选择开始时间"
-            value-format="YYYY-MM-DD HH:mm"
-            format="YYYY-MM-DD HH:mm"
-            class="w-full" />
-        </template>
-      </el-table-column>
-      <el-table-column label="结束时间" min-width="200">
-        <template #default="scope">
-          <el-date-picker
-            v-if="rowShowEndTime(scope.row)"
-            v-model="scope.row.endTime"
-            type="datetime"
-            placeholder="选择结束时间"
-            value-format="YYYY-MM-DD HH:mm"
-            format="YYYY-MM-DD HH:mm"
-            class="w-full" />
-        </template>
-      </el-table-column>
-
-      <el-table-column
-        min-width="100"
-        v-for="item in currentTableColumns"
-        :key="item.identifier"
-        :prop="item.identifier">
-        <template #header>
-          <span>
-            <span v-if="isRequiredColumn(item)" class="required-mark">*</span>
-            {{ item.unit ? `${item.name}(${item.unit})` : item.name }}
-          </span>
-        </template>
-        <template #default="scope">
-          <!-- <el-input type="number" v-model="scope.row[item.identifier]" placeholder="请输入" /> -->
-          <el-input-number
-            style="width: 150px"
-            v-model="scope.row[item.identifier]"
-            :min="0"
-            placeholder="请输入" />
-        </template>
-      </el-table-column>
+        <el-table-column label="开始时间" min-width="200">
+          <template #default="scope">
+            <el-date-picker
+              v-model="scope.row.startTime"
+              type="datetime"
+              placeholder="选择开始时间"
+              value-format="YYYY-MM-DD HH:mm"
+              format="YYYY-MM-DD HH:mm"
+              class="w-full" />
+          </template>
+        </el-table-column>
+        <el-table-column label="结束时间" min-width="200">
+          <template #default="scope">
+            <el-date-picker
+              v-if="rowShowEndTime(scope.row)"
+              v-model="scope.row.endTime"
+              type="datetime"
+              placeholder="选择结束时间"
+              value-format="YYYY-MM-DD HH:mm"
+              format="YYYY-MM-DD HH:mm"
+              class="w-full" />
+          </template>
+        </el-table-column>
+
+        <el-table-column
+          min-width="100"
+          v-for="item in currentTableColumns"
+          :key="item.identifier"
+          :prop="item.identifier">
+          <template #header>
+            <span>
+              <span v-if="isRequiredColumn(item)" class="required-mark">*</span>
+              {{ item.unit ? `${item.name}(${item.unit})` : item.name }}
+            </span>
+          </template>
+          <template #default="scope">
+            <!-- <el-input type="number" v-model="scope.row[item.identifier]" placeholder="请输入" /> -->
+            <el-input-number
+              v-model="scope.row[item.identifier]"
+              size="default"
+              :min="0"
+              class="plan-number-input"
+              placeholder="请输入" />
+          </template>
+        </el-table-column>
 
-      <el-table-column label="操作" width="100" align="center">
-        <template #default="scope">
-          <el-button link type="danger" @click="removeRow(scope.$index)">删除</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
+        <el-table-column label="操作" width="100" align="center">
+          <template #default="scope">
+            <el-button link type="danger" @click="removeRow(scope.$index)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
 
     <template #footer>
-      <span class="dialog-footer">
-        <el-button @click="planDialogVisible = false">取消</el-button>
-        <el-button type="primary" :loading="saveLoading" @click="savePlan">保存</el-button>
-      </span>
+      <div class="plan-dialog-footer">
+        <span>请确认各计划时间和工作量填写完整</span>
+        <div>
+          <el-button size="default" @click="planDialogVisible = false">取消</el-button>
+          <el-button size="default" type="primary" :loading="saveLoading" @click="savePlan">
+            <Icon icon="ep:check" class="mr-5px" />保存
+          </el-button>
+        </div>
+      </div>
     </template>
   </el-dialog>
 
@@ -709,6 +740,138 @@ onMounted(async () => {
   margin-bottom: 0;
 }
 
+.plan-dialog-title {
+  display: flex;
+  align-items: center;
+  min-width: 0;
+  padding-right: 40px;
+}
+
+.plan-dialog-title__icon {
+  display: inline-flex;
+  flex: 0 0 auto;
+  align-items: center;
+  justify-content: center;
+  width: 40px;
+  height: 40px;
+  margin-right: 12px;
+  color: #3478f6;
+  background: #eaf2ff;
+  border-radius: 8px;
+}
+
+.plan-dialog-title__content {
+  min-width: 0;
+}
+
+.plan-dialog-title h3 {
+  margin: 0;
+  font-size: 16px;
+  font-weight: 600;
+  line-height: 24px;
+  color: var(--el-text-color-primary);
+}
+
+.plan-dialog-title p {
+  margin: 2px 0 0;
+  overflow: hidden;
+  font-size: 12px;
+  line-height: 18px;
+  color: var(--el-text-color-secondary);
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.plan-dialog-title__separator {
+  margin: 0 6px;
+  color: var(--el-text-color-placeholder);
+}
+
+.plan-dialog :deep(.el-dialog__header) {
+  padding: 16px 20px;
+  margin-right: 0;
+  border-bottom: 1px solid var(--el-border-color-lighter);
+}
+
+.plan-dialog :deep(.el-dialog__body) {
+  padding: 16px 20px;
+}
+
+.plan-dialog :deep(.el-dialog__footer) {
+  padding: 12px 20px;
+  border-top: 1px solid var(--el-border-color-lighter);
+}
+
+.plan-dialog-content {
+  overflow: hidden;
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 6px;
+}
+
+.plan-toolbar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  min-height: 60px;
+  padding: 10px 14px;
+  background: var(--el-fill-color-extra-light);
+  border-bottom: 1px solid var(--el-border-color-lighter);
+}
+
+.plan-toolbar__title {
+  font-size: 14px;
+  font-weight: 600;
+  line-height: 22px;
+  color: var(--el-text-color-primary);
+}
+
+.plan-toolbar__subtitle {
+  margin-top: 1px;
+  font-size: 12px;
+  line-height: 18px;
+  color: var(--el-text-color-secondary);
+}
+
+.plan-toolbar__actions {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.plan-count {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  white-space: nowrap;
+}
+
+.plan-table {
+  width: 100%;
+}
+
+.plan-table :deep(.el-table__cell) {
+  padding: 8px 0;
+}
+
+.plan-number-input {
+  width: 100%;
+}
+
+.plan-dialog-footer {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 16px;
+}
+
+.plan-dialog-footer > span {
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+}
+
+.plan-dialog-footer :deep(.el-button) {
+  margin-left: 8px;
+}
+
 .required-mark {
   margin-right: 2px;
   color: var(--el-color-danger);
@@ -779,6 +942,24 @@ onMounted(async () => {
     width: 100%;
   }
 
+  .plan-dialog .plan-dialog-title p,
+  .plan-dialog .plan-toolbar__subtitle,
+  .plan-dialog .plan-dialog-footer > span {
+    display: none;
+  }
+
+  .plan-dialog .plan-toolbar {
+    align-items: flex-start;
+  }
+
+  .plan-dialog .plan-toolbar__actions {
+    gap: 8px;
+  }
+
+  .plan-dialog .plan-dialog-footer {
+    justify-content: flex-end;
+  }
+
   .query-row {
     grid-template-columns: minmax(0, 1fr);
   }

Some files were not shown because too many files changed in this diff