This commit is contained in:
shijing 2026-05-22 11:10:47 +08:00
commit 562b6bda4a
20 changed files with 1262 additions and 214 deletions

View File

@ -42,3 +42,142 @@
.common-main .yzm .el-button {margin-left: 10px;}
.common-main .link {color: var(--el-color-primary);cursor: pointer;}
.common-main .link:hover {color: var(--el-color-primary-light-3);}
/* ============================================================
* 工作流表单结构化样式
* 抽屉内打开的所有 *_form.vue 流程表单统一布局规范
* - 主区: el-main > el-form 内容卡片化分组分区
* - 底部: 操作按钮 sticky 底栏
* - 侧栏: 提升与正文的视觉区分
* ============================================================ */
.el-drawer__body {
.el-container {
height: 100%;
background: var(--el-fill-color-light);
}
.el-main {
padding: 18px 20px 0 20px;
overflow-y: auto;
position: relative;
}
/* 表单卡片化 */
.el-main > .el-form {
background: var(--el-bg-color);
border: 1px solid var(--el-border-color-lighter);
border-radius: 12px;
padding: 22px 26px 8px 22px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
margin-bottom: 76px; /* 给 sticky footer 让位 */
max-width: 1240px;
margin-left: auto;
margin-right: auto;
}
.el-main > .el-form .el-form-item {
margin-bottom: 18px;
}
.el-main > .el-form .el-form-item__label {
font-weight: 500;
color: var(--el-text-color-regular);
}
/* 关键:除非显式 gridel-row/el-col或表格类输入单列 form-item 内容控件不拉满 */
.el-main > .el-form > .el-form-item .el-form-item__content > .el-input,
.el-main > .el-form > .el-form-item .el-form-item__content > .el-input-number,
.el-main > .el-form > .el-form-item .el-form-item__content > .el-select,
.el-main > .el-form > .el-form-item .el-form-item__content > .el-cascader,
.el-main > .el-form > .el-form-item .el-form-item__content > .el-date-editor:not(.el-date-editor--datetimerange):not(.el-date-editor--daterange) {
max-width: 460px;
width: 100%;
}
/* textarea 单独放宽,便于书写多行 */
.el-main > .el-form > .el-form-item .el-form-item__content > .el-input.el-input--textarea,
.el-main > .el-form > .el-form-item .el-form-item__content > .el-textarea {
max-width: 720px;
}
/* 复选/单选组保持自然宽度 */
/* 复用:表单内分区标题 */
.wf-form-section {
display: flex;
align-items: center;
gap: 8px;
margin: 6px 0 14px;
padding-bottom: 8px;
border-bottom: 1px solid var(--el-border-color-lighter);
}
.wf-form-section::before {
content: "";
width: 3px;
height: 14px;
border-radius: 2px;
background: var(--el-color-primary);
}
.wf-form-section > .title {
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
letter-spacing: 0.4px;
}
.wf-form-section > .desc {
font-size: 12px;
color: var(--el-text-color-secondary);
margin-left: auto;
}
/* sticky 底部操作栏 */
.el-main > .el-footer,
.el-main > .el-form + .el-footer,
.el-main > .el-form .el-footer {
position: sticky;
bottom: 0;
left: 0;
right: 0;
height: auto !important;
padding: 12px 22px;
background: var(--el-bg-color);
border-top: 1px solid var(--el-border-color-lighter);
box-shadow: 0 -4px 16px -10px rgba(0, 0, 0, 0.15);
display: flex;
justify-content: flex-end;
flex-wrap: wrap;
gap: 8px;
z-index: 5;
margin: 0 -20px -18px;
}
.el-main > .el-footer .el-button + .el-button,
.el-main > .el-form + .el-footer .el-button + .el-button {
margin-left: 0;
}
/* 右侧审批栏 */
.el-aside {
background: var(--el-fill-color-light);
border-left: 1px solid var(--el-border-color-lighter);
padding: 14px 10px 14px 14px;
overflow-y: auto;
}
}
/* ============================================================
* 抽屉头部美化所有工单详情
* ============================================================ */
.el-drawer__header {
padding: 14px 22px !important;
margin-bottom: 0 !important;
border-bottom: 1px solid var(--el-border-color-lighter);
background: linear-gradient(
180deg,
var(--el-bg-color) 0%,
var(--el-fill-color-light) 100%
);
}
.el-drawer__header > h4 {
font-size: 16px !important;
font-weight: 600;
letter-spacing: 0.4px;
margin: 0;
}

View File

@ -1,7 +1,10 @@
<template>
<el-container>
<el-main class="nopadding">
<el-form label-width="110px" :model="formData" style="padding: 20px;">
<el-form label-width="110px" :model="formData">
<div class="wf-form-section">
<span class="title">入库基本信息</span>
</div>
<el-form-item label="创建时间" v-if="localMode !== 'add'">
{{ formData.create_time }}
</el-form-item>
@ -36,6 +39,10 @@
:disabled="localMode !== 'add'"
></el-date-picker>
</el-form-item>
<div class="wf-form-section">
<span class="title">入库明细</span>
<span class="desc">逐项录入资产</span>
</div>
<el-form-item label="入库明细" required>
<sc-form-table
v-model="formData.items"

View File

