Browse Source

update

dev
zhengzhou 4 years ago
parent
commit
343f6d1b80
7 changed files with 560 additions and 60 deletions
  1. +1
    -0
      src/utils/request.js
  2. +363
    -28
      src/views/main_web/workspace/components/archive-view/index.vue
  3. +28
    -3
      src/views/main_web/workspace/components/archive-view/item-list.vue
  4. +54
    -10
      src/views/main_web/workspace/components/archive-view/list-file-item.vue
  5. +112
    -19
      src/views/main_web/workspace/components/archive-view/list-folder-item.vue
  6. +1
    -0
      src/views/main_web/workspace/index.vue
  7. +1
    -0
      src/views/main_web/workspace/service.js

+ 1
- 0
src/utils/request.js View File

@@ -137,6 +137,7 @@ function mapApiPathToFullPath(path) {
case "file/updateProjArchiveHistory":
case "templateCompany/addTemplateCompany":
case "project/addProjectGobalConfig":
case 'file/submitDeliverables':
// case 'template/deleteTemplateNodeModelFile':
method = 'POST';
break;


+ 363
- 28
src/views/main_web/workspace/components/archive-view/index.vue View File

@@ -2,11 +2,25 @@
<div class="archive-view">
<div class="view-left">
<div class="view-left-title">从成果中选择交付物</div>
<div class="view-left-button">查看接收方消息</div>
<div class="view-left-button" @click="receiverModalVisible = true">查看接收方消息</div>
<el-collapse v-model="collapseActiveKeys" class="panel-wrapper">
<el-collapse-item class="panel" title="项目成果" name="1">
<div class="list-item "><span>显示所有勾选文件(xx/xx)</span></div>
<div class="list-item list-item-active"><span>节点名称</span></div>
<div
class="list-item"
:class="{ 'list-item-active': tabKey === 'all' }"
@click.stop="filterArchive()"
>
<span>显示所有勾选文件{{selectedKeyCount ? ` (${selectedKeyCount}/${fileTotalCount})`:''}}</span>
</div>
<div
class="list-item"
v-for="folder in currentArchiveTreeList.filter(i => i.nodeType === 'folder')"
:class="{ 'list-item-active': tabKey === folder.id }"
:key="folder.id"
@click.stop="filterArchive(folder)"
>
<span>{{folder.folderName}}</span>
</div>
</el-collapse-item>
<el-collapse-item class="panel" title="历史提交记录" name="2">
<div
@@ -14,6 +28,7 @@
:key="commit.id"
class="list-item"
:class="{ 'list-item-active': tabKey === commit.id }"
@click.stop="checkCommitHistory(commit)"
>
{{commit.createTime | formatTime}}
</div>
@@ -22,32 +37,130 @@
</div>

<div class="view-right">
<el-button type="primary" class="view-right-button">点击认证并交付所有勾选文件</el-button>
<el-button type="primary" class="view-right-button" :disabled="!selectedKeyCount" @click.stop="authModalVisible = true">点击认证并交付所有勾选文件</el-button>
<div class="table-like">
<div class="table-header">
<div class="table-th">由工作成果提交的文件</div>
<div class="table-th">版本号</div>
<div class="table-th">种类</div>
<div class="table-th">文件类型</div>
<div class="table-th">创建人</div>
<div class="table-th">提交时间</div>
<div class="table-th">操作</div>
</div>
<item-list />
<template
v-for="node in rightPanelItemList"
>
<list-file-item
:file="node"
:key="node.id"
v-if="node.nodeType === 'file'"
:readOnly="readOnly"
:checked="selectedKeyMap[node.id]"
@check="toggleCheck"
/>
<div v-else :key="node.id" class="table-node">
<div class="table-node-header">
<div class="table-node-header-th">{{node.folderName}}</div>
<div class="table-node-header-extra" v-show="!readOnly">
<el-checkbox class="table-node-header-checkbox" @change="toggleCheck(node)" />
全选
</div>
</div>
<item-list
:list="node.children"
:readOnly="readOnly"
:selectedKeyMap="selectedKeyMap"
@check="toggleCheck"
/>
</div>
</template>
</div>
</div>
<el-dialog
:visible.sync="authModalVisible"
width="460px"
top="30vh"
class="auth-modal"
>
<template #title>
<div class="auth-title">账户认证</div>
<div class="auth-hint">请输入您的账户密码,一旦交付将不可撤回!</div>
</template>
<el-input
v-model="authInpVal"
placeholder="请输入密码"
type="password"
auto-complete="new-password"
@keyup.enter.native="doCommit"
/>
<div slot="footer" class="dialog-footer">
<el-button @click="authModalVisible = false">取 消</el-button>
<el-button :disabled="!authInpVal" type="primary" @click="doCommit" >认证并提交交付物</el-button>
</div>
</el-dialog>
<el-dialog
:visible.sync="receiverModalVisible"
width="660px"
class="receiver-modal"
>
<div class="template-summary">
<top-header
class="set-header-position header-wrap"
theme=""
:onBackBtnClick="returnToDistWorkClick"
backBtnTitle="接收方信息"
:showUserCenter="false"
:showBackBtn="true"
>
<div slot="right" class="edit-template-btn" @click.stop="receiverModalVisible = false">关闭</div>
</top-header>
<div class="summary-wrap">
<div class="item">
<div class="item-title">企业名称:</div>
<div class="item-value"></div>
</div>
<div class="item">
<div class="item-title">项目名称:</div>
<div class="item-value"></div>
</div>
<div class="item">
<div class="item-title">关联模板名称:</div>
<div class="item-value"></div>
</div>
<div class="item">
<div class="item-title">对方项目负责人:</div>
<div class="item-value"></div>
</div>
<div class="item">
<div class="item-title">对接模板节点:</div>
<div class="item-value"></div>
</div>
<div class="item">
<div class="item-title">对接节点负责人:</div>
<div class="item-value"></div>
</div>
</div>
</div>
</el-dialog>
</div>
</template>

<script>
import ItemList from './item-list.vue';
import * as services from '../../service';
import { firstCharToLowerCase } from '@/utils/tool';
import { firstCharToLowerCase, firstCharToUpperCase, notify } from '@/utils/tool';
import moment from 'dayjs';
import { VirtualFolder } from '../../helper';
import ListFileItem from './list-file-item.vue';
import { wrapErrorHint } from '@/utils/request';
import TopHeader from '@/components/app-header/components/top-header.vue';

export default {
components: { ItemList },
components: { ItemList, ListFileItem, TopHeader },
props: {
currentNodeFolder: Object,
projectId: String,
},
filters: {
formatTime(v) {
@@ -55,22 +168,74 @@ export default {
}
},
data() {
const accountName = this.$store.state.accountName;
return {
accountName,
userId: sessionStorage.userId, // 当前用户id
collapseActiveKeys: ['1', '2'],
selectedItemKeys: [], // 选中
readonly: false, // 查看历史提交记录时改为true
// selectedItemKeys: [], // 选中
readOnly: false, // 查看历史提交记录时改为true
commitHisList: [],
tabKey: 'all', // 左侧菜单选中key
currentArchivePlainList: [],
currentArchiveTreeList: [],
rightPanelItemList: [],
selectedKeyMap: {}, // 选中的文件的hash

// 认证弹窗
authModalVisible: false,
authInpVal: '',

//
receiverModalVisible: false,
receiverInfo: {},
};
},
computed: {
selectedKeyCount() {
return Object.values(this.selectedKeyMap).filter(i => i).length;
},
},
mounted() {
// 1. 查询节点文件
this.fetchFolderFiles();
// 2. 查询历史提交记录
this.fetchArchiveHistory();
},
watch: {
authModalVisible(v) {
if(!v) {
this.authInpVal = '';
}
},
receiverModalVisible(flag) {
if(flag) {
this.fetchReceiverInfo();
}
}
},
methods: {
//
toggleCheck(node) {
const tempMap = this.selectedKeyMap;
if(node.nodeType === 'file') {
this.selectedKeyMap = { ...tempMap, [node.id]: !tempMap[node.id] }
} else { // folder
const relationKeys = [];
let checkFlag = true;
recursionForEach(node.children, (fileNode) => {
relationKeys.push(fileNode.id);
checkFlag = checkFlag && tempMap[fileNode.id];
});
const stateMap = relationKeys.reduce((h, key) => (h[key] = !checkFlag, h), {});
this.selectedKeyMap = { ...tempMap, ...stateMap };
}
},
filterArchive(folder) {
this.tabKey = !folder ? 'all' : folder.id;
this.readOnly = false;
this.rightPanelItemList = !folder ? this.currentArchiveTreeList : this.currentArchiveTreeList.filter(i => i.id === folder.id);
},
/**
* 查询当前文件夹内容
*/
@@ -78,35 +243,108 @@ export default {
const nodeFolder = this.currentNodeFolder;
const userId = this.userId;
if(!nodeFolder || !nodeFolder.id) return;
// 先清空当前的文件队列 在这清空队列会有闪烁问题,得放在切换显示文件夹内容的地方触发
// this.uploadFileList = [];
// this.workFileList = [];
// this.workSubFolderList = [];
// this.cooperationFileList = [];
// todo 接口去除公共文件夹配置
const folderResInfo = await services.fetchFolderFileList(nodeFolder.id, userId, nodeFolder.id);
console.log(folderResInfo);

if(!folderResInfo) return;
// 快速切换节点的时候
if(this.currentNodeFolder.id !== nodeFolder.id) return;
// this.workFileList = folderResInfo.file;
// this.subFolderMap = { ...this.subFolderMap, ...folderResInfo.folderMap };
// this.resolveUploadFileList(this.workFileList);
// this.workSubFolderList = folderResInfo.folder;
// this.tempWorkSubFolderList = this.tempWorkSubFolderList.filter(tempFolder => !folderResInfo.folder.some(folder => folder.id === tempFolder.id));
// this.cooperationFileList = folderResInfo.coordinationFiles;

// this.coopSubFolderList = this.dealWithCoopFiles(this.cooperationFileList);
// this.resolveUploadFileList(this.cooperationFileList);
this.currentArchiveTreeList = resolveFileListToTree(folderResInfo.deliverables);
this.fileTotalCount = folderResInfo.deliverables.length;
this.currentArchivePlainList = folderResInfo.deliverables;
this.rightPanelItemList = this.currentArchiveTreeList;
},
/**
* 查看提交历史记录
*/
async fetchArchiveHistory() {
const res = await this.$fetchApi('file/queryProjDeliverablesSubmitByDeliverablesFolderId', { deliverablesFolderId: this.currentNodeFolder.id });
console.log(res);
this.commitHisList = (res.Data || []).map(firstCharToLowerCase);
},
/**
* 查看历史提交详情
*/
async checkCommitHistory(commit) {
this.tabKey = commit.id;
const res = await this.$fetchApi('file/queryProjDeliverablesSubmitDetailByDeliverablesSubmitId', { deliverablesSubmitId: commit.id });
this.rightPanelItemList = resolveFileListToTree((res.Data || []).map(firstCharToLowerCase));
this.readOnly = true;
},
/**
* 提交新的交付物
*/
async doCommit() {
const res = await this.$fetchApi('authentication/passwordCheck', { password: this.authInpVal, userName: this.accountName });
wrapErrorHint(res);
if(res.Code) { return; }
const selectedKeyMap = this.selectedKeyMap;
const commitFileList = this.currentArchivePlainList.filter(file => selectedKeyMap[file.id]);
const commitRes = await this.$fetchApi('file/submitDeliverables', {
DeliverablesFolderId: this.currentNodeFolder.id,
ProjId: this.projectId,
DeliverablesFile: commitFileList.map(firstCharToUpperCase),
});
wrapErrorHint(commitRes);
console.log(commitRes);
if(commitRes.Code) { return; }
notify.success('交付成功');
this.authModalVisible = false;
this.selectedKeyMap = {};
this.fetchArchiveHistory();
// this.removeLink();
},
/**
* 查看接收方信息
*/
async fetchReceiverInfo() {

},
}
}

function recursionForEach(list, f) {
list.forEach(iNode => {
if(iNode.nodeType === 'file') {
f(iNode);
return;
}
recursionForEach(iNode.children, f);
})
}

function resolveFileListToTree(fileList) {
const folderMap = {}; // [key: folderPath]: folder
const headList = [];
fileList.forEach(file => {
file.nodeType = 'file';
file.nodeFolderName = file.nodeName;
// delete file.nodeName;
if(!file.nodeFolderName) { headList.push(file); return; }
const filePath = `${file.nodeFolderName}${file.relativePath ? `/${file.relativePath}`: ''}`;
generateFolder(folderMap, filePath);
const folder = folderMap[filePath];
folder.children.push(file);
});

return headList.concat(Object.values(folderMap).filter(folder => !folder.parentFolderPath));
}

function generateFolder(folderMap, filePath) {
const paths = filePath.split('/');
paths.forEach((_, idx) => {
const fullRelativePath = paths.slice(0, idx + 1).join('/');
if(folderMap[fullRelativePath]) return;
const folder = new VirtualFolder(fullRelativePath);
folder.children = [];
folder.nodeType = 'folder';
const parentRelativePath = folder.parentFolderPath;
folderMap[fullRelativePath] = folder;
if(folderMap[parentRelativePath]) {
folderMap[parentRelativePath].children.push(folder);
}
});
}
</script>

<style lang="less" scoped>
@@ -114,6 +352,12 @@ export default {
height: 100%;
padding: 0 40px 0 48px;
@leftViewWidth: 312px;
// 修改该页面下所有checkbox样式
// ::v-deep .el-checkbox__input:not(.is-checked) .el-checkbox__inner {
// background-color: #cbcbce;
// }


.view-left {
display: inline-block;
vertical-align: top;
@@ -194,6 +438,29 @@ export default {
}
}
}

.table-node-header {
display: flex;
flex-direction: row;
justify-content: space-between;
height: 44px;
line-height: 44px;
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
font-size: 16px;
&-checkbox {
margin-left: 10px;
::v-deep .el-checkbox__inner {
width: 20px;
height: 20px;
}
}
&-th {
flex: 918;
}
&-extra {
flex: 90;
}
}
}
}

