完善设备端物模型管理

This commit is contained in:
ChenYi 2025-12-24 11:51:37 +08:00
parent 0ffc2a3098
commit 142584be52
8 changed files with 837 additions and 682 deletions

View File

@ -1312,20 +1312,23 @@ export const CopyIoTPlatformThingModelToDeviceInputSchema = {
} as const; } as const;
export const CopyStandardThingModelInputSchema = { export const CopyStandardThingModelInputSchema = {
required: ['filedTypes', 'ioTPlatform', 'ioTPlatformProductId'],
type: 'object', type: 'object',
properties: { properties: {
filedType: { filedTypes: {
type: 'string', type: 'array',
description: '物联网平台中对应产品物模型属性或者事件类型 JiShe.ServicePro.Core.DataDictionaryTypeConst', items: {
nullable: true type: 'string'
},
description: '物联网平台中对应产品物模型属性或者事件类型 JiShe.ServicePro.Core.DataDictionaryTypeConst'
}, },
ioTPlatform: { ioTPlatform: {
'$ref': '#/components/schemas/IoTPlatformTypeEnum' '$ref': '#/components/schemas/IoTPlatformTypeEnum'
}, },
ioTPlatformProductId: { ioTPlatformProductId: {
minLength: 1,
type: 'string', type: 'string',
description: '平台产品ID', description: '平台产品ID'
nullable: true
}, },
dataDictionaries: { dataDictionaries: {
type: 'array', type: 'array',
@ -2990,6 +2993,11 @@ export const DeviceThingModelPropertyInfoDtoSchema = {
description: '反转获取数量', description: '反转获取数量',
format: 'int32', format: 'int32',
nullable: true nullable: true
},
filedTypeName: {
type: 'string',
description: '物模型属性或者事件类型名称',
nullable: true
} }
}, },
additionalProperties: false, additionalProperties: false,
@ -5492,6 +5500,11 @@ export const IoTPlatformThingModelInfoDtoSchema = {
type: 'string', type: 'string',
description: '物联网平台中对应产品物模型标识符扩展,用于扩展结构体类型', description: '物联网平台中对应产品物模型标识符扩展,用于扩展结构体类型',
nullable: true nullable: true
},
filedTypeName: {
type: 'string',
description: '物模型属性或者事件类型名称',
nullable: true
} }
}, },
additionalProperties: false, additionalProperties: false,

View File

