| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548 |
- <template>
- <div class="pblm-detail-wrapper">
- <van-form @submit="onSubmit">
- <div class="pblm-detail-label">
- <span>问题详情</span>
- <!-- <van-button size="mini" type="primary" @click="showTacObjPblmstbPopup()">选择违规事项</van-button> -->
- </div>
- <van-cell-group inset>
- <van-field v-model="tacObjPblmstb.nm" placeholder="请输入对象名称" label="对象名称" />
- <van-field
- v-model="tacObjPblmstb.inspPblmsName"
- is-link
- readonly
- label="问题类别"
- placeholder="选择对象"
- @click="showPicker = true"
- />
- <van-popup v-model:show="showPicker" destroy-on-close round position="bottom">
- <van-picker
- :columns="columns"
- @change="changeC1"
- @cancel="showPicker = false"
- @confirm="onConfirm"
- />
- </van-popup>
- <van-field
- v-model="tacObjPblmstb.checkPoint"
- is-link
- readonly
- label="问题项目"
- placeholder="选择对象"
- @click="showPicker1 = true"
- />
- <van-popup v-model:show="showPicker1" destroy-on-close round position="bottom">
- <van-picker
- :columns="columns1"
- @change="changeC2"
- @cancel="showPicker1 = false"
- @confirm="onConfirm1"
- />
- </van-popup>
- <van-field
- v-model="tacObjPblmstb.pblmDesc"
- is-link
- readonly
- label="问题描述"
- placeholder="选择对象"
- @click="showPicker2 = true"
- />
- <van-popup v-model:show="showPicker2" destroy-on-close round position="bottom">
- <van-picker
- :columns="columns2"
- @cancel="showPicker2 = false"
- @confirm="onConfirm2"
- />
- </van-popup>
- <van-field v-model="tacObjPblmstb.inspPblmOrgName" placeholder="请输入对象名称" label="问题责任单位" />
- <van-field v-model="tacObjPblmstb.inspPblmDesc" placeholder="请输入对象名称" label="详细描述" rows="1" type="textarea"/>
- <van-field
- v-model="tacObjPblmstb.ifCasePblm"
- is-link
- readonly
- label="是否典型问题"
- placeholder=""
- @click="showPicker3 = true"
- />
- <van-popup v-model:show="showPicker3" destroy-on-close round position="bottom">
- <van-picker
- :columns="columns3"
- :model-value="pickerValue3"
- @cancel="showPicker3 = false"
- @confirm="onConfirm3"
- />
- </van-popup>
- <van-field v-model="tacObjPblmstb.score" readonly label="扣分值" />
- <van-field v-model="tacObjPblmstb.persName" readonly label="上报人" />
- <van-field v-model="tacObjPblmstb.collTime" readonly label="上报时间" />
- <div style="display: flex;align-items: center;margin-top: -1%;">
- <div style="width: 20%;text-align: center;font-weight: 700;font-size: 14px;">上传照片</div>
- <van-uploader v-model="fileList" :after-read="afterRead" style=""/>
- </div>
- </van-cell-group>
- <div style="margin: 16px;">
- <van-button v-if="objData.state !== '2'" @click="save" block native-type="submit" round type="primary">
- 保存问题
- </van-button>
- </div>
- </van-form>
- <van-popup v-model:show="tacObjPblmstbShow" :style="{ width: '80%', height: '100%' }" position="left">
- <tacObjPblmstbList :listType="pblm.listType" style="z-index:3000;"
- @change="changeTacObjPblmstb"></tacObjPblmstbList>
- </van-popup>
- </div>
- </template>
- <script setup>
- import { showSuccessToast, showFailToast } from 'vant';
- import {computed, onMounted, ref, watch} from "vue";
- import {useRoute,useRouter} from "vue-router";
- import { getToken } from "@/utils/auth";
- import { useAppStore } from '@/stores/app';
- import {useUserStore} from "@/stores/user";
- import tacObjPblmstbList from './TacObjPblmstbList.vue';
- import {getTacQuestionById} from "@/api/inspect";
- import { showConfirmDialog } from 'vant';
- import {addTacQuestion, getIllegalActById, getTacUnitList} from "@/api/questions";
- import request from "@/utils/request";
- const route = useRoute();
- const router1 = useRouter();
- const userStore = useUserStore();
- const fileList = ref([]);
- const listType = ref(route.query.inspectType || '1');
- const pblm = ref({});
- const tacObjPblmstb = ref({
- });
- const url = process.env.VUE_APP_BASE_API
- const columns = ref([]);
- const columns1 = ref([]);
- const columns2 = ref([]);
- const columns3 = ref([
- { text: '是', value: '1' },
- { text: '否', value: '0' }
- ]);
- const showPicker = ref(false);
- const showPicker1 = ref(false);
- const showPicker2 = ref(false);
- const showPicker3 = ref(false);
- const pickerValue3 = ref([])
- const pickerValue = ref([])
- const pickerValue1 = ref([])
- const pickerValue2 = ref([])
- const router = useRoute();
- const parSectino = ref([]);
- const subjectList = ref([]);
- const tacObjPblmstbShow = ref(false);
- const accessToken = localStorage.getItem('accessToken') || '';
- function onConfirm3(selectedValues, selectedOptions) {
- tacObjPblmstb.value.ifCasePblm = selectedValues.selectedOptions[0].text
- showPicker3.value = false;
- }
- function onConfirm(selectedValues, selectedOptions) {
- tacObjPblmstb.value.inspPblmsName = selectedValues.selectedOptions[0].text
- showPicker.value = false;
- }
- function onConfirm1(selectedValues, selectedOptions) {
- tacObjPblmstb.value.checkPoint = selectedValues.selectedOptions[0].text
- showPicker1.value = false;
- }
- function onConfirm2(selectedValues, selectedOptions) {
- tacObjPblmstb.value.pblmDesc = selectedValues.selectedOptions[0].text
- showPicker2.value = false;
- }
- function changeC1(selectedValues, selectedOptions) {
- columns1.value = []
- columns2.value = []
- parSectino.value.forEach(item => {
- if(item.inspPblmsName === selectedValues.selectedValues[0]){
- var par = {
- text: item.checkPoint,
- value: item.checkPoint
- }
- columns1.value.push(par);
- }
- columns1.value = [...new Set(columns1.value.map(item => JSON.stringify(item)))].map(str => JSON.parse(str))
- })
- }
- function changeC2(selectedValues, selectedOptions) {
- columns2.value = []
- parSectino.value.forEach(item => {
- if(item.checkPoint === selectedValues.selectedValues[0]){
- var par = {
- text: item.pblmDesc,
- value: item.pblmDesc
- }
- columns2.value.push(par);
- }
- columns2.value = [...new Set(columns2.value.map(item => JSON.stringify(item)))].map(str => JSON.parse(str))
- })
- console.log(columns1.value)
- }
- function formatTime(timestamp) {
- const date = new Date(timestamp);
- const year = date.getFullYear();
- const month = ('0' + (date.getMonth() + 1)).slice(-2);
- const day = ('0' + date.getDate()).slice(-2);
- const hours = ('0' + date.getHours()).slice(-2);
- const minutes = ('0' + date.getMinutes()).slice(-2);
- const seconds = ('0' + date.getSeconds()).slice(-2);
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
- }
- async function getSelection(){
- await request.get(`/dc/insp/pblms/listByObjId/${pblm.value.objId}`).then(res=>{
- parSectino.value = res.data
- const uniqueInspPblmsName = [...new Set(res.data.map(item => item.inspPblmsName))];
- const uniquePblmDesc = [...new Set(res.data.map(item => item.pblmDesc))];
- const uniqueCheckPoint = [...new Set(res.data.map(item => item.checkPoint))];
- // columns1.value = uniqueCheckPoint.map(item => ({ text: item, value: item }));
- // columns2.value = uniquePblmDesc.map(item => ({ text: item, value: item }));
- columns.value = uniqueInspPblmsName.map(item => ({ text: item, value: item }));
- })
- }
- const parData = ref({});
- function getDe(){
- request.get(`/dc/insp/pblm/${pblm.value.pblmId}`).then(res=>{
- if(res.success){
- parData.value = res.data
- tacObjPblmstb.value = res.data;
- tacObjPblmstb.value.ifCasePblm = tacObjPblmstb.value.ifCasePblm === '1' ? '是' : '否';
- tacObjPblmstb.value.collTime = formatTime(tacObjPblmstb.value.collTime);
- columns1.value = []
- columns2.value = []
- parSectino.value.forEach(item => {
- if(item.inspPblmsName === tacObjPblmstb.value.inspPblmsName){
- console.log(item);
- var par = {
- text: item.checkPoint,
- value: item.checkPoint
- }
- columns1.value.push(par);
- }
- columns1.value = [...new Set(columns1.value.map(item => JSON.stringify(item)))].map(str => JSON.parse(str))
- })
- parSectino.value.forEach(item => {
- if(item.checkPoint === tacObjPblmstb.value.checkPoint){
- console.log(item);
- var par = {
- text: item.pblmDesc,
- value: item.pblmDesc
- }
- columns2.value.push(par);
- }
- columns2.value = [...new Set(columns2.value.map(item => JSON.stringify(item)))].map(str => JSON.parse(str))
- })
- res.data.gwComFiles.forEach(item => {
- item.filePath = item.filePath.replace(/\\/g, '/');
- fileList.value.push({
- url: url + item.filePath,
- name: item.fileName,
- isImage: true,
- id: item.id
- })
- console.log(fileList.value);
- })
- }
- })
- }
- // 获取需要认证的图片并转换为 base64
- async function getAuthImageAsBase64(imageUrl) {
- try {
- // 1. 使用 fetch 请求图片,携带 token
- const response = await fetch(imageUrl, {
- headers: {
- 'Authorization': `Bearer ${localStorage.getItem('token')}`,
- 'X-Token': localStorage.getItem('token') // 根据实际情况调整
- }
- });
- if (!response.ok) {
- throw new Error(`HTTP错误: ${response.status}`);
- }
- // 2. 获取图片的 ArrayBuffer
- const arrayBuffer = await response.arrayBuffer();
- // 3. 转换为 base64
- const base64 = btoa(
- new Uint8Array(arrayBuffer).reduce(
- (data, byte) => data + String.fromCharCode(byte),
- ''
- )
- );
- // 4. 根据图片类型生成 data URL
- const contentType = response.headers.get('content-type') || 'image/png';
- return `data:${contentType};base64,${base64}`;
- } catch (error) {
- console.error('获取认证图片失败:', error);
- throw error;
- }
- }
- async function save(){
- console.log(tacObjPblmstb.value);
- tacObjPblmstb.value.ifCasePblm = tacObjPblmstb.value.ifCasePblm === '是' ? '1' : '0';
- // tacObjPblmstb.value.collTime = parData.value.collTime;
- delete tacObjPblmstb.value.inspPblmCode;
- delete tacObjPblmstb.value.pblmsTypeId;
- delete tacObjPblmstb.value.collTime;
- // uploadFileToServer(newFile.value, parData.value.pblmId)
- await request.post('/dc/insp/pblm/update', tacObjPblmstb.value).then((res) => {
- if (res.success) {
- fileList.value.forEach(item => {
- console.log(item)
- if(!item.id){
- uploadFileToServer(item, parData.value.pblmId)
- }
- })
- showSuccessToast('保存成功!');
- router1.go(-1)
- fileList.value = []
- }
- });
- getDe();
- }
- watch(() => tacObjPblmstb.value.inspPblmsName, (newVal, oldVal) => {
- if (newVal !== oldVal) {
- columns1.value = []
- columns2.value = []
- parSectino.value.forEach(item => {
- if(item.inspPblmsName === newVal){
- var par = {
- text: item.checkPoint,
- value: item.checkPoint
- }
- columns1.value.push(par);
- }
- columns1.value = [...new Set(columns1.value.map(item => JSON.stringify(item)))].map(str => JSON.parse(str))
- })
- }
- });
- watch(() => tacObjPblmstb.value.checkPoint, (newVal, oldVal) => {
- if (newVal !== oldVal) {
- columns2.value = []
- parSectino.value.forEach(item => {
- if(item.checkPoint === newVal){
- var par = {
- text: item.pblmDesc,
- value: item.pblmDesc
- }
- columns2.value.push(par);
- }
- columns2.value = [...new Set(columns2.value.map(item => JSON.stringify(item)))].map(str => JSON.parse(str))
- })
- }
- });
- function getObjectArrayDiff(arr1, arr2, key) {
- if (!key) throw new Error('必须指定对比的唯一字段(如id)');
- // 提取两个数组的key值集合
- const keySet1 = new Set(arr1.map(item => item[key]));
- const keySet2 = new Set(arr2.map(item => item[key]));
- // 仅在arr1中存在的对象
- const onlyInArr1 = arr1.filter(item => !keySet2.has(item[key]));
- // 仅在arr2中存在的对象
- const onlyInArr2 = arr2.filter(item => !keySet1.has(item[key]));
- // 所有差异对象(合并)
- const allDiff = [...onlyInArr1, ...onlyInArr2];
- return {
- onlyInArr1,
- onlyInArr2,
- allDiff
- };
- }
- const dataURLtoBlob = (dataURL) => {
- try {
- const arr = dataURL.split(',');
- const mime = arr[0].match(/:(.*?);/)[1];
- const bstr = atob(arr[1]);
- let n = bstr.length;
- const u8arr = new Uint8Array(n);
- while (n--) {
- u8arr[n] = bstr.charCodeAt(n);
- }
- return new Blob([u8arr], { type: mime });
- } catch (e) {
- console.error('base64转Blob失败:', e);
- return new Blob([], { type: 'application/octet-stream' });
- }
- };
- // 2. 手动构建multipart/form-data(保留之前验证正确的逻辑)
- const buildMultipartFormData = async (file) => {
- if (!file) throw new Error('文件为空');
- // 生成标准boundary
- const boundary = `----WebKitFormBoundary${Math.random().toString(36).substr(2, 16)}`;
- // 处理文件为Blob
- let fileBlob;
- if (file.content instanceof Blob || file.content instanceof File) {
- fileBlob = file.content;
- } else if (typeof file.content === 'string' && file.content.startsWith('data:')) {
- fileBlob = dataURLtoBlob(file.content);
- } else {
- throw new Error(`不支持的文件格式: ${typeof file.content}`);
- }
- // 读取文件为ArrayBuffer
- const fileBuffer = await new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onload = () => resolve(reader.result);
- reader.onerror = reject;
- reader.readAsArrayBuffer(fileBlob);
- });
- // 手动拼接multipart格式(字符串转Uint8Array,兼容App端)
- const stringToUint8Array = (str) => {
- const arr = [];
- for (let i = 0; i < str.length; i++) {
- arr.push(str.charCodeAt(i));
- }
- return new Uint8Array(arr);
- };
- const parts = [];
- // 拼接分隔符+元信息
- parts.push(stringToUint8Array(`--${boundary}\r\n`));
- parts.push(stringToUint8Array(`Content-Disposition: form-data; name="file"; filename="${encodeURIComponent(file.name || 'upload.png')}"\r\n`));
- parts.push(stringToUint8Array(`Content-Type: ${fileBlob.type || 'application/octet-stream'}\r\n\r\n`));
- // 拼接文件二进制数据
- parts.push(new Uint8Array(fileBuffer));
- // 拼接结束分隔符
- parts.push(stringToUint8Array(`\r\n--${boundary}--\r\n`));
- // 合并所有片段
- const totalLength = parts.reduce((sum, part) => sum + part.length, 0);
- const resultBuffer = new Uint8Array(totalLength);
- let offset = 0;
- parts.forEach(part => {
- resultBuffer.set(part, offset);
- offset += part.length;
- });
- return {
- body: resultBuffer,
- contentType: `multipart/form-data; boundary=${boundary}`
- };
- };
- // 3. 核心:改用Fetch API上传文件(替换Axios)
- const uploadFileToServer = async (file, bizId) => {
- if (!file || !bizId) {
- showFailToast('文件或业务ID为空');
- return null;
- }
- try {
- // 构建标准multipart请求体
- const { body, contentType } = await buildMultipartFormData(file);
- // 获取token和自定义头(和Axios拦截器一致)
- const appStore = useAppStore();
- const userStore = useUserStore();
- const token = getToken();
- // 拼接完整接口地址(补充baseURL,和你的request封装一致)
- const baseURL = process.env.VUE_APP_BASE_API;
- const fullUrl = `${baseURL}/file/insert?bizId=${bizId}`;
- // 使用Fetch API发送请求(原生支持二进制,无兼容性问题)
- const formData = new FormData();
- formData.append('file', fileList.value[0].content); // 文件参数
- const response = await fetch(fullUrl, {
- method: 'POST',
- headers: {
- 'Content-Type': contentType, // 带boundary的标准格式
- 'Accept': 'application/json',
- 'Persid': userStore.userId,
- 'Accesstoken': token,
- 'Cache-Control': 'no-cache'
- },
- body: new Blob([body], { type: contentType }), // 二进制请求体
- timeout: 30000,
- credentials: 'include' // 保持cookie/token会话(和Axios一致)
- });
- // 解析响应
- if (!response.ok) {
- throw new Error(`请求失败: ${response.status}`);
- }
- const resData = await response.json();
- console.log('文件上传成功:', resData);
- return resData;
- } catch (error) {
- console.error('文件上传失败:', error);
- showFailToast('文件上传失败');
- throw error;
- }
- };
- // /pdcApi/file
- const newFile = ref({})
- const isAdding = ref(false);
- watch(() => fileList.value, (newVal, oldVal) => {
- if (newVal.length>oldVal.length && objData.value.state !== '2') {
- isAdding.value = true;
- newFile.value = getObjectArrayDiff(newVal, oldVal, 'id').onlyInArr1[0]
- console.log(newFile.value);
- }
- else if(newVal.length<oldVal.length&&newVal.length>0 && objData.value.state !== '2'){
- isAdding.value = false;
- newFile.value = getObjectArrayDiff(newVal, oldVal, 'id').onlyInArr2[0];
- showConfirmDialog({
- title: '删除',
- message:
- '是否确认删除',
- })
- .then(() => {
- request.post(`/file/${newFile.value.id}`).then(res=>{
- if(res.success){
- showSuccessToast('删除成功!');
- }
- })
- })
- .catch(() => {
- fileList.value = oldVal;
- });
- }
- });
- const objData = ref({});
- onMounted(() => {
- var par = JSON.parse(router.query.object);
- objData.value = par;
- objData.value.state = route.params.id
- console.log(objData.value);
- pblm.value.objId = par.objId
- pblm.value.pblmId = par.pblmId;
- getSelection().then(() => {
- getDe();
- })
- })
- </script>
- <style lang="scss" scoped>
- .pblm-detail-wrapper {
- height: 120%;
- padding: 10px 0;
- overflow: auto;
- .pblm-detail-label {
- padding: 10px 16px;
- color: #606266;
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
- }
- </style>
- <style lang="scss">
- .van-cell-group--inset {
- margin: 0 10px;
- }
- </style>
|