@ -1,7 +1,11 @@
<template>
<el-container>
<el-main class="nopadding">
<el-form label-width="110px" :model="formData" style="padding: 20px;">
<el-form label-width="110px" :model="formData">
<div class="wf-form-section">
<span class="title">报修信息</span>
<span class="desc" v-if="localMode === 'add'">填写设备及报修事由</span>
</div>
<el-form-item label="创建时间" v-if="localMode !== 'add'">
{{ formData.create_time }}
</el-form-item>
@ -28,28 +32,32 @@
:readonly="localMode === 'show'"
></el-input>
</el-form-item>
<el-form-item label="故障分类" required v-if="localMode != 'add'">
<template v-if="localMode != 'add'">
<div class="wf-form-section">
<span class="title">分派与维修</span>
</div>
<el-form-item label="故障分类" required>
<el-select
v-model="ticket_data.fault_cate"
placeholder="请选择故障分类"
v-if="formData.ticket && formData.ticket_.state_.name == '条保负责人分派'"
v-if="formData.ticket && formData.ticket_.state_.name == '条保负责人分派'"
>
<el-option label="机械" value="机械"></el-option>
<el-option label="电气" value="电气"></el-option>
</el-select>
<span v-else>{{ formData.fault_cate }}</span>
</el-form-item>
<el-form-item label="维修人" required v-if="localMode != 'add'">
<xtSelect :apiObj="$API.system.user.list"
<el-form-item label="维修人" required>
<xtSelect :apiObj="$API.system.user.list"
v-if="formData.ticket && formData.ticket_.state_.name == '条保负责人分派'" v-model="ticket_data.repair_user">
<el-table-column prop="name" label="姓名"></el-table-column>
<el-table-column prop="belong_dept_name" label="部门"></el-table-column>
</xtSelect>
<span v-else>{{ formData.repair_user_name }}</span>
</el-form-item>
<el-form-item label="维修开始时间" required v-if="localMode != 'add'">
<el-form-item label="维修开始时间" required>
<el-date-picker
v-if="formData.ticket && formData.ticket_.state_.name == '维修人员处理'"
v-if="formData.ticket && formData.ticket_.state_.name == '维修人员处理'"
v-model="ticket_data.repair_start_time"
type="datetime"
placeholder="请选择维修开始时间"
@ -57,12 +65,12 @@
></el-date-picker>
<span v-else>{{ formData.repair_start_time }}</span>
</el-form-item>
<el-form-item label="维修工时" required v-if="localMode != 'add'">
<el-input-number v-model="ticket_data.repair_duration" :min="0" :precision="1"
<el-form-item label="维修工时" required>
<el-input-number v-model="ticket_data.repair_duration" :min="0" :precision="1"
v-if="formData.ticket && formData.ticket_.state_.name == '维修人员处理'" ></el-input-number>
<span v-else>{{ formData.repair_duration }}</span>
</el-form-item>
<el-form-item label="维修描述" required v-if="localMode != 'add'">
<el-form-item label="维修描述" required>
<el-input
type="textarea"
:rows="3"
@ -72,6 +80,7 @@
></el-input>
<span v-else>{{ formData.repair_description }}</span>
</el-form-item>
</template>
</el-form>
<el-footer>
<el-button type="danger"

View File

@ -1,7 +1,10 @@
<template>
<el-container>
<el-main class="nopadding">
<el-form label-width="130px" :model="formData" style="padding: 20px;" :disabled="localMode === 'show'">
<el-form label-width="130px" :model="formData" :disabled="localMode === 'show'">
<div class="wf-form-section">
<span class="title">员工信息</span>
</div>
<el-form-item label="员工信息" required>
<xtSelect
v-model="formData.employee"
@ -57,6 +60,9 @@
</el-form-item>
</el-col>
</el-row>
<div class="wf-form-section">
<span class="title">调岗信息</span>
</div>
<el-form-item label="原部门" required>
<el-cascader
v-model="formData.original_dept"
@ -127,6 +133,9 @@
<el-form-item label="调岗原因">
<el-input v-model="formData.reason" type="textarea" :rows="2" style="width: 300px;"/>
</el-form-item>
<div class="wf-form-section">
<span class="title">薪资调整</span>
</div>
<el-form-item label="原岗位薪资">
<el-input-number
v-model="formData.original_slary"

View File

@ -1,7 +1,10 @@
<template>
<el-container>
<el-main class="nopadding">
<el-form label-width="120px" :model="formData" style="padding: 20px;" :disabled="localMode === 'show'">
<el-form label-width="120px" :model="formData" :disabled="localMode === 'show'">
<div class="wf-form-section">
<span class="title">合同变更</span>
</div>
<el-form-item label="申请部门" required>
<el-cascader
v-model="formData.dept_need"

View File

@ -1,7 +1,10 @@
<template>
<el-container>
<el-main class="nopadding">
<el-form label-width="80px" :model="formData" style="padding: 20px;" :disabled="localMode === 'show'">
<el-form label-width="80px" :model="formData" :disabled="localMode === 'show'">
<div class="wf-form-section">
<span class="title">人员交接</span>
</div>
<el-form-item label="需求部门" required>
<el-cascader
v-model="formData.dept_need"

View File

@ -1,7 +1,10 @@
<template>
<el-container>
<el-main class="nopadding">
<el-form label-width="130px" :model="formData" style="padding: 20px;" :disabled="localMode === 'show'">
<el-form label-width="130px" :model="formData" :disabled="localMode === 'show'">
<div class="wf-form-section">
<span class="title">用人需求</span>
</div>
<el-form-item label="需求部门" required>
<el-cascader
v-model="formData.dept_need"

View File

@ -1,7 +1,10 @@
<template>
<el-container>
<el-main class="nopadding">
<el-form label-width="80px" :model="formData" style="padding: 20px;">
<el-form label-width="80px" :model="formData">
<div class="wf-form-section">
<span class="title">离职申请</span>
</div>
<el-form-item label="员工信息" required>
<xtSelect
v-model="formData.employee"

View File

@ -1,7 +1,10 @@
<template>
<el-container>
<el-main class="nopadding">
<el-form label-width="130px" :model="formData" style="padding: 20px;" :disabled="localMode === 'show'">
<el-form label-width="130px" :model="formData" :disabled="localMode === 'show'">
<div class="wf-form-section">
<span class="title">员工信息</span>
</div>
<el-form-item label="员工信息" required>
<xtSelect
v-model="formData.employee"

View File

@ -7,7 +7,11 @@
ref="addForm"
label-width="100px"
label-position="left"
>
>
<div class="wf-form-section">
<span class="title">档案借阅申请</span>
<span class="desc">填写档案用途与时间</span>
</div>
<el-form-item label="档案名称" prop="borrow_file">
<xtSelect
:apiObj="apiObjM2"

View File

@ -6,7 +6,11 @@
:rules="rules"
ref="addForm"
label-width="100px"
>
>
<div class="wf-form-section">
<span class="title">会议基本信息</span>
<span class="desc">填写会议名称会议室参会人员等</span>
</div>
<el-row>
<el-col :span="12">
<el-form-item label="会议名称" prop="title">
@ -57,6 +61,10 @@
</el-form-item>
</el-col> -->
</el-row>
<div class="wf-form-section">
<span class="title">选择会议时间段</span>
<span class="desc">点击空闲时段进行预定</span>
</div>
<el-row>
<div v-for="(item,$index) in timesList" :key="item.value">
<div v-if="item.sloted" class="timeBlock slotedTimeBlock" @click="slotMessage">{{ item.label }}</div>

View File

