添加顶部导航,添加采购与合同审批,另外添加采购流合同流说明

This commit is contained in:
2026-06-16 17:27:22 +08:00
parent 7efc03570d
commit 184202b82f
14 changed files with 1272 additions and 8 deletions

View File

@@ -0,0 +1,43 @@
import request from '@/utils/request'
// ===== 审批配置 =====
export function listApprovalConfig() {
return request({ url: '/oa/approval/config/list', method: 'get' })
}
export function getApprovalConfig(businessType) {
return request({ url: '/oa/approval/config/' + businessType, method: 'get' })
}
export function saveApprovalConfig(data) {
return request({ url: '/oa/approval/config', method: 'post', data })
}
export function delApprovalConfig(id) {
return request({ url: '/oa/approval/config/' + id, method: 'delete' })
}
// ===== 我的审批 =====
export function listMyPending(query) {
return request({ url: '/oa/approval/mine/pending', method: 'get', params: query })
}
export function listMyDone(query) {
return request({ url: '/oa/approval/mine/done', method: 'get', params: query })
}
export function listMySubmitted(query) {
return request({ url: '/oa/approval/mine/submitted', method: 'get', params: query })
}
export function listAllApproval(query) {
return request({ url: '/oa/approval/list', method: 'get', params: query })
}
// ===== 操作 =====
export function actApproval(data) {
return request({ url: '/oa/approval/act', method: 'post', data })
}
export function withdrawApproval(instanceId) {
return request({ url: '/oa/approval/withdraw/' + instanceId, method: 'post' })
}
export function getApprovalDetail(instanceId) {
return request({ url: '/oa/approval/detail/' + instanceId, method: 'get' })
}
export function getLatestApproval(businessType, businessId) {
return request({ url: '/oa/approval/latest', method: 'get', params: { businessType, businessId } })
}

View File

