项目文档报告/文档功能实现
continuous-integration/drone/push Build is passing Details

uat
wangxiaoshuang 2025-02-28 17:55:10 +08:00
parent 541e309d1d
commit fa339dcdb0
4 changed files with 318 additions and 164 deletions

View File

@ -60,67 +60,35 @@ export function param2Obj(url) {
) )
} }
export function deepClone(target) { export function deepClone(source, map = new WeakMap()) {
const map = new WeakMap() // 处理基本类型和函数(直接返回)
if (typeof source !== 'object' || source === null) {
function isObject(target) { return source;
return (typeof target === 'object' && target) || typeof target === 'function'
} }
function clone(data) { // 处理循环引用
if (!isObject(data)) { if (map.has(source)) {
return data return map.get(source);
}
if ([Date, RegExp].includes(data.constructor)) {
return new data.constructor(data)
}
if (typeof data === 'function') {
return new Function('return ' + data.toString())()
}
const exist = map.get(data)
if (exist) {
return exist
}
if (data instanceof Map) {
const result = new Map()
map.set(data, result)
data.forEach((val, key) => {
if (isObject(val)) {
result.set(key, clone(val))
} else {
result.set(key, val)
}
})
return result
}
if (data instanceof Set) {
const result = new Set()
map.set(data, result)
data.forEach(val => {
if (isObject(val)) {
result.add(clone(val))
} else {
result.add(val)
}
})
return result
}
const keys = Reflect.ownKeys(data)
const allDesc = Object.getOwnPropertyDescriptors(data)
const result = Object.create(Object.getPrototypeOf(data), allDesc)
map.set(data, result)
keys.forEach(key => {
const val = data[key]
if (isObject(val)) {
result[key] = clone(val)
} else {
result[key] = val
}
})
return result
} }
return clone(target) // 创建新容器
} const target = Array.isArray(source) ? [] : {};
map.set(source, target); // 记录克隆关系
// 克隆普通键值
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = deepClone(source[key], map);
}
}
// 克隆Symbol键值ES6+
const symbolKeys = Object.getOwnPropertySymbols(source);
for (const symKey of symbolKeys) {
if (source.propertyIsEnumerable(symKey)) {
target[symKey] = deepClone(source[symKey], map);
}
}
return target;
}

View File