@@ -258,7 +525,75 @@ export default {
}
}
}
.auth-modal {
::v-deep .el-dialog__body {
padding-top: 0;
}
.auth-title {
margin-bottom: 8px;
}
.auth-hint {
font-size: 16px;
color: #32323c;
text-align: center;
margin-bottom: 20px;
}
}

.receiver-modal {
::v-deep .el-dialog__header {
display: none;
}
::v-deep .el-dialog__body {
padding: 0;
padding-bottom: 20px;
}
.set-header-position {
position: relative !important;
color: #32323c !important;
z-index: 99;
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
::v-deep .app-header-content {
padding-left: 24px !important;
}
}
.edit-template-btn{
position: relative;
left: 24px;
cursor: pointer;
padding: 0 16px;
height: 32px;
line-height: 32px;
margin: 9px 0;
border-radius: 8px;
background-color: rgba(120, 80, 255, 1);
color: rgba(245, 245, 247, 100);
font-size: 14px;
text-align: center;
font-family: PingFangSC-Regular;
}
.item {
display: flex;
width: 100%;
margin: 14px 0;
font-size: 14px;
font-family: PingFangSC-Regular;
color: #32323C;
.item-title {
color: #62492f;
width: 24%;
text-align: right;
}
.item-value {
margin: 0 0 0 12px;
&:not(.cover-img-wrap) {
flex: 1;
}
}
}
}



