'-'
zhangwei
2025-08-14 89c7ddeb37bb9355e7272f9ed2bfdeb8faabdedc
'-'
13个文件已修改
3个文件已添加
901 ■■■■■ 已修改文件
src/api/item/index.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/upload/index.ts 41 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/util.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.html 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.ts 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/modules/item.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/commonFunction.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dept/detail.vue 220 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dept/form.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dept/index.vue 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dept/uploadform.vue 325 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dept/utils/detail.tsx 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dept/utils/hook.tsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dept/utils/types.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/item/index.ts
@@ -81,8 +81,10 @@
};
// 获取非政府订单处理详情
export const getTenderOrderDetail = () => {
  return http.request<Result>("get", baseUrlApi("/api/tenderOrder/detail"));
export const getTenderOrderDetail = params => {
  return http.request<Result>("get", baseUrlApi("/api/tenderOrder/detail"), {
    params
  });
};
// 下载非政府订单处理数据导入模板
@@ -168,3 +170,25 @@
    data
  });
};
// 采购代理人发布招标
export const fabuzhaobiao = (data?: object) => {
  return http.request<Result>(
    "post",
    baseUrlApi("/api/tenderOrder/fabuzhaobiao"),
    {
      data
    }
  );
};
// 采购代理人发布招标
export const changezhaobiao = (data?: object) => {
  return http.request<Result>(
    "post",
    baseUrlApi("/api/tenderOrder/changezhaobiao"),
    {
      data
    }
  );
};
src/api/upload/index.ts
@@ -5,19 +5,38 @@
 */