@ -20,7 +20,7 @@ function zipFiles(zipName, files) {
ctrl.close(); ctrl.close();
} else { } else {
let { name, url } = fileInfo.value; let { name, url } = fileInfo.value;
url = decodeUtf8(url); // url = decodeUtf8(url); // 待定,可能做过特殊处理
return fetch(url).then(res => { return fetch(url).then(res => {
ctrl.enqueue({ ctrl.enqueue({
name, name,

View File

@ -2,7 +2,7 @@
<base-model :config="config"> <base-model :config="config">
<div slot="dialog-body"> <div slot="dialog-body">
<el-form <el-form
ref="browserForm" ref="reportDocForm"
:model="form" :model="form"
label-width="140px" label-width="140px"
size="small" size="small"
@ -10,73 +10,165 @@
> >
<div class="base-dialog-body"> <div class="base-dialog-body">
<el-form-item <el-form-item
:label="$t('dictionary:browser:form:title')" v-if="!config.upload"
prop="Title" :label="$t('trials:trialDocument:reportDoc:form:name')"
prop="Name"
> >
<el-input v-model="form.Title" /> <el-input v-model="form.Name" />
</el-form-item> </el-form-item>
<el-form-item <el-form-item
:label="$t('dictionary:browser:form:exploreType')" v-if="!config.upload"
prop="ExploreType" :label="$t('trials:trialDocument:reportDoc:form:version')"
>
<el-input v-model="form.ExploreType" />
</el-form-item>
<el-form-item
:label="$t('dictionary:browser:form:version')"
prop="Version" prop="Version"
> >
<el-input v-model="form.Version" type="textarea" rows="5" /> <el-input v-model="form.Version" />
</el-form-item> </el-form-item>
<el-form-item <el-form-item
:label="$t('dictionary:browser:form:downloadUrl')" v-if="!config.upload"
prop="DownloadUrl" :label="$t('trials:trialDocument:reportDoc:form:isAuthorizedView')"
prop="IsAuthorizedView"
> >
<el-input v-model="form.DownloadUrl" /> <el-switch
v-model="form.IsAuthorizedView"
:active-value="true"
:inactive-value="false"
:active-text="$fd('YesOrNo', true)"
:inactive-text="$fd('YesOrNo', false)"
>
</el-switch>
</el-form-item> </el-form-item>
<el-form-item :label="$t('dictionary:browser:form:addFile')"> <el-form-item
v-if="!config.upload || config.upload === 'PDF'"
:label="$t('trials:trialDocument:reportDoc:form:pdfFileRecord')"
prop="PDFFileRecord"
>
<el-upload <el-upload
class="upload-demo" class="upload-demo"
action action
:before-upload="beforeUpload" :before-upload="(param) => beforeUpload(param, 'PDF', '.pdf')"
:http-request="handleUploadFile" :http-request="(param) => handleUploadFile(param, 'PDF')"
:on-preview="handlePreview" :on-remove="() => handleRemoveFile('PDF')"
:on-remove="handleRemoveFile"
:show-file-list="true"
:limit="1" :limit="1"
:file-list="fileList" accept=".pdf"
:file-list="PDFFile"
> >
<el-button <el-button
size="small" size="small"
type="primary" type="primary"
:disabled="fileList.length > 0" :disabled="
>{{ $t('common:button:upload') }}</el-button !!form.PDFFileRecord && !!form.PDFFileRecord.FilePath
> "
>{{ $t('common:button:upload') }}
</el-button>
<span slot="tip" class="el-upload__tip">
{{ $t('trials:trialDocument:reportDoc:rule:mustPDF') }}
</span>
</el-upload> </el-upload>
</el-form-item> </el-form-item>
<el-form-item :label="$t('dictionary:browser:form:isDeleted')"> <el-form-item
<el-switch v-if="!config.upload || config.upload === 'Word'"
v-model="form.IsDeleted" :label="$t('trials:trialDocument:reportDoc:form:wordFileRecord')"
:active-value="false" >
:inactive-value="true" <el-upload
class="upload-demo"
action
:before-upload="(param) => beforeUpload(param, 'Word', '.docx')"
:http-request="(param) => handleUploadFile(param, 'Word')"
:on-remove="() => handleRemoveFile('Word')"
:limit="1"
accept=".docx"
:file-list="WordFile"
> >
</el-switch> <el-button
size="small"
type="primary"
:disabled="
!!form.WordFileRecord && !!form.WordFileRecord.FilePath
"
>{{ $t('common:button:upload') }}
</el-button>
<span slot="tip" class="el-upload__tip">
{{ $t('trials:trialDocument:reportDoc:rule:mustDOCX') }}
</span>
</el-upload>
</el-form-item>
<el-form-item
v-if="!config.upload || config.upload === 'Sign'"
:label="$t('trials:trialDocument:reportDoc:form:signFileRecord')"
:prop="
rowData.IsConfirmRecord &&
(!config.upload || config.upload === 'Sign')
? 'SignFileRecord'
: ''
"
>
<el-upload
class="upload-demo"
action
:before-upload="(param) => beforeUpload(param, 'Sign', '.pdf')"
:http-request="(param) => handleUploadFile(param, 'Sign')"
:on-remove="() => handleRemoveFile('Sign')"
:limit="1"
accept=".pdf"
:file-list="SignFile"
>
<el-button
size="small"
type="primary"
:disabled="
!!form.SignFileRecord && !!form.SignFileRecord.FilePath
"
>{{ $t('common:button:upload') }}
</el-button>
<span slot="tip" class="el-upload__tip">
{{ $t('trials:trialDocument:reportDoc:rule:mustPDF') }}
</span>
</el-upload>
</el-form-item>
<el-form-item
v-if="!config.upload || config.upload === 'History'"
:label="$t('trials:trialDocument:reportDoc:form:historyFileRecord')"
>
<el-upload
class="upload-demo"
action
:before-upload="(param) => beforeUpload(param, 'History', '.zip')"
:http-request="(param) => handleUploadFile(param, 'History')"
:on-remove="() => handleRemoveFile('History')"
:limit="1"
accept=".zip"
:file-list="HistoryFile"
>
<el-button
size="small"
type="primary"
:disabled="
!!form.HistoryFileRecord && !!form.HistoryFileRecord.FilePath
"
>{{ $t('common:button:upload') }}
</el-button>
<span slot="tip" class="el-upload__tip">
{{ $t('trials:trialDocument:reportDoc:rule:mustZIP') }}
</span>
</el-upload>
</el-form-item> </el-form-item>
</div> </div>
</el-form> </el-form>
</div> </div>
<div slot="dialog-footer"> <div slot="dialog-footer">
<el-button size="small" @click="canel"> <el-button size="small" @click="canel">
{{ $t('dictionary:browser:button:canel') }} {{ $t('trials:trialDocument:reportDoc:button:canel') }}
</el-button> </el-button>
<el-button size="small" type="primary" :loading="loading" @click="save"> <el-button size="small" type="primary" :loading="loading" @click="save">
{{ $t('dictionary:browser:button:save') }} {{ $t('trials:trialDocument:reportDoc:button:save') }}
</el-button> </el-button>
</div> </div>
</base-model> </base-model>
</template> </template>
<script> <script>
import baseModel from '@/components/BaseModel' import baseModel from '@/components/BaseModel'
import { addOrUpdateExploreRecommend } from '@/api/dictionary' import { addOrUpdateTrialFinalRecord } from '@/api/dictionary'
import { config } from 'process'
export default { export default {
props: { props: {
config: { config: {
@ -91,6 +183,16 @@ export default {
return {} return {}
}, },
}, },
ArchiveTypeEnum: {
type: Number,
default: 0,
},
rowData: {
type: Object,
default: () => {
return {}
},
},
}, },
components: { components: {
'base-model': baseModel, 'base-model': baseModel,
@ -99,22 +201,15 @@ export default {
return { return {
form: { form: {
Version: null, Version: null,
Title: null, Name: null,
IsDeleted: true, IsAuthorizedView: true,
ExploreType: null, PDFFileRecord: null,
DownloadUrl: null, WordFileRecord: null,
Path: null, SignFileRecord: null,
FileName: null, HistoryFileRecord: null,
}, },
rules: { rules: {
Title: [ Name: [
{
required: true,
message: this.$t('common:ruleMessage:specify'),
trigger: ['blur', 'change'],
},
],
ExploreType: [
{ {
required: true, required: true,
message: this.$t('common:ruleMessage:specify'), message: this.$t('common:ruleMessage:specify'),
@ -128,16 +223,65 @@ export default {
trigger: ['blur', 'change'], trigger: ['blur', 'change'],
}, },
], ],
DownloadUrl: [ IsAuthorizedView: [
{ {
required: true, required: true,
message: this.$t('common:ruleMessage:specify'), message: this.$t('common:ruleMessage:select'),
trigger: ['blur', 'change'],
},
],
PDFFileRecord: [
{
required: true,
validator: (rule, value, callback) => {
if (
!this.form.PDFFileRecord ||
!this.form.PDFFileRecord.FilePath
) {
callback(
new Error(
this.$t(
'trials:trialDocument:reportDoc:msg:noPdfFileRecord'
)
)
)
} else {
callback()
}
},
// message: this.$t('common:ruleMessage:specify'),
trigger: ['blur', 'change'],
},
],
SignFileRecord: [
{
required: true,
validator: (rule, value, callback) => {
if (
!this.form.SignFileRecord ||
!this.form.SignFileRecord.FilePath
) {
callback(
new Error(
this.$t(
'trials:trialDocument:reportDoc:msg:noSignFileRecord'
)
)
)
} else {
callback()
}
},
// message: this.$t('common:ruleMessage:specify'),
trigger: ['blur', 'change'], trigger: ['blur', 'change'],
}, },
], ],
}, },
fileList: [],
loading: false, loading: false,
PDFFile: [],
WordFile: [],
SignFile: [],
HistoryFile: [],
} }
}, },
created() { created() {
@ -145,25 +289,33 @@ export default {
Object.keys(this.form).forEach((key) => { Object.keys(this.form).forEach((key) => {
this.form[key] = this.data[key] this.form[key] = this.data[key]
}) })
if (this.form.FileName) { let arr = ['PDF', 'Word', 'Sign', 'History']
this.fileList[0] = {
name: this.form.FileName, arr.forEach((key) => {
path: this.form.Path, if (
fullPath: this.form.Path, this.form[`${key}FileRecord`] &&
this.form[`${key}FileRecord`].FilePath
) {
this[`${key}File`] = []
this[`${key}File`].push({
name: this.form[`${key}FileRecord`].FileName,
url: this.form[`${key}FileRecord`].FilePath,
})
} }
} })
} }
}, },
methods: { methods: {
async save() { async save() {
try { try {
let validate = await this.$refs.browserForm.validate() let validate = await this.$refs.reportDocForm.validate()
if (!validate) return false if (!validate) return false
this.loading = true this.loading = true
this.form.TrialFileTypeId = this.rowData.Id
if (this.config.status === 'edit') { if (this.config.status === 'edit') {
this.form.Id = this.data.Id this.form.Id = this.data.Id
} }
let res = await addOrUpdateExploreRecommend(this.form) let res = await addOrUpdateTrialFinalRecord(this.form)
if (res.IsSuccess) { if (res.IsSuccess) {
this.$emit('close') this.$emit('close')
this.$emit('getList') this.$emit('getList')
@ -176,37 +328,52 @@ export default {
canel() { canel() {
this.$emit('close') this.$emit('close')
}, },
handleRemoveFile() { handleRemoveFile(key) {
this.form.FileName = null this.form[`${key}FileRecord`] = null
this.form.Path = null this[`${key}File`] = []
this.fileList = []
}, },
beforeUpload() { beforeUpload(param, key, accept) {
if (this.fileList.length > 0) { if (this[`${key}File`] && this[`${key}File`][0]) {
this.$alert(this.$t('trials:trialDocument:reportDoc:msg:message1')) this.$alert(
this.$t('trials:trialDocument:reportDoc:msg:hasFile')
).catch()
return
}
let extendName = param.name
.substring(param.name.lastIndexOf('.'))
.toLocaleLowerCase()
if (accept !== extendName) {
this.$alert(
this.$t('trials:trialDocument:reportDoc:msg:typeErr')
).catch()
return return
} }
}, },
handlePreview(row, r2) { async handleUploadFile(param, key) {
if (row.fullPath) { let trialId = this.$route.query.trialId
window.open(row.fullPath, '_blank') let extendName = param.file.name
} .substring(param.file.name.lastIndexOf('.'))
}, .toLocaleLowerCase()
async handleUploadFile(param) { if (!trialId)
return this.$alert(
this.$t('trials:trialDocument:reportDoc:msg:noTrialId')
).catch()
this.loading = true this.loading = true
var file = await this.fileToBlob(param.file) var file = await this.fileToBlob(param.file)
let types = this.ArchiveTypeEnum === 1 ? 'Report' : 'Doc'
let fileNameNoType = param.file.name
.substring(0, param.file.name.lastIndexOf('.'))
.toLocaleLowerCase()
const res = await this.OSSclient.put( const res = await this.OSSclient.put(
`/System/Browser/${param.file.name}`, `/${trialId}/Document/${types}/${fileNameNoType}${extendName}`,
file file
) )
this.fileList.push({ this.form[`${key}FileRecord`] = {
name: param.file.name, FileName: param.file.name,
path: this.$getObjectName(res.url), FilePath: this.$getObjectName(res.url),
fullPath: this.$getObjectName(res.url), FileSize: param.file.size,
url: this.$getObjectName(res.url), FileFormat: extendName,
}) }
this.form.Path = this.$getObjectName(res.url)
this.form.FileName = param.file.name
this.loading = false this.loading = false
}, },
}, },

View File

@ -175,7 +175,7 @@
/> />
<!--定稿PDF--> <!--定稿PDF-->
<el-table-column <el-table-column
prop="PdfFileRecord" prop="PDFFileRecord"
:label="$t('trials:trialDocument:reportDoc:table:pdfFileRecord')" :label="$t('trials:trialDocument:reportDoc:table:pdfFileRecord')"
show-overflow-tooltip show-overflow-tooltip
sortable="custom" sortable="custom"
@ -183,25 +183,25 @@
<template slot-scope="scope"> <template slot-scope="scope">
<div <div
v-if=" v-if="
scope.row.PdfFileRecord && scope.row.PdfFileRecord.TrialFileTypeId scope.row.PDFFileRecord && scope.row.PDFFileRecord.TrialFileTypeId
" "
style="display: flex; align-items: center" style="display: flex; align-items: center"
> >
<span class="fileName">{{ scope.row.PdfFileRecord.FileName }}</span> <span class="fileName">{{ scope.row.PDFFileRecord.FileName }}</span>
<div v-if="isManage && !viewStatus" class="fileBtnBox"> <div v-if="isManage && !viewStatus" class="fileBtnBox">
<i <i
class="el-icon-view" class="el-icon-view"
@click.stop="preview(scope.row.PdfFileRecord)" @click.stop="preview(scope.row.PDFFileRecord)"
/> />
<i <i
class="el-icon-download" class="el-icon-download"
v-if="hasDownLoad" v-if="hasDownLoad"
@click.stop="downLoad(false, scope.row.PdfFileRecord, 'file')" @click.stop="downLoad(false, scope.row.PDFFileRecord, 'file')"
/> />
<i <i
class="el-icon-delete" class="el-icon-delete"
v-if="hasDel" v-if="hasDel"
@click.stop="delFile(scope.row, 'Pdf')" @click.stop="delFile(scope.row, 'PDF')"
/> />
</div> </div>
</div> </div>
@ -209,6 +209,7 @@
v-else-if="isManage && !viewStatus" v-else-if="isManage && !viewStatus"
class="el-icon-upload2" class="el-icon-upload2"
style="cursor: pointer; color: #409eff" style="cursor: pointer; color: #409eff"
@click.stop="upload(scope.row, 'PDF')"
/> />
</template> </template>
</el-table-column> </el-table-column>
@ -248,6 +249,7 @@
v-else-if="isManage && !viewStatus" v-else-if="isManage && !viewStatus"
class="el-icon-upload2" class="el-icon-upload2"
style="cursor: pointer; color: #409eff" style="cursor: pointer; color: #409eff"
@click.stop="upload(scope.row, 'Word')"
/> />
</template> </template>
</el-table-column> </el-table-column>
@ -291,6 +293,7 @@
v-else-if="isManage && !viewStatus" v-else-if="isManage && !viewStatus"
class="el-icon-upload2" class="el-icon-upload2"
style="cursor: pointer; color: #409eff" style="cursor: pointer; color: #409eff"
@click.stop="upload(scope.row, 'Sign')"
/> />
</template> </template>
</el-table-column> </el-table-column>
@ -332,6 +335,7 @@
v-else-if="isManage && !viewStatus" v-else-if="isManage && !viewStatus"
class="el-icon-upload2" class="el-icon-upload2"
style="cursor: pointer; color: #409eff" style="cursor: pointer; color: #409eff"
@click.stop="upload(scope.row, 'History')"
/> />
</template> </template>
</el-table-column> </el-table-column>
@ -380,9 +384,9 @@
size="mini" size="mini"
circle circle
:disabled=" :disabled="
!scope.row.PdfFileRecord || !scope.row.PdfFileRecord.FilePath !scope.row.PDFFileRecord || !scope.row.PDFFileRecord.FilePath
" "
@click.stop="preview(scope.row.PdfFileRecord)" @click.stop="preview(scope.row.PDFFileRecord)"
/> />
<el-button <el-button
v-if="hasEdit && isManage && !viewStatus" v-if="hasEdit && isManage && !viewStatus"
@ -421,6 +425,8 @@
@pagination="getList" @pagination="getList"
/> />
<reportDoc-form <reportDoc-form
:ArchiveTypeEnum="ArchiveTypeEnum"
:rowData="rowData"
:config="config" :config="config"
:data="selectData" :data="selectData"
v-if="config.visible" v-if="config.visible"
@ -492,33 +498,46 @@ export default {
title: '', title: '',
appendToBody: true, appendToBody: true,
status: 'add', status: 'add',
upload: null,
}, },
selectData: {}, selectData: {},
} }
}, },
methods: { methods: {
//
upload(row, key) {
this.selectData = deepClone(row)
this.config.title = `${this.$t(
'trials:trialDocument:reportDoc:form:title:upload'
)}
-
${this.isEN ? this.rowData.Name : this.rowData.NameCN}`
this.config.status = 'edit'
this.config.upload = key
this.config.visible = true
},
// //
handleAdd() { handleAdd() {
this.selectData = {} this.selectData = {}
this.config.title = this.config.title = `${this.$t(
this.$t('trials:trialDocument:reportDoc:form:title:add') + 'trials:trialDocument:reportDoc:form:title:add'
'-' + )}
this.isEN -
? this.rowData.Name ${this.isEN ? this.rowData.Name : this.rowData.NameCN}`
: this.rowData.NameCN
this.config.status = 'add' this.config.status = 'add'
// this.config.visible = true this.config.upload = null
this.config.visible = true
}, },
// //
handleEdit(row) { handleEdit(row) {
this.selectData = deepClone(row) this.selectData = deepClone(row)
this.config.title = this.config.title = `${this.$t(
this.$t('trials:trialDocument:reportDoc:form:title:edit') + 'trials:trialDocument:reportDoc:form:title:edit'
'-' + )}
this.isEN -
? this.rowData.Name ${this.isEN ? this.rowData.Name : this.rowData.NameCN}`
: this.rowData.NameCN
this.config.status = 'edit' this.config.status = 'edit'
this.config.upload = null
this.config.visible = true this.config.visible = true
}, },
// //
@ -568,7 +587,7 @@ export default {
formatDownloadFile(isArray = true, row) { formatDownloadFile(isArray = true, row) {
let files = [], let files = [],
name = `${this.TITLE}_${new Date().getTime()}.zip`, name = `${this.TITLE}_${new Date().getTime()}.zip`,
arr = ['Pdf', 'Word', 'Sign', 'History'] arr = ['PDF', 'Word', 'Sign', 'History']
if (!isArray) { if (!isArray) {
name = `${row.Name}_${row.Version}_${new Date().getTime()}.zip` name = `${row.Name}_${row.Version}_${new Date().getTime()}.zip`
arr.forEach((key) => { arr.forEach((key) => {
@ -646,7 +665,7 @@ export default {
} }
}, },
// //
handleDel() { handleDel(row) {
this.$confirm( this.$confirm(
this.$t('trials:trialDocument:reportDoc:message:deleteMessage'), this.$t('trials:trialDocument:reportDoc:message:deleteMessage'),
{ {
@ -803,7 +822,7 @@ export default {
} }
.fileName { .fileName {
display: inline-block; display: inline-block;
max-width: calc(100% - 100px); max-width: calc(100% - 55px);
white-space: nowrap; /* 文本不换行 */ white-space: nowrap; /* 文本不换行 */
overflow: hidden; /* 超出部分隐藏 */ overflow: hidden; /* 超出部分隐藏 */
text-overflow: ellipsis; text-overflow: ellipsis;