Explorar o código

ToDo 优化总台,航次详情地图;投标详情

wzg hai 1 ano
pai
achega
a1630a99ad

+ 1 - 0
package.json

@@ -9,6 +9,7 @@
     "serve": "vite build --mode release && vite preview"
   },
   "dependencies": {
+    "@amap/amap-jsapi-loader": "^1.0.1",
     "@cloudbase/js-sdk": "^1.7.1",
     "@element-plus/icons": "^0.0.11",
     "@element-plus/icons-vue": "^2.3.1",

+ 34 - 0
src/apis/fetch.js

@@ -570,4 +570,38 @@ export default {
   downloadDischargeTemp(data) {
     return $http("/voyage/download/discharge/temp", data);
   },
+  // 招标详情
+  getTenderDetail(data) {
+    return $http("/tender/detail", data);
+  },
+
+  // 招标详情-参与投标-新增/修改船舶
+  tenderShip(data) {
+    return $http("/tender/detail/bid", data);
+  },
+
+  // 招标详情-删除投标
+  deleteTender(data) {
+    return $http("/tender/detail/bid/delete", data);
+  },
+
+  // 招标详情-放弃投标
+  giveupTender(data) {
+    return $http("/tender/detail/bid/giveup", data);
+  },
+
+  // 招标详情-参与投标-船舶选择
+  tenderSelectShip(data) {
+    return $http("/tender/detail/bid/selectShip", data);
+  },
+
+  // 招标详情-创建航次
+  createTenderVoyage(data) {
+    return $http("/tender/detail/bid/voyage", data);
+  },
+
+  // 投标列表
+  getTenderList(data) {
+    return $http("/tender/list", data);
+  },
 };

+ 12 - 0
src/auth/menuData.js

@@ -62,6 +62,18 @@ let menuData = [
       },
     ],
   },
+  {
+    icon: "el-icon-s-data",
+    title: "投标管理",
+    code: "SHIPOWNERLIST",
+    items: [
+      {
+        path: "/tenderManage/tenderList",
+        name: "投标列表",
+        code: "SHIPOWNERLIST",
+      },
+    ],
+  },
   {
     icon: "el-icon-s-data",
     title: "账户管理",

+ 136 - 0
src/components/AMapContainer.vue

@@ -0,0 +1,136 @@
+<template>
+  <div :id="mapId"></div>
+</template>
+<script setup>
+import { onMounted, onUnmounted, ref } from "vue";
+import AMapLoader from "@amap/amap-jsapi-loader";
+const props = defineProps({
+  longitude: {
+    type: Number,
+    default: 120.498409,
+  },
+  latitude: {
+    type: Number,
+    default: 32.039665,
+  },
+  ships: {
+    type: Array,
+    default: [],
+  },
+  loadPorts: {
+    type: Array,
+    default: [],
+  },
+  dischargePorts: {
+    type: Array,
+    default: [],
+  },
+  mapId: {
+    type: String,
+    default: "container",
+  },
+});
+const mapId = ref(props.mapId);
+const map = ref({});
+function initMap() {
+  AMapLoader.load({
+    key: "0b84075e96d01623f704867a601139bb", // 申请好的Web端开发者Key,首次调用 load 时必填
+    version: "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15需要使用的的插件列表,如比例尺'AMap.Scale',支持添加多个如:['...','...']
+  })
+    .then((AMap) => {
+      map.value = new AMap.Map(props.mapId, {
+        zoom: 12, // 初始化地图级别
+        center: [props.longitude, props.latitude], // 初始化地图中心点位置
+        mapStyle: "amap://styles/f48d96805f5fa7f5aada657c5ee37017",
+        zoomEnable: true,
+        dragEnable: true,
+      });
+      let markers = [];
+
+      for (let i of props.ships) {
+        let marker = generateMarker({
+          longitude: i.longitude,
+          latitude: i.latitude,
+          label: i.shipName,
+        });
+        markers.push(marker);
+      }
+      for (let i of props.loadPorts) {
+        let marker = generateMarker({
+          longitude: i.longitude,
+          latitude: i.latitude,
+          iconSrc:
+            "https://hhd-pat-1255802371.cos.ap-shanghai.myqcloud.com/frontend/port-green.png",
+          iconWidth: 30,
+          iconHeight: 30,
+          offsetY: -30,
+          label: "装货港",
+        });
+        markers.push(marker);
+      }
+      for (let i of props.dischargePorts) {
+        let marker = generateMarker({
+          longitude: i.longitude,
+          latitude: i.latitude,
+          iconSrc:
+            "https://hhd-pat-1255802371.cos.ap-shanghai.myqcloud.com/frontend/port-red.png",
+          iconWidth: 30,
+          iconHeight: 30,
+          offsetY: -30,
+          label: "卸货港",
+        });
+        markers.push(marker);
+      }
+      let overlayGroups = new AMap.OverlayGroup(markers);
+      map.value.add(overlayGroups);
+      map.value.setFitView(markers, true, [150, 50, 100, 100], 9);
+    })
+    .catch((e) => {
+      console.log(e);
+    });
+}
+
+function generateMarker({
+  longitude = 120.498409,
+  latitude = 32.039665,
+  iconSrc = "https://hhd-pat-1255802371.cos.ap-shanghai.myqcloud.com/frontend/ship-red-icon.png",
+  offsetX = -80,
+  offsetY = -20,
+  label = "",
+  iconWidth = 20,
+  iconHeight = 20,
+}) {
+  let marker = new AMap.Marker({
+    content: `<div style='width:160px'>
+          <img src='${iconSrc}' style='display:block;width:${iconWidth}px;height:${iconHeight}px;margin:6px auto'/
+        </div>`,
+    zIndex: 5,
+    position: new AMap.LngLat(longitude, latitude),
+    offset: new AMap.Pixel(offsetX, offsetY),
+  });
+  if (label) {
+    marker.setLabel({
+      direction: "top",
+      offset: new AMap.Pixel(0, 0), //设置文本标注偏移量
+      content: label, //设置文本标注内容
+      style: "",
+    });
+  }
+
+  return marker;
+}
+
+onMounted(() => {
+  initMap();
+});
+
+onUnmounted(() => {
+  map.value.destroy();
+});
+</script>
+
+<style scoped>
+#container {
+  width: 100%;
+}
+</style>