@ -547,7 +547,7 @@ export const postDeviceThingModelManagementBuildAnalysisScriptAsync = <ThrowOnEr
}; };
/** /**
* ID将解析脚本更新Redis并发布订阅 * ID和解析标识决定是否将解析脚本更新Redis并发布订阅
*/ */
export const postDeviceThingModelManagementUpdateAnalysisScriptByIdAsync = <ThrowOnError extends boolean = false>(options?: Options<PostDeviceThingModelManagementUpdateAnalysisScriptByIdAsyncData, ThrowOnError>) => { export const postDeviceThingModelManagementUpdateAnalysisScriptByIdAsync = <ThrowOnError extends boolean = false>(options?: Options<PostDeviceThingModelManagementUpdateAnalysisScriptByIdAsyncData, ThrowOnError>) => {
return (options?.client ?? client).post<PostDeviceThingModelManagementUpdateAnalysisScriptByIdAsyncResponse, PostDeviceThingModelManagementUpdateAnalysisScriptByIdAsyncError, ThrowOnError>({ return (options?.client ?? client).post<PostDeviceThingModelManagementUpdateAnalysisScriptByIdAsyncResponse, PostDeviceThingModelManagementUpdateAnalysisScriptByIdAsyncError, ThrowOnError>({

View File

@ -302,12 +302,12 @@ export type CopyStandardThingModelInput = {
/** /**
* JiShe.ServicePro.Core.DataDictionaryTypeConst * JiShe.ServicePro.Core.DataDictionaryTypeConst
*/ */
filedType?: (string) | null; filedTypes: Array<(string)>;
ioTPlatform?: IoTPlatformTypeEnum; ioTPlatform: IoTPlatformTypeEnum;
/** /**
* ID * ID
*/ */
ioTPlatformProductId?: (string) | null; ioTPlatformProductId: string;
/** /**
* *
*/ */
@ -1712,6 +1712,10 @@ export type DeviceThingModelPropertyInfoDto = {
* *
*/ */
reversalTakeNumber?: (number) | null; reversalTakeNumber?: (number) | null;
/**
*
*/
filedTypeName?: (string) | null;
}; };
export type DeviceThingModelPropertyInfoDtoPagedResultDto = { export type DeviceThingModelPropertyInfoDtoPagedResultDto = {
@ -2782,6 +2786,10 @@ export type IoTPlatformThingModelInfoDto = {
* *
*/ */
ioTPlatformRawFieldExtension?: (string) | null; ioTPlatformRawFieldExtension?: (string) | null;
/**
*
*/
filedTypeName?: (string) | null;
}; };
export type IoTPlatformThingModelInfoDtoPagedResultDto = { export type IoTPlatformThingModelInfoDtoPagedResultDto = {

View File

@ -44,7 +44,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: { gridOptions: {
columns: [ columns: [
{ {
field: 'filedType', field: 'filedTypeName',
title: '物模型类型', title: '物模型类型',
minWidth: 160, minWidth: 160,
showOverflow: 'tooltip', showOverflow: 'tooltip',
@ -223,6 +223,7 @@ const [EditPropertyFormModal, editPropertyFormModalApi] = useVbenModal({
// //
const [CopyPropertyModal, copyPropertyModalApi] = useVbenModal({ const [CopyPropertyModal, copyPropertyModalApi] = useVbenModal({
draggable: true, draggable: true,
centered: true,
footer: true, footer: true,
showCancelButton: true, showCancelButton: true,
showConfirmButton: true, showConfirmButton: true,
@ -598,16 +599,16 @@ watch(
}, },
); );
// //
const [CopyPropertyForm, copyPropertyFormApi] = useVbenForm({ const [CopyPropertyForm, copyPropertyFormApi] = useVbenForm({
collapsed: false, collapsed: false,
commonConfig: { commonConfig: {
labelWidth: 140, labelWidth: 140,
componentProps: { componentProps: {
class: 'w-4/5', class: 'w-full',
}, },
}, },
layout: 'horizontal', layout: 'vertical',
schema: [ schema: [
{ {
component: 'ApiSelect', component: 'ApiSelect',
@ -690,7 +691,7 @@ const [CopyPropertyForm, copyPropertyFormApi] = useVbenForm({
], ],
showCollapseButton: false, showCollapseButton: false,
showDefaultActions: false, showDefaultActions: false,
wrapperClass: 'grid-cols-2', wrapperClass: 'grid-cols-1',
}); });
// //

View File

@ -634,14 +634,14 @@ onMounted(async () => {
auth: ['AbpIdentity.Users.Update'], auth: ['AbpIdentity.Users.Update'],
onClick: openPropertyModal.bind(null, row), onClick: openPropertyModal.bind(null, row),
}, },
{
label: '指令管理',
type: 'link',
size: 'small',
auth: ['AbpIdentity.Users.Update'],
onClick: openCommandModal.bind(null, row),
},
]" :drop-down-actions="[ ]" :drop-down-actions="[
{
label: '指令管理',
type: 'link',
size: 'small',
auth: ['AbpIdentity.Users.Update'],
onClick: openCommandModal.bind(null, row),
},
{ {
label: row.functionAnalysisFlag ? '禁用解析' : '启用解析', label: row.functionAnalysisFlag ? '禁用解析' : '启用解析',
type: 'input', type: 'input',

View File

@ -152,7 +152,7 @@ export const tableSchema = computed(() => [
{ {
field: 'action', field: 'action',
title: $t('common.action'), title: $t('common.action'),
width: 160, width: 250,
fixed: 'right', fixed: 'right',
slots: { default: 'action' }, slots: { default: 'action' },
}, },

View File

@ -23,8 +23,9 @@ import { TableAction } from '#/components/table-action';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { import {
getAddThingModelFormSchema, copyStandardThingModelFormSchema,
copyThingModelFormSchema, copyThingModelFormSchema,
getAddThingModelFormSchema,
getEditThingModelFormSchema, getEditThingModelFormSchema,
querySchema, querySchema,
tableSchema, tableSchema,
@ -44,26 +45,28 @@ const ioTPlatform = ref<string>((route.query.ioTPlatform as string) || '2');
const formOptions: VbenFormProps = { const formOptions: VbenFormProps = {
schema: querySchema.value, schema: querySchema.value,
initialValues: { initialValues: {
ioTPlatform: route.query.ioTPlatform ? String(route.query.ioTPlatform) : undefined, ioTPlatform: route.query.ioTPlatform
ioTPlatformProductId: route.query.productId ? String(route.query.productId) : undefined, ? String(route.query.ioTPlatform)
: undefined,
ioTPlatformProductId: route.query.productId
? String(route.query.productId)
: undefined,
}, },
submitOnChange: false, submitOnChange: false,
handleValuesChange: async (values, changedFields) => { handleValuesChange: async (values, changedFields) => {
// ID // ID
if (changedFields.includes('ioTPlatform')) { if (changedFields.includes('ioTPlatform') && values.ioTPlatform) {
if (values.ioTPlatform) { ioTPlatform.value = String(values.ioTPlatform);
ioTPlatform.value = String(values.ioTPlatform); // ID
// ID if (gridApi?.formApi) {
if (gridApi?.formApi) { await gridApi.formApi.setValues({
await gridApi.formApi.setValues({ ioTPlatformProductId: undefined,
ioTPlatformProductId: undefined, });
});
}
productId.value = '';
productName.value = '';
} }
productId.value = '';
productName.value = '';
} }
// ID // ID
if (changedFields.includes('ioTPlatformProductId')) { if (changedFields.includes('ioTPlatformProductId')) {
if (values.ioTPlatformProductId) { if (values.ioTPlatformProductId) {
@ -74,7 +77,7 @@ const formOptions: VbenFormProps = {
productId.value = ''; productId.value = '';
productName.value = ''; productName.value = '';
} }
// //
await nextTick(); await nextTick();
setTimeout(async () => { setTimeout(async () => {
@ -115,7 +118,7 @@ const gridOptions: VxeGridProps<any> = {
: formValues || {}; : formValues || {};
// 使API使formValues // 使API使formValues
const finalFormValues = { ...(formValues || {}), ...currentFormValues }; const finalFormValues = { ...formValues, ...currentFormValues };
const currentPlatform = finalFormValues?.ioTPlatform; const currentPlatform = finalFormValues?.ioTPlatform;
const currentProductId = finalFormValues?.ioTPlatformProductId; const currentProductId = finalFormValues?.ioTPlatformProductId;
@ -135,7 +138,9 @@ const gridOptions: VxeGridProps<any> = {
// //
if (currentPlatform) { if (currentPlatform) {
queryParams.ioTPlatform = Number.parseInt(String(currentPlatform)) as 1 | 2; queryParams.ioTPlatform = Number.parseInt(
String(currentPlatform),
) as 1 | 2;
} }
// ID // ID
@ -184,29 +189,31 @@ const [ThingModelModal, thingModelModalApi] = useVbenModal({
await nextTick(); await nextTick();
const formApi = editRow.value.id ? editFormApi : addFormApi; const formApi = editRow.value.id ? editFormApi : addFormApi;
const isEdit = !!editRow.value.id; const isEdit = !!editRow.value.id;
// //
let platformValue: 1 | 2; let platformValue: 1 | 2;
let productIdValue: string; let productIdValue: string;
if (isEdit) { if (isEdit) {
// editRow.value // editRow.value
platformValue = editRow.value.ioTPlatform platformValue = editRow.value.ioTPlatform
? (typeof editRow.value.ioTPlatform === 'string' ? ((typeof editRow.value.ioTPlatform === 'string'
? Number.parseInt(editRow.value.ioTPlatform) ? Number.parseInt(editRow.value.ioTPlatform)
: editRow.value.ioTPlatform) as 1 | 2 : editRow.value.ioTPlatform) as 1 | 2)
: Number.parseInt(ioTPlatform.value) as 1 | 2; : (Number.parseInt(ioTPlatform.value) as 1 | 2);
productIdValue = editRow.value.ioTPlatformProductId || productId.value; productIdValue = editRow.value.ioTPlatformProductId || productId.value;
} else { } else {
// //
const formValues = gridApi?.formApi ? await gridApi.formApi.getValues() : {}; const formValues = gridApi?.formApi
? await gridApi.formApi.getValues()
: {};
platformValue = formValues.ioTPlatform platformValue = formValues.ioTPlatform
? (typeof formValues.ioTPlatform === 'string' ? ((typeof formValues.ioTPlatform === 'string'
? Number.parseInt(formValues.ioTPlatform) ? Number.parseInt(formValues.ioTPlatform)
: formValues.ioTPlatform) as 1 | 2 : formValues.ioTPlatform) as 1 | 2)
: Number.parseInt(ioTPlatform.value) as 1 | 2; : (Number.parseInt(ioTPlatform.value) as 1 | 2);
productIdValue = formValues.ioTPlatformProductId || productId.value; productIdValue = formValues.ioTPlatformProductId || productId.value;
// //
if (formValues.ioTPlatform) { if (formValues.ioTPlatform) {
ioTPlatform.value = String(formValues.ioTPlatform); ioTPlatform.value = String(formValues.ioTPlatform);
@ -215,23 +222,25 @@ const [ThingModelModal, thingModelModalApi] = useVbenModal({
productId.value = String(formValues.ioTPlatformProductId); productId.value = String(formValues.ioTPlatformProductId);
} }
} }
console.log('设置表单值:', { console.log('设置表单值:', {
isEdit, isEdit,
platformValue, platformValue,
productIdValue, productIdValue,
editRowValue: isEdit ? editRow.value : null, editRowValue: isEdit ? editRow.value : null,
}); });
await formApi.setValues({ await formApi.setValues({
...(isEdit ? editRow.value : {}), ...(isEdit ? editRow.value : {}),
_ioTPlatform: platformValue, _ioTPlatform: platformValue,
_ioTPlatformProductId: productIdValue, _ioTPlatformProductId: productIdValue,
}); });
// //
setTimeout(() => { setTimeout(() => {
const fieldRef = formApi.getFieldComponentRef('ioTPlatformRawFieldName'); const fieldRef = formApi.getFieldComponentRef(
'ioTPlatformRawFieldName',
);
if (fieldRef && typeof fieldRef.updateParam === 'function') { if (fieldRef && typeof fieldRef.updateParam === 'function') {
fieldRef.updateParam({ fieldRef.updateParam({
body: { body: {
@ -262,12 +271,29 @@ const [CopyModal, copyModalApi] = useVbenModal({
}, },
}); });
//
const [CopyStandardModal, copyStandardModalApi] = useVbenModal({
draggable: true,
footer: true,
showCancelButton: true,
showConfirmButton: true,
onConfirm: submitCopyStandard,
onBeforeClose: () => {
return true;
},
onCancel: () => {
copyStandardModalApi.close();
},
});
// schemaID // schemaID
// 使 _ioTPlatform _ioTPlatformProductId // 使 _ioTPlatform _ioTPlatformProductId
const addThingModelFormSchema = getAddThingModelFormSchema( const addThingModelFormSchema = getAddThingModelFormSchema(
() => { () => {
// //
return ioTPlatform.value ? Number.parseInt(ioTPlatform.value) as 1 | 2 : undefined; return ioTPlatform.value
? (Number.parseInt(ioTPlatform.value) as 1 | 2)
: undefined;
}, },
() => { () => {
// //
@ -301,7 +327,9 @@ const editThingModelFormSchema = getEditThingModelFormSchema(
: editRow.value.ioTPlatform; : editRow.value.ioTPlatform;
} }
// 使 // 使
return ioTPlatform.value ? Number.parseInt(ioTPlatform.value) as 1 | 2 : undefined; return ioTPlatform.value
? (Number.parseInt(ioTPlatform.value) as 1 | 2)
: undefined;
}, },
() => { () => {
// editRow // editRow
@ -340,6 +368,22 @@ const [CopyForm, copyFormApi] = useVbenForm({
wrapperClass: 'grid-cols-2', wrapperClass: 'grid-cols-2',
}); });
//
const [CopyStandardForm, copyStandardFormApi] = useVbenForm({
collapsed: false,
commonConfig: {
labelWidth: 160,
componentProps: {
class: 'w-4/5',
},
},
layout: 'horizontal',
schema: copyStandardThingModelFormSchema.value,
showCollapseButton: false,
showDefaultActions: false,
wrapperClass: 'grid-cols-1',
});
// //
watch( watch(
() => [route.query.productId, route.query.ioTPlatform], () => [route.query.productId, route.query.ioTPlatform],
@ -353,7 +397,7 @@ watch(
if (route.query.productName) { if (route.query.productName) {
productName.value = route.query.productName as string; productName.value = route.query.productName as string;
} }
// //
if (gridApi) { if (gridApi) {
setTimeout(async () => { setTimeout(async () => {
@ -380,7 +424,7 @@ onMounted(async () => {
if (route.query.ioTPlatform) { if (route.query.ioTPlatform) {
ioTPlatform.value = route.query.ioTPlatform as string; ioTPlatform.value = route.query.ioTPlatform as string;
} }
// //
// 使 productId // 使 productId
await nextTick(); await nextTick();
@ -425,7 +469,7 @@ async function submit() {
const { valid } = await formApi.validate(); const { valid } = await formApi.validate();
if (!valid) return; if (!valid) return;
const formValues = await formApi.getValues(); const formValues = await formApi.getValues();
const fetchParams: any = { const fetchParams: any = {
...formValues, ...formValues,
// //
@ -451,9 +495,6 @@ async function submit() {
} }
} catch (error) { } catch (error) {
console.error('提交失败:', error); console.error('提交失败:', error);
Message.error(
editRow.value.id ? $t('common.editFail') : $t('common.addFail'),
);
} }
} }
@ -461,12 +502,12 @@ async function onEdit(record: any) {
editRow.value = record; editRow.value = record;
// ID // ID
const platformValue = record.ioTPlatform const platformValue = record.ioTPlatform
? (typeof record.ioTPlatform === 'string' ? ((typeof record.ioTPlatform === 'string'
? Number.parseInt(record.ioTPlatform) ? Number.parseInt(record.ioTPlatform)
: record.ioTPlatform) as 1 | 2 : record.ioTPlatform) as 1 | 2)
: Number.parseInt(ioTPlatform.value) as 1 | 2; : (Number.parseInt(ioTPlatform.value) as 1 | 2);
const productIdValue = record.ioTPlatformProductId || productId.value; const productIdValue = record.ioTPlatformProductId || productId.value;
thingModelModalApi.open(); thingModelModalApi.open();
await nextTick(); await nextTick();
setTimeout(async () => { setTimeout(async () => {
@ -475,9 +516,11 @@ async function onEdit(record: any) {
_ioTPlatform: platformValue, _ioTPlatform: platformValue,
_ioTPlatformProductId: productIdValue, _ioTPlatformProductId: productIdValue,
}); });
// //
const fieldRef = editFormApi.getFieldComponentRef('ioTPlatformRawFieldName'); const fieldRef = editFormApi.getFieldComponentRef(
'ioTPlatformRawFieldName',
);
if (fieldRef && typeof fieldRef.updateParam === 'function') { if (fieldRef && typeof fieldRef.updateParam === 'function') {
fieldRef.updateParam({ fieldRef.updateParam({
body: { body: {
@ -494,12 +537,12 @@ const openAddModal = async () => {
// ID // ID
const formValues = gridApi?.formApi ? await gridApi.formApi.getValues() : {}; const formValues = gridApi?.formApi ? await gridApi.formApi.getValues() : {};
const platformValue = formValues.ioTPlatform const platformValue = formValues.ioTPlatform
? (typeof formValues.ioTPlatform === 'string' ? ((typeof formValues.ioTPlatform === 'string'
? Number.parseInt(formValues.ioTPlatform) ? Number.parseInt(formValues.ioTPlatform)
: formValues.ioTPlatform) as 1 | 2 : formValues.ioTPlatform) as 1 | 2)
: Number.parseInt(ioTPlatform.value) as 1 | 2; : (Number.parseInt(ioTPlatform.value) as 1 | 2);
const productIdValue = formValues.ioTPlatformProductId || productId.value; const productIdValue = formValues.ioTPlatformProductId || productId.value;
// //
if (formValues.ioTPlatform) { if (formValues.ioTPlatform) {
ioTPlatform.value = String(formValues.ioTPlatform); ioTPlatform.value = String(formValues.ioTPlatform);
@ -507,7 +550,7 @@ const openAddModal = async () => {
if (formValues.ioTPlatformProductId) { if (formValues.ioTPlatformProductId) {
productId.value = String(formValues.ioTPlatformProductId); productId.value = String(formValues.ioTPlatformProductId);
} }
thingModelModalApi.open(); thingModelModalApi.open();
// //
await nextTick(); await nextTick();
@ -516,7 +559,7 @@ const openAddModal = async () => {
_ioTPlatform: platformValue, _ioTPlatform: platformValue,
_ioTPlatformProductId: productIdValue, _ioTPlatformProductId: productIdValue,
}); });
// //
const fieldRef = addFormApi.getFieldComponentRef('ioTPlatformRawFieldName'); const fieldRef = addFormApi.getFieldComponentRef('ioTPlatformRawFieldName');
if (fieldRef && typeof fieldRef.updateParam === 'function') { if (fieldRef && typeof fieldRef.updateParam === 'function') {
@ -580,7 +623,59 @@ async function submitCopy() {
} }
} catch (error) { } catch (error) {
console.error('复制模型失败:', error); console.error('复制模型失败:', error);
Message.error('复制模型失败'); }
}
//
const openCopyStandardThingModelModal = () => {
console.log('打开复制标准模型模态框,当前参数:', {
productId: productId.value,
productName: productName.value,
ioTPlatform: ioTPlatform.value,
});
copyStandardModalApi.open();
};
//
async function submitCopyStandard() {
// ID
if (!productId.value) {
Message.error('产品ID不存在无法复制标准模型');
return;
}
const { valid } = await copyStandardFormApi.validate();
if (!valid) return;
const formValues = await copyStandardFormApi.getValues();
console.log('复制标准模型提交参数:', {
formValues,
params: {
productId: productId.value,
productName: productName.value,
ioTPlatform: ioTPlatform.value,
},
});
try {
const resp = await postIoTplatformThingModelInfoCopyStandardThingModel({
body: {
ioTPlatform: Number.parseInt(ioTPlatform.value) as 1 | 2,
ioTPlatformProductId: productId.value,
// 使 filedTypes key
filedTypes: formValues.filedTypes,
},
});
if (resp.data) {
Message.success('复制标准模型成功');
copyStandardModalApi.close();
gridApi.reload();
} else {
Message.error('复制标准模型失败');
}
} catch (error) {
console.error('复制标准模型失败:', error);
} }
} }
@ -598,36 +693,10 @@ async function onDel(record: any) {
} }
} catch (error) { } catch (error) {
console.error('删除失败:', error); console.error('删除失败:', error);
Message.error($t('common.deleteFail'));
} }
} }
// // 使 + submitCopyStandard
async function copyStandardThingModel() {
// ID
if (!productId.value) {
Message.error('产品ID不存在无法复制标准模型');
return;
}
try {
const resp = await postIoTplatformThingModelInfoCopyStandardThingModel({
body: {
ioTPlatform: Number.parseInt(ioTPlatform.value) as 1 | 2,
ioTPlatformProductId: productId.value,
},
});
if (resp.data) {
Message.success('复制标准模型成功');
gridApi.reload();
} else {
Message.error('复制标准模型失败');
}
} catch (error) {
console.error('复制标准模型失败:', error);
Message.error('复制标准模型失败');
}
}
</script> </script>
<template> <template>
@ -647,7 +716,7 @@ async function copyStandardThingModel() {
label: $t('abp.thingModelInfos.copyStandardThingModel'), label: $t('abp.thingModelInfos.copyStandardThingModel'),
type: 'default', type: 'default',
icon: 'ant-design:copy-outlined', icon: 'ant-design:copy-outlined',
onClick: copyStandardThingModel, onClick: openCopyStandardThingModelModal,
auth: ['AbpIdentity.Users.Create'], auth: ['AbpIdentity.Users.Create'],
disabled: !productId, disabled: !productId,
}, },
@ -703,5 +772,10 @@ async function copyStandardThingModel() {
<CopyModal title="复制已有模型" class="w-[600px]"> <CopyModal title="复制已有模型" class="w-[600px]">
<CopyForm /> <CopyForm />
</CopyModal> </CopyModal>
<!-- 复制标准模型按物模型类型多选模态框 -->
<CopyStandardModal title="复制标准模型" class="w-[600px]">
<CopyStandardForm />
</CopyStandardModal>
</Page> </Page>
</template> </template>