html
<template>
<el-dialog v-model="visible" title="模板管理" width="900px">
<el-table :data="searchStore.templates" style="width: 100%" stripe>
<el-table-column prop="id" label="编号" width="60" />
<el-table-column label="模板类型" width="100">
<template #default="scope">
<el-tag :type="scope.row.type === 'public' ? 'warning' : 'info'">
{{ scope.row.type === 'public' ? '公用' : '个人' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="模板名称" min-width="150">
<template #default="scope">
<el-button link type="primary" @click="handleApply(scope.row)">
{{ scope.row.name }}
</el-button>
</template>
</el-table-column>
<el-table-column prop="creator" label="创建人" width="120" />
<el-table-column prop="createTime" label="创建时间" width="180" />
<el-table-column label="设为默认" width="100" align="center">
<template #default="scope">
<el-switch
:model-value="scope.row.isDefault"
:loading="switchLoadingMap[scope.row.id]"
:disabled="scope.row.isDefault"
@change="() => handleSetDefault(scope.row)"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template #default="scope">
<div class="action-buttons">
<el-tooltip content="分享模板" placement="top" v-if="scope.row.permission?.canShare">
<el-button link type="primary" :icon="Share" @click="handleShareOpen(scope.row)" />
</el-tooltip>
<el-tooltip content="删除模板" placement="top" v-if="scope.row.permission?.canDelete">
<el-button link type="danger" :icon="Delete" @click="handleDelete(scope.row)" />
</el-tooltip>
</div>
</template>
</el-table-column>
</el-table>
</el-dialog>
<el-dialog v-model="shareVisible" title="共享模板" width="450px" append-to-body>
<el-form label-position="top">
<el-form-item label="模板名称">
<el-input v-model="currentShareRow.name" disabled />
</el-form-item>
<el-form-item label="共享给 (个人/群组)">
<el-input v-model="shareTarget" placeholder="请输入工号或群组名" />
</el-form-item>
<el-form-item label="描述">
<el-input type="textarea" placeholder="请输入描述信息" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="shareVisible = false" :disabled="shareSubmitting">取消</el-button>
<el-button type="primary" @click="handleShareSubmit" :loading="shareSubmitting">
确定分享
</el-button>
</template>
</el-dialog>
</template>
<script setup>
import { computed, ref, reactive } from 'vue'
import { useSearchStore } from '../../stores/searchStore'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Share, Delete } from '@element-plus/icons-vue'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const searchStore = useSearchStore()
const visible = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val),
})
// === 1. 设为默认的相关逻辑 ===
const switchLoadingMap = reactive({}) // 存储每一行的 loading 状态 { id: boolean }
// 定义超时时长 (毫秒)
const API_TIMEOUT = 3000
const handleSetDefault = async (row) => {
// 如果已经是默认的,不做操作(UI上其实已经disable了)
if (row.isDefault) return
// 1. 开启 Loading
switchLoadingMap[row.id] = true
try {
// 2. 构造超时 Promise
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error('请求超时,请稍后重试'))
}, API_TIMEOUT)
})
// 3. Race: 真实请求 vs 超时
await Promise.race([
searchStore.apiSetDefault(row.id), // 调用 Store 的异步 Action
timeoutPromise,
])
// 成功后,Loading 会在 finally 移除,State 由 Store 更新
ElMessage.success('设置默认模板成功')
} catch (error) {
// 4. 失败或超时处理
console.error(error)
ElMessage.error(error.message || '设置失败')
// 注意:因为我们绑定的是 model-value 而不是 v-model,
// 且数据更新依赖 store,所以失败时只要不改 store,Switch 就会自动保持原样。
} finally {
// 5. 关闭 Loading
switchLoadingMap[row.id] = false
}
}
// === 2. 删除相关逻辑 ===
const handleDelete = (row) => {
ElMessageBox.confirm(`确定删除模板 "${row.name}" 吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
beforeClose: async (action, instance, done) => {
if (action === 'confirm') {
instance.confirmButtonLoading = true // 让 MessageBox 的按钮转圈
instance.confirmButtonText = '删除中...'
try {
await searchStore.apiDeleteTemplate(row.id)
ElMessage.success('删除成功')
done()
} catch (e) {
ElMessage.error('删除失败')
} finally {
instance.confirmButtonLoading = false
}
} else {
done()
}
},
})
.then(() => {
// confirm 回调
})
.catch(() => {
// cancel 回调
})
}
// === 3. 分享相关逻辑 ===
const shareVisible = ref(false)
const shareSubmitting = ref(false)
const currentShareRow = ref({})
const shareTarget = ref('')
const handleShareOpen = (row) => {
currentShareRow.value = { ...row }
shareTarget.value = '' // 重置输入
shareVisible.value = true
}
const handleShareSubmit = async () => {
if (!shareTarget.value) {
ElMessage.warning('请输入分享对象')
return
}
shareSubmitting.value = true
try {
await searchStore.apiShareTemplate(currentShareRow.value.id, shareTarget.value)
ElMessage.success('分享成功')
shareVisible.value = false
} catch (error) {
ElMessage.error('分享失败,请重试')
} finally {
shareSubmitting.value = false
}
}
// 点击名称应用模板
const handleApply = (row) => {
searchStore.applyTemplate(row)
visible.value = false
}
</script>
<style scoped>
.action-buttons {
display: flex;
gap: 8px;
justify-content: center;
}
</style>