提交代码

This commit is contained in:
zlwu committed 2025-08-08 03:59:16 +08:00
1 parent d9f760fe1c
commit 01588bf87d
484 files changed
+7228 -104711

No files matched your search

@@ -0,0 +1,110 @@
<template>
<view class="approval-button-container">
<!-- 审批状态 -->
<view v-if="status === 'waiting'" class="button-group">
<u-button type="success" @click="handleApproval" class="action-button">审批</u-button>
<u-button type="info" @click="handleApprovalRecord" class="action-button">
查看记录
</u-button>
</view>
<!-- 默认状态 -->
<view v-else class="button-group">
<u-button
type="info"
:loading="buttonLoading"
@click="handleSubmit('draft')"
class="action-button"
>
暂存
</u-button>
<u-button
type="primary"
:loading="buttonLoading"
@click="handleSubmit('submit')"
class="action-button"
>
提交
</u-button>
</view>
</view>
</template>
<script>
export default {
name: 'ApprovalButton',
props: {
id: {
type: [String, Number],
default: '',
},
status: {
type: String,
default: 'draft',
},
pageType: {
type: String,
default: '',
},
taskId: {
type: String,
default: '',
},
buttonLoading: {
type: Boolean,
default: false,
},
},
methods: {
// 提交表单
handleSubmit(status) {
this.$emit('submitForm', status);
},
// 审批操作
handleApproval() {
this.$emit('approvalVerifyOpen');
},
// 查看审批记录
handleApprovalRecord() {
this.$emit('handleApprovalRecord');
},
},
};
</script>
<style scoped>
.approval-button-container {
padding: 20rpx;
background: #fff;
border-radius: 12rpx;
margin-bottom: 20rpx;
}
.button-group {
display: flex;
gap: 20rpx;
flex-wrap: wrap;
}
.action-button {
flex: 1;
min-width: 200rpx;
height: 80rpx;
border-radius: 40rpx;
font-size: 28rpx;
font-weight: 500;
}
/* 响应式布局 */
@media (max-width: 750rpx) {
.button-group {
flex-direction: column;
}
.action-button {
width: 100%;
}
}
</style>
@@ -0,0 +1,291 @@
<template>
<u-popup
v-model="visible"
mode="center"
width="90%"
height="70%"
border-radius="12"
@close="handleClose"
>
<view class="approval-record-container">
<view class="header">
<text class="title">审批记录</text>
<u-icon name="close" size="24" @click="handleClose" />
</view>
<view class="content">
<u-loading-page v-if="loading" />
<view v-else-if="recordList.length === 0" class="empty">
<u-icon name="info-circle" size="48" color="#ccc" />
<text class="empty-text">暂无审批记录</text>
</view>
<scroll-view v-else scroll-y class="record-list">
<view v-for="(record, index) in recordList" :key="index" class="record-item">
<view class="record-header">
<view class="approver-info">
<u-avatar
:src="record.avatar || '/static/images/default-avatar.png'"
size="60"
/>
<view class="approver-details">
<text class="approver-name">
{{ record.approverName || '未知' }}
</text>
<text class="approver-role">
{{ record.roleName || '未知角色' }}
</text>
</view>
</view>
<view :class="['status-badge', getStatusClass(record.result)]">
{{ getStatusText(record.result) }}
</view>
</view>
<view v-if="record.comment" class="comment">
<text class="comment-label">审批意见:</text>
<text class="comment-text">{{ record.comment }}</text>
</view>
<view class="record-footer">
<text class="time">{{ formatTime(record.createTime) }}</text>
</view>
</view>
</scroll-view>
</view>
</view>
</u-popup>
</template>
<script>
import { request } from '@/common/request.js';
export default {
name: 'ApprovalRecord',
data() {
return {
visible: false,
loading: false,
recordList: [],
currentBusinessId: '',
};
},
methods: {
// 打开弹窗
async init(businessId) {
this.currentBusinessId = businessId.toString();
this.visible = true;
await this.loadApprovalRecords();
},
// 关闭弹窗
handleClose() {
this.visible = false;
this.recordList = [];
},
// 加载审批记录
async loadApprovalRecords() {
if (!this.currentBusinessId) return;
try {
this.loading = true;
const response = await request({
url: `/workflow/task/approvalRecords/${this.currentBusinessId}`,
method: 'GET',
});
if (response.code === 200) {
this.recordList = response.data || [];
} else {
uni.showToast({
title: response.msg || '获取审批记录失败',
icon: 'none',
});
}
} catch (error) {
console.error('获取审批记录失败:', error);
uni.showToast({
title: '获取审批记录失败',
icon: 'none',
});
} finally {
this.loading = false;
}
},
// 获取状态样式类
getStatusClass(result) {
switch (result) {
case 'approve':
return 'status-approve';
case 'reject':
return 'status-reject';
default:
return 'status-pending';
}
},
// 获取状态文本
getStatusText(result) {
switch (result) {
case 'approve':
return '同意';
case 'reject':
return '拒绝';
default:
return '待审批';
}
},
// 格式化时间
formatTime(time) {
if (!time) return '-';
return new Date(time).toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
});
},
},
};
</script>
<style scoped>
.approval-record-container {
height: 100%;
display: flex;
flex-direction: column;
background: #fff;
border-radius: 12rpx;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.content {
flex: 1;
position: relative;
}
.empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
padding: 60rpx;
}
.empty-text {
margin-top: 20rpx;
font-size: 28rpx;
color: #999;
}
.record-list {
height: 100%;
padding: 20rpx;
}
.record-item {
background: #f8f9fa;
border-radius: 12rpx;
padding: 24rpx;
margin-bottom: 20rpx;
}
.record-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.approver-info {
display: flex;
align-items: center;
gap: 16rpx;
}
.approver-details {
display: flex;
flex-direction: column;
gap: 4rpx;
}
.approver-name {
font-size: 28rpx;
font-weight: 500;
color: #333;
}
.approver-role {
font-size: 24rpx;
color: #666;
}
.status-badge {
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
font-weight: 500;
}
.status-approve {
background: #e8f5e8;
color: #52c41a;
}
.status-reject {
background: #fff2f0;
color: #ff4d4f;
}
.status-pending {
background: #fff7e6;
color: #fa8c16;
}
.comment {
margin-bottom: 16rpx;
padding: 16rpx;
background: #fff;
border-radius: 8rpx;
}
.comment-label {
font-size: 26rpx;
color: #666;
margin-right: 8rpx;
}
.comment-text {
font-size: 26rpx;
color: #333;
line-height: 1.5;
}
.record-footer {
display: flex;
justify-content: flex-end;
}
.time {
font-size: 24rpx;
color: #999;
}
</style>
@@ -0,0 +1,261 @@
<template>
<u-popup
v-model="visible"
mode="center"
width="90%"
height="80%"
border-radius="12"
@close="handleClose"
>
<view class="submit-verify-container">
<view class="header">
<text class="title">审批操作</text>
<u-icon name="close" size="24" @click="handleClose" />
</view>
<view class="content">
<view class="form-item">
<text class="label">审批结果</text>
<u-radio-group v-model="formData.result" @change="handleResultChange">
<u-radio label="approve" name="approve" class="radio-item">
<text class="radio-text">同意</text>
</u-radio>
<u-radio label="reject" name="reject" class="radio-item">
<text class="radio-text">拒绝</text>
</u-radio>
</u-radio-group>
</view>
<view class="form-item">
<text class="label">审批意见</text>
<u-textarea
v-model="formData.opinion"
placeholder="请输入审批意见"
:maxlength="500"
height="120"
count
/>
</view>
<view v-if="formData.result === 'reject'" class="form-item">
<text class="label">拒绝原因</text>
<u-textarea
v-model="formData.rejectReason"
placeholder="请输入拒绝原因"
:maxlength="200"
height="100"
count
/>
</view>
</view>
<view class="footer">
<u-button type="info" @click="handleClose" class="cancel-btn">取消</u-button>
<u-button
type="primary"
:loading="submitLoading"
@click="handleSubmit"
class="submit-btn"
>
提交
</u-button>
</view>
</view>
</u-popup>
</template>
<script>
import { request } from '@/common/request.js';
export default {
name: 'SubmitVerify',
props: {
taskVariables: {
type: Object,
default: () => ({}),
},
},
data() {
return {
visible: false,
submitLoading: false,
currentTaskId: '',
formData: {
opinion: '',
result: 'approve',
rejectReason: '',
},
};
},
methods: {
// 打开弹窗
openDialog(taskId) {
this.currentTaskId = taskId;
this.visible = true;
// 重置表单
this.formData.opinion = '';
this.formData.result = 'approve';
this.formData.rejectReason = '';
},
// 关闭弹窗
handleClose() {
this.visible = false;
this.submitLoading = false;
},
// 审批结果变化
handleResultChange(value) {
this.formData.result = value;
if (value === 'approve') {
this.formData.rejectReason = '';
}
},
// 提交审批
async handleSubmit() {
if (!this.formData.opinion.trim()) {
uni.showToast({
title: '请输入审批意见',
icon: 'none',
});
return;
}
if (this.formData.result === 'reject' && !this.formData.rejectReason.trim()) {
uni.showToast({
title: '请输入拒绝原因',
icon: 'none',
});
return;
}
try {
this.submitLoading = true;
// 构建提交数据
const submitData = {
taskId: this.currentTaskId,
opinion: this.formData.opinion,
result: this.formData.result,
rejectReason: this.formData.rejectReason,
variables: this.taskVariables,
};
// 调用审批API
const response = await request({
url: '/workflow/task/complete',
method: 'POST',
data: submitData,
});
if (response.code === 200) {
uni.showToast({
title: '审批成功',
icon: 'success',
});
this.handleClose();
this.$emit('submit-callback');
} else {
uni.showToast({
title: response.msg || '审批失败',
icon: 'none',
});
}
} catch (error) {
console.error('审批失败:', error);
uni.showToast({
title: '审批失败',
icon: 'none',
});
} finally {
this.submitLoading = false;
}
},
},
};
</script>
<style scoped>
.submit-verify-container {
height: 100%;
display: flex;
flex-direction: column;
background: #fff;
border-radius: 12rpx;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.content {
flex: 1;
padding: 30rpx;
overflow-y: auto;
}
.form-item {
margin-bottom: 30rpx;
}
.label {
display: block;
font-size: 28rpx;
font-weight: 500;
color: #333;
margin-bottom: 16rpx;
}
.radio-group {
display: flex;
gap: 40rpx;
}
.radio-item {
display: flex;
align-items: center;
gap: 8rpx;
}
.radio-text {
font-size: 28rpx;
color: #333;
}
.footer {
display: flex;
gap: 20rpx;
padding: 30rpx;
border-top: 1rpx solid #f0f0f0;
}
.cancel-btn,
.submit-btn {
flex: 1;
height: 80rpx;
border-radius: 40rpx;
font-size: 28rpx;
font-weight: 500;
}
.cancel-btn {
background: #f5f5f5;
color: #666;
}
.submit-btn {
background: #2979ff;
color: #fff;
}
</style>
+35 -6
View File
@@ -3,14 +3,17 @@
<view class="tree-item" :class="{ active: selectedId === node.id }" @click="handleSelect">
<view class="tree-item-content">
<view class="tree-item-left">
<u-icon
<view
v-if="node.children && node.children.length > 0"
:name="expanded ? 'arrow-down' : 'arrow-right'"
size="16"
color="#666"
class="expand-icon-wrapper"
@click.stop="toggleExpand"
style="margin-right: 8px"
></u-icon>
>
<u-icon
:name="expanded ? 'arrow-down' : 'arrow-right'"
size="16"
color="#666"
></u-icon>
</view>
<text class="tree-item-text">{{ node.label }}</text>
</view>
</view>
@@ -23,6 +26,7 @@
:key="child.id"
:node="child"
:selected-id="selectedId"
:default-expanded="defaultExpanded"
@select="handleChildSelect"
></tree-node>
</view>
@@ -41,12 +45,22 @@ export default {
type: String,
default: '',
},
defaultExpanded: {
type: Boolean,
default: false,
},
},
data() {
return {
expanded: false,
};
},
mounted() {
// 如果设置了默认展开,则展开当前节点
if (this.defaultExpanded) {
this.expanded = true;
}
},
methods: {
handleSelect() {
this.$emit('select', this.node);
@@ -95,6 +109,21 @@ export default {
flex: 1;
}
.expand-icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
margin-right: 8px;
border-radius: 4px;
transition: background-color 0.2s;
}
.expand-icon-wrapper:active {
background-color: #e6f7ff;
}
.tree-item-text {
font-size: 14px;
color: #333;