@ -7,7 +7,11 @@
:rules="rules"
ref="addForm"
label-position="left"
>
>
<div class="wf-form-section">
<span class="title">稿件信息</span>
<span class="desc">标题文件及作者</span>
</div>
<el-form-item label="送审稿件标题" prop="title">
<el-input v-model="addForm.title" clearable :disabled="localMode ==='show'"></el-input>
</el-form-item>
@ -28,6 +32,9 @@
<el-form-item label="部室/研究院" prop="pub_dept">
<el-input v-model="addForm.pub_dept" clearable :disabled="localMode ==='show'"></el-input>
</el-form-item>
<div class="wf-form-section">
<span class="title">涉密与内容</span>
</div>
<el-form-item label="第一撰稿人涉密等级" prop="level">
<el-radio-group v-model="addForm.level" :disabled="localMode ==='show'">
<el-radio label="重要"></el-radio>
@ -52,6 +59,9 @@
<el-form-item label="宣传报道目的" prop="report_purpose">
<el-input v-model="addForm.report_purpose" clearable :disabled="localMode ==='show'"></el-input>
</el-form-item>
<div class="wf-form-section">
<span class="title">宣传渠道</span>
</div>
<el-form-item label="宣传渠道" prop="channel">
<el-checkbox-group v-model="addForm.channel" :disabled="localMode ==='show'">
<el-checkbox label="互联网"></el-checkbox>

View File

@ -2,6 +2,10 @@
<el-container>
<el-main>
<el-form :model="localForm" label-width="100px" label-position="left" :rules="rules">
<div class="wf-form-section">
<span class="title">印章使用申请</span>
<span class="desc">填写文件信息与印章类型</span>
</div>
<el-form-item label="文件名称" prop="filename">
<el-input v-model="localForm.filename" clearable :disabled="localMode ==='show'"></el-input>
</el-form-item>

View File

@ -6,7 +6,11 @@
:rules="rules"
ref="addForm"
label-width="100px"
>
>
<div class="wf-form-section">
<span class="title">用车需求</span>
<span class="desc">填写用车事由目的地及车辆</span>
</div>
<el-row>
<el-col :span="12">
<el-form-item label="用车事由" prop="reason">
@ -76,6 +80,9 @@
</el-form-item>
</el-col>
</el-row>
<div class="wf-form-section">
<span class="title">选择用车时段</span>
</div>
<el-row>
<div v-for="(item,$index) in timesList" :key="item.value">
<div v-if="item.sloted" class="timeBlock slotedTimeBlock" @click="slotMessage">{{ item.label }}</div>

View File

@ -1,7 +1,10 @@
<template>
<el-container>
<el-main class="nopadding">
<el-form label-width="100px" :model="formData" style="padding: 20px;">
<el-form label-width="100px" :model="formData">
<div class="wf-form-section">
<span class="title">供应商信息</span>
</div>
<el-form-item label="供应商名称" required>
<el-input v-model="formData.name" placeholder="请输入供应商名称" :readonly="localMode === 'show'"></el-input>
</el-form-item>
@ -11,6 +14,10 @@
<el-form-item label="物料名称" required>
<el-input v-model="formData.material_name" :readonly="localMode === 'show'"></el-input>
</el-form-item>
<div class="wf-form-section">
<span class="title">资质文件</span>
<span class="desc">上传调查表营业执照质量证书</span>
</div>
<el-form-item label="调查表" required>
<sc-upload-filed
v-model="formData.survery_form"

View File

@ -2,6 +2,9 @@
<el-container>
<el-main>
<el-form :model="localForm" label-width="150px" label-position="left" :rules="rules">
<div class="wf-form-section">
<span class="title">论文基本信息</span>
</div>
<el-form-item label="拟发表论文名称" prop="paper_name">
<el-input v-model="localForm.paper_name" clearable :disabled="localMode ==='show'"></el-input>
</el-form-item>
@ -14,6 +17,9 @@
<el-form-item label="作者" prop="author" >
<el-input v-model="localForm.author" clearable ></el-input>
</el-form-item>
<div class="wf-form-section">
<span class="title">发表详情</span>
</div>
<el-form-item label="拟发表文章类型" prop="paper_type">
<el-select v-model="localForm.paper_type" placeholder="请选择拟发表文章类型" :disabled="localMode ==='show'">
<el-option label="研究论文" value="research"></el-option>

View File

