Forráskód Böngészése

feat(workStation): 新增年检通知、船员培训通知和新能源政策管理功能

- 新增年检通知管理页面和相关API接口
- 新增船员培训通知管理页面和相关API接口
- 新增新能源政策管理页面和相关API接口
- 优化侧边菜单和路由配置,增加新功能入口
- 修复基础URL配置,移除末尾斜线
wzg 8 hónapja
szülő
commit
06e1713cc2

+ 1 - 1
.env.release

@@ -1,2 +1,2 @@
 VITE_PROJECT_ENV = 'release'
-VITE_BASEURL = 'https://interface.huihenduo.cc/hhd-shipping/'
+VITE_BASEURL = 'https://interface.huihenduo.cc/hhd-shipping'

+ 110 - 0
src/apis/fetch.js

@@ -294,4 +294,114 @@ export default {
   getLegalAidRequestList(data) {
     return $http("/legal/aid/request/list", data);
   },
+
+  // 新能源船舶政策解读-添加政策
+  addNewEnergyPolicy(data) {
+    return $http("/new/energy/policy/add", data);
+  },
+
+  // 新能源船舶政策解读-政策列表
+  getNewEnergyPolicyList(data) {
+    return $http("/new/energy/policy/list", data);
+  },
+
+  // 新能源船舶政策解读-修改政策
+  updateNewEnergyPolicy(data) {
+    return $http("/new/energy/policy/update", data);
+  },
+
+  // 新能源船舶政策解读-删除政策
+  deleteNewEnergyPolicy(data) {
+    return $http("/new/energy/policy/delete", data);
+  },
+
+  // 年检-添加通知
+  addAnnualInspectionNotice(data) {
+    return $http("/annual/inspection/notice/add", data);
+  },
+
+  // 年检-通知列表
+  getAnnualInspectionNoticeList(data) {
+    return $http("/annual/inspection/notice/list", data);
+  },
+
+  // 年检-修改通知
+  updateAnnualInspectionNotice(data) {
+    return $http("/annual/inspection/notice/update", data);
+  },
+
+  // 年检-删除通知
+  deleteAnnualInspectionNotice(data) {
+    return $http("/annual/inspection/notice/delete", data);
+  },
+
+  // 学校-学校信息
+  getCrewSchoolInfo(data) {
+    return $http("/crew/school/info", data);
+  },
+
+  // 学校-修改学校
+  updateCrewSchool(data) {
+    return $http("/crew/school/update", data);
+  },
+
+  // 学校-添加培训公告
+  addCrewSchoolTrainingNotice(data) {
+    return $http("/crew/school/training/notice/add", data);
+  },
+
+  // 学校-培训公告列表
+  getCrewSchoolTrainingNoticeList(data) {
+    return $http("/crew/school/training/notice/list", data);
+  },
+
+  // 学校-修改培训公告
+  updateCrewSchoolTrainingNotice(data) {
+    return $http("/crew/school/training/notice/update", data);
+  },
+
+  // 学校-删除培训公告
+  deleteCrewSchoolTrainingNotice(data) {
+    return $http("/crew/school/training/notice/delete", data);
+  },
+
+  // 船厂-添加船厂
+  addShipyard(data) {
+    return $http("/shipyard/add", data);
+  },
+
+  // 船厂-船厂列表
+  getShipyardList(data) {
+    return $http("/shipyard/list", data);
+  },
+
+  // 船厂-修改船厂
+  updateShipyard(data) {
+    return $http("/shipyard/update", data);
+  },
+
+  // 船厂-删除船厂
+  deleteShipyard(data) {
+    return $http("/shipyard/delete", data);
+  },
+
+  // 码头服务-添加人员
+  addTerminalServiceStaff(data) {
+    return $http("/terminal/service/staff/add", data);
+  },
+
+  // 码头服务-人员列表
+  getTerminalServiceStaffList(data) {
+    return $http("/terminal/service/staff/list", data);
+  },
+
+  // 码头服务-修改人员
+  updateTerminalServiceStaff(data) {
+    return $http("/terminal/service/staff/update", data);
+  },
+
+  // 码头服务-删除人员
+  deleteTerminalServiceStaff(data) {
+    return $http("/terminal/service/staff/delete", data);
+  },
 };

+ 20 - 0
src/layout/Aside.vue

@@ -140,6 +140,26 @@ if (store.state.shippingCompany === "汇很多船务公司") {
     path: "/workStation/legalAidManage",
     name: "法律援助管理",
   });
+  menu.value.at(-1).items.push({
+    path: "/workStation/annualInspectionNoticeManage",
+    name: "年检通知管理",
+  });
+  menu.value.at(-1).items.push({
+    path: "/workStation/crewSchoolTrainingNoticeManage",
+    name: "船员培训通知管理",
+  });
+  menu.value.at(-1).items.push({
+    path: "/workStation/newEnergyPolicyManage",
+    name: "新能政策管理",
+  });
+  menu.value.at(-1).items.push({
+    path: "/workStation/shipyardManage",
+    name: "船舶坞修管理",
+  });
+  menu.value.at(-1).items.push({
+    path: "/workStation/terminalServiceStaffManage",
+    name: "码头服务人员管理",
+  });
 }
 </script>
 <style scoped>

+ 51 - 0
src/router/index.js

@@ -222,6 +222,57 @@ const router = createRouter({
       component: () =>
         import("../views/fireSafetyManage/fireSafetyHistoryCheckList.vue"),
     },
+    {
+      path: "/workStation/newEnergyPolicyManage",
+      name: "newEnergyPolicyManage",
+      meta: {
+        title: "新能源船舶政策解读",
+      },
+      component: () => import("../views/workStation/newEnergyPolicyManage.vue"),
+    },
+    {
+      path: "/workStation/annualInspectionNoticeManage",
+      name: "annualInspectionNoticeManage",
+      meta: {
+        title: "年检通知管理",
+      },
+      component: () =>
+        import("../views/workStation/annualInspectionNoticeManage.vue"),
+    },
+    {
+      path: "/workStation/crewSchoolTrainingNoticeManage",
+      name: "crewSchoolTrainingNoticeManage",
+      meta: {
+        title: "学校培训公告管理",
+      },
+      component: () =>
+        import("../views/workStation/crewSchoolTrainingNoticeManage.vue"),
+    },
+    {
+      path: "/workStation/shipyardManage",
+      name: "shipyardManage",
+      meta: {
+        title: "船厂管理",
+      },
+      component: () => import("../views/workStation/shipyardManage.vue"),
+    },
+    {
+      path: "/workStation/terminalServiceStaffManage",
+      name: "terminalServiceStaffManage",
+      meta: {
+        title: "码头服务人员管理",
+      },
+      component: () =>
+        import("../views/workStation/terminalServiceStaffManage.vue"),
+    },
+    {
+      path: "/workStation/crewSchoolInfoManage",
+      name: "crewSchoolInfoManage",
+      meta: {
+        title: "学校信息管理",
+      },
+      component: () => import("../views/workStation/crewSchoolInfoManage.vue"),
+    },
   ],
 });
 

+ 2 - 0
src/store/index.js