</style>

+ 28
- 3
src/views/main_web/workspace/components/archive-view/item-list.vue View File

@@ -1,15 +1,40 @@
<template>
<div>
<list-file-item />
<list-folder-item />
<template v-for="node in list">
<list-file-item
v-if="node.nodeType === 'file'"
:key="node.id"
:file="node"
:readOnly="readOnly"
:checked="selectedKeyMap[node.id]"
@check="check"
/>
<list-folder-item
v-else
:key="node.id"
:folder="node"
:readOnly="readOnly"
@check="check"
:selectedKeyMap="selectedKeyMap"
/>
</template>
</div>
</template>

<script>
import ListFileItem from './list-file-item';
import ListFolderItem from './list-folder-item';

export default {
components: { ListFileItem, ListFolderItem },
props: {
list: Array,
readOnly: Boolean,
selectedKeyMap: Object,
},
methods: {
check(node) {
this.$emit('check', node);
}
}
}
</script>

+ 54
- 10
src/views/main_web/workspace/components/archive-view/list-file-item.vue View File

@@ -1,27 +1,68 @@
<template>
<div class="list-file-item">
<div class="list-file-item-cell" :style="{ flex: '359' }">
<el-checkbox class="list-file-item-checkbox" />
<span>文件名称.txt</span>
<el-checkbox v-show="!readOnly" :value="checked" @change="onCheckChange" class="list-file-item-checkbox" />
<span :title="fileName">{{fileName}}</span>
</div>
<div class="list-file-item-cell" :style="{ flex: '124' }">
<span>版本号</span>
<div class="list-file-item-cell" :style="{ flex: '124' }" @click="onCheckChange">
<span>V{{file.version}}</span>
</div>
<div class="list-file-item-cell" :style="{ flex: '124' }">
<span>种类</span>
<span>{{fileType}}</span>
</div>
<div class="list-file-item-cell" :style="{ flex: '124' }">
<span>创建人</span>
<span>{{file.createUserName}}</span>
</div>
<div class="list-file-item-cell" :style="{ flex: '187' }">
<span>提交时间</span>
</div>
<div class="list-file-item-cell" :style="{ flex: '90' }">
<span>操作</span>
<span>{{file.modifyTime | formatTime}}</span>
</div>
<div class="list-file-item-cell" :style="{ flex: '90' }"></div>
</div>
</template>

