<script lang="ts" setup>
|
import type { MesPdTemplateApi } from '#/api/mes/pro/processdesign/template';
|
|
import { computed, ref } from 'vue';
|
|
import { DICT_TYPE } from '@vben/constants';
|
import { IconifyIcon } from '@vben/icons';
|
import { getDictOptions } from '@vben/hooks';
|
|
import { Input, Modal, Spin, Tag } from 'ant-design-vue';
|
|
import { getTemplatePage } from '#/api/mes/pro/processdesign/template';
|
|
defineOptions({ name: 'WorkbenchTemplateSelectDialog' });
|
|
const emit = defineEmits<{
|
select: [template: MesPdTemplateApi.Template];
|
}>();
|
|
const visible = ref(false);
|
const loading = ref(false);
|
const templates = ref<MesPdTemplateApi.Template[]>([]);
|
const searchKeyword = ref('');
|
|
/** 打开弹窗 */
|
async function open() {
|
visible.value = true;
|
searchKeyword.value = '';
|
await loadTemplates();
|
}
|
|
/** 加载模板列表 */
|
async function loadTemplates() {
|
loading.value = true;
|
try {
|
const result = await getTemplatePage({ pageNo: 1, pageSize: 200 });
|
templates.value = result.list ?? [];
|
} finally {
|
loading.value = false;
|
}
|
}
|
|
/** 过滤后的模板 */
|
const filtered = computed(() => {
|
const kw = searchKeyword.value.trim().toLowerCase();
|
if (!kw) return templates.value;
|
return templates.value.filter((t) => {
|
const fields = [t.templateCode, t.templateName, t.remark];
|
return fields.some((f) => String(f ?? '').toLowerCase().includes(kw));
|
});
|
});
|
|
/** 模板类型字典 */
|
const templateTypeOptions = getDictOptions(DICT_TYPE.MES_PD_TEMPLATE_TYPE, 'number') ?? [];
|
|
function getTemplateTypeLabel(type?: number) {
|
const opt = templateTypeOptions.find((o: any) => o.value === type);
|
return opt?.label ?? '';
|
}
|
|
function getTemplateTypeColor(type?: number) {
|
const colors: Record<number, string> = { 1: 'blue', 2: 'green', 3: 'orange' };
|
return type ? (colors[type] ?? 'default') : 'default';
|
}
|
|
/** 选择模板 */
|
function handleSelect(t: MesPdTemplateApi.Template) {
|
emit('select', t);
|
visible.value = false;
|
}
|
|
defineExpose({ open });
|
</script>
|
|
<template>
|
<Modal
|
v-model:open="visible"
|
title="选择工艺参数模板"
|
:footer="null"
|
width="900px"
|
:body-style="{ padding: '16px 20px', maxHeight: '70vh', overflow: 'auto' }"
|
centered
|
destroy-on-close
|
>
|
<!-- 搜索框 -->
|
<div class="tpl-select__search">
|
<Input
|
v-model:value="searchKeyword"
|
placeholder="搜索模板编码/名称"
|
allow-clear
|
>
|
<template #prefix>
|
<IconifyIcon icon="lucide:search" class="text-gray-400" />
|
</template>
|
</Input>
|
</div>
|
|
<Spin :spinning="loading">
|
<!-- 空状态 -->
|
<div v-if="!filtered.length && !loading" class="tpl-select__empty">
|
<IconifyIcon icon="lucide:inbox" class="tpl-select__empty-icon" />
|
<p>暂无工艺参数模板</p>
|
</div>
|
|
<!-- 卡片网格 -->
|
<div v-else class="tpl-select__grid">
|
<button
|
v-for="t in filtered"
|
:key="t.id"
|
type="button"
|
class="tpl-card"
|
@click="handleSelect(t)"
|
>
|
<div class="tpl-card__head">
|
<span class="tpl-card__code">{{ t.templateCode }}</span>
|
<Tag v-if="t.templateType" :color="getTemplateTypeColor(t.templateType)">
|
{{ getTemplateTypeLabel(t.templateType) }}
|
</Tag>
|
</div>
|
<div class="tpl-card__name">{{ t.templateName }}</div>
|
<div v-if="t.details?.length" class="tpl-card__params">
|
<IconifyIcon icon="lucide:settings-2" class="tpl-card__params-icon" />
|
<span>{{ t.details.length }} 个参数</span>
|
</div>
|
<div v-if="t.remark" class="tpl-card__remark">{{ t.remark }}</div>
|
<div class="tpl-card__action">
|
<IconifyIcon icon="lucide:pointer" />
|
点击选择
|
</div>
|
</button>
|
</div>
|
</Spin>
|
</Modal>
|
</template>
|
|
<style lang="scss" scoped>
|
.tpl-select__search {
|
margin-bottom: 16px;
|
}
|
|
.tpl-select__empty {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
gap: 8px;
|
padding: 48px 0;
|
color: #8c8c8c;
|
|
p {
|
margin: 0;
|
font-size: 15px;
|
}
|
}
|
|
.tpl-select__empty-icon {
|
font-size: 40px;
|
opacity: 0.5;
|
}
|
|
.tpl-select__grid {
|
display: grid;
|
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
gap: 16px;
|
}
|
|
.tpl-card {
|
display: flex;
|
flex-direction: column;
|
gap: 10px;
|
padding: 16px;
|
border: 2px solid #e8e8e8;
|
border-radius: 12px;
|
text-align: left;
|
cursor: pointer;
|
background: linear-gradient(
|
145deg,
|
#f0fdf4 0%,
|
#dcfce7 52%,
|
#bbf7d0 100%
|
);
|
transition:
|
transform 0.18s ease,
|
box-shadow 0.18s ease,
|
border-color 0.18s ease;
|
|
&:hover {
|
transform: translateY(-2px);
|
border-color: #22c55e;
|
box-shadow: 0 8px 24px rgb(34 197 94 / 20%);
|
}
|
|
&:active {
|
transform: translateY(0);
|
}
|
}
|
|
.tpl-card__head {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
gap: 8px;
|
}
|
|
.tpl-card__code {
|
font-size: 18px;
|
font-weight: 800;
|
letter-spacing: 0.03em;
|
color: #166534;
|
line-height: 1.1;
|
}
|
|
.tpl-card__name {
|
font-size: 15px;
|
font-weight: 600;
|
color: #14532d;
|
line-height: 1.3;
|
}
|
|
.tpl-card__params {
|
display: flex;
|
align-items: center;
|
gap: 6px;
|
font-size: 13px;
|
color: #475569;
|
}
|
|
.tpl-card__params-icon {
|
font-size: 14px;
|
color: #64748b;
|
}
|
|
.tpl-card__remark {
|
font-size: 12px;
|
color: #6b7280;
|
line-height: 1.4;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
display: -webkit-box;
|
-webkit-line-clamp: 2;
|
-webkit-box-orient: vertical;
|
}
|
|
.tpl-card__action {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
gap: 6px;
|
padding: 8px;
|
border-radius: 8px;
|
background: rgb(34 197 94 / 12%);
|
border: 1px solid rgb(34 197 94 / 20%);
|
font-size: 14px;
|
font-weight: 600;
|
color: #16a34a;
|
transition: all 0.2s;
|
margin-top: auto;
|
|
.tpl-card:hover & {
|
background: rgb(34 197 94 / 20%);
|
}
|
}
|
</style>
|