@@ -2,6 +2,7 @@ import { createStore } from "vuex";
 
 console.log(import.meta.env.VITE_PROJECT_ENV);
 let baseurl = import.meta.env.VITE_BASEURL;
+const uploadUrl = `${baseurl}/cos/uploadTempFile`;
 const shipOwnerUpdateUrl = `${baseurl}/cos/uploadShipOwnerDocUpdate`;
 const shipOwnerCacheUrl = `${baseurl}/cos/uploadShipOwnerDocNew`;
 const shipCertUpdateUrl = `${baseurl}/cos/uploadShipCertUpdate`;
@@ -14,6 +15,7 @@ const store = createStore({
     secondTitle: "",
     currentMenuItem: "/voyage/voyageList",
     baseurl,
+    uploadUrl,
     shipOwnerUpdateUrl,
     shipOwnerCacheUrl,
     shipCertUpdateUrl,

+ 329 - 0
src/views/workStation/annualInspectionNoticeManage.vue

@@ -0,0 +1,329 @@
+<template>
+  <div class="full-container-p24">
+    <div class="mb30 df aic jcsb">
+      <div class="df aic">
+        <el-input
+          v-model="searchKey"
+          placeholder="请输入搜索关键词"
+          class="w300"
+          @keyup.enter="handleSearch"
+          clearable
+          @clear="handleSearch"
+        />
+        <el-button class="ml10" type="primary" @click="handleSearch">
+          搜索
+        </el-button>
+      </div>
+      <el-button class="ml10" type="primary" @click="handleAdd">
+        新增通知
+      </el-button>
+    </div>
+
+    <el-table border :data="tableData" stripe>
+      <el-table-column type="index" label="序号" width="80" align="center" />
+      <el-table-column
+        prop="noticeTitle"
+        label="通知主题"
+        min-width="180"
+        align="center"
+      />
+      <el-table-column
+        prop="noticeContent"
+        label="通知内容"
+        min-width="250"
+        align="center"
+        :show-overflow-tooltip="true"
+      />
+      <el-table-column
+        prop="contactName"
+        label="联系人姓名"
+        min-width="120"
+        align="center"
+      />
+      <el-table-column
+        prop="contactPhone"
+        label="联系人手机号"
+        min-width="150"
+        align="center"
+      />
+      <el-table-column
+        prop="postedBy"
+        label="发布人"
+        min-width="120"
+        align="center"
+      />
+      <el-table-column
+        prop="createTime"
+        label="发布日期"
+        min-width="150"
+        align="center"
+      >
+        <template v-slot="scope">
+          {{ subTimeStr(scope.row.createTime) }}
+        </template>
+      </el-table-column>
+
+      <el-table-column label="操作" width="180" align="center">
+        <template #default="scope">
+          <el-button type="primary" text @click="handleEdit(scope.row)">
+            修改
+          </el-button>
+          <el-button type="danger" text @click="deleteNotice(scope.row)">
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div class="df aic jcfe mt40 mr20">
+      <el-pagination
+        background
+        layout="prev, pager, next"
+        :current-page="currentPage"
+        :total="total"
+        @current-change="pageChange"
+      />
+    </div>
+
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600">
+      <el-form
+        ref="formRef"
+        :model="formData"
+        :rules="rules"
+        label-width="120px"
+      >
+        <el-form-item label="通知主题" prop="noticeTitle">
+          <el-input v-model="formData.noticeTitle" class="w400" />
+        </el-form-item>
+        <el-form-item label="通知内容" prop="noticeContent">
+          <el-input
+            v-model="formData.noticeContent"
+            type="textarea"
+            :rows="6"
+            class="w400"
+          />
+        </el-form-item>
+        <el-form-item label="通知图片" prop="noticeImgFileKey">
+          <Uploader
+            v-model="formData.noticeImgFileKey"
+            :limit="1"
+            :action-url="store.state.uploadUrl"
+            :fileList="fileList"
+            @onUploadFileList="handleFileListUpdate"
+            @onRemoveFileList="handleFileListRemove"
+          />
+        </el-form-item>
+        <el-form-item label="联系人姓名" prop="contactName">
+          <el-input v-model="formData.contactName" class="w400" />
+        </el-form-item>
+        <el-form-item label="联系人手机号" prop="contactPhone">
+          <el-input v-model="formData.contactPhone" class="w400" />
+        </el-form-item>
+        <el-form-item label="发布人" prop="postedBy">
+          <el-input
+            v-model="formData.postedBy"
+            class="w400"
+            placeholder="默认:汇很多"
+          />
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitForm">确定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import api from "../../apis/fetch";
+import { subTimeStr } from "../../utils/utils";
+import Uploader from "../../components/Uploader.vue";
+import store from "../../store";
+
+const searchKey = ref("");
+const currentPage = ref(1);
+const total = ref(0);
+const tableData = ref([]);
+const fileList = ref([]);
+
+const dialogVisible = ref(false);
+const dialogTitle = ref("新增通知");
+const formRef = ref(null);
+const formData = ref({
+  noticeId: "",
+  noticeTitle: "",
+  noticeImgFileKey: "",
+  noticeContent: "",
+  contactName: "",
+  contactPhone: "",
+  postedBy: "汇很多",
+});
+
+const rules = reactive({
+  noticeTitle: [{ required: true, message: "请输入通知主题", trigger: "blur" }],
+  noticeContent: [
+    { required: true, message: "请输入通知内容", trigger: "blur" },
+  ],
+  contactName: [
+    { required: true, message: "请输入联系人姓名", trigger: "blur" },
+  ],
+  contactPhone: [
+    { required: true, message: "请输入联系人手机号", trigger: "blur" },
+    {
+      pattern: /^1[3-9]\d{9}$/,
+      message: "请输入正确的手机号",
+      trigger: "blur",
+    },
+  ],
+});
+
+onMounted(() => {
+  getNoticeList();
+});
+
+// 获取通知列表
+async function getNoticeList() {
+  try {
+    const { data } = await api.getAnnualInspectionNoticeList({
+      term: searchKey.value,
+      currentPage: currentPage.value,
+      size: 10,
+    });
+    if (data.status === 0) {
+      tableData.value = data.result;
+      total.value = data.total;
+    }
+  } catch (error) {
+    ElMessage.error("获取数据失败");
+  }
+}
+
+// 分页切换
+function pageChange(page) {
+  currentPage.value = page;
+  getNoticeList();
+}
+
+// 搜索
+function handleSearch() {
+  currentPage.value = 1;
+  getNoticeList();
+}
+
+const handleFileListRemove = () => {
+  formData.value.noticeImgFileKey = "";
+  fileList.value = [];
+};
+const handleFileListUpdate = ({ response: data }) => {
+  if (data.status === 0) {
+    formData.value.noticeImgFileKey = data.result.key;
+    fileList.value = [
+      { viewUrl: data.result.viewUrl, fileKey: data.result.key },
+    ];
+    ElMessage.success("上传成功");
+  } else {
+    ElMessage.error(data.msg || "上传失败");
+  }
+};
+
+// 新增
+function handleAdd() {
+  dialogTitle.value = "新增通知";
+  formData.value = {
+    // 直接重新赋值整个对象
+    noticeId: "",
+    noticeTitle: "",
+    noticeImgFileKey: "",
+    noticeContent: "",
+    contactName: "",
+    contactPhone: "",
+    postedBy: "汇很多",
+  };
+  fileList.value = [];
+  dialogVisible.value = true;
+}
+
+// 编辑
+function handleEdit(row) {
+  dialogTitle.value = "修改通知";
+  formData.value.noticeId = row.id;
+  formData.value.noticeTitle = row.noticeTitle;
+  formData.value.noticeContent = row.noticeContent;
+  formData.value.noticeImgFileKey = row.noticeImgFileKey || "";
+  formData.value.contactName = row.contactName;
+  formData.value.contactPhone = row.contactPhone;
+  formData.value.postedBy = row.postedBy || "汇很多";
+
+  // 设置文件列表
+  if (row.noticeImgFileKey) {
+    fileList.value = [
+      {
+        noticeImgFileKey: row.noticeImgFileKey,
+        viewUrl: row.noticeImgUrl,
+        fileKey: row.noticeImgFileKey,
+      },
+    ];
+  }
+
+  dialogVisible.value = true;
+}
+
+// 删除通知
+async function deleteNotice(row) {
+  try {
+    await ElMessageBox.confirm("确定要删除该通知吗?", "提示", {
+      confirmButtonText: "确定",
+      cancelButtonText: "取消",
+      type: "warning",
+    });
+
+    const { data } = await api.deleteAnnualInspectionNotice({
+      noticeId: row.id,
+    });
+
+    if (data.status === 0) {
+      ElMessage.success("删除成功");
+      getNoticeList();
+    } else {
+      ElMessage.error(data.message || "删除失败");
+    }
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+// 提交表单
+async function submitForm() {
+  try {
+    await formRef.value.validate();
+    const apiMethod = !formData.value.noticeId
+      ? api.addAnnualInspectionNotice
+      : api.updateAnnualInspectionNotice;
+
+    const { data } = await apiMethod(formData.value);
+    if (data.status === 0) {
+      ElMessage.success("操作成功");
+      dialogVisible.value = false;
+      getNoticeList();
+    } else {
+      ElMessage.error(data.message || "操作失败");
+    }
+  } catch (error) {
+    console.error(error);
+  }
+}
+</script>
+
+<style scoped>
+.w300 {
+  width: 300px;
+}
+
+.w400 {
+  width: 400px;
+}
+</style>

+ 298 - 0
src/views/workStation/crewSchoolInfoManage.vue

@@ -0,0 +1,298 @@
+<template>
+  <div class="full-container-p24">
+    <div class="card-container">
+      <el-card class="box-card">
+        <template #header>
+          <div class="card-header">
+            <span>学校基本信息</span>
+            <el-button type="primary" @click="handleEdit">编辑信息</el-button>
+          </div>
+        </template>
+        <div class="info-content">
+          <div class="info-item">
+            <span class="label">学校名称:</span>
+            <span>{{ schoolInfo.crewSchoolName || "暂无" }}</span>
+          </div>
+          <div class="info-item">
+            <span class="label">场地规模:</span>
+            <span>
+              {{
+                schoolInfo.crewSchoolSiteSize
+                  ? `${schoolInfo.crewSchoolSiteSize} 平方米`
+                  : "暂无"
+              }}
+            </span>
+          </div>
+          <div class="info-item">
+            <span class="label">年结业人数:</span>
+            <span>
+              {{ schoolInfo.crewSchoolAnnualGraduationNum || "暂无" }}
+            </span>
+          </div>
+          <div class="info-item">
+            <span class="label">学校介绍:</span>
+            <div class="introduce-content">
+              {{ schoolInfo.crewSchoolIntroduce || "暂无介绍" }}
+            </div>
+          </div>
+          <div class="info-item" v-if="schoolInfo.crewSchoolImgFileKey">
+            <span class="label">学校图片:</span>
+            <div class="image-container">
+              <el-image
+                :src="schoolInfo.crewSchoolImgFileKey"
+                :preview-src-list="[schoolInfo.crewSchoolImgFileKey]"
+                fit="cover"
+                class="school-image"
+              />
+            </div>
+          </div>
+        </div>
+      </el-card>
+    </div>
+
+    <el-dialog v-model="dialogVisible" title="编辑学校信息" width="600">
+      <el-form
+        ref="formRef"
+        :model="formData"
+        :rules="rules"
+        label-width="120px"
+      >
+        <el-form-item label="学校名称" prop="crewSchoolName">
+          <el-input v-model="formData.crewSchoolName" class="w400" />
+        </el-form-item>
+        <el-form-item label="场地规模" prop="crewSchoolSiteSize">
+          <el-input v-model="formData.crewSchoolSiteSize" class="w400">
+            <template #append>平方米</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label="年结业人数" prop="crewSchoolAnnualGraduationNum">
+          <el-input
+            v-model="formData.crewSchoolAnnualGraduationNum"
+            class="w400"
+          />
+        </el-form-item>
+        <el-form-item label="学校介绍" prop="crewSchoolIntroduce">
+          <el-input
+            v-model="formData.crewSchoolIntroduce"
+            type="textarea"
+            :rows="6"
+            class="w400"
+          />
+        </el-form-item>
+        <el-form-item label="学校图片" prop="crewSchoolImgFileKey">
+          <Uploader
+            v-model="formData.crewSchoolImgFileKey"
+            :limit="1"
+            :action-url="store.state.uploadUrl"
+            :fileList="fileList"
+            @onUploadFileList="handleFileListUpdate"
+            @onRemoveFileList="handleFileListRemove"
+          />
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitForm">确定</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 培训公告入口 -->
+    <div class="notice-entry mt30">
+      <el-card class="box-card">
+        <template #header>
+          <div class="card-header">
+            <span>培训公告管理</span>
+            <el-button type="primary" @click="goToTrainingNotice">
+              查看培训公告
+            </el-button>
+          </div>
+        </template>
+        <div class="notice-description">
+          在这里您可以管理学校的培训公告,包括添加、修改和删除培训公告信息。
+        </div>
+      </el-card>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage } from "element-plus";
+import { useRouter } from "vue-router";
+import api from "../../apis/fetch";
+import Uploader from "../../components/Uploader.vue";
+import store from "../../store";
+
+const router = useRouter();
+const schoolInfo = ref({});
+const dialogVisible = ref(false);
+const formRef = ref(null);
+const fileList = ref([]);
+
+const formData = reactive({
+  crewSchoolId: "",
+  crewSchoolName: "",
+  crewSchoolImgFileKey: "",
+  crewSchoolIntroduce: "",
+  crewSchoolSiteSize: "",
+  crewSchoolAnnualGraduationNum: "",
+});
+
+const rules = reactive({
+  crewSchoolName: [
+    { required: true, message: "请输入学校名称", trigger: "blur" },
+  ],
+  crewSchoolSiteSize: [
+    { required: true, message: "请输入场地规模", trigger: "blur" },
+    { pattern: /^\d+$/, message: "请输入数字", trigger: "blur" },
+  ],
+  crewSchoolAnnualGraduationNum: [
+    { required: true, message: "请输入年结业人数", trigger: "blur" },
+    { pattern: /^\d+$/, message: "请输入数字", trigger: "blur" },
+  ],
+});
+
+onMounted(() => {
+  getSchoolInfo();
+});
+
+// 获取学校信息
+async function getSchoolInfo() {
+  try {
+    const { data } = await api.getCrewSchoolInfo({});
+    if (data.status === 0) {
+      schoolInfo.value = data.result || {};
+    }
+  } catch (error) {
+    ElMessage.error("获取学校信息失败");
+    console.error(error);
+  }
+}
+
+// 处理文件列表更新
+const handleFileListUpdate = ({ response: data }) => {
+  if (data.status === 0) {
+    formData.crewSchoolImgFileKey = data.result.key;
+    fileList.value = [
+      { viewUrl: data.result.viewUrl, fileKey: data.result.key },
+    ];
+    ElMessage.success("上传成功");
+  } else {
+    ElMessage.error(data.msg || "上传失败");
+  }
+};
+
+// 处理文件列表移除
+const handleFileListRemove = () => {
+  formData.crewSchoolImgFileKey = "";
+  fileList.value = [];
+};
+
+// 编辑学校信息
+function handleEdit() {
+  formData.crewSchoolId = schoolInfo.value.id || "";
+  formData.crewSchoolName = schoolInfo.value.crewSchoolName || "";
+  formData.crewSchoolImgFileKey = schoolInfo.value.crewSchoolImgFileKey || "";
+  formData.crewSchoolIntroduce = schoolInfo.value.crewSchoolIntroduce || "";
+  formData.crewSchoolSiteSize = schoolInfo.value.crewSchoolSiteSize || "";
+  formData.crewSchoolAnnualGraduationNum =
+    schoolInfo.value.crewSchoolAnnualGraduationNum || "";
+
+  // 设置文件列表
+  fileList.value = [];
+  if (schoolInfo.value.crewSchoolImgFileKey) {
+    fileList.value = [
+      {
+        viewUrl:
+          schoolInfo.value.crewSchoolImgUrl ||
+          `/api/file/view?fileKey=${schoolInfo.value.crewSchoolImgFileKey}`,
+        fileKey: schoolInfo.value.crewSchoolImgFileKey,
+      },
+    ];
+  }
+
+  dialogVisible.value = true;
+}
+
+// 提交表单
+async function submitForm() {
+  try {
+    await formRef.value.validate();
+    const { data } = await api.updateCrewSchool(formData);
+    if (data.status === 0) {
+      ElMessage.success("更新成功");
+      dialogVisible.value = false;
+      getSchoolInfo();
+    } else {
+      ElMessage.error(data.message || "更新失败");
+    }
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+// 跳转到培训公告页面
+function goToTrainingNotice() {
+  router.push("/workStation/crewSchoolTrainingNoticeManage");
+}
+</script>
+
+<style scoped>
+.card-container {
+  margin-bottom: 20px;
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.info-content {
+  padding: 10px;
+}
+
+.info-item {
+  margin-bottom: 15px;
+  line-height: 24px;
+}
+
+.label {
+  font-weight: bold;
+  margin-right: 10px;
+  display: inline-block;
+  width: 100px;
+  vertical-align: top;
+}
+
+.introduce-content {
+  display: inline-block;
+  width: calc(100% - 120px);
+  white-space: pre-wrap;
+}
+
+.image-container {
+  margin-top: 10px;
+  display: inline-block;
+}
+
+.school-image {
+  width: 300px;
+  height: 200px;
+  border-radius: 4px;
+}
+
+.w400 {
+  width: 400px;
+}
+
+.mt30 {
+  margin-top: 30px;
+}
+
+.notice-description {
+  color: #606266;
+  padding: 10px 0;
+}
+</style>

+ 317 - 0
src/views/workStation/crewSchoolTrainingNoticeManage.vue

@@ -0,0 +1,317 @@
+<template>
+  <div class="full-container-p24">
+    <div class="mb30 df aic jcsb">
+      <div class="df aic">
+        <el-input
+          v-model="searchKey"
+          placeholder="请输入培训公告标题"
+          class="w300"
+          @keyup.enter="handleSearch"
+          clearable
+          @clear="handleSearch"
+        />
+        <el-button class="ml10" type="primary" @click="handleSearch">
+          搜索
+        </el-button>
+      </div>
+      <div>
+        <el-button class="ml10" type="primary" @click="goToSchoolInfo">
+          学校信息
+        </el-button>
+        <el-button class="ml10" type="primary" @click="handleAdd">
+          新增培训公告
+        </el-button>
+      </div>
+    </div>
+
+    <el-table border :data="tableData" stripe>
+      <el-table-column type="index" label="序号" width="80" align="center" />
+      <el-table-column
+        prop="trainingNoticeTitle"
+        label="公告标题"
+        min-width="200"
+        align="center"
+      />
+      <el-table-column
+        prop="trainingNoticeContent"
+        label="公告内容"
+        min-width="300"
+        align="center"
+        :show-overflow-tooltip="true"
+      />
+      <el-table-column
+        prop="createTime"
+        label="发布日期"
+        min-width="150"
+        align="center"
+      >
+        <template v-slot="scope">
+          {{ subTimeStr(scope.row.createTime) }}
+        </template>
+      </el-table-column>
+
+      <el-table-column label="操作" width="180" align="center">
+        <template #default="scope">
+          <el-button type="primary" text @click="handleEdit(scope.row)">
+            修改
+          </el-button>
+          <el-button type="danger" text @click="deleteNotice(scope.row)">
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div class="df aic jcfe mt40 mr20">
+      <el-pagination
+        background
+        layout="prev, pager, next"
+        :current-page="currentPage"
+        :total="total"
+        @current-change="pageChange"
+      />
+    </div>
+
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600">
+      <el-form
+        ref="formRef"
+        :model="formData"
+        :rules="rules"
+        label-width="120px"
+      >
+        <el-form-item label="公告标题" prop="trainingNoticeTitle">
+          <el-input v-model="formData.trainingNoticeTitle" class="w400" />
+        </el-form-item>
+        <el-form-item label="公告内容" prop="trainingNoticeContent">
+          <el-input
+            v-model="formData.trainingNoticeContent"
+            type="textarea"
+            :rows="6"
+            class="w400"
+          />
+        </el-form-item>
+        <el-form-item label="公告图片" prop="trainingNoticeImgFileKey">
+          <Uploader
+            v-model="formData.trainingNoticeImgFileKey"
+            :limit="1"
+            :action-url="store.state.uploadUrl"
+            :fileList="fileList"
+            @onUploadFileList="handleFileListUpdate"
+            @onRemoveFileList="handleFileListRemove"
+          />
+        </el-form-item>
+        <el-form-item label="发布人" prop="postedBy">
+          <el-input
+            v-model="formData.postedBy"
+            class="w400"
+            placeholder="默认:汇很多"
+          />
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitForm">确定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import { useRouter } from "vue-router";
+import api from "../../apis/fetch";
+import { subTimeStr } from "../../utils/utils";
+import Uploader from "../../components/Uploader.vue";
+import store from "../../store";
+
+const router = useRouter();
+
+const searchKey = ref("");
+const currentPage = ref(1);
+const total = ref(0);
+const tableData = ref([]);
+const fileList = ref([]);
+
+const dialogVisible = ref(false);
+const dialogTitle = ref("新增培训公告");
+const formRef = ref(null);
+const formData = reactive({
+  trainingNoticeId: "",
+  crewSchoolId: "",
+  trainingNoticeTitle: "",
+  trainingNoticeContent: "",
+  trainingNoticeImgFileKey: "",
+  postedBy: "汇很多",
+});
+
+const rules = reactive({
+  trainingNoticeTitle: [
+    { required: true, message: "请输入公告标题", trigger: "blur" },
+  ],
+  trainingNoticeContent: [
+    { required: true, message: "请输入公告内容", trigger: "blur" },
+  ],
+  postedBy: [{ required: true, message: "请输入发布人", trigger: "blur" }],
+});
+
+// 获取培训公告列表
+async function getNoticeList() {
+  try {
+    const { data } = await api.getCrewSchoolTrainingNoticeList({
+      term: searchKey.value,
+      currentPage: currentPage.value,
+      size: 10,
+      crewSchoolId: 1,
+    });
+    if (data.status === 0) {
+      tableData.value = data.result;
+      total.value = data.total;
+    } else {
+      tableData.value = [];
+      total.value = 0;
+      ElMessage.error(data.msg);
+    }
+  } catch (error) {
+    ElMessage.error("获取数据失败");
+  }
+}
+
+// 处理文件列表更新
+const handleFileListUpdate = ({ response: data }) => {
+  if (data.status === 0) {
+    formData.trainingNoticeImgFileKey = data.result.key;
+    fileList.value = [
+      { viewUrl: data.result.viewUrl, fileKey: data.result.key },
+    ];
+    ElMessage.success("上传成功");
+  } else {
+    ElMessage.error(data.msg || "上传失败");
+  }
+};
+
+// 处理文件列表移除
+const handleFileListRemove = () => {
+  formData.trainingNoticeImgFileKey = "";
+  fileList.value = [];
+};
+
+// 跳转到学校信息页面
+function goToSchoolInfo() {
+  router.push("/workStation/crewSchoolInfoManage");
+}
+
+// 分页切换
+function pageChange(page) {
+  currentPage.value = page;
+  getNoticeList();
+}
+
+// 搜索
+function handleSearch() {
+  currentPage.value = 1;
+  getNoticeList();
+}
+
+// 新增
+function handleAdd() {
+  dialogTitle.value = "新增培训公告";
+  Object.keys(formData).forEach((key) => {
+    if (key !== "postedBy") {
+      formData[key] = "";
+    }
+    if (key === "crewSchoolId") {
+      formData[key] = 1;
+    }
+  });
+  formData.postedBy = "汇很多";
+  fileList.value = [];
+  dialogVisible.value = true;
+}
+
+// 编辑
+function handleEdit(row) {
+  dialogTitle.value = "修改培训公告";
+  formData.trainingNoticeId = row.id;
+  formData.trainingNoticeTitle = row.trainingNoticeTitle;
+  formData.trainingNoticeContent = row.trainingNoticeContent;
+  formData.trainingNoticeImgFileKey = row.trainingNoticeImgFileKey || "";
+  formData.postedBy = row.postedBy || "汇很多";
+
+  // 设置文件列表
+  fileList.value = [];
+  if (row.trainingNoticeImgFileKey) {
+    fileList.value = [
+      {
+        viewUrl:
+          row.trainingNoticeImgUrl ||
+          `/api/file/view?fileKey=${row.trainingNoticeImgFileKey}`,
+        fileKey: row.trainingNoticeImgFileKey,
+      },
+    ];
+  }
+
+  dialogVisible.value = true;
+}
+
+// 提交表单
+async function submitForm() {
+  try {
+    await formRef.value.validate();
+    const apiMethod = !formData.trainingNoticeId
+      ? api.addCrewSchoolTrainingNotice
+      : api.updateCrewSchoolTrainingNotice;
+
+    const { data } = await apiMethod(formData);
+    if (data.status === 0) {
+      ElMessage.success("操作成功");
+      dialogVisible.value = false;
+      getNoticeList();
+    } else {
+      ElMessage.error(data.message || "操作失败");
+    }
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+// 删除
+function deleteNotice(row) {
+  ElMessageBox.confirm("确认删除该培训公告?", "警告", {
+    confirmButtonText: "确认",
+    cancelButtonText: "取消",
+    type: "warning",
+  }).then(async () => {
+    try {
+      const { data } = await api.deleteCrewSchoolTrainingNotice({
+        trainingNoticeId: row.id,
+      });
+      if (data.status === 0) {
+        ElMessage.success("删除成功");
+        getNoticeList();
+      } else {
+        ElMessage.error(data.message || "删除失败");
+      }
+    } catch (error) {
+      ElMessage.error("删除失败");
+    }
+  });
+}
+
+onMounted(() => {
+  getNoticeList();
+});
+</script>
+
+<style scoped>
+.w300 {
+  width: 300px;
+}
+.w400 {
+  width: 400px;
+}
+.ml10 {
+  margin-left: 10px;
+}
+</style>

+ 279 - 0
src/views/workStation/newEnergyPolicyManage.vue

@@ -0,0 +1,279 @@
+<template>
+  <div class="full-container-p24">
+    <div class="mb30 df aic jcsb">
+      <div class="df aic">
+        <el-select
+          v-model="policyType"
+          placeholder="政策类型"
+          class="w150"
+          @change="handleSearch"
+        >
+          <el-option :value="1" label="拆船"></el-option>
+          <el-option :value="2" label="造船"></el-option>
+        </el-select>
+        <el-button class="ml10" type="primary" @click="handleSearch">
+          搜索
+        </el-button>
+      </div>
+      <el-button class="ml10" type="primary" @click="handleAdd">
+        新增政策
+      </el-button>
+    </div>
+
+    <el-table border :data="tableData" stripe>
+      <el-table-column type="index" label="序号" width="80" align="center" />
+      <el-table-column
+        prop="shipTons"
+        label="吨位"
+        min-width="120"
+        align="center"
+      />
+      <el-table-column
+        prop="policyType"
+        label="政策类型"
+        min-width="120"
+        align="center"
+      >
+        <template v-slot="scope">
+          {{ scope.row.policyType === 1 ? "拆船" : "造船" }}
+        </template>
+      </el-table-column>
+      <el-table-column
+        prop="newEnergyType"
+        label="新能源类型"
+        min-width="120"
+        align="center"
+      >
+        <template v-slot="scope">
+          {{ scope.row.policyType === 2 ? scope.row.newEnergyType : "-" }}
+        </template>
+      </el-table-column>
+      <el-table-column
+        prop="subsidyAmount"
+        label="补贴额度(万元/艘)"
+        min-width="150"
+        align="center"
+      />
+
+      <el-table-column label="操作" width="180" align="center">
+        <template #default="scope">
+          <el-button type="primary" text @click="handleEdit(scope.row)">
+            修改
+          </el-button>
+          <el-button type="danger" text @click="deletePolicy(scope.row)">
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div class="df aic jcfe mt40 mr20">
+      <el-pagination
+        background
+        layout="prev, pager, next"
+        :current-page="currentPage"
+        :total="total"
+        @current-change="pageChange"
+      />
+    </div>
+
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600">
+      <el-form
+        ref="formRef"
+        :model="formData"
+        :rules="rules"
+        label-width="150px"
+      >
+        <el-form-item label="吨位" prop="shipTons">
+          <el-input v-model="formData.shipTons" class="w300" />
+        </el-form-item>
+        <el-form-item label="政策类型" prop="policyType">
+          <el-select
+            v-model="formData.policyType"
+            placeholder="请选择政策类型"
+            class="w300"
+          >
+            <el-option :value="1" label="拆船"></el-option>
+            <el-option :value="2" label="造船"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item
+          label="新能源类型"
+          prop="newEnergyType"
+          v-if="formData.policyType === 2"
+        >
+          <el-input v-model="formData.newEnergyType" class="w300" />
+        </el-form-item>
+        <el-form-item label="补贴额度(万元/艘)" prop="subsidyAmount">
+          <el-input v-model="formData.subsidyAmount" class="w300" />
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitForm">确定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import api from "../../apis/fetch";
+
+const policyType = ref(1); // 默认拆船政策
+const currentPage = ref(1);
+const total = ref(0);
+const tableData = ref([]);
+
+const dialogVisible = ref(false);
+const dialogTitle = ref("新增政策");
+const formRef = ref(null);
+const formData = reactive({
+  policyId: 0,
+  shipTons: "",
+  policyType: 1,
+  newEnergyType: "",
+  subsidyAmount: "",
+});
+
+const rules = reactive({
+  shipTons: [{ required: true, message: "请输入吨位", trigger: "blur" }],
+  policyType: [
+    { required: true, message: "请选择政策类型", trigger: "change" },
+  ],
+  newEnergyType: [
+    { required: true, message: "请输入新能源类型", trigger: "blur" },
+  ],
+  subsidyAmount: [
+    { required: true, message: "请输入补贴额度", trigger: "blur" },
+  ],
+});
+
+// 获取政策列表
+async function getPolicyList() {
+  try {
+    const { data } = await api.getNewEnergyPolicyList({
+      loginAccountId: localStorage.getItem("loginAccountId"),
+      policyType: policyType.value,
+      currentPage: currentPage.value,
+      size: 10,
+    });
+    if (data.status === 0) {
+      tableData.value = data.result;
+      total.value = data.total;
+    } else {
+      tableData.value = [];
+      total.value = 0;
+    }
+  } catch (error) {
+    ElMessage.error("获取数据失败");
+  }
+}
+
+// 分页切换
+function pageChange(page) {
+  currentPage.value = page;
+  getPolicyList();
+}
+
+// 搜索
+function handleSearch() {
+  currentPage.value = 1;
+  getPolicyList();
+}
+
+// 新增
+function handleAdd() {
+  dialogTitle.value = "新增政策";
+  Object.keys(formData).forEach((key) => {
+    if (key !== "policyId" && key !== "policyType") formData[key] = "";
+  });
+  formData.policyId = 0;
+  formData.policyType = 1;
+  dialogVisible.value = true;
+}
+
+// 编辑
+function handleEdit(row) {
+  dialogTitle.value = "修改政策";
+  Object.keys(formData).forEach((key) => {
+    formData[key] = row[key];
+  });
+  formData.policyId = row.id;
+  dialogVisible.value = true;
+}
+
+// 提交表单
+async function submitForm() {
+  try {
+    await formRef.value.validate();
+    const params = {
+      loginAccountId: localStorage.getItem("loginAccountId"),
+      shipTons: formData.shipTons,
+      policyType: formData.policyType,
+      subsidyAmount: formData.subsidyAmount,
+    };
+
+    if (formData.policyType === 2) {
+      params.newEnergyType = formData.newEnergyType;
+    }
+
+    if (formData.policyId !== 0) {
+      params.policyId = formData.policyId;
+    }
+
+    const apiMethod =
+      formData.policyId === 0
+        ? api.addNewEnergyPolicy
+        : api.updateNewEnergyPolicy;
+    const { data } = await apiMethod(params);
+
+    if (data.status === 0) {
+      ElMessage.success("操作成功");
+      dialogVisible.value = false;
+      getPolicyList();
+    }
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+// 删除
+function deletePolicy(row) {
+  ElMessageBox.confirm("确认删除该政策信息?", "警告", {
+    confirmButtonText: "确认",
+    cancelButtonText: "取消",
+    type: "warning",
+  }).then(async () => {
+    try {
+      const { data } = await api.deleteNewEnergyPolicy({
+        loginAccountId: localStorage.getItem("loginAccountId"),
+        policyId: row.id,
+      });
+      if (data.status === 0) {
+        ElMessage.success("删除成功");
+        getPolicyList();
+      }
+    } catch (error) {
+      ElMessage.error("删除失败");
+    }
+  });
+}
+
+// 初始化获取数据
+getPolicyList();
+</script>
+
+<style scoped>
+.w150 {
+  width: 150px;
+}
+.w300 {
+  width: 300px;
+}
+.ml10 {
+  margin-left: 10px;
+}
+</style>

+ 355 - 0
src/views/workStation/shipyardManage.vue

@@ -0,0 +1,355 @@
+<template>
+  <div class="full-container-p24">
+    <div class="mb30 df aic jcsb">
+      <div class="df aic">
+        <el-input
+          v-model="searchKey"
+          placeholder="请输入船厂名称"
+          class="w300"
+          @keyup.enter="handleSearch"
+          clearable
+          @clear="handleSearch"
+        />
+        <el-button class="ml10" type="primary" @click="handleSearch">
+          搜索
+        </el-button>
+      </div>
+      <el-button class="ml10" type="primary" @click="handleAdd">
+        新增船厂
+      </el-button>
+    </div>
+
+    <el-table border :data="tableData" stripe>
+      <el-table-column type="index" label="序号" width="80" align="center" />
+      <el-table-column
+        prop="shipyardName"
+        label="船厂名称"
+        min-width="150"
+        align="center"
+      />
+      <el-table-column
+        prop="shipyardAddress"
+        label="地址"
+        min-width="200"
+        align="center"
+      />
+      <el-table-column
+        prop="contactName"
+        label="联系人"
+        min-width="120"
+        align="center"
+      />
+      <el-table-column
+        prop="contactPhone"
+        label="联系电话"
+        min-width="150"
+        align="center"
+      />
+      <el-table-column
+        prop="shipyardIntroduce"
+        label="船厂介绍"
+        min-width="150"
+        align="center"
+        :show-overflow-tooltip="true"
+      />
+      <el-table-column
+        prop="shipyardDockNum"
+        label="船坞数量"
+        min-width="100"
+        align="center"
+      />
+      <el-table-column
+        prop="shipyardAnnualProdCapacity"
+        label="年产能(艘)"
+        min-width="100"
+        align="center"
+      />
+
+      <el-table-column label="操作" width="180" align="center">
+        <template #default="scope">
+          <el-button type="primary" text @click="handleEdit(scope.row)">
+            修改
+          </el-button>
+          <el-button type="danger" text @click="deleteShipyard(scope.row)">
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div class="df aic jcfe mt40 mr20">
+      <el-pagination
+        background
+        layout="prev, pager, next"
+        :current-page="currentPage"
+        :total="total"
+        @current-change="pageChange"
+      />
+    </div>
+
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700">
+      <el-form
+        ref="formRef"
+        :model="formData"
+        :rules="rules"
+        label-width="120px"
+      >
+        <el-form-item label="船厂名称" prop="shipyardName">
+          <el-input v-model="formData.shipyardName" class="w400" />
+        </el-form-item>
+        <el-form-item label="船厂图片" prop="shipyardImgFileKey">
+          <Uploader
+            :limit="1"
+            :file-list="shipyardImgList"
+            action-url="/api/file/upload"
+            @on-upload-file-list="handleUploadImg"
+            @on-remove-file-list="handleRemoveImg"
+          />
+        </el-form-item>
+        <el-form-item label="地址" prop="shipyardAddress">
+          <el-input v-model="formData.shipyardAddress" class="w400" />
+        </el-form-item>
+        <el-form-item label="联系人" prop="contactName">
+          <el-input v-model="formData.contactName" class="w400" />
+        </el-form-item>
+        <el-form-item label="联系电话" prop="contactPhone">
+          <el-input v-model="formData.contactPhone" class="w400" />
+        </el-form-item>
+        <el-form-item label="船坞数量" prop="shipyardDockNum">
+          <el-input-number
+            v-model="formData.shipyardDockNum"
+            :min="0"
+            class="w400"
+          />
+        </el-form-item>
+        <el-form-item label="年产能(艘)" prop="shipyardAnnualProdCapacity">
+          <el-input-number
+            v-model="formData.shipyardAnnualProdCapacity"
+            :min="0"
+            class="w400"
+          />
+        </el-form-item>
+        <el-form-item label="船厂介绍" prop="shipyardIntroduce">
+          <el-input
+            v-model="formData.shipyardIntroduce"
+            type="textarea"
+            :rows="4"
+            class="w400"
+          />
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitForm">确定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import api from "../../apis/fetch";
+import Uploader from "../../components/Uploader.vue";
+import { useStore } from "vuex";
+
+const store = useStore();
+const loginAccountId = store.state.loginAccountId;
+
+const searchKey = ref("");
+const currentPage = ref(1);
+const total = ref(0);
+const tableData = ref([]);
+
+const dialogVisible = ref(false);
+const dialogTitle = ref("新增船厂");
+const formRef = ref(null);
+const shipyardImgList = ref([]);
+
+const formData = reactive({
+  loginAccountId: loginAccountId,
+  shipyardId: 0,
+  shipyardName: "",
+  shipyardImgFileKey: "",
+  shipyardAddress: "",
+  contactName: "",
+  contactPhone: "",
+  shipyardIntroduce: "",
+  shipyardDockNum: 0,
+  shipyardAnnualProdCapacity: 0,
+});
+
+const rules = reactive({
+  shipyardName: [
+    { required: true, message: "请输入船厂名称", trigger: "blur" },
+  ],
+  shipyardAddress: [{ required: true, message: "请输入地址", trigger: "blur" }],
+  contactName: [{ required: true, message: "请输入联系人", trigger: "blur" }],
+  contactPhone: [
+    { required: true, message: "请输入联系电话", trigger: "blur" },
+  ],
+  shipyardDockNum: [
+    { required: true, message: "请输入船坞数量", trigger: "blur" },
+  ],
+  shipyardAnnualProdCapacity: [
+    { required: true, message: "请输入年产能", trigger: "blur" },
+  ],
+});
+
+// 获取船厂列表
+async function getShipyardList() {
+  try {
+    const { data } = await api.getShipyardList({
+      loginAccountId: loginAccountId,
+      term: searchKey.value,
+      currentPage: currentPage.value,
+      size: 10,
+    });
+    if (data.status === 0) {
+      tableData.value = data.result;
+      total.value = data.total;
+    } else {
+      tableData.value = [];
+      total.value = 0;
+      ElMessage.error(data.msg || "获取数据失败");
+    }
+  } catch (error) {
+    ElMessage.error("获取数据失败");
+  }
+}
+
+// 处理图片上传
+const handleUploadImg = ({ response: data }) => {
+  if (data.status === 0) {
+    formData.shipyardImgFileKey = data.result.key;
+    shipyardImgList.value = [
+      { viewUrl: data.result.viewUrl, fileKey: data.result.key },
+    ];
+    ElMessage.success("上传成功");
+  } else {
+    ElMessage.error(data.msg || "上传失败");
+  }
+};
+
+// 处理图片删除
+const handleRemoveImg = () => {
+  formData.shipyardImgFileKey = "";
+  shipyardImgList.value = [];
+};
+
+// 分页切换
+function pageChange(page) {
+  currentPage.value = page;
+  getShipyardList();
+}
+
+// 搜索
+function handleSearch() {
+  currentPage.value = 1;
+  getShipyardList();
+}
+
+// 新增
+function handleAdd() {
+  dialogTitle.value = "新增船厂";
+  Object.keys(formData).forEach((key) => {
+    if (key !== "shipyardId" && key !== "loginAccountId") {
+      if (key === "shipyardDockNum" || key === "shipyardAnnualProdCapacity") {
+        formData[key] = 0;
+      } else {
+        formData[key] = "";
+      }
+    }
+  });
+  formData.shipyardId = 0;
+  shipyardImgList.value = [];
+  dialogVisible.value = true;
+}
+
+// 编辑
+function handleEdit(row) {
+  dialogTitle.value = "修改船厂";
+  formData.shipyardId = row.shipyardId;
+  formData.shipyardName = row.shipyardName;
+  formData.shipyardAddress = row.shipyardAddress;
+  formData.contactName = row.contactName;
+  formData.contactPhone = row.contactPhone;
+  formData.shipyardIntroduce = row.shipyardIntroduce;
+  formData.shipyardDockNum = row.shipyardDockNum || 0;
+  formData.shipyardAnnualProdCapacity = row.shipyardAnnualProdCapacity || 0;
+  formData.shipyardImgFileKey = row.shipyardImgFileKey || "";
+
+  // 设置图片
+  shipyardImgList.value = [];
+  if (row.shipyardImgFileKey) {
+    shipyardImgList.value = [
+      {
+        fileKey: row.shipyardImgFileKey,
+        viewUrl: `/api/file/view?fileKey=${row.shipyardImgFileKey}`,
+      },
+    ];
+  }
+
+  dialogVisible.value = true;
+}
+
+// 提交表单
+async function submitForm() {
+  try {
+    await formRef.value.validate();
+    const apiMethod =
+      formData.shipyardId === 0 ? api.addShipyard : api.updateShipyard;
+    const { data } = await apiMethod(formData);
+    if (data.status === 0) {
+      ElMessage.success("操作成功");
+      dialogVisible.value = false;
+      getShipyardList();
+    } else {
+      ElMessage.error(data.message || "操作失败");
+    }
+  } catch (error) {
+    console.error(error);
+    ElMessage.error("操作失败");
+  }
+}
+
+// 删除
+function deleteShipyard(row) {
+  ElMessageBox.confirm("确认删除该船厂信息?", "警告", {
+    confirmButtonText: "确认",
+    cancelButtonText: "取消",
+    type: "warning",
+  }).then(async () => {
+    try {
+      const { data } = await api.deleteShipyard({
+        loginAccountId: loginAccountId,
+        shipyardId: row.shipyardId,
+      });
+      if (data.status === 0) {
+        ElMessage.success("删除成功");
+        getShipyardList();
+      } else {
+        ElMessage.error(data.message || "删除失败");
+      }
+    } catch (error) {
+      ElMessage.error("删除失败");
+    }
+  });
+}
+
+// 初始化获取数据
+getShipyardList();
+</script>
+
+<style scoped>
+.w300 {
+  width: 300px;
+}
+.w400 {
+  width: 400px;
+}
+.ml10 {
+  margin-left: 10px;
+}
+</style>

+ 240 - 0
src/views/workStation/terminalServiceStaffManage.vue

@@ -0,0 +1,240 @@
+<template>
+  <div class="full-container-p24">
+    <div class="mb30 df aic jcsb">
+      <div class="df aic">
+        <el-input
+          v-model="searchKey"
+          placeholder="请输入姓名/工种/手机号"
+          class="w300"
+          @keyup.enter="handleSearch"
+          clearable
+          @clear="handleSearch"
+        />
+        <el-button class="ml10" type="primary" @click="handleSearch">
+          搜索
+        </el-button>
+      </div>
+      <el-button class="ml10" type="primary" @click="handleAdd">
+        新增人员
+      </el-button>
+    </div>
+
+    <el-table border :data="tableData" stripe>
+      <el-table-column type="index" label="序号" width="80" align="center" />
+      <el-table-column
+        prop="serviceStaffName"
+        label="姓名"
+        min-width="120"
+        align="center"
+      />
+      <el-table-column
+        prop="serviceType"
+        label="服务类型"
+        min-width="120"
+        align="center"
+      />
+      <el-table-column
+        prop="serviceStaffPhone"
+        label="联系电话"
+        min-width="150"
+        align="center"
+      />
+
+      <el-table-column label="操作" width="180" align="center">
+        <template #default="scope">
+          <el-button type="primary" text @click="handleEdit(scope.row)">
+            修改
+          </el-button>
+          <el-button type="danger" text @click="deleteStaff(scope.row)">
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div class="df aic jcfe mt40 mr20">
+      <el-pagination
+        background
+        layout="prev, pager, next"
+        :current-page="currentPage"
+        :total="total"
+        @current-change="pageChange"
+      />
+    </div>
+
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600">
+      <el-form
+        ref="formRef"
+        :model="formData"
+        :rules="rules"
+        label-width="120px"
+      >
+        <el-form-item label="姓名" prop="serviceStaffName">
+          <el-input v-model="formData.serviceStaffName" class="w400" />
+        </el-form-item>
+        <el-form-item label="服务类型" prop="serviceType">
+          <el-input v-model="formData.serviceType" class="w400" />
+        </el-form-item>
+        <el-form-item label="联系电话" prop="serviceStaffPhone">
+          <el-input v-model="formData.serviceStaffPhone" class="w400" />
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitForm">确定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import api from "../../apis/fetch";
+import { useStore } from "vuex";
+
+const store = useStore();
+const loginAccountId = store.state.loginAccountId;
+
+const searchKey = ref("");
+const currentPage = ref(1);
+const total = ref(0);
+const tableData = ref([]);
+
+const dialogVisible = ref(false);
+const dialogTitle = ref("新增人员");
+const formRef = ref(null);
+const formData = reactive({
+  loginAccountId: loginAccountId,
+  serviceStaffId: 0,
+  serviceStaffName: "",
+  serviceType: "",
+  serviceStaffPhone: "",
+});
+
+const rules = reactive({
+  serviceStaffName: [
+    { required: true, message: "请输入姓名", trigger: "blur" },
+  ],
+  serviceType: [{ required: true, message: "请输入服务类型", trigger: "blur" }],
+  serviceStaffPhone: [
+    { required: true, message: "请输入联系电话", trigger: "blur" },
+  ],
+});
+
+// 获取人员列表
+async function getStaffList() {
+  try {
+    const { data } = await api.getTerminalServiceStaffList({
+      loginAccountId: loginAccountId,
+      term: searchKey.value,
+      currentPage: currentPage.value,
+      size: 10,
+    });
+    if (data.status === 0) {
+      tableData.value = data.result;
+      total.value = data.total;
+    } else {
+      tableData.value = [];
+      total.value = 0;
+      ElMessage.error(data.msg);
+    }
+  } catch (error) {
+    ElMessage.error("获取数据失败");
+  }
+}
+
+// 分页切换
+function pageChange(page) {
+  currentPage.value = page;
+  getStaffList();
+}
+
+// 搜索
+function handleSearch() {
+  currentPage.value = 1;
+  getStaffList();
+}
+
+// 新增
+function handleAdd() {
+  dialogTitle.value = "新增人员";
+  Object.keys(formData).forEach((key) => {
+    if (key !== "serviceStaffId" && key !== "loginAccountId")
+      formData[key] = "";
+  });
+  formData.serviceStaffId = 0;
+  dialogVisible.value = true;
+}
+
+// 编辑
+function handleEdit(row) {
+  dialogTitle.value = "修改人员";
+  formData.serviceStaffId = row.id;
+  formData.serviceStaffName = row.serviceStaffName;
+  formData.serviceType = row.serviceType;
+  formData.serviceStaffPhone = row.serviceStaffPhone;
+  dialogVisible.value = true;
+}
+
+// 提交表单
+async function submitForm() {
+  try {
+    await formRef.value.validate();
+    const apiMethod =
+      formData.serviceStaffId === 0
+        ? api.addTerminalServiceStaff
+        : api.updateTerminalServiceStaff;
+    const { data } = await apiMethod(formData);
+    if (data.status === 0) {
+      ElMessage.success("操作成功");
+      dialogVisible.value = false;
+      getStaffList();
+    } else {
+      ElMessage.error(data.message || "操作失败");
+    }
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+// 删除
+function deleteStaff(row) {
+  ElMessageBox.confirm("确认删除该人员信息?", "警告", {
+    confirmButtonText: "确认",
+    cancelButtonText: "取消",
+    type: "warning",
+  }).then(async () => {
+    try {
+      const { data } = await api.deleteTerminalServiceStaff({
+        loginAccountId: loginAccountId,
+        serviceStaffId: row.id,
+      });
+      if (data.status === 0) {
+        ElMessage.success("删除成功");
+        getStaffList();
+      } else {
+        ElMessage.error(data.message || "删除失败");
+      }
+    } catch (error) {
+      ElMessage.error("删除失败");
+    }
+  });
+}
+
+// 初始化获取数据
+getStaffList();
+</script>
+
+<style scoped>
+.w300 {
+  width: 300px;
+}
+.w400 {
+  width: 400px;
+}
+.ml10 {
+  margin-left: 10px;
+}
+</style>