+ 1 - 4
src/components/Header.vue

@@ -4,10 +4,7 @@
       <!-- <img class="first" src="../assets/three.png" alt="" />
       <div class="shu"></div> -->
       <img class="logo" src="../assets/white-logo.png" alt="" />
-      <div
-        class="ml20"
-        style="color: #fff; font-size: 18px; height: 60px; padding-top: 50px"
-      >
+      <div class="ml20" style="color: #fff; font-size: 18px">
         version:{{ timelineData[0]?.version }}
       </div>
     </div>

+ 89 - 0
src/components/TenderTable.vue

@@ -0,0 +1,89 @@
+<template>
+  <div>
+    <el-table class="mb10" :data="tableData" border stripe>
+      <el-table-column label="序号" type="index" width="60" />
+      <el-table-column label="货种" prop="cargo" />
+      <el-table-column label="发货吨位" prop="tons" />
+      <el-table-column label="装货港" prop="loadPortName" />
+      <el-table-column label="卸货港" prop="dischargePortName" />
+      <el-table-column
+        label="截止时间"
+        prop="bidDeadlineDatetime"
+        min-width="100px"
+      />
+      <el-table-column label="发起人" prop="sponsorName" />
+      <el-table-column label="投标数量" prop="tenderQuantity" />
+      <el-table-column label="操作">
+        <template #default="scope">
+          <slot name="action" :row="scope.row" />
+        </template>
+      </el-table-column>
+    </el-table>
+    <div class="df aic jcfe" style="width: 100%">
+      <el-pagination
+        v-model:current-page="currentPage"
+        v-model:page-size="pageSize"
+        :page-sizes="[10, 50, 100, 200]"
+        background
+        layout="sizes, prev, pager, next"
+        :total="total"
+        @size-change="getTenderList()"
+        @current-change="getTenderList()"
+      />
+    </div>
+  </div>
+</template>
+<script setup>
+import { onMounted, onUnmounted, ref, watch } from "vue";
+import api from "apis/fetch";
+import router from "router/index";
+const props = defineProps({
+  transType: {
+    type: Number,
+    default: 1,
+  },
+  type: {
+    type: Number,
+    default: 1,
+  },
+  term: {
+    type: String,
+    default: "",
+  },
+});
+
+const tableData = ref([]);
+const total = ref(0);
+const currentPage = ref(1);
+const pageSize = ref(10);
+async function getTenderList() {
+  let { data } = await api.getTenderList({
+    transType: props.transType,
+    type: props.type,
+    currentPage: currentPage.value,
+    size: pageSize.value,
+    term: props.term,
+  });
+  if (data.status === 0) {
+    tableData.value = data.result;
+  } else {
+    tableData.value = [];
+  }
+  total.value = data.total;
+}
+watch(
+  () => [props.transType, props.type],
+  () => {
+    getTenderList();
+  }
+);
+
+defineExpose({
+  getTenderList,
+});
+onMounted(() => {
+  getTenderList();
+});
+</script>
+
+<style scoped></style>

