项目文档报告/文档功能实现
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) {
const map = new WeakMap()
function isObject(target) {
return (typeof target === 'object' && target) || typeof target === 'function'
export function deepClone(source, map = new WeakMap()) {
// 处理基本类型和函数(直接返回)
if (typeof source !== 'object' || source === null) {
return source;
}
function clone(data) {
if (!isObject(data)) {
return data
}
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
// 处理循环引用
if (map.has(source)) {
return map.get(source);
}
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();
} else {
let { name, url } = fileInfo.value;
url = decodeUtf8(url);
// url = decodeUtf8(url); // 待定,可能做过特殊处理
return fetch(url).then(res => {
ctrl.enqueue({
name,

View File

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

View File

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