实现流程项目逻辑删除与恢复
This commit is contained in:
@@ -52,6 +52,14 @@ export function delProject(projectIds) {
|
||||
})
|
||||
}
|
||||
|
||||
// 恢复已删除初核项目
|
||||
export function restoreProject(projectId) {
|
||||
return request({
|
||||
url: '/ccdi/project/' + projectId + '/restore',
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
// 导出初核项目
|
||||
export function exportProject(query) {
|
||||
return request({
|
||||
|
||||
@@ -92,23 +92,66 @@
|
||||
|
||||
<el-table-column
|
||||
label="操作"
|
||||
width="350"
|
||||
width="390"
|
||||
align="left"
|
||||
fixed="right"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
v-if="['0', '3', '4'].includes(scope.row.status)"
|
||||
size="mini"
|
||||
type="text"
|
||||
:icon="canOperate(scope.row) ? 'el-icon-right' : 'el-icon-view'"
|
||||
@click="handleEnter(scope.row)"
|
||||
>
|
||||
{{ canOperate(scope.row) ? "进入项目" : "查看项目" }}
|
||||
</el-button>
|
||||
|
||||
<template v-if="scope.row.status === '1'">
|
||||
<template v-if="deletedList">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-refresh-left"
|
||||
@click="handleRestore(scope.row)"
|
||||
>
|
||||
恢复
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<el-button
|
||||
v-if="['0', '3', '4'].includes(scope.row.status)"
|
||||
size="mini"
|
||||
type="text"
|
||||
:icon="canOperate(scope.row) ? 'el-icon-right' : 'el-icon-view'"
|
||||
@click="handleEnter(scope.row)"
|
||||
>
|
||||
{{ canOperate(scope.row) ? "进入项目" : "查看项目" }}
|
||||
</el-button>
|
||||
|
||||
<template v-if="scope.row.status === '1'">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-view"
|
||||
@click="handleViewResult(scope.row)"
|
||||
>
|
||||
查看结果
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canOperate(scope.row)"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-refresh"
|
||||
:loading="reAnalyzeLoadingMap[String(scope.row.projectId)]"
|
||||
:disabled="reAnalyzeLoadingMap[String(scope.row.projectId)]"
|
||||
@click="handleReAnalyze(scope.row)"
|
||||
>
|
||||
重新分析
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canOperate(scope.row)"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-folder"
|
||||
@click="handleArchive(scope.row)"
|
||||
>
|
||||
归档
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<el-button
|
||||
v-if="scope.row.status === '2'"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-view"
|
||||
@@ -116,37 +159,18 @@
|
||||
>
|
||||
查看结果
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
v-if="canOperate(scope.row)"
|
||||
v-if="canDelete(scope.row) && scope.row.status !== '5'"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-refresh"
|
||||
:loading="reAnalyzeLoadingMap[String(scope.row.projectId)]"
|
||||
:disabled="reAnalyzeLoadingMap[String(scope.row.projectId)]"
|
||||
@click="handleReAnalyze(scope.row)"
|
||||
class="delete-button"
|
||||
icon="el-icon-delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
>
|
||||
重新分析
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canOperate(scope.row)"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-folder"
|
||||
@click="handleArchive(scope.row)"
|
||||
>
|
||||
归档
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<el-button
|
||||
v-if="scope.row.status === '2'"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-view"
|
||||
@click="handleViewResult(scope.row)"
|
||||
>
|
||||
查看结果
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -193,6 +217,10 @@ export default {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
deletedList: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getStatusColor(status) {
|
||||
@@ -202,6 +230,7 @@ export default {
|
||||
2: "#8c8c8c",
|
||||
3: "#fa8c16",
|
||||
4: "#f56c6c",
|
||||
5: "#f56c6c",
|
||||
};
|
||||
return colorMap[status] || "#8c8c8c";
|
||||
},
|
||||
@@ -230,9 +259,18 @@ export default {
|
||||
handleArchive(row) {
|
||||
this.$emit("archive", row);
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$emit("delete", row);
|
||||
},
|
||||
handleRestore(row) {
|
||||
this.$emit("restore", row);
|
||||
},
|
||||
canOperate(row) {
|
||||
return !row || row.canOperate !== false;
|
||||
},
|
||||
canDelete(row) {
|
||||
return !row || row.canDelete !== false;
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.$emit("pagination", {
|
||||
pageNum: this.pageParams.pageNum,
|
||||
@@ -367,6 +405,15 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-button--text.delete-button {
|
||||
color: #f56c6c;
|
||||
|
||||
&:hover {
|
||||
color: #dd6161;
|
||||
background-color: rgba(245, 108, 108, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-pagination {
|
||||
margin-top: 24px;
|
||||
text-align: right;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</div>
|
||||
<div class="tab-filters">
|
||||
<div
|
||||
v-for="tab in tabs"
|
||||
v-for="tab in visibleTabs"
|
||||
:key="tab.value"
|
||||
:class="['tab-item', { active: activeTab === tab.value }]"
|
||||
@click="handleTabChange(tab.value)"
|
||||
@@ -41,8 +41,13 @@ export default {
|
||||
'1': 0,
|
||||
'2': 0,
|
||||
'3': 0,
|
||||
'4': 0
|
||||
'4': 0,
|
||||
'5': 0
|
||||
})
|
||||
},
|
||||
showDeletedTab: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -55,20 +60,32 @@ export default {
|
||||
{ label: '已完成', value: '1', count: 0 },
|
||||
{ label: '已归档', value: '2', count: 0 },
|
||||
{ label: '打标中', value: '3', count: 0 },
|
||||
{ label: '打标失败', value: '4', count: 0 }
|
||||
{ label: '打标失败', value: '4', count: 0 },
|
||||
{ label: '已删除', value: 'deleted', count: 0 }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
visibleTabs() {
|
||||
return this.tabs.filter(tab => tab.value !== 'deleted' || this.showDeletedTab)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
tabCounts: {
|
||||
handler(newVal) {
|
||||
this.tabs = this.tabs.map(tab => ({
|
||||
...tab,
|
||||
count: newVal[tab.value] || 0
|
||||
count: newVal[tab.value === 'deleted' ? '5' : tab.value] || 0
|
||||
}))
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
},
|
||||
showDeletedTab(newVal) {
|
||||
if (!newVal && this.activeTab === 'deleted') {
|
||||
this.activeTab = 'all'
|
||||
this.emitQuery()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -83,9 +100,11 @@ export default {
|
||||
},
|
||||
/** 发送查询 */
|
||||
emitQuery() {
|
||||
const includeDeleted = this.activeTab === 'deleted'
|
||||
this.$emit('query', {
|
||||
projectName: this.searchKeyword || null,
|
||||
status: this.activeTab === 'all' ? null : this.activeTab
|
||||
status: includeDeleted || this.activeTab === 'all' ? null : this.activeTab,
|
||||
includeDeleted
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<search-bar
|
||||
:show-search="showSearch"
|
||||
:tab-counts="tabCounts"
|
||||
:show-deleted-tab="isProjectAdmin"
|
||||
@query="handleQuery"
|
||||
/>
|
||||
|
||||
@@ -20,11 +21,14 @@
|
||||
:total="total"
|
||||
:page-params="queryParams"
|
||||
:re-analyze-loading-map="reAnalyzeLoadingMap"
|
||||
:deleted-list="queryParams.includeDeleted === true"
|
||||
@pagination="handlePagination"
|
||||
@enter="handleEnter"
|
||||
@view-result="handleViewResult"
|
||||
@re-analyze="handleReAnalyze"
|
||||
@archive="handleArchive"
|
||||
@delete="handleDelete"
|
||||
@restore="handleRestore"
|
||||
/>
|
||||
|
||||
<!-- 快捷入口区 -->
|
||||
@@ -63,7 +67,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {archiveProject, getStatusCounts, listProject, rebuildProjectTags} from '@/api/ccdiProject'
|
||||
import {archiveProject, delProject, getStatusCounts, listProject, rebuildProjectTags, restoreProject} from '@/api/ccdiProject'
|
||||
import SearchBar from './components/SearchBar'
|
||||
import ProjectTable from './components/ProjectTable'
|
||||
import QuickEntry from './components/QuickEntry'
|
||||
@@ -96,7 +100,8 @@ export default {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
projectName: null,
|
||||
status: null
|
||||
status: null,
|
||||
includeDeleted: false
|
||||
},
|
||||
// 标签页数量统计
|
||||
tabCounts: {
|
||||
@@ -105,7 +110,8 @@ export default {
|
||||
'1': 0,
|
||||
'2': 0,
|
||||
'3': 0,
|
||||
'4': 0
|
||||
'4': 0,
|
||||
'5': 0
|
||||
},
|
||||
// 新增/编辑弹窗
|
||||
addDialogVisible: false,
|
||||
@@ -123,6 +129,12 @@ export default {
|
||||
created() {
|
||||
this.getList();
|
||||
},
|
||||
computed: {
|
||||
isProjectAdmin() {
|
||||
const roles = this.$store && this.$store.getters ? this.$store.getters.roles || [] : []
|
||||
return roles.includes("admin") || roles.includes("manager")
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 查询项目列表 */
|
||||
getList() {
|
||||
@@ -145,7 +157,8 @@ export default {
|
||||
'1': counts.status1 || 0,
|
||||
'2': counts.status2 || 0,
|
||||
'3': counts.status3 || 0,
|
||||
'4': counts.status4 || 0
|
||||
'4': counts.status4 || 0,
|
||||
'5': counts.status5 || 0
|
||||
}
|
||||
|
||||
this.loading = false
|
||||
@@ -157,10 +170,21 @@ export default {
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery(queryParams) {
|
||||
const nextQueryParams = { ...this.queryParams }
|
||||
if (queryParams) {
|
||||
this.queryParams = { ...this.queryParams, ...queryParams };
|
||||
Object.assign(nextQueryParams, queryParams)
|
||||
}
|
||||
this.queryParams.pageNum = 1;
|
||||
if (nextQueryParams.includeDeleted && !this.isProjectAdmin) {
|
||||
nextQueryParams.includeDeleted = false
|
||||
nextQueryParams.status = null
|
||||
}
|
||||
if (nextQueryParams.includeDeleted) {
|
||||
nextQueryParams.status = null
|
||||
} else {
|
||||
nextQueryParams.includeDeleted = false
|
||||
}
|
||||
nextQueryParams.pageNum = 1
|
||||
this.queryParams = nextQueryParams
|
||||
this.getList();
|
||||
},
|
||||
/** 分页事件处理 */
|
||||
@@ -312,9 +336,60 @@ export default {
|
||||
this.$modal.msgError(message)
|
||||
}
|
||||
},
|
||||
/** 删除项目 */
|
||||
async handleDelete(row) {
|
||||
if (!this.canDelete(row)) {
|
||||
this.$modal.msgWarning("当前项目不能删除")
|
||||
return
|
||||
}
|
||||
try {
|
||||
await this.$modal.confirm(
|
||||
`确认删除项目“${row.projectName}”吗?删除后项目进入已删除列表,项目内数据不会删除。`
|
||||
)
|
||||
} catch (confirmError) {
|
||||
if (confirmError === "cancel" || confirmError === "close") {
|
||||
return
|
||||
}
|
||||
throw confirmError
|
||||
}
|
||||
try {
|
||||
await delProject(row.projectId)
|
||||
this.$modal.msgSuccess("项目删除成功")
|
||||
this.getList()
|
||||
} catch (error) {
|
||||
const message = error && error.message ? error.message : "项目删除失败,请稍后重试"
|
||||
this.$modal.msgError(message)
|
||||
}
|
||||
},
|
||||
/** 恢复已删除项目 */
|
||||
async handleRestore(row) {
|
||||
if (!this.isProjectAdmin) {
|
||||
this.$modal.msgWarning("当前用户不能恢复项目")
|
||||
return
|
||||
}
|
||||
try {
|
||||
await this.$modal.confirm(`确认恢复项目“${row.projectName}”吗?项目将恢复为已完成状态。`)
|
||||
} catch (confirmError) {
|
||||
if (confirmError === "cancel" || confirmError === "close") {
|
||||
return
|
||||
}
|
||||
throw confirmError
|
||||
}
|
||||
try {
|
||||
await restoreProject(row.projectId)
|
||||
this.$modal.msgSuccess("项目恢复成功")
|
||||
this.getList()
|
||||
} catch (error) {
|
||||
const message = error && error.message ? error.message : "项目恢复失败,请稍后重试"
|
||||
this.$modal.msgError(message)
|
||||
}
|
||||
},
|
||||
canOperate(row) {
|
||||
return !row || row.canOperate !== false
|
||||
},
|
||||
canDelete(row) {
|
||||
return !row || row.canDelete !== false
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -10,6 +10,46 @@ const dialogPath = path.resolve(
|
||||
const pageSource = fs.readFileSync(pagePath, "utf8");
|
||||
const dialogSource = fs.readFileSync(dialogPath, "utf8");
|
||||
|
||||
assert(
|
||||
pageSource.includes("delProject") && pageSource.includes("restoreProject"),
|
||||
"项目列表页应引入删除和恢复接口"
|
||||
);
|
||||
|
||||
assert(
|
||||
pageSource.includes("includeDeleted: false"),
|
||||
"默认查询参数应不包含已删除项目"
|
||||
);
|
||||
|
||||
assert(
|
||||
pageSource.includes(':show-deleted-tab="isProjectAdmin"'),
|
||||
"已删除入口应仅对项目管理员角色展示"
|
||||
);
|
||||
|
||||
assert(
|
||||
pageSource.includes("'5': counts.status5 || 0"),
|
||||
"状态统计应接入已删除数量"
|
||||
);
|
||||
|
||||
assert(
|
||||
pageSource.includes('await delProject(row.projectId)'),
|
||||
"删除确认后应调用项目删除接口"
|
||||
);
|
||||
|
||||
assert(
|
||||
pageSource.includes('await restoreProject(row.projectId)'),
|
||||
"恢复确认后应调用项目恢复接口"
|
||||
);
|
||||
|
||||
assert(
|
||||
pageSource.includes("项目内数据不会删除"),
|
||||
"删除确认文案应明确项目内数据不会删除"
|
||||
);
|
||||
|
||||
assert(
|
||||
pageSource.includes("项目将恢复为已完成状态"),
|
||||
"恢复确认文案应明确恢复到已完成状态"
|
||||
);
|
||||
|
||||
assert(
|
||||
pageSource.includes("await archiveProject(data.projectId)"),
|
||||
"确认归档后应调用真实归档接口"
|
||||
|
||||
@@ -6,6 +6,48 @@ const pagePath = path.resolve(__dirname, "../../src/views/ccdiProject/index.vue"
|
||||
const tablePath = path.resolve(__dirname, "../../src/views/ccdiProject/components/ProjectTable.vue");
|
||||
const pageSource = fs.readFileSync(pagePath, "utf8");
|
||||
const tableSource = fs.readFileSync(tablePath, "utf8");
|
||||
const searchBarPath = path.resolve(__dirname, "../../src/views/ccdiProject/components/SearchBar.vue");
|
||||
const searchBarSource = fs.readFileSync(searchBarPath, "utf8");
|
||||
|
||||
assert(
|
||||
searchBarSource.includes("{ label: '已删除', value: 'deleted'"),
|
||||
"搜索条应提供独立的已删除列表入口"
|
||||
);
|
||||
|
||||
assert(
|
||||
searchBarSource.includes("tab.value !== 'deleted' || this.showDeletedTab"),
|
||||
"已删除入口应由 showDeletedTab 控制可见性"
|
||||
);
|
||||
|
||||
assert(
|
||||
searchBarSource.includes("const includeDeleted = this.activeTab === 'deleted'"),
|
||||
"切换已删除入口时应生成 includeDeleted 查询态"
|
||||
);
|
||||
|
||||
assert(
|
||||
searchBarSource.includes("status: includeDeleted || this.activeTab === 'all' ? null : this.activeTab"),
|
||||
"已删除列表不应和普通状态 tab 混用"
|
||||
);
|
||||
|
||||
assert(
|
||||
tableSource.includes('v-if="deletedList"'),
|
||||
"删除列表应使用独立操作区"
|
||||
);
|
||||
|
||||
assert(
|
||||
tableSource.includes('@click="handleRestore(scope.row)"'),
|
||||
"删除列表应提供恢复按钮"
|
||||
);
|
||||
|
||||
assert(
|
||||
tableSource.includes('v-if="canDelete(scope.row) && scope.row.status !== \'5\'"'),
|
||||
"普通列表应按 canDelete 展示删除按钮且排除已删除状态"
|
||||
);
|
||||
|
||||
assert(
|
||||
/<template v-if="deletedList">[\s\S]*?handleRestore\(scope\.row\)[\s\S]*?<\/template>\s*<template v-else>[\s\S]*?handleViewResult/.test(tableSource),
|
||||
"删除列表只展示恢复动作,进入项目和查看结果应留在普通列表分支"
|
||||
);
|
||||
|
||||
assert(
|
||||
pageSource.includes("rebuildProjectTags({ projectId: row.projectId })"),
|
||||
|
||||
@@ -23,5 +23,14 @@ assert(
|
||||
!source.includes("::v-deep .el-table"),
|
||||
"项目列表不应继续保留自定义深度表格皮肤"
|
||||
);
|
||||
assert(
|
||||
source.includes('class="delete-button"'),
|
||||
"项目列表删除按钮应使用独立红色样式类"
|
||||
);
|
||||
assert(
|
||||
source.includes("::v-deep .el-button--text.delete-button") &&
|
||||
source.includes("color: #f56c6c"),
|
||||
"项目列表删除按钮应渲染为红色"
|
||||
);
|
||||
|
||||
console.log("project-table-style test passed");
|
||||
|
||||
Reference in New Issue
Block a user