+ 7 - 0
src/main.js

@@ -7,17 +7,24 @@ import router from "./router";
 import store from "./store";
 import md5 from "md5";
 import "./styles/index.css";
+import "./style/index.css";
 import Uploader from "./components/Uploader.vue";
 import Certs from "./components/Certs.vue";
 import RemoteSearch from "./components/RemoteSearch.vue";
 import RemoteSelect from "./components/RemoteSelect.vue";
 
+import AMapContainer from "./components/AMapContainer.vue";
+import TenderTable from "./components/TenderTable.vue";
+
 const app = createApp(App);
 app.component("RemoteSelect", RemoteSelect);
 app.component("RemoteSearch", RemoteSearch);
 app.component("Certs", Certs);
 app.component("Uploader", Uploader);
 
+app.component("AMapContainer", AMapContainer);
+app.component("TenderTable", TenderTable);
+
 let userId = localStorage.userId;
 if (userId) {
   store.dispatch("GetBasePermissionData", localStorage.loginAccountId);

+ 18 - 0
src/router/index.js

@@ -170,6 +170,24 @@ const router = createRouter({
       },
       component: () => import("../views/voyage/portDeclarationDetail.vue"),
     },
+    {
+      path: "/tenderManage/tenderList",
+      name: "tenderList",
+      meta: {
+        title: "投标列表",
+        code: "DECLAREDETAIL",
+      },
+      component: () => import("../views/tenderManage/tenderList.vue"),
+    },
+    {
+      path: "/tenderManage/tenderDetail",
+      name: "tenderDetail",
+      meta: {
+        title: "投标详情",
+        code: "DECLAREDETAIL",
+      },
+      component: () => import("../views/tenderManage/tenderDetail.vue"),
+    },
   ],
 });
 

+ 76 - 0
src/style/color.css