import { http } from "@/utils/http";
import { baseUrlApi } from "../util";
import { baseUrlApi, uploadUrlApi } from "../util";
import type { Result } from "../types";
//上传和识别营业执照
export const upBizLicense = (data?: object) => {
  return http.request<Result>("post", baseUrlApi("/api/upFile/upBizLicense"), {
    data
  });
// 获取上传Token
export const getUploadToken = () => {
  return http.request<Result>("get", baseUrlApi("/api/upFile/token"));
};
// 上传身份证
export const uploadIdCord = (params?: object) => {
  return http.request<Result>("post", baseUrlApi("/api/upFile/uploadIdCord"), {
    params
  });
export const uploadFileAli11 = (data, key) => {
  return http.request<Result>(
    "post",
    uploadUrlApi(`/${key}`),
    { data },
    {
      headers: {
        "Content-Type": "multipart/form-data", // 设置内容类型为 multipart/form-data,这对于文件上传是必须的
        "Access-Control-Allow-Origin": "*"
      }
    }
  );
};
export const uploadFileAli = (data, url) => {
  return http.request<Result>(
    "post",
    `${url}`,
    { data },
    {
      headers: {
        "Content-Type": "multipart/form-data", // 设置内容类型为 multipart/form-data,这对于文件上传是必须的
        "Access-Control-Allow-Origin": "*"
      }
    }
  );
};
src/api/util.ts
@@ -3,3 +3,7 @@
  process.env.NODE_ENV === "development"
    ? `/api${url}`
    : `192.168.18.52:5005${url}`;
export const uploadUrlApi = (url: string) =>
  process.env.NODE_ENV === "development"
    ? `/oss${url}`
    : `192.168.18.52:5005${url}`;
src/router/index.html
New file
@@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>服务端生成签名上传文件到OSS</title>
</head>
<body>
<div class="container">
    <form>
        <div class="mb-3">
            <label for="file" class="form-label">选择文件:</label>
            <input type="file" class="form-control" id="file" name="file" required />
        </div>
        <button type="submit" class="btn btn-primary">上传</button>
    </form>
</div>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function () {
    const form = document.querySelector("form");
    const fileInput = document.querySelector("#file");
    form.addEventListener("submit", (event) => {
        event.preventDefault();
        const file = fileInput.files[0];
        if (!file) {
            alert('请选择一个文件再上传。');
            return;
        }
        const filename = file.name;
        fetch("http://192.168.18.52:5005/api/upFile/token", { method: "GET",headers: {
    "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjY5MDg3Njg4MTk3MzMxNywiVGVuYW50SWQiOjEzMDAwMDAwMDAwMDEsIkFjY291bnQiOiIxMzIxOTEyMjMyMCIsIlJlYWxOYW1lIjoi5byg5LiJIiwiVXNlclR5cGUiOiJDdXN0b21lciIsIkN1c3RvbWVyTG9nb2luVHlwZSI6IkRMSkciLCJpYXQiOjE3NTUxMzYwODcsIm5iZiI6MTc1NTEzNjA4NywiZXhwIjoxNzU1NzQwODg3LCJpc3MiOiJBZG1pbi5ORVQiLCJhdWQiOiJBZG1pbi5ORVQifQ.bnB_Tlq856XfOMhtVMI8QP1TTG7JqgnEFeXTdha3Sso"
  } })
            .then((response) => {
                if (!response.ok) {
                    throw new Error("获取签名失败");
                }
                return response.json();
            })
            .then((data) => {
                let formData = new FormData();
                formData.append("success_action_status", "200");
                formData.append("policy", data.policy);
                formData.append("x-oss-signature", data.signature);
                formData.append("x-oss-signature-version", "OSS4-HMAC-SHA256");
                formData.append("x-oss-credential", data.x_oss_credential);
                formData.append("x-oss-date", data.x_oss_date);
                formData.append("key", data.dir + file.name); // 文件名
                formData.append("x-oss-security-token", data.security_token);
                formData.append("file", file); // file 必须为最后一个表单域
                return fetch(`https://feizhengcai.oss-cn-chengdu.aliyuncs.com/cylsg/20250814094826_8462.pdf`, {
                    method: "POST",
                    body: formData
                });
            })
            .then((response) => {
                if (response.ok) {
                    console.log("上传成功");
                    alert("文件已上传");
                } else {
                    console.log("上传失败", response);
                    alert("上传失败,请稍后再试");
                }
            })
            .catch((error) => {
                console.error("发生错误:", error);
            });
    });
});
</script>
</body>
</html>
src/router/index.ts
@@ -107,7 +107,7 @@
/** 路由白名单 */
const whiteList = ["/login"];
const noLoginList = ["/index", "/register", "/registersucess"];
const noLoginList = ["/index", "/register", "/registernav", "/registersucess"];
// const whiteList = [];
const { VITE_HIDE_HOME } = import.meta.env;
@@ -194,11 +194,11 @@
    }
  } else {
    if (to.path !== "/login") {
      if (whiteList.indexOf(to.path) !== -1) {
      if (noLoginList.indexOf(to.path) !== -1) {
        next();
      } else {
        removeToken();
        next({ path: "/login" });
        next({ path: "/index" });
      }
    } else {
      next();
src/router/modules/item.ts
@@ -35,7 +35,7 @@
        name: "aboutItem",
        meta: {
          title: "关注项目",
          roles: ["DLJG", "GYS"]
          roles: ["GYS"]
          // showLink:false
        }
      }
src/utils/commonFunction.ts
@@ -81,7 +81,7 @@
    return Str.replace(/^\s+|\s+$/g, "");
  };
  // 点击复制文本
  // const copyText = (text: string) => {
  const copyText = (text: string) => {
  //   return new Promise((resolve, reject) => {
  //     try {
  //       //复制
@@ -95,7 +95,7 @@
  //       reject(e);
  //     }
  //   });
  // };
  };
  // 去掉Html标签(取前面5个字符)
  const removeHtmlSub = (value: string) => {
    const str = value.replace(/<[^>]+>/g, "");
src/views/home/index.vue
@@ -161,7 +161,7 @@
              class="hover:cursor-pointer"
              @click="toApply(item)"
              ><span v-if="item.name !== '评审专家'">申请</span></el-link
            ><span v-if="item.name !== '评审专家'" class="m-2">|</span>
            ><span v-if="!state.accessToken" class="m-2">|</span>
            <el-link
              v-if="state.rolesCode.indexOf(item.code) !== -1"
              :underline="false"
src/views/system/dept/detail.vue
@@ -1,45 +1,57 @@
<script setup lang="ts">
<script setup lang="tsx">
import { ref, reactive, onMounted, computed, PropType } from "vue";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { useDept } from "./utils/hook";
import { FormItemProps } from "./utils/types";
import { useDetail } from "./utils/detail";
import { getTenderOrderDetail } from "@/api/item/index";
const isLoading = ref(false);
import { addDialog } from "@/components/ReDialog";
interface Emits {
  (e: "backListPage"): void;
}
const emit = defineEmits<Emits>();
const props = defineProps({
  nowInfo: Object as PropType<FormItemProps>
  nowID: null
});
import Delete from "~icons/ep/delete";
import EditPen from "~icons/ep/edit-pen";
import IconParkSolidBack from "~icons/icon-park-solid/back";
const {
  state,
  loading,
  selectedNum,
  dataList,
  onSearch,
  resetForm,
  openDialog,
  handleDelete,
  handleSelectionChange,
  handleSizeChange,
  handleCurrentChange,
  pagination,
  checkboxAsRadio,
  onSelectionCancel,
  onbatchDel
} = useDept(ref({}));
const { openDialog } = useDept(ref({}));
const { openUploadDialog, state } = useDetail();
defineOptions({
  name: "itemdetail"
});
onMounted(() => {
  console.log(props.nowInfo);
onMounted(async () => {
  let res = await getTenderOrderDetail({ id: props.nowID });
  state.nowInfo = res.result;
});
const backListPage = () => {
  emit("backListPage");
};
const previewPdf = pdfUrl => {
  addDialog({
    width: "80%",
    title: "确认信息",
    contentRenderer: () => (
      <iframe
        type="application/pdf"
        src={pdfUrl}
        width="800"
        height="600"
      ></iframe>
    ), // jsx 语法 (注意在.vue文件启用jsx语法,需要在script开启lang="tsx")
    closeCallBack: ({ options, args }) => {
      // options.props 是响应式的
      // const { formInline } = options.props as FormProps;
      // const text = `姓名:${formInline.user} 城市:${formInline.region}`;
      if (args?.command === "cancel") {
        // 您点击了取消按钮
        // active.value -= 1;
      } else if (args?.command === "sure") {
      } else {
      }
    }
  });
};
</script>
@@ -48,21 +60,14 @@
    <el-card>
      <template v-slot:header>
        <div class="flex justify-between">
          <div>{{ nowInfo.projectName }}</div>
          <div>{{ state.nowInfo.projectName }}</div>
          <div>
            <el-button
              class="reset-margin"
              link
              type="primary"
              :icon="useRenderIcon(EditPen)"
              @click="openDialog('修改', nowInfo)"
            />
            <el-button
              class="reset-margin"
              link
              type="primary"
              :icon="useRenderIcon(Delete)"
              @click="openDialog('修改', nowInfo)"
              @click="openDialog('修改', state.nowInfo)"
            />
            <el-button
              class="reset-margin"
@@ -75,27 +80,154 @@
        </div>
      </template>
      <div>
        <el-descriptions class="margin-top" :column="3" :size="size" border>
        <el-descriptions class="margin-top" :column="3" border>
          <el-descriptions-item label="项目编号">
            {{ nowInfo.projectCode }}
            {{ state.nowInfo.projectCode }}
          </el-descriptions-item>
          <el-descriptions-item label="行业品目"
            >18100000000</el-descriptions-item
          >
          <el-descriptions-item label="行业品目">
            {{ state.nowInfo.hangyepinmuName }}
          </el-descriptions-item>
          <el-descriptions-item label="采购方式">
            {{ nowInfo.caigoufangshiName }}
            {{ state.nowInfo.caigoufangshiName }}
          </el-descriptions-item>
          <el-descriptions-item label="联合体投标">
            {{ nowInfo.lianhetitoubiao }}
            {{ state.nowInfo.lianhetitoubiao }}
          </el-descriptions-item>
          <el-descriptions-item label="Remarks">
            <el-tag size="small">School</el-tag>
          <!-- <el-descriptions-item label="Remarks">
          </el-descriptions-item>
          <el-descriptions-item label="Address" />
          <el-descriptions-item label="Address" /> -->
        </el-descriptions>
      </div>
    </el-card>
    <div class="mt-4.5">
      <el-card>
        <el-tabs
          tab-position="left"
          style="height: calc(100vh - 380px)"
          class="demo-tabs"
          type="border-card"
        >
          <el-tab-pane label="公告文件上传">
            <h4>公告文件上传</h4>
            <el-divider />
            <el-scrollbar height="calc(100vh - 430px)">
              <div>
                <el-form label-width="auto" style="max-width: 1000px">
                  <el-form-item label="项目信息:">
                    投标报名开始时间:
                    {{ state.nowInfo.toubiaoStartDate ?? "暂无" }}
                    投标报名截止时间:
                    {{ state.nowInfo.toubiaoEndDate }}
                    开标时间:{{ state.nowInfo.kaibiaoDate }}
                  </el-form-item>
                  <el-form-item label="上传公告:">
                    <el-button
                      :disabled="!!state.nowInfo.zhaobiaowenjian"
                      type="primary"
                      plain
                      size="small"
                      @click="openUploadDialog('上传')"
                    >
                      上传公告
                    </el-button>
                    <el-button
                      v-if="state.nowInfo.zhaobiaowenjian"
                      type="primary"
                      plain
                      size="small"
                      @click="openUploadDialog('上传变更', state.nowInfo)"
                    >
                      上传变更公告
                    </el-button>
                  </el-form-item>
                  <el-form-item v-if="state.nowInfo.zhaobiaowenjian" label=" ">
                    <div class="border-1 w-[100%] rounded-md p-3">
                      <p>{{ state.nowInfo.projectName }}</p>
                      <p>上传时间:2025-8-12 13:41:00</p>
                      <el-button
                        type="primary"
                        plain
                        size="small"
                        @click="previewPdf(state.nowInfo.zhaobiaowenjian)"
                      >
                        点击预览
                      </el-button>
                      <el-button type="primary" plain size="small">
                        修改标题
                      </el-button>
                    </div>
                  </el-form-item>
                  <!-- <el-form-item label="磋商文件:">
                    <el-button type="primary" plain size="small">
                      上传磋商文件
                    </el-button>
                  </el-form-item>
                  <el-form-item label=" ">
                    <div class="border-1 w-[100%] rounded-md p-3 pl-3">
                      <p>磋商文件名称</p>
                      <p>发布时间:2025-8-12 13:41:00</p>
                      <el-button type="primary" plain size="small">
                        点击预览
                      </el-button>
                      <el-button type="primary" plain size="small">
                        点击下载
                      </el-button>
                    </div>
                  </el-form-item> -->
                  <!-- <el-form-item label="澄清与答疑文件:">
                    <el-button type="primary" plain size="small">
                      文件管理
                    </el-button>
                  </el-form-item> -->
                </el-form>
              </div>
              <!-- <el-row :gutter="10" align="right">
              <el-col :span="3">
                <el-text size="large"> 项目信息: </el-text>
              </el-col>
              <el-col :span="20">
                <el-button type="primary" plain>上传公告</el-button>
                <el-button type="primary" plain>上传变更公告</el-button>
              </el-col>
            </el-row>
            <el-row :gutter="10" align="right">
              <el-col :span="3">
                <el-text size="large"> 公告上传: </el-text>
              </el-col>
              <el-col :span="20">
                <el-button type="primary" plain>上传公告</el-button>
                <el-button type="primary" plain>上传变更公告</el-button>
              </el-col>
            </el-row>
            <el-row :gutter="10" align="right">
              <el-col :span="3">
                <el-text size="large"> 磋商文件: </el-text>
              </el-col>
              <el-col :span="20">
                <el-button type="primary" plain>上传磋商文件</el-button>
              </el-col>
            </el-row>
            <el-row :gutter="10" align="right">
              <el-col :span="3">
                <el-text size="large"> 澄清与答疑文件: </el-text>
              </el-col>
              <el-col :span="20">
                <el-button type="primary" plain>文件管理</el-button>
              </el-col>
            </el-row> -->
            </el-scrollbar>
          </el-tab-pane>
          <!-- <el-tab-pane label="开评标管理">开评标管理</el-tab-pane> -->
          <el-tab-pane label="中标公示">中标公示</el-tab-pane>
          <el-tab-pane label="项目完结">项目完结</el-tab-pane>
        </el-tabs>
      </el-card>
    </div>
  </div>
</template>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.el-divider--horizontal {
  margin: 11px 0;
}
</style>
src/views/system/dept/form.vue
@@ -99,7 +99,7 @@
        <el-form-item label="项目编号">
          <el-input
            v-model="newFormInline.projectCode"
            disabled
            clearable
            placeholder="请输入项目编号"
          />
src/views/system/dept/index.vue
@@ -8,7 +8,7 @@
import EditPen from "~icons/ep/edit-pen";
import Refresh from "~icons/ep/refresh";
import AddFill from "~icons/ri/add-circle-line";
import IonEnterOutline from "~icons/ion/enter-outline";
import IconamoonEnterDuotone from "~icons/iconamoon/enter-duotone";
import { useUserStoreHook } from "@/store/modules/user";
defineOptions({
@@ -18,7 +18,9 @@
const formRef = ref();
const tableRef = ref();
const isList = ref(true);
const nowItem = ref({});
const nowItem = ref({
  id: ""
});
const {
  form,
  state,
@@ -317,10 +319,10 @@
                link
                type="primary"
                :size="size"
                :icon="useRenderIcon(IonEnterOutline)"
                :icon="useRenderIcon(IconamoonEnterDuotone)"
                @click="enterDetail(row)"
              />
              <!-- <el-popconfirm
              <el-popconfirm
                :title="`是否确认删除项目名称为${row.projectName}的这条数据`"
                @confirm="handleDelete(row)"
              >
@@ -333,14 +335,14 @@
                    :icon="useRenderIcon(Delete)"
                  />
                </template>
              </el-popconfirm> -->
              </el-popconfirm>
            </template>
          </pure-table>
        </template>
      </PureTableBar>
    </template>
    <template v-else>
      <detail :now-info="nowItem" @backListPage="backListPage" />
      <detail :nowID="nowItem?.id" @backListPage="backListPage" />
    </template>
  </div>
</template>
src/views/system/dept/uploadform.vue
New file
@@ -0,0 +1,325 @@
<script setup lang="ts">
import { onMounted, ref, reactive } from "vue";
import ReCol from "@/components/ReCol";
import { message } from "@/utils/message";
import { TenderProps } from "./utils/types";
import { useDetail } from "./utils/detail";
import { usePublicHooks } from "../hooks";
import type {
  FormInstance,
  FormRules,
  UploadProps,
  UploadInstance,
  UploadRawFile
} from "element-plus";
import { baseUrlApi } from "@/api/util";
import { genFileId } from "element-plus";
const { openUploadDialog, state, generateTimestampWithRandom } = useDetail();
import { getUploadToken, uploadFileAli, uploadFileAli11 } from "@/api/upload";
const props = withDefaults(defineProps<TenderProps>(), {
  formInline: () => ({
    id: "", // 主键Id(必填)
    projectName: "",
    toubiaoStartDate: "", // 投标报名开始时间(必填,格式:yyyy-MM-dd HH:mm:ss)
    toubiaoEndDate: "", // 投标报名结束时间(必填,格式:yyyy-MM-dd HH:mm:ss)
    kaibiaoDate: "", // 开标时间(必填,格式:yyyy-MM-dd HH:mm:ss)
    zhaobiaowenjian: "", // 招标文件(必填,长度1-512字符)
    biangengwenjian: "",
    fujian: "", // 附件(可选,最大长度512字符,可为空)
    kaibiaodidian: "" // 开标地点(必填,长度1-250字符)
  }),
  isChange: null
});
const gonggaoRules = reactive({
  projectName: [{ required: true, message: "请输入标题", trigger: "change" }],
  toubiaoStartDate: [
    { required: true, message: "请选择投标报名开始时间", trigger: "change" }
  ],
  toubiaoEndDate: [
    { required: true, message: "请选择投标报名结束时间", trigger: "change" },
    {
      validator: (rule, value, callback) => {
        if (value && newFormInline.value.toubiaoStartDate) {
          if (
            new Date(value).getTime() <
            new Date(newFormInline.value.toubiaoStartDate).getTime()
          ) {
            callback(new Error("结束时间不能早于开始时间"));
          } else {
            callback();
          }
        } else {
          callback();
        }
      },
      trigger: "change"
    }
  ],
  kaibiaoDate: [
    { required: true, message: "请选择开标时间", trigger: "change" },
    {
      validator: (rule, value, callback) => {
        if (value && newFormInline.value.toubiaoEndDate) {
          if (
            new Date(value).getTime() <
            new Date(newFormInline.value.toubiaoEndDate).getTime()
          ) {
            callback(new Error("开标时间不能早于投标结束时间"));
          } else {
            callback();
          }
        } else {
          callback();
        }
      },
      trigger: "change"
    }
  ],
  zhaobiaowenjian: [
    { required: true, message: "请上传招标文件", trigger: "blur" }
  ],
  biangengwenjian: [
    { required: true, message: "请上传变更文件", trigger: "blur" }
  ],
  fujian: [{ max: 512, message: "附件长度不能超过512个字符", trigger: "blur" }],
  kaibiaodidian: [
    { required: true, message: "请输入开标地点", trigger: "blur" }
  ]
});
const isLoading = ref(false);
const ruleFormRef = ref();
const { switchStyle } = usePublicHooks();
const newFormInline = ref(props.formInline);
const validateForm = reactive({
  fileList: [],
  date: ""
});
const upload = ref<UploadInstance>();
const file = ref(null);
const uploadedUrl = ref("");
function getRef() {
  return ruleFormRef.value;
}
const handleAvatarSuccess: UploadProps["onSuccess"] = (
  response,
  uploadFile
) => {
  if (response.code == "200") {
    // state.ruleForm.businessLicense = URL.createObjectURL(uploadFile.raw!);
    if (props.isChange) {
      newFormInline.value.biangengwenjian = response.result;
    } else {
      newFormInline.value.zhaobiaowenjian = response.result;
    }
  } else {
    message(response.message, {
      type: "error"
    });
  }
  isLoading.value = false;
};
const handleChange = file => {
  console.log(file);
  if (file.status !== "ready") return;
  let suffName = file.name.substring(file.name.lastIndexOf(".") + 1);
  const extension = suffName === "pdf";
  // const isLt10M = file.size / 1024 / 1024 < 10;
  if (!extension) {
    message(`仅支持pdf格式,请上传pdf`, {
      type: "error"
    });
    validateForm.fileList = [];
    return false;
  }
  fetchCredentials(file);
  // upload.value!.submit();
};
// state.formDataNew = {
//   policy: res.result.policy, //表单域
//   "x-oss-signature-version": res.result.x_oss_signature_version, //指定签名的版本和算法
//   "x-oss-credential": res.result.x_oss_credential, //指明派生密钥的参数集
//   "x-oss-date": res.result.x_oss_date, //请求的时间
//   "x-oss-signature": res.result.signature, //签名认证描述信息
//   "x-oss-security-token": res.result.security_token, //安全令牌
//   success_action_status: "200" //上传成功后响应状态码
// };
const fetchCredentials = async file => {
  // 这里应调用你自己的后端接口获取临时凭证
  let res = await getUploadToken();
  if (res.code == 200) {
    let keyVal = generateTimestampWithRandom(res.result.DirPath, file.name);
    let formData = new FormData();
    formData.append("policy", res.result.policy);
    formData.append(
      "x-oss-signature-version",
      res.result.x_oss_signature_version
    );
    formData.append("x-oss-credential", res.result.x_oss_credential);
    formData.append("x-oss-date", res.result.x_oss_date);
    // formData.append("Signature", res.result.signature);
    formData.append("x-oss-signature", res.result.signature);
    formData.append("x-oss-security-token", res.result.security_token);
    // formData.append("x-oss-content-type", "application/pdf");
    formData.append("success_action_status", "200");
    formData.append("key", keyVal); // 文件名
    formData.append("file", file.raw); // file 必须为最后一个表单域
    uploadFileAli(formData, res.result.url).then(res => {
      let path = res.result.url + "/" + res.result.DirPath + keyVal;
      console.log(path);
      if (props.isChange) {
        newFormInline.value.biangengwenjian = path;
      } else {
        newFormInline.value.zhaobiaowenjian = path;
      }
    });
  }
};
const beforeAvatarUpload: UploadProps["beforeUpload"] = rawFile => {
  isLoading.value = true;
  if (rawFile.type !== "application/pdf") {
    message(`仅支持pdf格式,请上传pdf`, {
      type: "error"
    });
    return false;
  }
  // else if (rawFile.size / 1024 / 1024 > 2) {
  //   message(`图片大小不能超过 2MB!`, {
  //     type: "error"
  //   });
  //   return false;
  // }
  return true;
};
const handleExceed: UploadProps["onExceed"] = (files, fileList) => {
  upload.value!.clearFiles();
  const file = files[0] as UploadRawFile;
  file.uid = genFileId();
  upload.value!.handleStart(file);
};
defineExpose({ getRef });
onMounted(async () => {});
</script>
<template>
  <el-form
    ref="ruleFormRef"
    :model="newFormInline"
    :rules="gonggaoRules"
    label-width="140px"
  >
    <el-row :gutter="30">
      <div v-if="props.isChange">
        <re-col :value="24" :xs="24" :sm="24">
          <el-form-item label="标题" prop="projectName">
            <el-input
              v-model="newFormInline.projectName"
              clearable
              placeholder="请输入标题"
            />
          </el-form-item>
        </re-col>
        <re-col :value="24" :xs="24" :sm="24">
          <el-form-item label="变更文件" prop="biangengwenjian">
            <el-upload
              ref="upload"
              v-model:file-list="validateForm.fileList"
              :limit="1"
              :on-exceed="handleExceed"
              :on-success="handleAvatarSuccess"
              :before-upload="beforeAvatarUpload"
              :headers="state.headers"
              :auto-upload="false"
              accept=".pdf"
              @change="handleChange"
            >
              <el-button type="primary">点击上传</el-button>
              <template #tip>
                <div class="el-upload__tip">
                  只能上传单个文件,仅支持pdf格式
                </div>
              </template>
            </el-upload>
          </el-form-item>
        </re-col>
      </div>
      <re-col :value="24" :xs="24" :sm="24">
        <el-form-item label="投标报名开始时间" prop="toubiaoStartDate">
          <el-date-picker
            v-model="newFormInline.toubiaoStartDate"
            type="datetime"
            clearable
            placeholder="请选择投标报名开始时间"
            value-format="YYYY-MM-DD HH:mm:ss"
          />
        </el-form-item>
      </re-col>
      <re-col :value="24" :xs="24" :sm="24">
        <el-form-item label="投标报名结束时间" prop="toubiaoEndDate">
          <el-date-picker
            v-model="newFormInline.toubiaoEndDate"
            type="datetime"
            clearable
            placeholder="请选择投标报名结束时间"
            value-format="YYYY-MM-DD HH:mm:ss"
          />
        </el-form-item>
      </re-col>
      <re-col :value="24" :xs="24" :sm="24">
        <el-form-item label="开标时间" prop="kaibiaoDate">
          <el-date-picker
            v-model="newFormInline.kaibiaoDate"
            type="datetime"
            clearable
            placeholder="请选择开标时间"
            value-format="YYYY-MM-DD HH:mm:ss"
          />
        </el-form-item>
      </re-col>
      <re-col v-if="!props.isChange" :value="24" :xs="24" :sm="24">
        <el-form-item label="招标文件" prop="zhaobiaowenjian">
          <el-upload
            ref="upload"
            v-model:file-list="validateForm.fileList"
            :limit="1"
            :on-exceed="handleExceed"
            :auto-upload="false"
            :on-success="handleAvatarSuccess"
            :before-upload="beforeAvatarUpload"
            :headers="state.headers"
            accept=".pdf"
            @change="handleChange"
          >
            <el-button type="primary">点击上传</el-button>
            <template #tip>
              <div class="el-upload__tip">只能上传单个文件,仅支持pdf格式</div>
            </template>
          </el-upload>
        </el-form-item>
      </re-col>
      <re-col :value="24" :xs="24" :sm="24">
        <el-form-item label="开标地点" prop="kaibiaodidian">
          <el-input
            v-model="newFormInline.kaibiaodidian"
            clearable
            placeholder="请输入开标地点"
          />
        </el-form-item>
      </re-col>
    </el-row>
  </el-form>
</template>
<style lang="scss" scoped>
:deep .el-date-editor.el-input,
.el-date-editor.el-input__wrapper {
  width: 100%;
}
.upload__tip {
  width: 100%;
}
</style>
src/views/system/dept/utils/detail.tsx
New file
@@ -0,0 +1,140 @@
import tenderForm from "../uploadform.vue";
import { ref, h, reactive } from "vue";
import { addDialog } from "@/components/ReDialog";
import type { TenderInfo } from "./types";
import { message } from "@/utils/message";
import { cloneDeep, deviceDetection } from "@pureadmin/utils";
import type { FormItemProps } from "./types";
import { getToken } from "@/utils/auth";
import {
  fabuzhaobiao,
  changezhaobiao,
  getTenderOrderDetail
} from "@/api/item/index";
const formRef = ref();
export function useDetail() {
  function openUploadDialog(title = "上传", row?: TenderInfo) {
    addDialog({
      title: `${title}公告`,
      props: {
        formInline: {
          tenderId: title == "上传变更" ? row?.id : "",
          id: title == "上传" ? row?.id : "",
          projectName: row?.projectName ?? "",
          toubiaoStartDate: row?.toubiaoStartDate ?? "", //投标报名开始时间
          toubiaoEndDate: row?.toubiaoEndDate ?? "", //投标报名结束时间
          kaibiaoDate: row?.kaibiaoDate ?? "", //开标时间
          biangengwenjian: "", //招标文件
          fujian: row?.fujian ?? "", //附件
          kaibiaodidian: row?.kaibiaodidian ?? "" //开标地点
        }
      },
      width: "30%",
      draggable: true,
      fullscreen: deviceDetection(),
      fullscreenIcon: true,
      sureBtnLoading: true,
      closeOnClickModal: false,
      contentRenderer: () =>
        h(tenderForm, {
          ref: formRef,
          formInline: null,
          isChange: title == "上传" ? false : true
        }),
      beforeSure: (done, { options, closeLoading }) => {
        const FormRef = formRef.value.getRef();
        const curData = cloneDeep(options.props.formInline as TenderInfo);
        async function chores() {
          // message(`您${title}了项目名称为${curData.projectName}的这条数据`, {
          //   type: "success"
          // });
          // curData.dingbiaoguize = curData.dingbiaoguize.join("");
          let res;
          if (title == "上传") {
            curData.id = state.nowInfo.id;
            res = await fabuzhaobiao(curData);
          } else {
            res = await changezhaobiao(curData);
          }
          if (res.code == "200") {
            getTenderOrderDetail({ id: row?.id });
            done(); // 关闭弹框
          } else {
            closeLoading();
            message(res.message, {
              type: "error"
            });
          }
        }
        FormRef.validate((valid, obj) => {
          if (valid) {
            // 表单规则校验通过
            if (title === "新增") {
              // 实际开发先调用新增接口,再进行下面操作
              chores();
            } else {
              // 实际开发先调用修改接口,再进行下面操作
              chores();
            }
          } else {
            closeLoading();
            const fail = [];
            for (const key in obj) {
              fail.push(obj[key][0].message);
            }
            message(fail[0], {
              type: "warning"
            });
            return false;
          }
        });
      }
    });
  }
  const state = reactive({
    headers: {
      // Accept: "application/json, text/plain, */*",
      // "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
      // "X-Requested-With": "XMLHttpRequest",
      Authorization: `Bearer ${getToken()?.accessToken}`
    },
    nowInfo: Object as PropType<FormItemProps>,
    formDataNew: {}
  });
  //生成时间戳+随机数
  // 生成时间戳+随机数的函数
  function generateTimestampWithRandom(dirPath, filename) {
    // 获取当前日期和时间
    const now = new Date();
    // 获取年份,确保为四位数
    const year = now.getFullYear();
    // 获取月份,确保为两位数
    const month = String(now.getMonth() + 1).padStart(2, "0");
    // 获取日期,确保为两位数
    const day = String(now.getDate()).padStart(2, "0");
    // 获取小时,确保为两位数
    const hours = String(now.getHours()).padStart(2, "0");
    // 获取分钟,确保为两位数
    const minutes = String(now.getMinutes()).padStart(2, "0");
    // 获取秒数,确保为两位数
    const seconds = String(now.getSeconds()).padStart(2, "0");
    // 拼接时间戳
    const timestamp = `${year}${month}${day}${hours}${minutes}${seconds}`;
    // 生成一个 0 到 9999 之间的随机数,并格式化为四位数
    const randomNumber = String(Math.floor(Math.random() * 10000)).padStart(
      4,
      "0"
    );
    // 拼接时间戳和随机数
    return (
      `${dirPath}/${timestamp}_${randomNumber}` + filename.match(/\.[^.]+$/)
    );
  }
  return {
    openUploadDialog,
    state,
    generateTimestampWithRandom
  };
}
src/views/system/dept/utils/hook.tsx
@@ -331,8 +331,6 @@
  }
  function openDialog(title = "新增", row?: FormItemProps) {
    console.log(row, "-");
    addDialog({
      title: `${title}项目`,
      props: {
src/views/system/dept/utils/types.ts
@@ -3,6 +3,7 @@
  projectCode: string; // 项目编号(必填)
  projectName: string; // 项目名称(必填)
  hangyepinmu: any | null; // 行业品目(可选)
  hangyepinmuName?: any | null;
  caigoufangshi: any | null; // 采购方式(可选)
  caigoufangshiName: any | null; // 采购方式(可选)
  caigouyusuan: any | null; // 采购预算(可选)
@@ -33,8 +34,33 @@
  dailiXiangmujingli: string | null; // 代理机构项目经理(可选)
  dailijingliLianxidianhua: string | null; // 代理机构项目经理联系电话(可选)
}
// 招标信息类型定义
interface TenderInfo {
  // 主键Id
  id: string;
  // 投标报名开始时间,格式为yyyy-MM-dd HH:mm:ss
  projectName: string;
  toubiaoStartDate: string;
  // 投标报名结束时间,格式为yyyy-MM-dd HH:mm:ss
  toubiaoEndDate: string;
  // 开标时间,格式为yyyy-MM-dd HH:mm:ss
  kaibiaoDate: string;
  // 招标文件,长度1-512字符
  zhaobiaowenjian: string;
  biangengwenjian: string;
  // 附件,可为空,最大长度512字符
  fujian?: string;
  // 开标地点,长度1-250字符
  kaibiaodidian: string;
}
interface FormProps {
  formInline: FormItemProps;
}
export type { FormItemProps, FormProps };
interface TenderProps {
  formInline: TenderInfo;
  isChange: boolean;
}
export type { FormItemProps, FormProps, TenderInfo, TenderProps };
vite.config.ts
@@ -30,6 +30,11 @@
          target: "http://192.168.18.52:5005",
          changeOrigin: true,
          rewrite: path => path.replace(/^\/api/, "")
        },
        "/oss": {
          target: "https://feizhengcai.oss-cn-chengdu.aliyuncs.com",
          changeOrigin: true,
          rewrite: path => path.replace(/^\/oss/, "")
        }
      },
      // 预热文件以提前转换和缓存结果,降低启动期间的初始页面加载时长并防止转换瀑布