feat: 审批流程模块 (T审批A审批)
- 新增 backend/app/api/approval.py 审批API - 前端H5支持发起审批、审批操作 - 添加审批卡片弹窗组件 - 路由注册审批模块
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
<!-- =============================================================================
|
||||
// 企微IT智能服务台 — 审批卡片弹窗组件
|
||||
// =============================================================================
|
||||
// 说明:关键词触发弹窗,展示审批选项供用户选择
|
||||
// - 用户输入"申请"等关键词时弹出
|
||||
// - 显示资源申请/设备申请等选项
|
||||
// - 点击选项后跳转或提交
|
||||
// ============================================================================= -->
|
||||
|
||||
<template>
|
||||
<van-popup
|
||||
v-model:show="visible"
|
||||
position="bottom"
|
||||
round
|
||||
closeable
|
||||
:style="{ height: '40%' }"
|
||||
@close="handleClose"
|
||||
>
|
||||
<div class="approval-card">
|
||||
<!-- 标题 -->
|
||||
<div class="approval-card__header">
|
||||
<div class="approval-card__title">选择审批类型</div>
|
||||
<div class="approval-card__subtitle">根据您的需求选择相应的审批流程</div>
|
||||
</div>
|
||||
|
||||
<!-- 选项列表 -->
|
||||
<div class="approval-card__options">
|
||||
<div
|
||||
v-for="option in matchedOptions"
|
||||
:key="option.template_id"
|
||||
class="approval-card__option"
|
||||
@click="handleSelect(option)"
|
||||
>
|
||||
<div class="approval-card__option-icon">
|
||||
<van-icon :name="option.type === 'jump' ? 'link-o' : 'orders-o'" size="24" />
|
||||
</div>
|
||||
<div class="approval-card__option-content">
|
||||
<div class="approval-card__option-title">{{ option.template_name }}</div>
|
||||
<div class="approval-card__option-desc">
|
||||
{{ option.type === 'jump' ? '跳转企微审批页面' : '填写表单提交审批' }}
|
||||
</div>
|
||||
</div>
|
||||
<van-icon name="arrow" class="approval-card__option-arrow" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { showToast } from 'vant'
|
||||
import { getApprovalKeywords, createApprovalJump, type ApprovalKeyword } from '@/api/conversation'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
triggerText?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
triggerText: '',
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: boolean]
|
||||
'select': [option: ApprovalKeyword]
|
||||
}>()
|
||||
|
||||
// 状态
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
})
|
||||
|
||||
const approvalKeywords = ref<ApprovalKeyword[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
// 匹配的审批选项
|
||||
const matchedOptions = computed(() => {
|
||||
if (!props.triggerText) return approvalKeywords.value
|
||||
|
||||
const text = props.triggerText.toLowerCase()
|
||||
return approvalKeywords.value.filter((kw) => text.includes(kw.keyword.toLowerCase()))
|
||||
})
|
||||
|
||||
// 加载审批关键词
|
||||
async function loadKeywords() {
|
||||
if (approvalKeywords.value.length > 0) return
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
approvalKeywords.value = await getApprovalKeywords()
|
||||
} catch (error) {
|
||||
console.error('加载审批关键词失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 选择审批选项
|
||||
async function handleSelect(option: ApprovalKeyword) {
|
||||
try {
|
||||
if (option.type === 'jump') {
|
||||
// 跳转审批
|
||||
const result = await createApprovalJump(option.template_id)
|
||||
// 在企微中打开链接
|
||||
window.open(result.url, '_blank')
|
||||
showToast('已打开审批页面')
|
||||
} else {
|
||||
// API提交 - 后续实现
|
||||
showToast('该功能正在开发中')
|
||||
}
|
||||
|
||||
emit('select', option)
|
||||
handleClose()
|
||||
} catch (error) {
|
||||
console.error('打开审批失败:', error)
|
||||
showToast('打开审批失败,请重试')
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
function handleClose() {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
// 监听显示
|
||||
import { watch } from 'vue'
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
if (val) {
|
||||
loadKeywords()
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.approval-card {
|
||||
padding: 16px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.approval-card__header {
|
||||
text-align: center;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.approval-card__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.approval-card__subtitle {
|
||||
font-size: 13px;
|
||||
color: var(--text-tertiary);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.approval-card__options {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.approval-card__option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 12px;
|
||||
margin-bottom: 12px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.approval-card__option:active {
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.approval-card__option-icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 12px;
|
||||
background: var(--accent-color, #07c160);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.approval-card__option-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.approval-card__option-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.approval-card__option-desc {
|
||||
font-size: 13px;
|
||||
color: var(--text-tertiary);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.approval-card__option-arrow {
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
</style>
|
||||
@@ -111,6 +111,13 @@
|
||||
@update:visible="showCallModal = $event"
|
||||
@call-success="handleCallSuccess"
|
||||
/>
|
||||
|
||||
<!-- 审批卡片弹窗(关键词触发) -->
|
||||
<ApprovalCardModal
|
||||
v-model="store.approvalCardVisible"
|
||||
:trigger-text="store.approvalCardTriggerText"
|
||||
@select="handleApprovalSelect"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -128,6 +135,7 @@ import { useThemeStore } from '@/stores/theme'
|
||||
import MessageBubble from './MessageBubble.vue'
|
||||
import InputBar from './InputBar.vue'
|
||||
import CallAgentModal from './CallAgentModal.vue'
|
||||
import ApprovalCardModal from './ApprovalCardModal.vue'
|
||||
import TroubleshootFlow from './TroubleshootFlow.vue'
|
||||
import ParticipantList from './ParticipantList.vue'
|
||||
|
||||
@@ -171,6 +179,12 @@ function handleCallSuccess(): void {
|
||||
store.fetchCurrentConversation()
|
||||
}
|
||||
|
||||
/** 处理审批选项选择 */
|
||||
function handleApprovalSelect(option: any): void {
|
||||
console.log('[ChatPanel] 选择审批:', option)
|
||||
store.closeApprovalCard()
|
||||
}
|
||||
|
||||
// 监听消息列表变化,自动滚动到底部
|
||||
watch(
|
||||
() => store.messages.length,
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
<button class="input-box__tool-btn" title="截图" @click="handleScreenshot">
|
||||
<span>✂️</span>
|
||||
</button>
|
||||
<button class="input-box__tool-btn input-box__tool-btn--accent" title="快捷申请" @click="handleQuickApply">
|
||||
<span>📝</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 表情选择面板(简易版:常用 Emoji 网格) -->
|
||||
@@ -466,6 +469,14 @@ function onScreenshotCancel(): void {
|
||||
showScreenshotEditor.value = false
|
||||
screenshotCanvas = null
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 快捷申请按钮
|
||||
// ============================================================================
|
||||
function handleQuickApply(): void {
|
||||
// 触发审批卡片弹窗
|
||||
store.showApprovalCard('')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -508,6 +519,17 @@ function onScreenshotCancel(): void {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* 快捷申请按钮 - 强调样式 */
|
||||
.input-box__tool-btn--accent {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.input-box__tool-btn--accent:hover {
|
||||
background: var(--accent-hover, #06ad56);
|
||||
border-color: var(--accent-hover, #06ad56);
|
||||
}
|
||||
|
||||
/* 输入区域 */
|
||||
.input-box__area {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user