@ -1,91 +1,133 @@
<template>
<el-container>
<el-main>
<el-form :model="localForm" label-width="150px" label-position="left" :rules="rules">
<el-form-item label="拟申请专利名称" prop="name">
<el-input v-model="localForm.name" clearable :disabled="localMode ==='show'"></el-input>
</el-form-item>
<el-form-item label="申请部门" prop="organization">
<el-input v-model="localForm.organization" clearable :disabled="localMode ==='show'"></el-input>
</el-form-item>
<el-form-item label="发明人(设计人)"
prop="author"
>
<el-input v-model="localForm.author" clearable ></el-input>
</el-form-item>
<el-form-item label="专利类型" prop="type">
<el-form :model="localForm" label-width="130px" label-position="left" :rules="rules">
<div class="wf-form-section">
<span class="title">专利基本信息</span>
<span class="desc">名称申请部门发明人</span>
</div>
<el-row :gutter="24">
<el-col :span="24">
<el-form-item label="拟申请专利名称" prop="name">
<el-input v-model="localForm.name" clearable :disabled="localMode ==='show'" placeholder="请输入专利名称"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="申请部门" prop="organization">
<el-input v-model="localForm.organization" clearable :disabled="localMode ==='show'" placeholder="请输入申请部门"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="发明人(设计人)" prop="author">
<el-input v-model="localForm.author" clearable placeholder="多人请用顿号分隔"></el-input>
</el-form-item>
</el-col>
</el-row>
<div class="wf-form-section">
<span class="title">申请详情</span>
</div>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="专利类型" prop="type">
<el-select v-model="localForm.type" placeholder="请选择专利类型" :disabled="localMode ==='show'">
<el-option label="发明专利" value="invention"></el-option>
<el-option label="实用新型专利" value="utility"></el-option>
<el-option label="外观设计专利" value="design"></el-option>
<el-option label="发明专利" value="invention"></el-option>
<el-option label="实用新型专利" value="utility"></el-option>
<el-option label="外观设计专利" value="design"></el-option>
</el-select>
</el-form-item>
<el-form-item label="是否公开" prop="is_public">
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否公开" prop="is_public">
<el-radio-group v-model="localForm.is_public" :disabled="localMode ==='show'">
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="拟申请地域" prop="area">
<el-radio-group v-model="localForm.area" :disabled="localMode ==='show'">
<el-radio label="国内申请" value="Domestic"></el-radio>
<el-radio label="国外申请" value="Foreign"></el-radio>
<el-radio label="PCT申请" value="PCT"></el-radio>
</el-radio-group>
<el-input
v-if="localForm.area && (localForm.area.includes('Foreign') || localForm.area.includes('PCT'))"
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="拟申请地域" prop="area">
<el-radio-group v-model="localForm.area" :disabled="localMode ==='show'">
<el-radio label="国内申请" value="Domestic"></el-radio>
<el-radio label="国外申请" value="Foreign"></el-radio>
<el-radio label="PCT申请" value="PCT"></el-radio>
</el-radio-group>
<el-input
v-if="localForm.area && (localForm.area.includes('Foreign') || localForm.area.includes('PCT'))"
v-model="localForm.other_area"
placeholder = "请输入申请的国家"
size = "small"
style="margin-top: 10;"
placeholder="请输入申请的国家"
size="small"
style="margin-top: 8px; max-width: 320px;"
:disabled="localMode ==='show'"
></el-input>
</el-form-item>
<el-form-item label="技术状态" prop="tech_status">
<el-row>
<el-col :span="12" v-for="(item, index) in localForm.tech_status || []" :key="index">
<div class="class-row">
<span>{{ item.name }}</span>
<el-radio-group v-model="item.status" @change="handleStatusChange(item)" style="margin-left: 20px;" :disabled="localMode ==='show'">
<el-radio label="是"></el-radio>
<el-radio label="否"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<div class="wf-form-section">
<span class="title">技术状态与文件</span>
</div>
<el-form-item label="技术状态" prop="tech_status" class="patent-tech-status">
<div class="patent-tech-grid">
<div
v-for="(item, index) in localForm.tech_status || []"
:key="index"
class="patent-tech-item"
:class="{ 'is-yes': item.status === '是' }"
>
<div class="patent-tech-item__row">
<span class="patent-tech-item__label">{{ item.name }}</span>
<el-radio-group
v-model="item.status"
@change="handleStatusChange(item)"
:disabled="localMode ==='show'"
size="small"
>
<el-radio label="是"></el-radio>
<el-radio label="否"></el-radio>
</el-radio-group>
</div>
<div v-if="item.status === '是'" class="upload-section">
<sc-upload-file
multiple
:limit="1"
:accept="['.xlsx', '.xls','.pdf','.docx', '.doc', '.jpg', '.png', '.jpeg']"
@success = "(res)=>fileUPSuccess(res, item)"
:disabled="localMode ==='show'"
>
<el-button type="primary" size="small" icon="el-icon-upload"> </el-button>
</sc-upload-file>
<div v-if="item.file && localMode ==='show'" class="file-list">
<div v-if="item.status === '是'" class="patent-tech-item__upload">
<sc-upload-file
multiple
:limit="1"
:accept="['.xlsx', '.xls','.pdf','.docx', '.doc', '.jpg', '.png', '.jpeg']"
@success="(res)=>fileUPSuccess(res, item)"
:disabled="localMode ==='show'"
>
<el-button type="primary" size="small" plain>
<el-icon><Upload /></el-icon><span style="margin-left:4px"></span>
</el-button>
</sc-upload-file>
<div v-if="item.file && localMode ==='show'" class="file-list">
<el-link type="primary" :href="item.file" target="_blank">
{{ item.file.split('/').pop() }}
{{ item.file.split('/').pop() }}
</el-link>
</div>
</div>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="技术文件" prop="tech_file">
<div class="file-item" v-for="(item, index) in localForm.tech_file || []" :key="index">
<el-checkbox class="file-checkbox" v-model="item.checked" @change="handleCheckChange(item)" :disabled="localMode ==='show'">
{{ item.name }}
</el-checkbox>
<el-input
class="page-input"
v-model="item.pages"
placeholder="页数"
:disabled="!item.checked || localMode ==='show'"
type="number"
min="0">
<template v-slot:append></template>
</el-input>
</div>
</el-form-item>
</div>
</div>
</el-form-item>
<el-form-item label="技术文件" prop="tech_file">
<div class="patent-tech-files">
<div class="patent-file-item" v-for="(item, index) in localForm.tech_file || []" :key="index">
<el-checkbox v-model="item.checked" @change="handleCheckChange(item)" :disabled="localMode ==='show'">
{{ item.name }}
</el-checkbox>
<el-input
class="patent-file-pages"
v-model="item.pages"
placeholder="页数"
:disabled="!item.checked || localMode ==='show'"
type="number"
min="0"
size="small"
>
<template v-slot:append></template>
</el-input>
</div>
</div>
</el-form-item>
</el-form>
<el-footer>
<ticketd_b
@ -114,6 +156,7 @@
<script>
import ticketd_b from "@/views/wf/ticketd_b.vue";
import ticketd from '@/views/wf/ticketd.vue'
import { Upload } from "@element-plus/icons-vue";
export default {
props: {
mode: {
@ -125,7 +168,7 @@ export default {
default: null
},
},
components: {ticketd, ticketd_b},
components: {ticketd, ticketd_b, Upload},
data() {
return {
ticket_data: {},
@ -225,4 +268,68 @@ export default {
},
};
</script>
</script>
<style scoped>
/* 技术状态网格 */
.patent-tech-status :deep(.el-form-item__content) {
display: block;
width: 100%;
line-height: 1.4;
}
.patent-tech-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 12px;
width: 100%;
}
.patent-tech-item {
border: 1px solid var(--el-border-color-lighter);
background: var(--el-fill-color-light);
border-radius: 8px;
padding: 10px 12px;
transition: background-color 0.2s ease, border-color 0.2s ease;
}
.patent-tech-item.is-yes {
background: var(--el-color-primary-light-9);
border-color: var(--el-color-primary-light-5);
}
.patent-tech-item__row {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
}
.patent-tech-item__label {
font-size: 13px;
color: var(--el-text-color-primary);
}
.patent-tech-item__upload {
margin-top: 10px;
padding-top: 10px;
border-top: 1px dashed var(--el-border-color);
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.file-list {
font-size: 12px;
}
/* 技术文件列表 */
.patent-tech-files {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 10px 24px;
width: 100%;
}
.patent-file-item {
display: flex;
align-items: center;
gap: 10px;
}
.patent-file-pages {
max-width: 110px;
}
</style>

View File

@ -2,6 +2,10 @@
<el-container>
<el-main>
<el-form :model="localForm" label-width="150px" label-position="left" :rules="rules">
<div class="wf-form-section">
<span class="title">平台审批</span>
<span class="desc">填写平台名称申请部门及日期</span>
</div>
<el-form-item label="名称" prop="name">
<el-input v-model="localForm.name" clearable :disabled="localMode ==='show'"></el-input>
</el-form-item>

View File

@ -141,31 +141,140 @@
@success="handleSuccess"></component>
</el-drawer>
</el-container>
<el-dialog v-model="dialogVisible" title="选择流程">
<div v-for="group in wfOptions" :key="group.category" class="category-group">
<h3 style="margin: 16px 0 8px 0; color: #606266;">{{ group.category }}</h3>
<el-card
v-for="item in group.items"
:key="item.id"
:body-style="{ padding: '10px', cursor: 'pointer' }"
style="width: 180px; display: inline-block; margin: 4px; background-color: #f5f7fa;"
shadow="hover"
@click="startTicket(item)"
>
{{ item.name }}
</el-card>
</div>
<el-dialog
v-model="dialogVisible"
title="选择流程"
width="960px"
top="6vh"
class="wf-picker-dialog"
:close-on-click-modal="false"
destroy-on-close
>
<div class="wf-picker">
<aside class="wf-picker__aside">
<div class="wf-picker__search">
<el-input
v-model="wfKeyword"
placeholder="搜索流程"
clearable
:prefix-icon="Search"
/>
</div>
<nav class="wf-picker__nav">
<a
v-for="group in filteredWfOptions"
:key="group.category"
class="wf-picker__nav-item"
:class="{ active: activeCategory === group.category }"
@click="scrollToCategory(group.category)"
>
<el-icon class="wf-picker__nav-icon">
<component :is="categoryIcon(group.category)" />
</el-icon>
<span class="wf-picker__nav-text">{{ group.category }}</span>
<span class="wf-picker__nav-count">{{ group.items.length }}</span>
</a>
</nav>
</aside>
<section ref="wfScroll" class="wf-picker__body" @scroll="onPickerScroll">
<template v-if="filteredWfOptions.length">
<div
v-for="group in filteredWfOptions"
:key="group.category"
class="wf-picker__group"
:data-category="group.category"
:ref="el => setGroupRef(group.category, el)"
>
<header class="wf-picker__group-header">
<el-icon class="wf-picker__group-icon">
<component :is="categoryIcon(group.category)" />
</el-icon>
<h3 class="wf-picker__group-title">{{ group.category }}</h3>
<span class="wf-picker__group-divider"></span>
<span class="wf-picker__group-meta">{{ group.items.length }} </span>
</header>
<div class="wf-picker__grid">
<button
v-for="item in group.items"
:key="item.id"
type="button"
class="wf-picker__card"
@click="startTicket(item)"
>
<span class="wf-picker__card-icon">
<el-icon><component :is="itemIcon(item.name, group.category)" /></el-icon>
</span>
<span class="wf-picker__card-name">{{ item.name }}</span>
<span class="wf-picker__card-arrow">
<el-icon><ArrowRight /></el-icon>
</span>
</button>
</div>
</div>
</template>
<el-empty v-else description="未找到匹配的流程" :image-size="80" />
</section>
</div>
</el-dialog>
</template>
<script>
import { defineAsyncComponent, markRaw } from 'vue'; //
import { actStateEnum, interveneTypeEnum } from "@/utils/enum.js";
import {
Search, ArrowRight,
UserFilled, SwitchButton, CircleClose,
Tools, Box, Postcard, Promotion, ChatLineRound,
Van, Monitor, OfficeBuilding, Document, Briefcase, ShoppingCart,
Cpu, Files, Grid, Tickets
} from "@element-plus/icons-vue";
const CATEGORY_ICON_MAP = {
"人事": UserFilled,
"设备": Tools,
"固资": Box,
"行政": Briefcase,
"科研": Cpu,
"采购": ShoppingCart,
"未分组": Grid,
};
const ITEM_ICON_RULES = [
[/合同|协议/, Document],
[/调岗|调动|交接/, SwitchButton],
[/用人|招聘|入职/, UserFilled],
[/离职|辞职/, CircleClose],
[/维修|检修|故障/, Tools],
[/入库|出库|盘点|资产/, Box],
[/档案|文件|借阅/, Files],
[/印章|盖章/, Postcard],
[/宣传|报道|发布/, Promotion],
[/会议|预定/, ChatLineRound],
[/用车|车辆|出行/, Van],
[/平台|系统|监控/, Monitor],
[/供应商|采购|订单/, OfficeBuilding],
[/请假|加班|考勤/, Tickets],
[/审批|报销|申请/, Document],
];
export default {
name: "myticket",
components: {
Search, ArrowRight,
UserFilled, SwitchButton, CircleClose,
Tools, Box, Postcard, Promotion, ChatLineRound,
Van, Monitor, OfficeBuilding, Document, Briefcase, ShoppingCart,
Cpu, Files, Grid, Tickets
},
data() {
return {
Search: markRaw(Search),
ArrowRight: markRaw(ArrowRight),
actStateEnum, interveneTypeEnum,
wfKeyword: "",
activeCategory: "",
wfGroupRefs: {},
drawerName: "工单详情",
drawer: false,
tvalue: "待办",
@ -217,7 +326,65 @@ export default {
}
this.getWfOptions();
},
computed: {
filteredWfOptions() {
const kw = (this.wfKeyword || "").trim().toLowerCase();
if (!kw) return this.wfOptions;
return this.wfOptions
.map(g => ({
category: g.category,
items: g.items.filter(it =>
(it.name || "").toLowerCase().includes(kw) ||
(g.category || "").toLowerCase().includes(kw)
)
}))
.filter(g => g.items.length);
},
},
watch: {
dialogVisible(v) {
if (v) {
this.wfKeyword = "";
this.$nextTick(() => {
this.activeCategory = this.filteredWfOptions[0]?.category || "";
});
}
},
},
methods: {
categoryIcon(cate) {
return CATEGORY_ICON_MAP[cate] || Grid;
},
itemIcon(name, cate) {
for (const [re, icon] of ITEM_ICON_RULES) {
if (re.test(name)) return icon;
}
return CATEGORY_ICON_MAP[cate] || Document;
},
setGroupRef(category, el) {
if (el) this.wfGroupRefs[category] = el;
},
scrollToCategory(category) {
const el = this.wfGroupRefs[category];
const scroller = this.$refs.wfScroll;
if (!el || !scroller) return;
this.activeCategory = category;
scroller.scrollTo({
top: el.offsetTop - 8,
behavior: "smooth",
});
},
onPickerScroll() {
const scroller = this.$refs.wfScroll;
if (!scroller) return;
const top = scroller.scrollTop;
let current = this.filteredWfOptions[0]?.category || "";
for (const g of this.filteredWfOptions) {
const el = this.wfGroupRefs[g.category];
if (el && el.offsetTop - 24 <= top) current = g.category;
}
if (current !== this.activeCategory) this.activeCategory = current;
},
async getWfOptions() {
let permissions = this.$TOOL.data.get("PERMISSIONS");
let userInfo = this.$TOOL.data.get("USER_INFO");
@ -303,4 +470,267 @@ export default {
};
</script>
<style scoped></style>
<style scoped>
/* ===== 选择流程对话框 ===== */
:deep(.wf-picker-dialog) {
border-radius: 14px;
overflow: hidden;
}
:deep(.wf-picker-dialog .el-dialog__header) {
padding: 18px 24px;
margin-right: 0;
border-bottom: 1px solid var(--el-border-color-lighter);
background: linear-gradient(
180deg,
var(--el-fill-color-blank) 0%,
var(--el-fill-color-light) 100%
);
}
:deep(.wf-picker-dialog .el-dialog__title) {
font-size: 16px;
font-weight: 600;
letter-spacing: 0.5px;
}
:deep(.wf-picker-dialog .el-dialog__body) {
padding: 0;
}
.wf-picker {
display: flex;
height: 66vh;
min-height: 440px;
max-height: 640px;
background: var(--el-bg-color);
}
/* 左侧分类导航 */
.wf-picker__aside {
width: 200px;
flex-shrink: 0;
display: flex;
flex-direction: column;
border-right: 1px solid var(--el-border-color-lighter);
background: var(--el-fill-color-light);
}
.wf-picker__search {
padding: 14px 12px 10px;
}
.wf-picker__nav {
flex: 1;
overflow-y: auto;
padding: 4px 8px 16px;
display: flex;
flex-direction: column;
gap: 2px;
}
.wf-picker__nav-item {
display: flex;
align-items: center;
gap: 10px;
padding: 9px 12px;
border-radius: 8px;
font-size: 13.5px;
color: var(--el-text-color-regular);
cursor: pointer;
user-select: none;
transition:
background-color 0.18s ease,
color 0.18s ease,
transform 0.18s ease;
position: relative;
}
.wf-picker__nav-item::before {
content: "";
position: absolute;
left: 4px;
top: 50%;
transform: translateY(-50%) scaleY(0);
width: 3px;
height: 18px;
background: var(--el-color-primary);
border-radius: 2px;
transition: transform 0.2s ease;
}
.wf-picker__nav-item:hover {
background: var(--el-fill-color);
color: var(--el-text-color-primary);
}
.wf-picker__nav-item.active {
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
font-weight: 600;
}
.wf-picker__nav-item.active::before {
transform: translateY(-50%) scaleY(1);
}
.wf-picker__nav-icon {
font-size: 16px;
flex-shrink: 0;
}
.wf-picker__nav-text {
flex: 1;
}
.wf-picker__nav-count {
font-size: 12px;
padding: 1px 7px;
border-radius: 10px;
background: var(--el-fill-color-darker);
color: var(--el-text-color-secondary);
font-weight: 500;
}
.wf-picker__nav-item.active .wf-picker__nav-count {
background: var(--el-color-primary);
color: #fff;
}
/* 右侧主体 */
.wf-picker__body {
flex: 1;
overflow-y: auto;
padding: 18px 24px 24px;
scroll-behavior: smooth;
}
.wf-picker__group + .wf-picker__group {
margin-top: 22px;
}
.wf-picker__group-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
padding-bottom: 8px;
}
.wf-picker__group-icon {
color: var(--el-color-primary);
font-size: 16px;
}
.wf-picker__group-title {
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
margin: 0;
letter-spacing: 0.4px;
}
.wf-picker__group-divider {
flex: 1;
height: 1px;
background: linear-gradient(
to right,
var(--el-border-color-lighter),
transparent
);
}
.wf-picker__group-meta {
font-size: 12px;
color: var(--el-text-color-secondary);
}
/* 卡片网格 */
.wf-picker__grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
gap: 10px;
}
.wf-picker__card {
all: unset;
box-sizing: border-box;
cursor: pointer;
display: flex;
align-items: center;
gap: 10px;
padding: 12px 14px;
border-radius: 10px;
background: var(--el-bg-color);
border: 1px solid var(--el-border-color-lighter);
transition:
border-color 0.18s ease,
box-shadow 0.18s ease,
transform 0.18s ease,
background-color 0.18s ease;
position: relative;
overflow: hidden;
}
.wf-picker__card::before {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: var(--el-color-primary);
transform: scaleY(0);
transform-origin: center;
transition: transform 0.2s ease;
}
.wf-picker__card:hover {
border-color: var(--el-color-primary-light-5);
box-shadow: 0 6px 16px -10px var(--el-color-primary);
transform: translateY(-1px);
background: var(--el-color-primary-light-9);
}
.wf-picker__card:hover::before {
transform: scaleY(1);
}
.wf-picker__card:active {
transform: translateY(0);
}
.wf-picker__card-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
flex-shrink: 0;
border-radius: 8px;
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
font-size: 16px;
transition: background-color 0.18s ease, color 0.18s ease;
}
.wf-picker__card:hover .wf-picker__card-icon {
background: var(--el-color-primary);
color: #fff;
}
.wf-picker__card-name {
flex: 1;
font-size: 13.5px;
color: var(--el-text-color-primary);
font-weight: 500;
line-height: 1.4;
text-align: left;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.wf-picker__card-arrow {
color: var(--el-text-color-placeholder);
font-size: 14px;
opacity: 0;
transform: translateX(-4px);
transition: opacity 0.18s ease, transform 0.18s ease, color 0.18s ease;
}
.wf-picker__card:hover .wf-picker__card-arrow {
color: var(--el-color-primary);
opacity: 1;
transform: translateX(0);
}
/* 响应式 */
@media (max-width: 768px) {
:deep(.wf-picker-dialog) {
--el-dialog-width: 92vw !important;
}
.wf-picker {
height: 72vh;
}
.wf-picker__aside {
width: 140px;
}
.wf-picker__nav-text {
font-size: 13px;
}
.wf-picker__body {
padding: 14px 14px 20px;
}
}
</style>

View File

@ -1,139 +1,191 @@
<template>
<el-collapse v-model="activeNames" style="padding-left: 2px; padding-right: 2px;">
<el-collapse-item title="基本信息" name="1">
<el-descriptions :column="1">
<el-descriptions-item>
<span style="color:darkblue;margin-right: 4px;">({{ ticketDetail.sn }})</span>{{ ticketDetail.title
}}
</el-descriptions-item>
<el-descriptions-item label="提交人:">
{{ ticketDetail.create_by_name }}
</el-descriptions-item>
<el-descriptions-item label="当前:">
<el-tag type="info" effect="plain">{{ ticketDetail.state_.name }}</el-tag>
<el-tag :type="actStateEnum[ticketDetail.act_state]?.type" effect="plain">
{{ actStateEnum[ticketDetail.act_state]?.text }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="可处理人:"
v-if="ticketDetail.participant_type == 2 || ticketDetail.participant_type == 1">
<span v-for="item in ticketDetail.participant_" :key="item.id">{{ item.name }}/</span>
</el-descriptions-item>
<el-descriptions-item label="操作:">
<el-button v-if="ticketDetail.state_.enable_retreat && isOwn" type="danger" size="small"
@click="handleRetreat">撤回</el-button>
</el-descriptions-item>
</el-descriptions>
</el-collapse-item>
<el-collapse-item title="审批历史" name="2">
<div style="padding:1px"><el-timeline>
<el-timeline-item v-for="item in ticketLog" :key="item.id" :timestamp="item.create_time"
:color="getColor(item)">
<div>
<strong>{{ item.state_?.name || "未知状态" }}</strong>
</div>
<div>
<span v-if="item.intervene_type != 0"><el-tag
:type="interveneTypeEnum[item.intervene_type]?.type" effect="plain">
{{ interveneTypeEnum[item.intervene_type]?.text }}
</el-tag></span>
<span v-if="item.transition_"
:style="{ color: item.transition_.attribute_type === 1 ? '#67C23A' : '#F56C6C' }">
{{ item.transition_.name }}
</span>
<span v-if="item.participant_">-{{ item.participant_.name }}</span>
</div>
<div v-if="item.suggestion">
<el-tooltip v-if="item.suggestion.length > 20" class="box-item" effect="dark"
:content="item.suggestion" placement="top">
<span>{{ item.suggestion.slice(0, 20) + "..." }}</span>
</el-tooltip>
<span v-else>
{{ item.suggestion }}
</span>
</div>
</el-timeline-item>
</el-timeline></div>
<div class="wf-side">
<!-- 工单基本信息卡 -->
<section class="wf-side__card">
<header class="wf-side__card-header">
<span class="wf-side__card-bar"></span>
<h4 class="wf-side__card-title">基本信息</h4>
<el-tag
v-if="ticketDetail.act_state !== undefined"
:type="actStateEnum[ticketDetail.act_state]?.type"
effect="dark"
size="small"
round
>{{ actStateEnum[ticketDetail.act_state]?.text }}</el-tag>
</header>
</el-collapse-item>
</el-collapse>
<el-dialog :title="dialogTitle" v-model="dialogVisible">
<el-input v-model="suggestion" type="textarea" :rows="2"></el-input>
<template #footer>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="dialogConfirm()"> </el-button>
</template>
</el-dialog>
<div class="wf-side__sn-row" v-if="ticketDetail.sn">
<span class="wf-side__sn-tag">{{ ticketDetail.sn }}</span>
<span class="wf-side__sn-title" :title="ticketDetail.title">{{ ticketDetail.title }}</span>
</div>
<dl class="wf-side__kv">
<div class="wf-side__kv-row">
<dt>提交人</dt>
<dd>{{ ticketDetail.create_by_name || '—' }}</dd>
</div>
<div class="wf-side__kv-row">
<dt>当前节点</dt>
<dd>
<el-tag type="info" effect="plain" size="small">{{ ticketDetail.state_?.name || '—' }}</el-tag>
</dd>
</div>
<div
class="wf-side__kv-row"
v-if="ticketDetail.participant_type == 2 || ticketDetail.participant_type == 1"
>
<dt>可处理人</dt>
<dd class="wf-side__participants">
<span
v-for="item in ticketDetail.participant_"
:key="item.id"
class="wf-side__chip"
>{{ item.name }}</span>
</dd>
</div>
</dl>
<div class="wf-side__actions" v-if="ticketDetail.state_?.enable_retreat && isOwn">
<el-button type="danger" size="small" plain @click="handleRetreat">
<el-icon><RefreshLeft /></el-icon><span style="margin-left:4px"></span>
</el-button>
</div>
</section>
<!-- 审批历史卡 -->
<section class="wf-side__card wf-side__card--history">
<header class="wf-side__card-header">
<span class="wf-side__card-bar"></span>
<h4 class="wf-side__card-title">审批历史</h4>
<span class="wf-side__count" v-if="ticketLog.length">{{ ticketLog.length }}</span>
</header>
<div class="wf-side__timeline" v-if="ticketLog.length">
<div
v-for="(item, idx) in ticketLog"
:key="item.id"
class="wf-side__tl-item"
:class="`is-${kindOf(item)}`"
>
<div class="wf-side__tl-rail">
<span class="wf-side__tl-dot"></span>
<span v-if="idx !== ticketLog.length - 1" class="wf-side__tl-line"></span>
</div>
<div class="wf-side__tl-body">
<div class="wf-side__tl-head">
<strong class="wf-side__tl-state">{{ item.state_?.name || '未知状态' }}</strong>
<span class="wf-side__tl-time">{{ shortTime(item.create_time) }}</span>
</div>
<div class="wf-side__tl-meta">
<el-tag
v-if="item.intervene_type != 0"
:type="interveneTypeEnum[item.intervene_type]?.type"
effect="plain"
size="small"
>{{ interveneTypeEnum[item.intervene_type]?.text }}</el-tag>
<span
v-if="item.transition_"
class="wf-side__tl-transition"
:class="{
'is-approve': item.transition_.attribute_type === 1,
'is-reject': item.transition_.attribute_type === 2
}"
>{{ item.transition_.name }}</span>
<span v-if="item.participant_" class="wf-side__tl-user">· {{ item.participant_.name }}</span>
</div>
<div class="wf-side__tl-suggestion" v-if="item.suggestion">
<el-tooltip
v-if="item.suggestion.length > 28"
effect="dark"
:content="item.suggestion"
placement="top"
>
<span>{{ item.suggestion.slice(0, 28) + '…' }}</span>
</el-tooltip>
<span v-else>{{ item.suggestion }}</span>
</div>
</div>
</div>
</div>
<el-empty v-else description="暂无审批记录" :image-size="60" />
</section>
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="420px">
<el-input v-model="suggestion" type="textarea" :rows="3" placeholder="请输入说明" />
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="dialogConfirm()">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, defineProps, onMounted, watch } from "vue";
import { actStateEnum, interveneTypeEnum } from "@/utils/enum.js";
import { RefreshLeft } from "@element-plus/icons-vue";
import API from '@/api';
import TOOL from "@/utils/tool.js";
const props = defineProps({
ticket_data: { type: Object, default: null },
ticket_: { type: Object, default: null, required: true },
});
const currentUser = ref(TOOL.data.get("USER_INFO").id)
const currentUser = ref(TOOL.data.get("USER_INFO").id);
const isOwn = ref(false);
const activeNames = ref(['1', '2']);
const ticketDetail = ref({
state_: {}
});
const ticketDetail = ref({ state_: {} });
const ticketLog = ref([]);
const ticketId = ref(null);
onMounted(() => {
//
if (props.ticket_ && props.ticket_.id) {
ticketId.value = props.ticket_.id
getTicketDetail()
getTicketLog()
ticketId.value = props.ticket_.id;
getTicketDetail();
getTicketLog();
}
//
watch(
() => props.ticket_,
async (newVal) => {
if (newVal && Object.keys(newVal).length > 0) {
ticketId.value = newVal.id
getTicketDetail()
getTicketLog()
ticketId.value = newVal.id;
getTicketDetail();
getTicketLog();
}
},
{ deep: true }
)
);
});
const emit = defineEmits(['success']);
const refreshTicket = async () => {
isOwn.value = false
ticketLog.value = []
activeNames.value = ['1', '2'] //
getTicketDetail()
getTicketLog()
emit('success')
}
isOwn.value = false;
ticketLog.value = [];
getTicketDetail();
getTicketLog();
emit('success');
};
const getTicketDetail = () => {
API.wf.ticket.ticketItem.req(ticketId.value).then(res => {
ticketDetail.value = res;
const isCreator = res.create_by === currentUser.value
if (isCreator) {
isOwn.value = true
}
if (res.create_by === currentUser.value) isOwn.value = true;
});
};
const getTicketLog = () => {
API.wf.ticket.ticketFlowlogs.req(ticketId.value).then(res => {
ticketLog.value = res;
})
}
});
};
function getColor(item) {
if (item.transition_?.attribute_type === 1) return "#67C23A"; // 绿
if (item.transition_?.attribute_type === 2) return "#F56C6C"; //
return "#909399"; //
function kindOf(item) {
if (item.transition_?.attribute_type === 1) return "approve";
if (item.transition_?.attribute_type === 2) return "reject";
return "neutral";
}
function shortTime(ts) {
if (!ts) return "";
return ts.length >= 16 ? ts.slice(5, 16) : ts;
}
const dialogVisible = ref(false);
@ -141,13 +193,243 @@ const dialogTitle = ref("意见/原因");
const suggestion = ref("");
const handleRetreat = () => {
dialogTitle.value = "撤回原因";
suggestion.value = "";
dialogVisible.value = true;
};
const dialogConfirm = () => {
API.wf.ticket.ticketRetreat
.req(ticketId.value, {"suggestion": suggestion.value})
.then((res) => {
refreshTicket();
});
.req(ticketId.value, { "suggestion": suggestion.value })
.then(() => {
dialogVisible.value = false;
refreshTicket();
});
};
</script>
</script>
<style scoped>
.wf-side {
display: flex;
flex-direction: column;
gap: 12px;
padding: 6px 4px 12px;
height: 100%;
overflow-y: auto;
}
.wf-side__card {
background: var(--el-bg-color);
border: 1px solid var(--el-border-color-lighter);
border-radius: 10px;
padding: 14px 14px 12px;
transition: box-shadow 0.2s ease;
}
.wf-side__card:hover {
box-shadow: 0 2px 12px -8px rgba(0, 0, 0, 0.18);
}
.wf-side__card--history {
padding-bottom: 16px;
}
.wf-side__card-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
}
.wf-side__card-bar {
width: 3px;
height: 14px;
border-radius: 2px;
background: var(--el-color-primary);
}
.wf-side__card-title {
margin: 0;
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
letter-spacing: 0.3px;
flex: 1;
}
.wf-side__count {
font-size: 11px;
color: var(--el-text-color-secondary);
background: var(--el-fill-color);
padding: 1px 7px;
border-radius: 8px;
}
.wf-side__sn-row {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px dashed var(--el-border-color-lighter);
}
.wf-side__sn-tag {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 11.5px;
padding: 2px 8px;
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
border-radius: 4px;
white-space: nowrap;
}
.wf-side__sn-title {
font-size: 13px;
color: var(--el-text-color-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
}
.wf-side__kv {
margin: 0;
display: flex;
flex-direction: column;
gap: 8px;
}
.wf-side__kv-row {
display: flex;
align-items: flex-start;
gap: 10px;
font-size: 13px;
line-height: 1.6;
}
.wf-side__kv-row dt {
width: 64px;
flex-shrink: 0;
color: var(--el-text-color-secondary);
font-weight: 400;
}
.wf-side__kv-row dd {
margin: 0;
color: var(--el-text-color-primary);
flex: 1;
word-break: break-all;
}
.wf-side__participants {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.wf-side__chip {
display: inline-block;
padding: 1px 8px;
font-size: 12px;
background: var(--el-fill-color);
color: var(--el-text-color-regular);
border-radius: 10px;
}
.wf-side__actions {
margin-top: 10px;
padding-top: 10px;
border-top: 1px dashed var(--el-border-color-lighter);
display: flex;
justify-content: flex-end;
}
/* ===== 时间线 ===== */
.wf-side__timeline {
display: flex;
flex-direction: column;
}
.wf-side__tl-item {
display: flex;
gap: 12px;
position: relative;
}
.wf-side__tl-rail {
position: relative;
width: 12px;
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 6px;
}
.wf-side__tl-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--el-color-info);
box-shadow: 0 0 0 3px var(--el-fill-color);
z-index: 1;
}
.wf-side__tl-line {
flex: 1;
width: 2px;
background: var(--el-border-color-lighter);
margin-top: 2px;
}
.wf-side__tl-item.is-approve .wf-side__tl-dot {
background: var(--el-color-success);
box-shadow: 0 0 0 3px var(--el-color-success-light-9);
}
.wf-side__tl-item.is-reject .wf-side__tl-dot {
background: var(--el-color-danger);
box-shadow: 0 0 0 3px var(--el-color-danger-light-9);
}
.wf-side__tl-body {
flex: 1;
padding-bottom: 14px;
min-width: 0;
}
.wf-side__tl-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 8px;
margin-bottom: 4px;
}
.wf-side__tl-state {
font-size: 13px;
color: var(--el-text-color-primary);
font-weight: 600;
}
.wf-side__tl-time {
font-size: 11.5px;
color: var(--el-text-color-placeholder);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.wf-side__tl-meta {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--el-text-color-regular);
margin-bottom: 4px;
}
.wf-side__tl-transition {
font-weight: 500;
}
.wf-side__tl-transition.is-approve { color: var(--el-color-success); }
.wf-side__tl-transition.is-reject { color: var(--el-color-danger); }
.wf-side__tl-user {
color: var(--el-text-color-secondary);
}
.wf-side__tl-suggestion {
background: var(--el-fill-color-light);
border-left: 2px solid var(--el-border-color);
padding: 5px 8px;
border-radius: 0 6px 6px 0;
font-size: 12px;
color: var(--el-text-color-regular);
line-height: 1.5;
margin-top: 4px;
}
.wf-side__tl-item.is-approve .wf-side__tl-suggestion {
border-left-color: var(--el-color-success);
background: var(--el-color-success-light-9);
}
.wf-side__tl-item.is-reject .wf-side__tl-suggestion {
border-left-color: var(--el-color-danger);
background: var(--el-color-danger-light-9);
}
</style>