@@ -0,0 +1,76 @@
+.black {
+  color: #000 !important;
+}
+
+.c1 {
+  color: #111 !important;
+}
+
+.c2 {
+  color: #222 !important;
+}
+
+.c3 {
+  color: #333 !important;
+}
+
+.c4 {
+  color: #444 !important;
+}
+
+.c5 {
+  color: #555 !important;
+}
+
+.c6 {
+  color: #666 !important;
+}
+
+.c7 {
+  color: #777 !important;
+}
+
+.c8 {
+  color: #888 !important;
+}
+
+.c9 {
+  color: #999 !important;
+}
+
+.ca {
+  color: #aaa !important;
+}
+
+.cb {
+  color: #bbb !important;
+}
+
+.cc {
+  color: #ccc !important;
+}
+
+.cd {
+  color: #ddd !important;
+}
+
+.ce {
+  color: #eee !important;
+}
+
+.white,.cf {
+  color: #fff !important;
+}
+
+.red {
+  color: red !important;
+}
+
+.primary {
+  padding: 20px !important;
+  font-size: 32px !important;
+  border-radius: 52px !important;
+  color: #fff !important;
+  text-align: center !important;
+  background: linear-gradient(270deg, #0089fd 0%, #43a9ff 100%) !important;
+}

+ 71 - 0
src/style/font-size.css

@@ -0,0 +1,71 @@
+.fs10 {
+  font-size: 10px !important;
+}
+
+.fs12 {
+  font-size: 12px !important;
+}
+
+.fs14 {
+  font-size: 14px !important;
+}
+
+.fs16 {
+  font-size: 16px !important;
+}
+
+.fs18 {
+  font-size: 18px !important;
+}
+
+.fs20 {
+  font-size: 20px !important;
+}
+
+.fs22 {
+  font-size: 22px !important;
+}
+
+.fs24 {
+  font-size: 24px !important;
+}
+
+.fs26 {
+  font-size: 26px !important;
+}
+
+.fs28 {
+  font-size: 28px !important;
+}
+
+.fs30 {
+  font-size: 28px !important;
+}
+
+.fs32 {
+  font-size: 32px !important;
+}
+
+.fs34 {
+  font-size: 34px !important;
+}
+
+.fs36 {
+  font-size: 36px !important;
+}
+
+.fs38 {
+  font-size: 38px !important;
+}
+
+.fs40 {
+  font-size: 40px !important;
+}
+
+.fs48 {
+  font-size: 48px !important;
+}
+
+.fs50 {
+  font-size: 50px !important;
+}

+ 71 - 0
src/style/height.css

@@ -0,0 +1,71 @@
+.h10 {
+  height: 10px !important;
+}
+
+.h12 {
+  height: 12px !important;
+}
+
+.h14 {
+  height: 14px !important;
+}
+
+.h16 {
+  height: 16px !important;
+}
+
+.h18 {
+  height: 18px !important;
+}
+
+.h20 {
+  height: 20px !important;
+}
+
+.h22 {
+  height: 22px !important;
+}
+
+.h24 {
+  height: 24px !important;
+}
+
+.h26 {
+  height: 26px !important;
+}
+
+.h28 {
+  height: 28px !important;
+}
+
+.h30 {
+  height: 28px !important;
+}
+
+.h32 {
+  height: 32px !important;
+}
+
+.h34 {
+  height: 34px !important;
+}
+
+.h36 {
+  height: 36px !important;
+}
+
+.h38 {
+  height: 38px !important;
+}
+
+.h40 {
+  height: 40px !important;
+}
+
+.h48 {
+  height: 48px !important;
+}
+
+.h50 {
+  height: 50px !important;
+}

+ 172 - 0
src/style/index.css

@@ -0,0 +1,172 @@
+@import "./margin.css";
+@import "./padding.css";
+@import "./font-size.css";
+@import "./color.css";
+@import "./width.css";
+@import "./height.css";
+
+* {
+  box-sizing: border-box;
+  margin: 0;
+  padding: 0;
+  font-family: "mjn2", "mjn3", Arial;
+}
+
+.tac {
+  text-align: center;
+}
+
+.tal {
+  text-align: left;
+}
+
+.tar {
+  text-align: right;
+}
+
+.df {
+  display: flex;
+}
+
+.jcsb {
+  justify-content: space-between;
+}
+
+.jcc {
+  justify-content: center !important;
+}
+
+.jcfe {
+  justify-content: flex-end;
+}
+
+.jcsa {
+  justify-content: space-around;
+}
+
+.jcs {
+  justify-content: start;
+}
+
+.aic {
+  align-items: center;
+}
+
+.aib {
+  align-items: baseline;
+}
+
+.fdc {
+  flex-direction: column;
+}
+
+.pointer {
+  cursor: pointer;
+}
+
+.fww {
+  flex-wrap: wrap;
+}
+
+.fs1 {
+  flex-shrink: 1;
+}
+
+.fg1 {
+  flex-grow: 1;
+}
+
+.wsn {
+  white-space: nowrap;
+}
+
+.icon {
+  width: 20px;
+  height: 20px;
+  margin-right: 6px;
+}
+
+.t32 {
+  border-left: 5px solid #409eff;
+  padding-left: 10px;
+  font-size: 32px;
+  height: 32px;
+  line-height: 32px;
+}
+
+.t28 {
+  border-left: 5px solid #409eff;
+  padding-left: 10px;
+  font-size: 28px;
+  height: 28px;
+  line-height: 28px;
+}
+
+.t24 {
+  border-left: 5px solid #409eff;
+  padding-left: 10px;
+  font-size: 24px;
+  height: 24px;
+  line-height: 24px;
+}
+
+.t22 {
+  border-left: 5px solid #409eff;
+  padding-left: 10px;
+  font-size: 22px;
+  height: 22px;
+  line-height: 22px;
+}
+
+.t20 {
+  border-left: 5px solid #409eff;
+  padding-left: 10px;
+  font-size: 20px;
+  height: 20px;
+  line-height: 20px;
+}
+
+.t18 {
+  border-left: 5px solid #409eff;
+  padding-left: 10px;
+  font-size: 18px;
+  height: 18px;
+  line-height: 18px;
+}
+
+.t16 {
+  border-left: 5px solid #409eff;
+  padding-left: 10px;
+  font-size: 16px;
+  height: 16px;
+  line-height: 16px;
+}
+
+.t14 {
+  border-left: 5px solid #409eff;
+  padding-left: 10px;
+  font-size: 14px;
+  height: 14px;
+  line-height: 14px;
+}
+
+.t12 {
+  border-left: 5px solid #409eff;
+  padding-left: 10px;
+  font-size: 12px;
+  height: 12px;
+  line-height: 12px;
+}
+
+.tips {
+  margin: 6px;
+  font-size: 13px;
+  color: #555;
+}
+
+.card {
+  border: 1px solid rgb(228, 231, 237);
+  padding: 20px;
+  background: #fff;
+  border-radius: 4px;
+}

+ 166 - 0
src/style/margin.css

@@ -0,0 +1,166 @@
+.m10 {
+  margin: 10px !important;
+}
+
+.m20 {
+  margin: 20px !important;
+}
+
+.m30 {
+  margin: 30px !important;
+}
+
+.m40 {
+  margin: 40px !important;
+}
+
+.m50 {
+  margin: 50px !important;
+}
+
+.m10a {
+  margin: 10px auto !important;
+}
+
+.m20a {
+  margin: 20px auto !important;
+}
+
+.m30a {
+  margin: 30px auto !important;
+}
+
+.m40a {
+  margin: 40px auto !important;
+}
+
+.m50a {
+  margin: 50px auto !important;
+}
+
+.mt10 {
+  margin-top: 10px !important;
+}
+
+.mt20 {
+  margin-top: 20px !important;
+}
+
+.mt30 {
+  margin-top: 30px !important;
+}
+
+.mt40 {
+  margin-top: 40px !important;
+}
+
+.mt50 {
+  margin-top: 50px !important;
+}
+
+.mt60 {
+  margin-top: 60px !important;
+}
+
+.mt80 {
+  margin-top: 80px !important;
+}
+
+.mt100 {
+  margin-top: 100px !important;
+}
+
+.mr10 {
+  margin-right: 10px !important;
+}
+
+.mr20 {
+  margin-right: 20px !important;
+}
+
+.mr30 {
+  margin-right: 30px !important;
+}
+
+.mr40 {
+  margin-right: 40px !important;
+}
+
+.mr50 {
+  margin-right: 50px !important;
+}
+
+.mr60 {
+  margin-right: 60px !important;
+}
+.mr70 {
+  margin-right: 70px !important;
+}
+.mr80 {
+  margin-right: 80px !important;
+}
+.mr90 {
+  margin-right: 90px !important;
+}
+.mr100 {
+  margin-right: 100px !important;
+}
+
+.mb10 {
+  margin-bottom: 10px !important;
+}
+
+.mb20 {
+  margin-bottom: 20px !important;
+}
+
+.mb30 {
+  margin-bottom: 30px !important;
+}
+
+.mb40 {
+  margin-bottom: 40px !important;
+}
+
+.mb50 {
+  margin-bottom: 50px !important;
+}
+
+.ml10 {
+  margin-left: 10px !important;
+}
+
+.ml20 {
+  margin-left: 20px !important;
+}
+
+.ml30 {
+  margin-left: 30px !important;
+}
+
+.ml40 {
+  margin-left: 40px !important;
+}
+
+.ml50 {
+  margin-left: 50px !important;
+}
+
+.ml60 {
+  margin-left: 60px !important;
+}
+.mt15 {
+  margin-top: 15px !important;
+}
+
+.mr15 {
+  margin-right: 15px !important;
+}
+
+.mb15 {
+  margin-bottom: 15px !important;
+}
+
+.ml15 {
+  margin-left: 15px !important;
+}

+ 172 - 0
src/style/padding.css

@@ -0,0 +1,172 @@
+.p10 {
+  padding: 10px !important;
+}
+
+.p20 {
+  padding: 20px !important;
+}
+
+.p30 {
+  padding: 30px !important;
+}
+
+.p40 {
+  padding: 40px !important;
+}
+
+.p50 {
+  padding: 50px !important;
+}
+
+.pt10 {
+  padding-top: 10px !important;
+}
+
+.pr10 {
+  padding-right: 10px !important;
+}
+
+.pb10 {
+  padding-bottom: 10px !important;
+}
+
+.pl10 {
+  padding-left: 10px !important;
+}
+
+.pt20 {
+  padding-top: 20px !important;
+}
+
+.pr20 {
+  padding-right: 20px !important;
+}
+
+.pb20 {
+  padding-bottom: 20px !important;
+}
+
+.pl20 {
+  padding-left: 20px !important;
+}
+
+.pt30 {
+  padding-top: 30px !important;
+}
+
+.pr30 {
+  padding-right: 30px !important;
+}
+
+.pb30 {
+  padding-bottom: 30px !important;
+}
+
+.pl30 {
+  padding-left: 30px !important;
+}
+
+.pt40 {
+  padding-top: 40px !important;
+}
+
+.pr40 {
+  padding-right: 40px !important;
+}
+
+.pb40 {
+  padding-bottom: 40px !important;
+}
+
+.pl40 {
+  padding-left: 40px !important;
+}
+
+.pt50 {
+  padding-top: 50px !important;
+}
+
+.pr50 {
+  padding-right: 50px !important;
+}
+
+.pb50 {
+  padding-bottom: 50px !important;
+}
+
+.pl50 {
+  padding-left: 50px !important;
+}
+
+.pt10p {
+  padding-top: 10%;
+}
+.pr10p {
+  padding-right: 10%;
+}
+
+.pb10p {
+  padding-bottom: 10%;
+}
+
+.pl10p {
+  padding-left: 10%;
+}
+
+.pt20p {
+  padding-top: 20%;
+}
+
+.pr20p {
+  padding-right: 20%;
+}
+
+.pb20p {
+  padding-bottom: 20%;
+}
+
+.pl20p {
+  padding-left: 20%;
+}
+
+.pt30p {
+  padding-top: 30%;
+}
+.pr30p {
+  padding-right: 30%;
+}
+.pb30p {
+  padding-bottom: 30%;
+}
+
+.pl30p {
+  padding-left: 30%;
+}
+
+.pt40p {
+  padding-top: 40%;
+}
+.pr40p {
+  padding-right: 40%;
+}
+.pb40p {
+  padding-bottom: 40%;
+}
+
+.pl40p {
+  padding-left: 40%;
+}
+
+.pt50p {
+  padding-top: 50%;
+}
+.pr50p {
+  padding-right: 50%;
+}
+.pb50p {
+  padding-bottom: 50%;
+}
+
+.pl50p {
+  padding-left: 50%;
+}

+ 117 - 0
src/style/width.css

@@ -0,0 +1,117 @@
+.w10 {
+  width: 10px !important;
+}
+
+.w20 {
+  width: 20px !important;
+}
+
+.w30 {
+  width: 30px !important;
+}
+
+.w40 {
+  width: 40px !important;
+}
+
+.w50 {
+  width: 50px !important;
+}
+
+.w60 {
+  width: 60px !important;
+}
+
+.w70 {
+  width: 70px !important;
+}
+
+.w80 {
+  width: 80px !important;
+}
+
+.w100 {
+  width: 100px !important;
+}
+
+.w120 {
+  width: 120px !important;
+}
+
+.w140 {
+  width: 140px !important;
+}
+
+.w160 {
+  width: 160px !important;
+}
+
+.w180 {
+  width: 180px !important;
+}
+
+.w200 {
+  width: 200px !important;
+}
+
+.w220 {
+  width: 220px !important;
+}
+
+.w240 {
+  width: 240px !important;
+}
+
+.w300 {
+  width: 300px !important;
+}
+.w400 {
+  width: 400px !important;
+}
+.w500 {
+  width: 500px !important;
+}
+
+.w10p {
+  width: 10% !important;
+}
+
+.w20p {
+  width: 20% !important;
+}
+
+.w30p {
+  width: 30% !important;
+}
+
+.w40p {
+  width: 40% !important;
+}
+
+.w50p {
+  width: 50% !important;
+}
+
+.w60p {
+  width: 60% !important;
+}
+
+.w70p {
+  width: 70% !important;
+}
+
+.w80p {
+  width: 80% !important;
+}
+
+.w90p {
+  width: 90% !important;
+}
+
+.w100p {
+  width: 100% !important;
+}
+
+.wfc {
+  width: fit-content;
+}

+ 153 - 0
src/views/tenderManage/tenderDetail.vue

@@ -0,0 +1,153 @@
+<template>
+  <div class="card">
+    <div class="df aic jcsb mb10">
+      <div class="t20">招标信息</div>
+      <div class="df aic">
+        <div
+          class="mr10 fs18"
+          :style="{ color: tenderData.status === 1 ? '#00B050' : '#FFA500' }"
+        >
+          {{ tenderData.status === 1 ? "招标中" : "" }}
+          {{ tenderData.status === 2 ? "招标完成" : "" }}
+          {{ tenderData.status === 3 ? "取消招标" : "" }}
+        </div>
+        <div class="mr10" v-if="tenderData.status === 1">
+          竞标截止剩余: {{ getLeftTime(tenderData.bidDeadlineDatetime) }}
+        </div>
+      </div>
+    </div>
+    <div class="card mb20">
+      <div class="df aic mb10">
+        <div class="info-title">发起时间:</div>
+        <div class="info-text">{{ tenderData.operatorDate }}</div>
+        <div class="info-title">货种:</div>
+        <div class="info-text">{{ tenderData.cargo }}</div>
+        <div class="info-title">装货港:</div>
+        <div class="info-text">{{ tenderData.loadPortName }}</div>
+      </div>
+      <div class="df aic mb10">
+        <div class="info-title">截止时间:</div>
+        <div class="info-text">{{ tenderData.bidDeadlineDatetime }}</div>
+        <div class="info-title">发货吨位:</div>
+        <div class="info-text">{{ tenderData.tons }} 吨</div>
+        <div class="info-title">卸货港:</div>
+        <div class="info-text">{{ tenderData.dischargePortName }}</div>
+      </div>
+    </div>
+    <div v-if="tenderData.initMethod === 3">
+      <div class="t20 mb10">前置航次信息</div>
+      <div class="card">
+        <AMapContainer
+          :ships="[voyage]"
+          :loadPorts="loadPorts"
+          :dischargePorts="dischargePorts"
+          style="width: 100%; height: 500px"
+        ></AMapContainer>
+        <div class="card mt10">
+          <div class="t16 mb10">航次信息</div>
+          <div class="df aic mb10">
+            <div class="info-title">船名:</div>
+            <div class="info-text">{{ voyage.shipName }}</div>
+            <div class="info-title">装货港:</div>
+            <div class="info-text">{{ voyage.loadPort }}</div>
+            <div class="info-title">货种:</div>
+            <div class="info-text">{{ voyage.cargo }}</div>
+          </div>
+          <div class="df aic mb10">
+            <div class="info-title">MMSI:</div>
+            <div class="info-text">{{ voyage.mmsi }}</div>
+            <div class="info-title">卸货港:</div>
+            <div class="info-text">{{ voyage.dischargePorts }}</div>
+            <div class="info-title">装载吨位:</div>
+            <div class="info-text">{{ voyage.tons }} 吨</div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="t20 mt20 mb10">运力要求</div>
+    <div class="card">
+      <pre style="white-space: pre-wrap">{{
+        tenderData.capacityRequirements
+      }}</pre>
+    </div>
+    <div class="t20 mt20 mb10">报价要求</div>
+    <div class="card">
+      <pre style="white-space: pre-wrap">{{ tenderData.quotationRequest }}</pre>
+    </div>
+    <el-divider></el-divider>
+    <div class="t20 mb10">投标信息</div>
+  </div>
+</template>
+
+<script setup>
+import api from "../../apis/fetch";
+import store from "../../store";
+import router from "../../router";
+import { ref, onMounted, reactive, computed } from "vue";
+import { ElNotification, ElMessage, ElMessageBox } from "element-plus";
+import { mapGetters } from "vuex";
+import { useRoute } from "vue-router";
+
+const route = useRoute();
+
+const tenderData = ref({
+  operatorDate: "",
+  bidDeadlineDatetime: "",
+  cargo: "",
+  loadPortName: "",
+  tons: "",
+  dischargePortName: "",
+  participateProxyNum: "",
+  capacityRequirements: "",
+  quotationRequest: "",
+  tenderProxies: [],
+});
+
+const voyage = ref({});
+const loadPorts = ref([]);
+const dischargePorts = ref([]);
+
+async function getTenderDetail() {
+  let { data } = await api.getTenderDetail({
+    tenderId: route.query.id,
+  });
+  tenderData.value = data.result;
+}
+
+function getLeftTime(givenTime = "2099-12-31 23:59:59") {
+  givenTime = new Date(givenTime);
+  const currentTime = new Date();
+  const differenceInMilliseconds = Math.abs(givenTime - currentTime);
+  const days = Math.floor(differenceInMilliseconds / (1000 * 60 * 60 * 24));
+  const hours = Math.floor(
+    (differenceInMilliseconds % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)
+  );
+  return `${days}天${hours}小时`;
+}
+
+onMounted(() => {
+  getTenderDetail();
+});
+</script>
+
+<style scoped>
+.info-title {
+  width: 110px;
+  text-align: right;
+  color: #666;
+  margin-right: 10px;
+}
+
+.info-text {
+  width: 160px;
+  color: #333;
+}
+.table-title {
+  color: #666;
+  margin-right: 10px;
+}
+.table-text {
+  color: #333;
+  margin-right: 30px;
+}
+</style>

+ 95 - 0
src/views/tenderManage/tenderList.vue

@@ -0,0 +1,95 @@
+<template>
+  <div class="line-container-p24">
+    <div class="mb10 df aic jcsb">
+      <el-button-group>
+        <el-button
+          :type="transType === 1 ? 'primary' : ''"
+          @click="changeTransType(1)"
+        >
+          江运
+        </el-button>
+        <el-button
+          :type="transType === 2 ? 'primary' : ''"
+          @click="changeTransType(2)"
+        >
+          海运
+        </el-button>
+      </el-button-group>
+      <el-button
+        type="primary"
+        @click="router.push('/tenderManage/inviteTender')"
+      >
+        发起招标
+      </el-button>
+    </div>
+    <div class="mb20 df aic">
+      <el-button-group>
+        <el-button :type="type === 1 ? 'primary' : ''" @click="changeType(1)">
+          可投标
+        </el-button>
+        <el-button :type="type === 2 ? 'primary' : ''" @click="changeType(2)">
+          已投标
+        </el-button>
+        <el-button :type="type === 3 ? 'primary' : ''" @click="changeType(3)">
+          已放弃
+        </el-button>
+        <el-button :type="type === 4 ? 'primary' : ''" @click="changeType(4)">
+          已中标
+        </el-button>
+      </el-button-group>
+      <el-input
+        class="ml20 mr10"
+        style="width: 200px"
+        v-model="term"
+        clearable
+        @clear="tableRef.getTenderList()"
+        @blur="tableRef.getTenderList()"
+        placeholder="搜索"
+        @keyup.enter="tableRef.getTenderList()"
+      ></el-input>
+    </div>
+    <TenderTable
+      ref="tableRef"
+      :transType="transType"
+      :type="type"
+      :term="term"
+    >
+      <template #action="{ row }">
+        <el-button
+          size="small"
+          type="primary"
+          @click="router.push(`/tenderManage/tenderDetail?id=${row.id}`)"
+        >
+          详情
+        </el-button>
+      </template>
+    </TenderTable>
+  </div>
+</template>
+
+<script setup>
+import api from "../../apis/fetch";
+import store from "../../store";
+import router from "../../router";
+import { ref, onMounted, reactive, computed } from "vue";
+import { ElNotification, ElMessageBox } from "element-plus";
+import { mapGetters } from "vuex";
+import { useRoute } from "vue-router";
+
+const route = useRoute();
+const tableRef = ref(null);
+const transType = ref(1);
+const type = ref(1);
+const term = ref("");
+function changeTransType(t) {
+  transType.value = t;
+}
+
+function changeType(s) {
+  type.value = s;
+}
+
+onMounted(() => {});
+</script>
+
+<style scoped></style>

+ 4 - 4
src/views/voyage/voyageDetail.vue

@@ -1280,7 +1280,7 @@
               <el-icon
                 class="pointer mr20"
                 color="#fff"
-                size="20"
+                :size="20"
                 @click="checkPolicy(item)"
               >
                 <ZoomIn />
@@ -1289,7 +1289,7 @@
                 @click="deletePolicy(item, index)"
                 class="pointer"
                 color="#fff"
-                size="20"
+                :size="20"
               >
                 <Delete />
               </el-icon>
@@ -2242,8 +2242,8 @@ function initMap() {
     zoom: 16, //级别
     center: [longitude, latitude], //中心点坐标
     mapStyle: "amap://styles/f48d96805f5fa7f5aada657c5ee37017",
-    zoomEnable: false,
-    dragEnable: false,
+    zoomEnable: true,
+    dragEnable: true,
   });
   let toolBar = new AMap.ToolBar({
     position: {