<script>
import dayjs from 'dayjs';

const imgType = ['jpg', 'png', 'bmp', 'gif', 'jpeg'].reduce((h, v) => (h[v] = true, h), {});
const docType =['doc','docx','ppt','pptx','xls','xlsx', 'pdf'].reduce((h, v) => (h[v] = true, h), {});
const videoType = ["avi", "mov", "wav","mp4"].reduce((h, v) => (h[v] = true, h), {});
const exeType = ['exe'].reduce((h, v) => (h[v] = true, h), {});
const zipType = ['zip'].reduce((h, v) => (h[v] = true, h), {});

export default {
props: {
file: Object,
readOnly: Boolean,
checked: Boolean,
},
filters: {
formatTime(v) {
return dayjs(v).format('YYYY年MM月DD日 A HH:mm');
}
},
computed: {
fileName(){
const file = this.file;
return `${file.archName}${file.extension ? `.${file.extension}`:''}`;
},
fileType() {
const ext = this.file.extension;
if(imgType[ext]) return '图片';
if(docType[ext]) return '文档';
if(videoType[ext]) return '视频';
if(exeType[ext]) return '可执行程序';
if(zipType[ext]) return '压缩文件';
return '未识别';
}
},
methods: {
onCheckChange(){
this.$emit('check', this.file);
}
}
}
</script>

