|
@@ -0,0 +1,329 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <Dialog v-model="dialogVisible" :title="dialogTitle" width="900px">
|
|
|
|
|
+ <el-form
|
|
|
|
|
+ ref="formRef"
|
|
|
|
|
+ v-loading="formLoading"
|
|
|
|
|
+ :model="formData"
|
|
|
|
|
+ :rules="formRules"
|
|
|
|
|
+ label-width="100px"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-form-item label="功能标识" prop="identifier">
|
|
|
|
|
+ <el-input v-model="formData.identifier" placeholder="请输入功能标识" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="功能名称" prop="name">
|
|
|
|
|
+ <el-input v-model="formData.name" placeholder="请输入功能名称" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="功能描述" prop="description">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="formData.description"
|
|
|
|
|
+ type="textarea"
|
|
|
|
|
+ :rows="3"
|
|
|
|
|
+ :maxlength="200"
|
|
|
|
|
+ placeholder="请输入功能描述"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="功能类型" prop="type">
|
|
|
|
|
+ <el-radio-group v-model="formData.type">
|
|
|
|
|
+ <el-radio-button
|
|
|
|
|
+ v-for="dict in getIntDictOptions(DICT_TYPE.IOT_THING_MODEL_TYPE)"
|
|
|
|
|
+ :key="dict.value"
|
|
|
|
|
+ :value="dict.value"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ dict.label }}
|
|
|
|
|
+ </el-radio-button>
|
|
|
|
|
+ </el-radio-group>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item v-if="formData.type === 1" label="属性" prop="property">
|
|
|
|
|
+ <div class="json-editor-field">
|
|
|
|
|
+ <div class="json-editor-toolbar">
|
|
|
|
|
+ <span class="json-editor-toolbar__tip">请输入属性 JSON 数据</span>
|
|
|
|
|
+ <div class="json-editor-toolbar__actions">
|
|
|
|
|
+ <el-button text type="primary" @click="formatJsonField('property')">
|
|
|
|
|
+ 美化 JSON
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button text @click="compressJsonField('property')">压缩 JSON</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <v-ace-editor
|
|
|
|
|
+ v-model:value="formData.property"
|
|
|
|
|
+ lang="json"
|
|
|
|
|
+ theme="monokai"
|
|
|
|
|
+ class="json-editor"
|
|
|
|
|
+ :options="editorOptions"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item v-if="formData.type === 3" label="事件" prop="event">
|
|
|
|
|
+ <div class="json-editor-field">
|
|
|
|
|
+ <div class="json-editor-toolbar">
|
|
|
|
|
+ <span class="json-editor-toolbar__tip">请输入事件 JSON 数据</span>
|
|
|
|
|
+ <div class="json-editor-toolbar__actions">
|
|
|
|
|
+ <el-button text type="primary" @click="formatJsonField('event')">美化 JSON</el-button>
|
|
|
|
|
+ <el-button text @click="compressJsonField('event')">压缩 JSON</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <v-ace-editor
|
|
|
|
|
+ v-model:value="formData.event"
|
|
|
|
|
+ lang="json"
|
|
|
|
|
+ theme="monokai"
|
|
|
|
|
+ class="json-editor"
|
|
|
|
|
+ :options="editorOptions"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item v-if="formData.type === 2" label="服务" prop="service">
|
|
|
|
|
+ <div class="json-editor-field">
|
|
|
|
|
+ <div class="json-editor-toolbar">
|
|
|
|
|
+ <span class="json-editor-toolbar__tip">请输入服务 JSON 数据</span>
|
|
|
|
|
+ <div class="json-editor-toolbar__actions">
|
|
|
|
|
+ <el-button text type="primary" @click="formatJsonField('service')">
|
|
|
|
|
+ 美化 JSON
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button text @click="compressJsonField('service')">压缩 JSON</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <v-ace-editor
|
|
|
|
|
+ v-model:value="formData.service"
|
|
|
|
|
+ lang="json"
|
|
|
|
|
+ theme="monokai"
|
|
|
|
|
+ class="json-editor"
|
|
|
|
|
+ :options="editorOptions"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <el-button :disabled="formLoading" type="primary" @click="submitForm">确定</el-button>
|
|
|
|
|
+ <el-button @click="dialogVisible = false">取消</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </Dialog>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script lang="ts" setup>
|
|
|
|
|
+import '@/components/mt-edit/ace-edit'
|
|
|
|
|
+import { VAceEditor } from 'vue3-ace-editor'
|
|
|
|
|
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
|
|
|
|
+import {
|
|
|
|
|
+ ThingModelCommon,
|
|
|
|
|
+ type CommonThingModelData
|
|
|
|
|
+} from '@/api/iot/thingmodel'
|
|
|
|
|
+
|
|
|
|
|
+defineOptions({ name: 'CommonThingModelForm' })
|
|
|
|
|
+
|
|
|
|
|
+const { t } = useI18n()
|
|
|
|
|
+const message = useMessage()
|
|
|
|
|
+
|
|
|
|
|
+const dialogVisible = ref(false)
|
|
|
|
|
+const dialogTitle = ref('')
|
|
|
|
|
+const formLoading = ref(false)
|
|
|
|
|
+const formType = ref('')
|
|
|
|
|
+const formRef = ref()
|
|
|
|
|
+const editorOptions = {
|
|
|
|
|
+ useWorker: true,
|
|
|
|
|
+ tabSize: 2,
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ wrap: true,
|
|
|
|
|
+ showPrintMargin: false,
|
|
|
|
|
+ enableBasicAutocompletion: true,
|
|
|
|
|
+ enableLiveAutocompletion: true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const createDefaultFormData = (): CommonThingModelData => ({
|
|
|
|
|
+ identifier: '',
|
|
|
|
|
+ name: '',
|
|
|
|
|
+ description: '',
|
|
|
|
|
+ type: 1,
|
|
|
|
|
+ property: '',
|
|
|
|
|
+ event: '',
|
|
|
|
|
+ service: ''
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const formData = ref<CommonThingModelData>(createDefaultFormData())
|
|
|
|
|
+
|
|
|
|
|
+const getJsonFieldByType = () => {
|
|
|
|
|
+ if (formData.value.type === 1) return 'property'
|
|
|
|
|
+ if (formData.value.type === 2) return 'service'
|
|
|
|
|
+ return 'event'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const tryFormatJson = (value?: string, indent = 2) => {
|
|
|
|
|
+ if (!value) return ''
|
|
|
|
|
+ const trimmed = value.trim()
|
|
|
|
|
+ if (!trimmed) return ''
|
|
|
|
|
+ return JSON.stringify(JSON.parse(trimmed), null, indent)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const formatJsonField = (field: 'property' | 'event' | 'service') => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ formData.value[field] = tryFormatJson(formData.value[field], 2)
|
|
|
|
|
+ message.success('JSON 已美化')
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ message.warning('当前内容不是合法 JSON,无法美化')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const compressJsonField = (field: 'property' | 'event' | 'service') => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ formData.value[field] = tryFormatJson(formData.value[field], 0)
|
|
|
|
|
+ message.success('JSON 已压缩')
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ message.warning('当前内容不是合法 JSON,无法压缩')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const validateJsonField = (field: 'property' | 'event' | 'service', label: string) => {
|
|
|
|
|
+ return (_rule: any, value: string, callback: (error?: Error) => void) => {
|
|
|
|
|
+ const currentType = formData.value.type
|
|
|
|
|
+ const fieldTypeMap = {
|
|
|
|
|
+ property: 1,
|
|
|
|
|
+ service: 2,
|
|
|
|
|
+ event: 3
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (currentType !== fieldTypeMap[field]) {
|
|
|
|
|
+ callback()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!value) {
|
|
|
|
|
+ callback(new Error(`${label}不能为空`))
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ JSON.parse(value)
|
|
|
|
|
+ callback()
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ callback(new Error(`${label}必须是合法的 JSON`))
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const formRules = reactive({
|
|
|
|
|
+ identifier: [
|
|
|
|
|
+ { required: true, message: '功能标识不能为空', trigger: 'blur' },
|
|
|
|
|
+ {
|
|
|
|
|
+ pattern: /^[a-zA-Z0-9_]{1,50}$/,
|
|
|
|
|
+ message: '功能标识支持字母、数字和下划线,长度不超过 50',
|
|
|
|
|
+ trigger: 'blur'
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ name: [{ required: true, message: '功能名称不能为空', trigger: 'blur' }],
|
|
|
|
|
+ type: [{ required: true, message: '功能类型不能为空', trigger: 'change' }],
|
|
|
|
|
+ property: [{ validator: validateJsonField('property', '属性'), trigger: 'blur' }],
|
|
|
|
|
+ event: [{ validator: validateJsonField('event', '事件'), trigger: 'blur' }],
|
|
|
|
|
+ service: [{ validator: validateJsonField('service', '服务'), trigger: 'blur' }]
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const open = async (type: string, id?: number) => {
|
|
|
|
|
+ dialogVisible.value = true
|
|
|
|
|
+ dialogTitle.value = t('action.' + type)
|
|
|
|
|
+ formType.value = type
|
|
|
|
|
+ resetForm()
|
|
|
|
|
+ if (!id) return
|
|
|
|
|
+
|
|
|
|
|
+ formLoading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const data = await ThingModelCommon.getCommonThingModel(id)
|
|
|
|
|
+ formData.value = {
|
|
|
|
|
+ ...createDefaultFormData(),
|
|
|
|
|
+ ...data
|
|
|
|
|
+ }
|
|
|
|
|
+ const activeField = getJsonFieldByType()
|
|
|
|
|
+ if (formData.value[activeField]) {
|
|
|
|
|
+ formData.value[activeField] = tryFormatJson(formData.value[activeField], 2)
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ formLoading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+defineExpose({ open, close: () => (dialogVisible.value = false) })
|
|
|
|
|
+
|
|
|
|
|
+const emit = defineEmits(['success'])
|
|
|
|
|
+
|
|
|
|
|
+const submitForm = async () => {
|
|
|
|
|
+ await formRef.value.validate()
|
|
|
|
|
+ formLoading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const payload: CommonThingModelData = {
|
|
|
|
|
+ ...formData.value,
|
|
|
|
|
+ property: formData.value.type === 1 ? formData.value.property : '',
|
|
|
|
|
+ service: formData.value.type === 2 ? formData.value.service : '',
|
|
|
|
|
+ event: formData.value.type === 3 ? formData.value.event : ''
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (formType.value === 'create') {
|
|
|
|
|
+ await ThingModelCommon.createCommonThingModel(payload)
|
|
|
|
|
+ message.success(t('common.createSuccess'))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ await ThingModelCommon.updateCommonThingModel(payload)
|
|
|
|
|
+ message.success(t('common.updateSuccess'))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ dialogVisible.value = false
|
|
|
|
|
+ emit('success')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ formLoading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const resetForm = () => {
|
|
|
|
|
+ formData.value = createDefaultFormData()
|
|
|
|
|
+ formRef.value?.resetFields()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+watch(
|
|
|
|
|
+ () => formData.value.type,
|
|
|
|
|
+ () => {
|
|
|
|
|
+ const activeField = getJsonFieldByType()
|
|
|
|
|
+ if (formData.value[activeField]) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ formData.value[activeField] = tryFormatJson(formData.value[activeField], 2)
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ // Keep original input when JSON is incomplete during editing.
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+)
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.json-editor-field {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ border: 1px solid var(--el-border-color);
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ background: #111827;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.json-editor-toolbar {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ padding: 10px 12px;
|
|
|
|
|
+ background: #1f2937;
|
|
|
|
|
+ border-bottom: 1px solid rgb(255 255 255 / 0.08);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.json-editor-toolbar__tip {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: rgb(255 255 255 / 0.72);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.json-editor-toolbar__actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.json-editor {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 320px;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|