@@ -3,8 +3,8 @@
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container"
@toggleClick="toggleSideBar" />
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!topNav" />
<top-nav id="topmenu-container" class="topmenu-container" v-if="topNav" />
<sub-menu-bar v-if="!topNav" />
<div class="right-menu">
<template v-if="device !== 'mobile'">
@@ -51,6 +51,7 @@ import RuoYiGit from "@/components/RuoYi/Git";
import Screenfull from "@/components/Screenfull";
import SizeSelect from "@/components/SizeSelect";
import TopNav from "@/components/TopNav";
import SubMenuBar from "@/layout/components/SubMenuBar/index.vue";
import AIChat from "@/layout/components/AIChat/index.vue";
import FeedbackEntry from "@/layout/components/FeedbackEntry.vue";
import { parseTime } from "@/utils/ruoyi";
@@ -65,6 +66,7 @@ export default {
// ChatComponent,
Breadcrumb,
TopNav,
SubMenuBar,
Hamburger,
Screenfull,
SizeSelect,

View File

@@ -0,0 +1,95 @@
<template>
<div class="sub-menu-bar" v-if="items.length">
<router-link
v-for="item in items"
:key="item.path"
:to="item.path"
class="sub-menu-item"
:class="{ active: isActive(item) }"
>{{ item.title }}</router-link>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
name: "SubMenuBar",
computed: {
...mapGetters(["sidebarRouters"]),
activeTopPath () {
const path = this.$route.path || "";
if (!path || path === "/") return "";
const seg = path.split("/").filter(Boolean)[0];
return seg ? "/" + seg : "";
},
items () {
const top = this.findTop(this.sidebarRouters, this.activeTopPath);
if (!top) return [];
const children = (top.children || []).filter(c => !c.hidden);
return children.map(c => ({
path: this.resolvePath(top.path, c.path),
title: c.meta?.title
})).filter(it => it.title);
}
},
methods: {
findTop (routes, activePath) {
if (!routes || !activePath) return null;
for (const r of routes) {
if (r.hidden) continue;
const rp = r.path?.startsWith("/") ? r.path : "/" + r.path;
if (rp === activePath) return r;
}
return null;
},
resolvePath (parent, child) {
if (!child) return parent;
if (/^https?:\/\//.test(child)) return child;
if (child.startsWith("/")) return child;
return (parent || "").replace(/\/+$/, "") + "/" + child;
},
isActive (item) {
return this.$route.path === item.path || this.$route.path.startsWith(item.path + "/");
}
}
};
</script>
<style lang="scss" scoped>
.sub-menu-bar {
float: left;
display: flex;
align-items: center;
height: 100%;
padding-left: 12px;
overflow-x: auto;
max-width: calc(100% - 360px);
}
.sub-menu-bar::-webkit-scrollbar {
height: 0;
}
.sub-menu-item {
display: inline-flex;
align-items: center;
height: 100%;
padding: 0 16px;
font-size: 13px;
color: #5a5e66;
white-space: nowrap;
text-decoration: none;
border-bottom: 2px solid transparent;
transition: color 0.15s;
}
.sub-menu-item:hover {
color: #303133;
}
.sub-menu-item.active {
color: #409eff;
border-bottom-color: #409eff;
}
</style>

View File

@@ -0,0 +1,169 @@
<template>
<div class="app-container">
<el-alert
title="配置每个业务的审批人和或签/会签规则。审批人 user_id 多选;保存后立即生效,但只影响新提交的审批单。"
type="info" show-icon :closable="false" style="margin-bottom: 12px;" />
<div style="margin-bottom: 10px;">
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd">新增业务配置</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="getList">刷新</el-button>
</div>
<el-table v-loading="loading" :data="list">
<el-table-column label="业务类型 key" prop="businessType" width="180" />
<el-table-column label="业务名称" prop="businessName" width="180" />
<el-table-column label="审批人" prop="approverNames" min-width="200" show-overflow-tooltip>
<template slot-scope="{ row }">
<el-tag v-for="(name, i) in (row.approverNames || '').split(',').filter(Boolean)"
:key="i" size="mini" style="margin-right: 4px;">{{ name }}</el-tag>
</template>
</el-table-column>
<el-table-column label="规则" prop="signType" width="100" align="center">
<template slot-scope="{ row }">
<el-tag v-if="row.signType === 1" type="success" size="mini">或签</el-tag>
<el-tag v-else type="warning" size="mini">会签</el-tag>
</template>
</el-table-column>
<el-table-column label="启用" prop="enabled" width="80" align="center">
<template slot-scope="{ row }">
<el-tag v-if="row.enabled === 1" size="mini" type="success">启用</el-tag>
<el-tag v-else size="mini" type="info">停用</el-tag>
</template>
</el-table-column>
<el-table-column label="备注" prop="remark" min-width="160" show-overflow-tooltip />
<el-table-column label="操作" width="180" align="center" fixed="right">
<template slot-scope="{ row }">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleEdit(row)">改审批人</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" style="color:#f56c6c" @click="handleDel(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog :title="form.id ? '修改审批配置' : '新增审批配置'" :visible.sync="open" width="640px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="120px" size="small">
<el-form-item label="业务类型 key" prop="businessType">
<el-input v-model="form.businessType" :disabled="!!form.id" placeholder="如 purchase_req / contract" />
<div class="form-tip">代码内调用 approvalService.submit 时使用的 key建表后不要修改</div>
</el-form-item>
<el-form-item label="业务名称" prop="businessName">
<el-input v-model="form.businessName" placeholder="如 采购需求" />
</el-form-item>
<el-form-item label="审批人" prop="approverIdList">
<el-select v-model="form.approverIdList" multiple filterable
placeholder="选择一个或多个审批人" style="width: 100%">
<el-option v-for="u in userOptions" :key="u.userId"
:label="u.nickName + (u.userName ? (''+u.userName+'') : '')" :value="u.userId" />
</el-select>
</el-form-item>
<el-form-item label="规则" prop="signType">
<el-radio-group v-model="form.signType">
<el-radio :label="1">或签任一人通过即通过</el-radio>
<el-radio :label="2">会签全部通过才通过</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="启用" prop="enabled">
<el-switch v-model="form.enabled" :active-value="1" :inactive-value="0" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="2" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="open = false">取消</el-button>
<el-button type="primary" @click="submit"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listApprovalConfig, saveApprovalConfig, delApprovalConfig } from '@/api/oa/approval'
import { listUser } from '@/api/system/user'
export default {
name: 'OaApprovalConfig',
data() {
return {
loading: false,
list: [],
userOptions: [],
open: false,
form: this.emptyForm(),
rules: {
businessType: [{ required: true, message: '业务类型不能为空', trigger: 'blur' }],
businessName: [{ required: true, message: '业务名称不能为空', trigger: 'blur' }],
approverIdList: [{ required: true, type: 'array', min: 1, message: '至少选择一个审批人', trigger: 'change' }],
signType: [{ required: true, message: '请选择规则', trigger: 'change' }]
}
}
},
created() {
this.getList()
this.loadUsers()
},
methods: {
emptyForm() {
return { id: null, businessType: '', businessName: '', approverIdList: [], signType: 1, enabled: 1, remark: '' }
},
getList() {
this.loading = true
listApprovalConfig().then(res => {
this.list = res.data || []
}).finally(() => { this.loading = false })
},
loadUsers() {
listUser({ pageNum: 1, pageSize: 1000, status: '0' }).then(res => {
this.userOptions = res.rows || []
})
},
handleAdd() {
this.form = this.emptyForm()
this.open = true
},
handleEdit(row) {
const ids = (row.approverIds || '').split(',').filter(Boolean).map(s => Number(s))
this.form = {
id: row.id,
businessType: row.businessType,
businessName: row.businessName,
approverIdList: ids,
signType: row.signType,
enabled: row.enabled,
remark: row.remark
}
this.open = true
},
submit() {
this.$refs.form.validate(valid => {
if (!valid) return
const payload = {
id: this.form.id,
businessType: this.form.businessType,
businessName: this.form.businessName,
approverIds: (this.form.approverIdList || []).join(','),
signType: this.form.signType,
enabled: this.form.enabled,
remark: this.form.remark
}
saveApprovalConfig(payload).then(() => {
this.$modal.msgSuccess('保存成功')
this.open = false
this.getList()
})
})
},
handleDel(row) {
this.$modal.confirm('确认删除业务【' + row.businessName + '】的审批配置?').then(() =>
delApprovalConfig(row.id)
).then(() => {
this.$modal.msgSuccess('删除成功')
this.getList()
}).catch(() => {})
}
}
}
</script>
<style scoped>
.form-tip { color: #909399; font-size: 12px; line-height: 1.4; margin-top: 4px; }
</style>

View File

@@ -0,0 +1,168 @@
<template>
<div class="app-container">
<el-tabs v-model="tab" @tab-click="onTabClick" class="compact-tabs">
<el-tab-pane name="pending" label="待我审批" />
<el-tab-pane name="done" label="我已审批" />
</el-tabs>
<el-form :model="queryParams" size="mini" :inline="true" class="compact-search">
<el-form-item label="业务类型">
<el-select v-model="queryParams.businessType" clearable placeholder="全部" style="width: 200px;">
<el-option v-for="c in configs" :key="c.businessType"
:label="c.businessName + '(' + c.businessType + ')'" :value="c.businessType" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="reset">重置</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="list">
<el-table-column label="业务" width="160">
<template slot-scope="{ row }">
{{ row.businessName || row.businessType }}
</template>
</el-table-column>
<el-table-column label="标题" prop="businessTitle" min-width="200" show-overflow-tooltip />
<el-table-column label="申请人" prop="applyUserName" width="100" />
<el-table-column label="申请时间" prop="applyTime" width="160" />
<el-table-column label="规则" width="80" align="center">
<template slot-scope="{ row }">
<el-tag size="mini" :type="row.signType === 1 ? 'success' : 'warning'">
{{ row.signType === 1 ? '或签' : '会签' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="审批人" prop="approverNames" min-width="160" show-overflow-tooltip />
<el-table-column label="状态" width="80" align="center">
<template slot-scope="{ row }">
<el-tag size="mini" :type="statusTag(row.status)">{{ statusText(row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center" fixed="right">
<template slot-scope="{ row }">
<el-button v-if="tab === 'pending'" type="success" size="mini" @click="openAct(row, 1)">通过</el-button>
<el-button v-if="tab === 'pending'" type="danger" size="mini" @click="openAct(row, 2)">驳回</el-button>
<el-button type="text" size="mini" icon="el-icon-document" @click="openDetail(row)">详情</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total"
:page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
@pagination="getList" />
<!-- 审批操作弹窗 -->
<el-dialog :title="actForm.action === 1 ? '审批通过' : '审批驳回'" :visible.sync="actOpen" width="500px" append-to-body>
<div style="margin-bottom: 10px;">
<b>{{ actForm.businessName }} - {{ actForm.businessTitle }}</b>
</div>
<el-input v-model="actForm.comment" type="textarea" :rows="3" placeholder="意见(可选)" />
<div slot="footer">
<el-button @click="actOpen = false">取消</el-button>
<el-button :type="actForm.action === 1 ? 'success' : 'danger'" @click="doAct">
{{ actForm.action === 1 ? '确认通过' : '确认驳回' }}
</el-button>
</div>
</el-dialog>
<!-- 详情弹窗 -->
<el-dialog title="审批详情" :visible.sync="detailOpen" width="640px" append-to-body>
<div v-if="detail">
<p><b>业务</b>{{ detail.businessName || detail.businessType }} #{{ detail.businessId }}</p>
<p><b>标题</b>{{ detail.businessTitle }}</p>
<p><b>申请人</b>{{ detail.applyUserName }}{{ detail.applyTime }}</p>
<p><b>审批人</b>{{ detail.approverNames }}{{ detail.signType === 1 ? '或签' : '会签' }}</p>
<p><b>状态</b>
<el-tag size="mini" :type="statusTag(detail.status)">{{ statusText(detail.status) }}</el-tag>
</p>
<el-divider>审批流水</el-divider>
<el-timeline>
<el-timeline-item v-for="r in (detail.records || [])" :key="r.id"
:type="r.action === 1 ? 'success' : 'danger'"
:timestamp="r.opTime">
<b>{{ r.approverName }}</b> {{ r.action === 1 ? '通过' : '驳回' }}
<div v-if="r.comment" style="color:#666;">{{ r.comment }}</div>
</el-timeline-item>
</el-timeline>
</div>
</el-dialog>
</div>
</template>
<script>
import {
listMyPending, listMyDone, listApprovalConfig,
actApproval, getApprovalDetail
} from '@/api/oa/approval'
export default {
name: 'OaApprovalPending',
data() {
return {
tab: 'pending',
loading: false,
total: 0,
list: [],
configs: [],
queryParams: { pageNum: 1, pageSize: 10, businessType: undefined },
actOpen: false,
actForm: { instanceId: null, action: 1, comment: '', businessName: '', businessTitle: '' },
detailOpen: false,
detail: null
}
},
created() {
this.getList()
listApprovalConfig().then(res => { this.configs = res.data || [] })
},
methods: {
statusText(s) {
return ({ 0: '待审', 1: '通过', 2: '驳回', 3: '撤回' })[s] || '-'
},
statusTag(s) {
return ({ 0: 'warning', 1: 'success', 2: 'danger', 3: 'info' })[s] || ''
},
onTabClick() {
this.queryParams.pageNum = 1
this.getList()
},
handleQuery() { this.queryParams.pageNum = 1; this.getList() },
reset() { this.queryParams = { pageNum: 1, pageSize: 10, businessType: undefined }; this.getList() },
getList() {
this.loading = true
const fn = this.tab === 'pending' ? listMyPending : listMyDone
fn(this.queryParams).then(res => {
this.list = res.rows || []
this.total = res.total || 0
}).finally(() => { this.loading = false })
},
openAct(row, action) {
this.actForm = {
instanceId: row.id, action, comment: '',
businessName: row.businessName || row.businessType,
businessTitle: row.businessTitle
}
this.actOpen = true
},
doAct() {
actApproval({
instanceId: this.actForm.instanceId,
action: this.actForm.action,
comment: this.actForm.comment
}).then(() => {
this.$modal.msgSuccess('已提交')
this.actOpen = false
this.getList()
})
},
openDetail(row) {
getApprovalDetail(row.id).then(res => {
this.detail = res.data
this.detailOpen = true
})
}
}
}
</script>

View File

@@ -0,0 +1,112 @@
<template>
<div class="app-container">
<el-form :model="queryParams" size="mini" :inline="true" class="compact-search">
<el-form-item label="业务类型">
<el-select v-model="queryParams.businessType" clearable placeholder="全部" style="width: 200px;">
<el-option v-for="c in configs" :key="c.businessType"
:label="c.businessName + '(' + c.businessType + ')'" :value="c.businessType" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="reset">重置</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="list">
<el-table-column label="业务" width="160">
<template slot-scope="{ row }">{{ row.businessName || row.businessType }}</template>
</el-table-column>
<el-table-column label="标题" prop="businessTitle" min-width="200" show-overflow-tooltip />
<el-table-column label="申请时间" prop="applyTime" width="160" />
<el-table-column label="审批人" prop="approverNames" min-width="160" show-overflow-tooltip />
<el-table-column label="状态" width="80" align="center">
<template slot-scope="{ row }">
<el-tag size="mini" :type="statusTag(row.status)">{{ statusText(row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="完成时间" prop="finishTime" width="160" />
<el-table-column label="操作" width="180" align="center" fixed="right">
<template slot-scope="{ row }">
<el-button v-if="row.status === 0" type="text" size="mini" style="color:#f56c6c"
@click="withdraw(row)">撤回</el-button>
<el-button type="text" size="mini" icon="el-icon-document" @click="openDetail(row)">详情</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total"
:page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
@pagination="getList" />
<el-dialog title="审批详情" :visible.sync="detailOpen" width="640px" append-to-body>
<div v-if="detail">
<p><b>业务</b>{{ detail.businessName || detail.businessType }} #{{ detail.businessId }}</p>
<p><b>标题</b>{{ detail.businessTitle }}</p>
<p><b>申请人</b>{{ detail.applyUserName }}{{ detail.applyTime }}</p>
<p><b>审批人</b>{{ detail.approverNames }}{{ detail.signType === 1 ? '或签' : '会签' }}</p>
<p><b>状态</b>
<el-tag size="mini" :type="statusTag(detail.status)">{{ statusText(detail.status) }}</el-tag>
</p>
<el-divider>审批流水</el-divider>
<el-timeline>
<el-timeline-item v-for="r in (detail.records || [])" :key="r.id"
:type="r.action === 1 ? 'success' : 'danger'"
:timestamp="r.opTime">
<b>{{ r.approverName }}</b> {{ r.action === 1 ? '通过' : '驳回' }}
<div v-if="r.comment" style="color:#666;">{{ r.comment }}</div>
</el-timeline-item>
</el-timeline>
</div>
</el-dialog>
</div>
</template>
<script>
import {
listMySubmitted, listApprovalConfig,
withdrawApproval, getApprovalDetail
} from '@/api/oa/approval'
export default {
name: 'OaApprovalSubmitted',
data() {
return {
loading: false, total: 0, list: [], configs: [],
queryParams: { pageNum: 1, pageSize: 10, businessType: undefined },
detailOpen: false, detail: null
}
},
created() {
this.getList()
listApprovalConfig().then(res => { this.configs = res.data || [] })
},
methods: {
statusText(s) { return ({ 0: '待审', 1: '通过', 2: '驳回', 3: '撤回' })[s] || '-' },
statusTag(s) { return ({ 0: 'warning', 1: 'success', 2: 'danger', 3: 'info' })[s] || '' },
handleQuery() { this.queryParams.pageNum = 1; this.getList() },
reset() { this.queryParams = { pageNum: 1, pageSize: 10, businessType: undefined }; this.getList() },
getList() {
this.loading = true
listMySubmitted(this.queryParams).then(res => {
this.list = res.rows || []
this.total = res.total || 0
}).finally(() => { this.loading = false })
},
withdraw(row) {
this.$modal.confirm('确认撤回审批单?').then(() =>
withdrawApproval(row.id)
).then(() => {
this.$modal.msgSuccess('已撤回')
this.getList()
}).catch(() => {})
},
openDetail(row) {
getApprovalDetail(row.id).then(res => {
this.detail = res.data
this.detailOpen = true
})
}
}
}
</script>

View File

@@ -0,0 +1,278 @@
<template>
<div class="app-container docs-page">
<h1>合同流 · 使用说明</h1>
<el-alert type="info" :closable="false" show-icon
title="合同从「谁起草」到「谁批」到「正式生效」的全过程。本页让业务员、法务、领导都清楚自己在哪一步。"
style="margin-bottom: 20px;" />
<!-- ============ 泳道图 ============ -->
<el-card class="doc-card" shadow="never">
<div slot="header"><b>流程全景泳道图</b></div>
<div class="swimlane-wrap">
<svg viewBox="0 0 1180 560" class="swimlane-svg" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#606266" /></marker>
<marker id="arrow-red" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#f56c6c" /></marker>
<marker id="arrow-green" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#67c23a" /></marker>
<marker id="arrow-dash" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#909399" /></marker>
</defs>
<!-- 泳道背景 -->
<g class="lanes">
<rect x="120" y="20" width="1040" height="100" fill="#f9f6ff" stroke="#d3adf7" />
<text x="20" y="76" class="lane-label">管理员</text>
<text x="20" y="92" class="lane-sub">配置审批人</text>
<rect x="120" y="125" width="1040" height="120" fill="#eef9ff" stroke="#91d5ff" />
<text x="20" y="185" class="lane-label">起草人</text>
<text x="20" y="201" class="lane-sub">业务员 / 法务</text>
<rect x="120" y="250" width="1040" height="150" fill="#fff7e6" stroke="#ffd591" />
<text x="20" y="325" class="lane-label">系统</text>
<text x="20" y="341" class="lane-sub">自动派单 / 提醒</text>
<rect x="120" y="405" width="1040" height="135" fill="#f6ffed" stroke="#b7eb8f" />
<text x="20" y="475" class="lane-label">审批人</text>
<text x="20" y="491" class="lane-sub">领导 / 法务 / 财务</text>
</g>
<!-- ===== 管理员 ===== -->
<g class="node node-admin">
<rect x="140" y="40" width="180" height="60" rx="6" />
<text x="230" y="64" class="t1"> 配置谁来批合同</text>
<text x="230" y="82" class="t2">选定审批人</text>
<text x="230" y="96" class="t2">设置或签 / 会签</text>
</g>
<!-- ===== 起草人 ===== -->
<g class="node node-user">
<rect x="140" y="150" width="180" height="70" rx="6" />
<text x="230" y="178" class="t1"> 起草合同</text>
<text x="230" y="196" class="t2">填合同名/甲乙方/金额</text>
<text x="230" y="210" class="t2">/合同管理/新增</text>
</g>
<g class="node node-user">
<rect x="900" y="150" width="210" height="70" rx="6" />
<text x="1005" y="178" class="t1"> 合同生效执行</text>
<text x="1005" y="196" class="t2">签订 / 履约 / 归档</text>
<text x="1005" y="210" class="t2">审批通过后才发出</text>
</g>
<!-- ===== 系统 ===== -->
<g class="node node-sys">
<rect x="350" y="270" width="170" height="70" rx="6" />
<text x="435" y="296" class="t1"> 自动生成审批单</text>
<text x="435" y="314" class="t2">把当前审批人 +</text>
<text x="435" y="328" class="t2">或签/会签规则定下来</text>
</g>
<g class="node node-sys">
<rect x="560" y="270" width="160" height="70" rx="6" />
<text x="640" y="296" class="t1"> 派单给审批人</text>
<text x="640" y="314" class="t2">我的审批</text>
<text x="640" y="328" class="t2">出现待办</text>
</g>
<g class="node node-sys">
<rect x="900" y="350" width="210" height="40" rx="6" />
<text x="1005" y="376" class="t1">' 判定整单结果(或/会签)</text>
</g>
<!-- ===== 审批人 ===== -->
<g class="node node-app">
<rect x="560" y="430" width="180" height="80" rx="6" />
<text x="650" y="456" class="t1">⑤ 审批人处理</text>
<text x="650" y="474" class="t2">通过 ✓ 或 驳回 ✗</text>
<text x="650" y="490" class="t2">可写意见</text>
<text x="650" y="506" class="t2">流水自动留痕</text>
</g>
<!-- 配置 → 生成(虚线) -->
<path d="M 320 80 C 400 80, 400 240, 435 270"
fill="none" stroke="#909399" stroke-width="1.4" stroke-dasharray="5 4" marker-end="url(#arrow-dash)" />
<text x="345" y="160" class="arrow-label dim">仅影响后续新单</text>
<!-- ② → ③ -->
<path d="M 320 200 C 360 200, 360 250, 435 270"
fill="none" stroke="#606266" stroke-width="1.6" marker-end="url(#arrow)" />
<!-- ③ → ④ -->
<line x1="520" y1="305" x2="560" y2="305" stroke="#606266" stroke-width="1.6" marker-end="url(#arrow)" />
<!-- ④ → ⑤ -->
<path d="M 640 340 C 640 380, 650 405, 650 430"
fill="none" stroke="#606266" stroke-width="1.6" marker-end="url(#arrow)" />
<text x="660" y="395" class="arrow-label">收到待办</text>
<!-- ⑤ → ⑥' -->
<path d="M 740 460 C 850 460, 880 410, 950 390"
fill="none" stroke="#606266" stroke-width="1.6" marker-end="url(#arrow)" />
<!-- ' → ⑦ 通过 -->
<path d="M 1005 350 L 1005 220"
fill="none" stroke="#67c23a" stroke-width="2" marker-end="url(#arrow-green)" />
<text x="1018" y="290" class="arrow-label ok">通过</text>
<!-- ⑥' 驳回闭环 -->
<path d="M 900 370 C 700 370, 400 380, 230 240"
fill="none" stroke="#f56c6c" stroke-width="2" stroke-dasharray="6 3" marker-end="url(#arrow-red)" />
<text x="540" y="408" class="arrow-label bad">驳回 改完再提自动开新单</text>
<!-- 完成 -->
<g class="node node-end">
<circle cx="1130" cy="185" r="22" />
<text x="1130" y="190" class="t-end">归档</text>
</g>
<line x1="1110" y1="185" x2="1108" y2="185" stroke="#606266" stroke-width="1.6" marker-end="url(#arrow)" />
</svg>
</div>
<div class="legend">
<span><i class="dot dot-admin"></i>管理员动作</span>
<span><i class="dot dot-user"></i>起草人动作</span>
<span><i class="dot dot-sys"></i>系统自动</span>
<span><i class="dot dot-app"></i>审批人动作</span>
<span><svg width="34" height="10"><line x1="0" y1="5" x2="30" y2="5" stroke="#67c23a" stroke-width="2"/></svg> 通过</span>
<span><svg width="34" height="10"><line x1="0" y1="5" x2="30" y2="5" stroke="#f56c6c" stroke-width="2" stroke-dasharray="6 3"/></svg> 驳回退回</span>
</div>
</el-card>
<!-- ============ 审批状态 ============ -->
<el-card class="doc-card" shadow="never">
<div slot="header"><b>合同列表里审批列的含义</b></div>
<p class="hint">合同管理列表会显示一列审批告诉你这份合同走到流程哪一步了</p>
<el-row :gutter="20">
<el-col :span="12">
<div class="state-card">
<h4>审批<span class="state-owner">合同到底批没批</span></h4>
<div class="state-row"><el-tag size="small" type="info">未提交</el-tag><span>合同审批未启用 / 未配置合同直接生效</span></div>
<div class="state-row"><el-tag size="small" type="warning">待审</el-tag><span>已派给审批人等他们处理</span></div>
<div class="state-row"><el-tag size="small" type="success">通过</el-tag><span>合同可以正式签订 / 发出</span></div>
<div class="state-row"><el-tag size="small" type="danger">驳回</el-tag><span>修改合同内容后再保存系统会自动新开一张审批单</span></div>
<div class="state-row"><el-tag size="small" type="info">撤回</el-tag><span>起草人主动撤回本单作废</span></div>
</div>
</el-col>
<el-col :span="12">
<div class="state-card warn">
<h4> 卡点未审批通过不能标记完结</h4>
<p>合同的字段甲乙方/金额/条款等<b>不受审批拦截</b>可以照常编辑但要把合同状态推进到 <el-tag size="mini" type="success">完结</el-tag> <b>最新审批必须是通过</b>否则系统会拒绝并提示该合同尚未审批通过</p>
<p class="hint">如果整个合同审批没启用 / 未配置列表显示"未提交"则不会有任何拦截完结照常推进</p>
</div>
</el-col>
</el-row>
</el-card>
<!-- ============ 角色操作速查 ============ -->
<el-card class="doc-card" shadow="never">
<div slot="header"><b>不同角色怎么用</b></div>
<el-table :data="roleSteps" border size="small">
<el-table-column label="你是" prop="role" width="100" />
<el-table-column label="去哪儿" prop="menu" width="240" />
<el-table-column label="做什么" prop="action" />
</el-table>
</el-card>
<!-- ============ 或签 vs 会签 ============ -->
<el-card class="doc-card" shadow="never">
<div slot="header"><b>或签 vs 会签在合同场景下怎么选</b></div>
<el-row :gutter="20">
<el-col :span="12">
<div class="state-card">
<h4>或签 <el-tag size="mini" type="success">任一人即可</el-tag></h4>
<p>审批人列表里 <b>任意一人</b> 通过 整张合同审批通过</p>
<p class="hint">适用日常采购合同小额合同找最快有空的领导批即可的场景</p>
</div>
</el-col>
<el-col :span="12">
<div class="state-card">
<h4>会签 <el-tag size="mini" type="warning">必须全员通过</el-tag></h4>
<p>审批人列表里 <b>所有人</b> 都通过 整张合同审批才通过</p>
<p class="hint">适用大额合同 / 跨部门合同 / 重大法务事项 法务 + 财务 + 主管领导都要背书</p>
</div>
</el-col>
</el-row>
</el-card>
<!-- ============ FAQ ============ -->
<el-card class="doc-card" shadow="never">
<div slot="header"><b>常见问题</b></div>
<el-collapse>
<el-collapse-item title="我新建了合同,但不想走审批,怎么办?">
找管理员在审批配置里把合同这一行的启用开关关掉新建合同就不再产生审批单可以直接执行
</el-collapse-item>
<el-collapse-item title="合同已经审批通过了,但条款要改怎么办?">
目前实现不会自动重新发起审批如果改动比较大有原则性变化建议起草人在我的发起撤回旧单如果还能撤回或者重新建一份合同记录走新流程留好痕迹
</el-collapse-item>
<el-collapse-item title="审批被驳回了,合同记录还在吗?">
合同本身不会消失改完字段重新保存时系统会自动新开一张审批单流程图里那条红色虚线
</el-collapse-item>
<el-collapse-item title="谁审过、什么意见去哪看?">
起草人去审批中心 我的发起找到这张合同的审批单点详情里面有完整的审批流水什么时间通过/驳回意见审批人本人在我的审批 我已审批也能看
</el-collapse-item>
<el-collapse-item title="我换了合同审批人,老合同的审批单上人没变?">
单子生成时已经把审批人和或签/会签规则定下来了 <b>不会回溯</b>修改只对之后新提交的合同生效
</el-collapse-item>
</el-collapse>
</el-card>
</div>
</template>
<script>
export default {
name: 'OaDocContractFlow',
data() {
return {
roleSteps: [
{ role: '管理员', menu: '审批中心 → 审批配置', action: '选定合同的审批人;切换或签 / 会签;启用或停用合同审批。修改不影响已发起的旧单。' },
{ role: '起草人', menu: '合同管理 → 新增', action: '填合同信息,保存后系统自动派单给审批人;列表的「审批」列会出现「待审」。' },
{ role: '起草人', menu: '审批中心 → 我的发起', action: '查自己提交过的所有合同审批单 + 流水 + 意见;待审中可撤回。' },
{ role: '审批人', menu: '审批中心 → 我的审批', action: '「待我审批」tab 里点通过 / 驳回并写意见;「我已审批」可回看历史。' }
]
}
}
}
</script>
<style scoped>
.docs-page { max-width: 1240px; }
.docs-page h1 { font-size: 22px; margin: 0 0 16px; }
.doc-card { margin-bottom: 16px; }
.swimlane-wrap { width: 100%; overflow-x: auto; }
.swimlane-svg { width: 100%; min-width: 1100px; height: auto; display: block; }
.swimlane-svg .lane-label { font-size: 15px; font-weight: 600; fill: #303133; }
.swimlane-svg .lane-sub { font-size: 11px; fill: #909399; }
.swimlane-svg .node rect { stroke-width: 1.3; }
.swimlane-svg .node .t1 { font-size: 13px; font-weight: 600; text-anchor: middle; fill: #303133; }
.swimlane-svg .node .t2 { font-size: 11px; text-anchor: middle; fill: #606266; }
.swimlane-svg .node-admin rect { fill: #fff; stroke: #b37feb; }
.swimlane-svg .node-user rect { fill: #fff; stroke: #40a9ff; }
.swimlane-svg .node-sys rect { fill: #fff; stroke: #ffa940; }
.swimlane-svg .node-app rect { fill: #fff; stroke: #73d13d; }
.swimlane-svg .node-end circle { fill: #67c23a; stroke: #67c23a; }
.swimlane-svg .node-end .t-end { font-size: 12px; fill: #fff; font-weight: 600; text-anchor: middle; }
.swimlane-svg .arrow-label { font-size: 11px; fill: #606266; }
.swimlane-svg .arrow-label.dim { fill: #909399; }
.swimlane-svg .arrow-label.ok { fill: #67c23a; font-weight: 600; }
.swimlane-svg .arrow-label.bad { fill: #f56c6c; font-weight: 600; }
.legend { margin-top: 12px; padding: 8px 12px; background: #fafafa; border-radius: 4px;
display: flex; flex-wrap: wrap; gap: 18px; font-size: 12px; color: #606266; align-items: center; }
.legend .dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; margin-right: 6px; vertical-align: middle; }
.legend .dot-sys { background: #ffa940; }
.legend .dot-user { background: #40a9ff; }
.legend .dot-app { background: #73d13d; }
.legend .dot-admin { background: #b37feb; }
.state-card { background: #fafafa; border-radius: 6px; padding: 14px 16px; height: 100%; }
.state-card.warn { background: #fff8e6; }
.state-card h4 { margin: 0 0 10px; font-size: 14px; }
.state-card .state-owner { font-weight: normal; color: #909399; margin-left: 6px; font-size: 12px; }
.state-card .state-row { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; font-size: 13px; color: #606266; }
.state-card p { line-height: 1.7; color: #303133; margin: 6px 0; }
.state-card p.hint { color: #909399; font-size: 12px; }
.docs-page .hint { color: #909399; font-size: 13px; margin-top: 0; }
</style>

View File

@@ -0,0 +1,295 @@
<template>
<div class="app-container docs-page">
<h1>采购流 · 使用说明</h1>
<el-alert type="info" :closable="false" show-icon
title="一张图看懂:采购需求从「谁提」到「谁审」再到「采购完成」全过程。下方的状态对照和速查表帮你弄清自己当下该做什么。"
style="margin-bottom: 20px;" />
<!-- ============ 泳道图 ============ -->
<el-card class="doc-card" shadow="never">
<div slot="header"><b>流程全景泳道图</b></div>
<div class="swimlane-wrap">
<svg viewBox="0 0 1180 560" class="swimlane-svg" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#606266" />
</marker>
<marker id="arrow-red" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#f56c6c" />
</marker>
<marker id="arrow-green" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#67c23a" />
</marker>
<marker id="arrow-dash" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#909399" />
</marker>
</defs>
<!-- 泳道背景 -->
<g class="lanes">
<rect x="120" y="20" width="1040" height="100" fill="#f9f6ff" stroke="#d3adf7" />
<text x="20" y="76" class="lane-label">管理员</text>
<text x="20" y="92" class="lane-sub">配置审批人</text>
<rect x="120" y="125" width="1040" height="120" fill="#eef9ff" stroke="#91d5ff" />
<text x="20" y="185" class="lane-label">发起人</text>
<text x="20" y="201" class="lane-sub">提需求 / 推进</text>
<rect x="120" y="250" width="1040" height="150" fill="#fff7e6" stroke="#ffd591" />
<text x="20" y="325" class="lane-label">系统</text>
<text x="20" y="341" class="lane-sub">自动派单 / 提醒</text>
<rect x="120" y="405" width="1040" height="135" fill="#f6ffed" stroke="#b7eb8f" />
<text x="20" y="475" class="lane-label">审批人</text>
<text x="20" y="491" class="lane-sub">通过 / 驳回</text>
</g>
<!-- ===== 管理员 ===== -->
<g class="node node-admin">
<rect x="140" y="40" width="180" height="60" rx="6" />
<text x="230" y="64" class="t1"> 配置谁来审</text>
<text x="230" y="82" class="t2">选定审批人</text>
<text x="230" y="96" class="t2">设置或签 / 会签</text>
</g>
<!-- ===== 发起人 ===== -->
<g class="node node-user">
<rect x="140" y="150" width="170" height="70" rx="6" />
<text x="225" y="178" class="t1"> 新建采购需求</text>
<text x="225" y="196" class="t2">填标题/物料/截止</text>
<text x="225" y="210" class="t2">/任务/采购需求</text>
</g>
<g class="node node-user">
<rect x="900" y="150" width="210" height="70" rx="6" />
<text x="1005" y="178" class="t1"> 推进业务状态</text>
<text x="1005" y="196" class="t2">未采购 采购中 完成</text>
<text x="1005" y="210" class="t2">审批通过后才放开</text>
</g>
<!-- ===== 系统 ===== -->
<g class="node node-sys">
<rect x="350" y="270" width="170" height="70" rx="6" />
<text x="435" y="296" class="t1"> 自动生成审批单</text>
<text x="435" y="314" class="t2">把当前审批人 +</text>
<text x="435" y="328" class="t2">或签/会签规则定下来</text>
</g>
<g class="node node-sys">
<rect x="560" y="270" width="160" height="70" rx="6" />
<text x="640" y="296" class="t1"> 派单给审批人</text>
<text x="640" y="314" class="t2">我的审批</text>
<text x="640" y="328" class="t2">出现待办</text>
</g>
<g class="node node-sys">
<rect x="900" y="350" width="210" height="40" rx="6" />
<text x="1005" y="376" class="t1">' 判定整单结果(或/会签)</text>
</g>
<!-- ===== 审批人 ===== -->
<g class="node node-app">
<rect x="560" y="430" width="180" height="80" rx="6" />
<text x="650" y="456" class="t1">⑤ 审批人处理</text>
<text x="650" y="474" class="t2">通过 ✓ 或 驳回 ✗</text>
<text x="650" y="490" class="t2">可写意见</text>
<text x="650" y="506" class="t2">流水自动留痕</text>
</g>
<!-- ============ 箭头 ============ -->
<!-- 配置 → 审批单生成(虚线,从管理员到 ③) -->
<path d="M 320 80 C 400 80, 400 240, 435 270"
fill="none" stroke="#909399" stroke-width="1.4"
stroke-dasharray="5 4" marker-end="url(#arrow-dash)" />
<text x="345" y="160" class="arrow-label dim">仅影响后续新单</text>
<!-- ② → ③ -->
<path d="M 310 200 C 360 200, 360 250, 435 270"
fill="none" stroke="#606266" stroke-width="1.6" marker-end="url(#arrow)" />
<!-- ③ → ④ -->
<line x1="520" y1="305" x2="560" y2="305" stroke="#606266" stroke-width="1.6" marker-end="url(#arrow)" />
<!-- ④ → ⑤ -->
<path d="M 640 340 C 640 380, 650 405, 650 430"
fill="none" stroke="#606266" stroke-width="1.6" marker-end="url(#arrow)" />
<text x="660" y="395" class="arrow-label">收到待办</text>
<!-- ⑤ → ⑥' 终结判定 -->
<path d="M 740 460 C 850 460, 880 410, 950 390"
fill="none" stroke="#606266" stroke-width="1.6" marker-end="url(#arrow)" />
<!-- ' → ⑦ 通过 -->
<path d="M 1005 350 L 1005 220"
fill="none" stroke="#67c23a" stroke-width="2" marker-end="url(#arrow-green)" />
<text x="1018" y="290" class="arrow-label ok">通过</text>
<!-- ⑥' 驳回闭环 / 红色 -->
<path d="M 900 370 C 700 370, 400 380, 225 240"
fill="none" stroke="#f56c6c" stroke-width="2" stroke-dasharray="6 3"
marker-end="url(#arrow-red)" />
<text x="540" y="408" class="arrow-label bad">驳回 改完再提自动开新单</text>
<!-- 完成 -->
<g class="node node-end">
<circle cx="1130" cy="185" r="22" />
<text x="1130" y="190" class="t-end">完成</text>
</g>
<line x1="1110" y1="185" x2="1108" y2="185" stroke="#606266" stroke-width="1.6" marker-end="url(#arrow)" />
</svg>
</div>
<div class="legend">
<span><i class="dot dot-admin"></i>管理员动作</span>
<span><i class="dot dot-user"></i>发起人动作</span>
<span><i class="dot dot-sys"></i>系统自动</span>
<span><i class="dot dot-app"></i>审批人动作</span>
<span><svg width="34" height="10"><line x1="0" y1="5" x2="30" y2="5" stroke="#67c23a" stroke-width="2"/></svg> 通过</span>
<span><svg width="34" height="10"><line x1="0" y1="5" x2="30" y2="5" stroke="#f56c6c" stroke-width="2" stroke-dasharray="6 3"/></svg> 驳回退回</span>
</div>
</el-card>
<!-- ============ 状态对照 ============ -->
<el-card class="doc-card" shadow="never">
<div slot="header"><b>两类状态对照务必区分</b></div>
<p class="hint">列表里你会看到两列状态左边审批表示这条采购需求 <b>谁来批批了没</b>右边状态表示 <b>采购到哪一步了</b>两者独立但有关联审批没通过业务状态推不动</p>
<el-row :gutter="20">
<el-col :span="12">
<div class="state-card">
<h4>审批<span class="state-owner">谁来批 · 批了没</span></h4>
<div class="state-row"><el-tag size="small" type="info">未提交</el-tag><span>该业务尚未配置审批或审批未启用</span></div>
<div class="state-row"><el-tag size="small" type="warning">待审</el-tag><span>已派给审批人等他们处理</span></div>
<div class="state-row"><el-tag size="small" type="success">通过</el-tag><span>审批结束可以推进业务了</span></div>
<div class="state-row"><el-tag size="small" type="danger">驳回</el-tag><span>审批人不通过改完字段再保存会自动开新单</span></div>
<div class="state-row"><el-tag size="small" type="info">撤回</el-tag><span>发起人自己撤回了本单作废</span></div>
</div>
</el-col>
<el-col :span="12">
<div class="state-card">
<h4>状态<span class="state-owner">采购到哪一步了</span></h4>
<div class="state-row"><el-tag size="small">未采购</el-tag><span>刚新建等审批通过后才能往下推</span></div>
<div class="state-row"><el-tag size="small" type="warning">采购中</el-tag><span>开始下单 / 联系供应商</span></div>
<div class="state-row"><el-tag size="small" type="success">完成</el-tag><span>采购到货闭环结束</span></div>
<div class="state-row"><el-tag size="small" type="info">取消</el-tag><span>不再需要的需求可直接取消无需审批</span></div>
</div>
</el-col>
</el-row>
</el-card>
<!-- ============ 角色操作速查 ============ -->
<el-card class="doc-card" shadow="never">
<div slot="header"><b>不同角色怎么用按你是谁找对应行</b></div>
<el-table :data="roleSteps" border size="small">
<el-table-column label="你是" prop="role" width="100" />
<el-table-column label="去哪儿" prop="menu" width="240" />
<el-table-column label="做什么 / 看什么" prop="action" />
</el-table>
</el-card>
<!-- ============ 或签 vs 会签 ============ -->
<el-card class="doc-card" shadow="never">
<div slot="header"><b>或签 vs 会签</b></div>
<el-row :gutter="20">
<el-col :span="12">
<div class="state-card">
<h4>或签 <el-tag size="mini" type="success">任一人即可</el-tag></h4>
<p>审批人列表里 <b>任意一人</b> 点通过整张单子就通过任一人点驳回整张单子立即驳回</p>
<p class="hint">适用日常事务找最快有空的同事盖章即可</p>
</div>
</el-col>
<el-col :span="12">
<div class="state-card">
<h4>会签 <el-tag size="mini" type="warning">必须全员通过</el-tag></h4>
<p>审批人列表里 <b>所有人</b> 都点通过整张单子才通过只要有一个人驳回整张单子立即驳回</p>
<p class="hint">适用跨部门需要每个负责人都背书的事项</p>
</div>
</el-col>
</el-row>
</el-card>
<!-- ============ 常见问题 ============ -->
<el-card class="doc-card" shadow="never">
<div slot="header"><b>常见问题</b></div>
<el-collapse>
<el-collapse-item title="状态下拉点不动,提示「尚未审批通过」?">
采购需求必须审批通过才能进入采购中/完成审批中心 我的发起看看自己这单是不是还在待审或者已经被驳回
</el-collapse-item>
<el-collapse-item title="我换了审批人,为什么旧单子上的人没变?">
单子在生成那一刻就把当时的审批人定下来了<b>不会回溯</b>这是为了避免审批中途偷偷换人你的修改对后面新提交的单子生效
</el-collapse-item>
<el-collapse-item title="审批被驳回了,我的采购需求还在吗?">
需求本身不会消失你改完字段重新保存系统会自动开一张新审批单就是流程图里那条红色虚线的回流路径
</el-collapse-item>
<el-collapse-item title="我的事项太急,能不走审批吗?">
找管理员在审批配置把对应业务的启用临时关掉新提交的就直接生效不走审批事项处理完再让管理员开回来
</el-collapse-item>
<el-collapse-item title="我发起后又不想审批了,能撤回吗?">
可以审批中心 我的发起找到这张单子只要还是待审状态就能撤回已经被审批人处理过的通过/驳回不能撤回
</el-collapse-item>
<el-collapse-item title="为什么我在「我的审批」里看不到任何待办?">
说明目前没人提交需要你审的单也可能管理员没把你配进审批人列表里去问下管理员
</el-collapse-item>
</el-collapse>
</el-card>
</div>
</template>
<script>
export default {
name: 'OaDocPurchaseFlow',
data() {
return {
roleSteps: [
{ role: '管理员', menu: '审批中心 → 审批配置', action: '选定每个业务的审批人;切换或签 / 会签;启用或停用某个业务的审批。修改不影响已经发起的旧单。' },
{ role: '发起人', menu: '任务 → 采购需求', action: '点「新增」填写需求,保存后系统自动派给审批人;列表里会出现「待审」徽标。' },
{ role: '发起人', menu: '审批中心 → 我的发起', action: '看自己提交过的所有审批单 + 完整流水 + 审批意见;待审中可撤回。' },
{ role: '审批人', menu: '审批中心 → 我的审批', action: '「待我审批」tab 里点通过 / 驳回并写意见;「我已审批」可回看自己处理过的历史。' }
]
}
}
}
</script>
<style scoped>
.docs-page { max-width: 1240px; }
.docs-page h1 { font-size: 22px; margin: 0 0 16px; }
.doc-card { margin-bottom: 16px; }
.swimlane-wrap { width: 100%; overflow-x: auto; }
.swimlane-svg { width: 100%; min-width: 1100px; height: auto; display: block; }
.swimlane-svg .lane-label { font-size: 15px; font-weight: 600; fill: #303133; }
.swimlane-svg .lane-sub { font-size: 11px; fill: #909399; }
.swimlane-svg .node rect { stroke-width: 1.3; }
.swimlane-svg .node .t1 { font-size: 13px; font-weight: 600; text-anchor: middle; fill: #303133; }
.swimlane-svg .node .t2 { font-size: 11px; text-anchor: middle; fill: #606266; }
.swimlane-svg .node-admin rect { fill: #fff; stroke: #b37feb; }
.swimlane-svg .node-user rect { fill: #fff; stroke: #40a9ff; }
.swimlane-svg .node-sys rect { fill: #fff; stroke: #ffa940; }
.swimlane-svg .node-app rect { fill: #fff; stroke: #73d13d; }
.swimlane-svg .node-end circle { fill: #67c23a; stroke: #67c23a; }
.swimlane-svg .node-end .t-end { font-size: 12px; fill: #fff; font-weight: 600; text-anchor: middle; }
.swimlane-svg .arrow-label { font-size: 11px; fill: #606266; }
.swimlane-svg .arrow-label.dim { fill: #909399; }
.swimlane-svg .arrow-label.ok { fill: #67c23a; font-weight: 600; }
.swimlane-svg .arrow-label.bad { fill: #f56c6c; font-weight: 600; }
.legend {
margin-top: 12px; padding: 8px 12px; background: #fafafa; border-radius: 4px;
display: flex; flex-wrap: wrap; gap: 18px; font-size: 12px; color: #606266; align-items: center;
}
.legend .dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; margin-right: 6px; vertical-align: middle; }
.legend .dot-sys { background: #ffa940; }
.legend .dot-user { background: #40a9ff; }
.legend .dot-app { background: #73d13d; }
.legend .dot-admin { background: #b37feb; }
.state-card { background: #fafafa; border-radius: 6px; padding: 14px 16px; height: 100%; }
.state-card h4 { margin: 0 0 10px; font-size: 14px; }
.state-card .state-owner { font-weight: normal; color: #909399; margin-left: 6px; font-size: 12px; }
.state-card .state-row { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; font-size: 13px; color: #606266; }
.state-card p { line-height: 1.7; color: #303133; margin: 6px 0; }
.state-card p.hint { color: #909399; font-size: 12px; }
.docs-page .hint { color: #909399; font-size: 13px; margin-top: 0; }
</style>

View File

@@ -93,6 +93,15 @@
<span>{{ parseTime(scope.row.signTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="审批" align="center" prop="approvalStatus" width="90">
<template slot-scope="{ row }">
<el-tag v-if="row.approvalStatus == null" size="mini" type="info">未提交</el-tag>
<el-tag v-else-if="row.approvalStatus === 0" size="mini" type="warning">待审</el-tag>
<el-tag v-else-if="row.approvalStatus === 1" size="mini" type="success">通过</el-tag>
<el-tag v-else-if="row.approvalStatus === 2" size="mini" type="danger">驳回</el-tag>
<el-tag v-else-if="row.approvalStatus === 3" size="mini" type="info">撤回</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="contractStatus" width="120">
<template slot-scope="scope">
<dict-tag :options="dict.type.sys_show_hide" :value="scope.row.contractStatus"/>

View File

@@ -114,6 +114,15 @@
</template>
</el-table-column>
<!-- 表格中的状态列替换原有列 -->
<el-table-column label="审批" align="center" prop="approvalStatus" width="90">
<template slot-scope="{ row }">
<el-tag v-if="row.approvalStatus == null" size="mini" type="info">未提交</el-tag>
<el-tag v-else-if="row.approvalStatus === 0" size="mini" type="warning">待审</el-tag>
<el-tag v-else-if="row.approvalStatus === 1" size="mini" type="success">通过</el-tag>
<el-tag v-else-if="row.approvalStatus === 2" size="mini" type="danger">驳回</el-tag>
<el-tag v-else-if="row.approvalStatus === 3" size="mini" type="info">撤回</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" width="160">
<template slot-scope="{ row }">
<el-select v-model="row.status" size="mini" placeholder="选择状态"