<style lang="less" scoped>
.list-file-item {
display: flex;
@@ -30,6 +71,9 @@
&-cell {
height: 32px;
line-height: 32px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
&:not(:first-child) {
&:before {
content: '';


+ 112
- 19
src/views/main_web/workspace/components/archive-view/list-folder-item.vue View File

@@ -1,36 +1,96 @@
<template>
<div class="list-folder-item">
<div class="list-folder-item-cell" :style="{ flex: '918' }">
<img class="list-folder-item-icon" src="static/img/file.png" />
<span>文件夹名称</span>
<div class="list-folder-item-checkall">
<el-checkbox label="全选" />
<div class="list-folder-item-header">
<div class="list-folder-item-cell" :style="{ flex: '918' }">
<img class="list-folder-item-icon" src="static/img/file.png" />
<span>{{folder.folderName}}</span>
<div v-if="!readOnly" class="list-folder-item-checkall">
<el-checkbox label="全选" :value="isChildrenAllChecked" @change="check(folder)" />
</div>
</div>
<div
class="list-folder-item-cell list-folder-item-lever"
:class="{ 'list-folder-item-expanded': expanded }"
:style="{ flex: '90' }"
@click.stop="expanded = !expanded"
>
<span :style="{ marginLeft: '10px' }">{{ expanded ? '折叠' : '展开' }}</span>
</div>
</div>
<div class="list-folder-item-cell" :style="{ flex: '90' }">
<span>展开折叠</span>
</div>
<item-list
class="list-folder-item-body"
:class="{ 'list-folder-item-body-hide': !expanded }"
:list="folder.children"
@check="check"
:readOnly="readOnly"
:selectedKeyMap="selectedKeyMap"
/>
</div>
</template>

<script>

export default {
components: { ItemList: () => import('./item-list') },
props: {
folder: Object,
readOnly: Boolean,
selectedKeyMap: Object,
},
data() {
return {
expanded: false
}
},
computed: {
isChildrenAllChecked() {
return recursionSome(this.folder.children, this.selectedKeyMap);
}
},
methods: {
check(node) {
this.$emit('check', node);
}
}
}

function recursionSome(list, checkedMap) {
return list.some(node => {
if(node.nodeType === 'file') return checkedMap[node.id];
return recursionSome(node.children, checkedMap);
})
}

</script>

<style lang="less" scoped>
.list-folder-item {
display: flex;
flex-direction: row;
&-header {
display: flex;
flex-direction: row;

border-bottom: 1px solid rgba(0, 0, 0, 0.2);
}
&-body {
overflow: hidden;
&-hide {
height: 0;
}
}
&-cell {
position: relative;
height: 32px;
line-height: 32px;
font-size: 12px;
&:not(:first-child) {
&:before {
content: '';
display: inline-block;
width: 1px;
height: 1px;
margin-right: 10px;
}
}
// &:not(:first-child) {
// &:before {
// content: '';
// display: inline-block;
// width: 1px;
// height: 1px;
// margin-right: 10px;
// }
// }
}
&-icon {
@iconWidth: 22px;
@@ -45,5 +105,38 @@
right: 17px;
top: 0;
}
&-lever {
cursor: pointer;
position: relative;
&:before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
margin: auto 0;
width: 1px;
height: 25px;
background-color: rgba(17, 17, 17, 0.1);
}
&:after {
content: '';
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
right: 10px;
width: 0;
height: 0;
border: 6px solid transparent;
border-top-color: #999b9d;
border-bottom-width: 0;
transition: transform .1s linear;
transform: rotate(-90deg);
}
}
&-expanded:after {
transform: rotate(0deg);
}
}
</style>

+ 1
- 0
src/views/main_web/workspace/index.vue View File

@@ -54,6 +54,7 @@
v-show="!workFlowVisible"
v-if="viewType === 'archive-view'"
:currentNodeFolder="currentNodeFolder"
:projectId="projectId"
/>
</template>



+ 1
- 0
src/views/main_web/workspace/service.js View File

@@ -75,6 +75,7 @@ export async function fetchFolderFileList(folderId, userId, commonFolderId) {
folder: folderList,
folderMap,
file: fileList,
deliverables: (data.deliverables || []).map(firstCharToLowerCase),
// myFile: (data.myFile || []).map(injectFileBasicValue),
// workFile: (data.workFile || []).map(injectFileBasicValue),
coordinationFiles: (data.coordinationFiles || []).map(firstCharToLowerCase),


Loading…
Cancel
Save