CommonThingModelForm.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. <template>
  2. <Dialog v-model="dialogVisible" :title="dialogTitle" width="900px">
  3. <el-form
  4. ref="formRef"
  5. v-loading="formLoading"
  6. :model="formData"
  7. :rules="formRules"
  8. label-width="100px">
  9. <el-form-item label="功能标识" prop="identifier">
  10. <el-input v-model="formData.identifier" placeholder="请输入功能标识" />
  11. </el-form-item>
  12. <el-form-item label="功能名称" prop="name">
  13. <el-input v-model="formData.name" placeholder="请输入功能名称" />
  14. </el-form-item>
  15. <el-form-item label="功能描述" prop="description">
  16. <el-input
  17. v-model="formData.description"
  18. type="textarea"
  19. :rows="3"
  20. :maxlength="200"
  21. placeholder="请输入功能描述" />
  22. </el-form-item>
  23. <el-form-item label="功能类型" prop="type">
  24. <el-radio-group v-model="formData.type">
  25. <el-radio-button
  26. v-for="dict in getIntDictOptions(DICT_TYPE.IOT_THING_MODEL_TYPE)"
  27. :key="dict.value"
  28. :value="dict.value">
  29. {{ dict.label }}
  30. </el-radio-button>
  31. </el-radio-group>
  32. </el-form-item>
  33. <el-form-item v-if="formData.type === 1" label="属性" prop="property">
  34. <div class="json-editor-field">
  35. <div class="json-editor-toolbar">
  36. <span class="json-editor-toolbar__tip">请输入属性 JSON 数据</span>
  37. <div class="json-editor-toolbar__actions">
  38. <el-button text type="primary" @click="formatJsonField('property')">
  39. 美化 JSON
  40. </el-button>
  41. <el-button text @click="compressJsonField('property')">压缩 JSON</el-button>
  42. </div>
  43. </div>
  44. <v-ace-editor
  45. v-model:value="formData.property"
  46. lang="json"
  47. theme="monokai"
  48. class="json-editor"
  49. :options="editorOptions" />
  50. </div>
  51. </el-form-item>
  52. <el-form-item v-if="formData.type === 3" label="事件" prop="event">
  53. <div class="json-editor-field">
  54. <div class="json-editor-toolbar">
  55. <span class="json-editor-toolbar__tip">请输入事件 JSON 数据</span>
  56. <div class="json-editor-toolbar__actions">
  57. <el-button text type="primary" @click="formatJsonField('event')">美化 JSON</el-button>
  58. <el-button text @click="compressJsonField('event')">压缩 JSON</el-button>
  59. </div>
  60. </div>
  61. <v-ace-editor
  62. v-model:value="formData.event"
  63. lang="json"
  64. theme="monokai"
  65. class="json-editor"
  66. :options="editorOptions" />
  67. </div>
  68. </el-form-item>
  69. <el-form-item v-if="formData.type === 2" label="服务" prop="service">
  70. <div class="json-editor-field">
  71. <div class="json-editor-toolbar">
  72. <span class="json-editor-toolbar__tip">请输入服务 JSON 数据</span>
  73. <div class="json-editor-toolbar__actions">
  74. <el-button text type="primary" @click="formatJsonField('service')">
  75. 美化 JSON
  76. </el-button>
  77. <el-button text @click="compressJsonField('service')">压缩 JSON</el-button>
  78. </div>
  79. </div>
  80. <v-ace-editor
  81. v-model:value="formData.service"
  82. lang="json"
  83. theme="monokai"
  84. class="json-editor"
  85. :options="editorOptions" />
  86. </div>
  87. </el-form-item>
  88. </el-form>
  89. <template #footer>
  90. <el-button :disabled="formLoading" type="primary" @click="submitForm">确定</el-button>
  91. <el-button @click="dialogVisible = false">取消</el-button>
  92. </template>
  93. </Dialog>
  94. </template>
  95. <script lang="ts" setup>
  96. import '@/components/mt-edit/ace-edit'
  97. import { VAceEditor } from 'vue3-ace-editor'
  98. import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
  99. import { ThingModelCommon, type CommonThingModelData } from '@/api/iot/thingmodel'
  100. defineOptions({ name: 'CommonThingModelForm' })
  101. const { t } = useI18n()
  102. const message = useMessage()
  103. const dialogVisible = ref(false)
  104. const dialogTitle = ref('')
  105. const formLoading = ref(false)
  106. const formType = ref('')
  107. const formRef = ref()
  108. const editorOptions = {
  109. useWorker: true,
  110. enableBasicAutocompletion: true,
  111. enableSnippets: true,
  112. enableLiveAutocompletion: true
  113. }
  114. const createDefaultFormData = (): CommonThingModelData => ({
  115. identifier: '',
  116. name: '',
  117. description: '',
  118. type: 1,
  119. property: '',
  120. event: '',
  121. service: ''
  122. })
  123. const formData = ref<CommonThingModelData>(createDefaultFormData())
  124. const getJsonFieldByType = () => {
  125. if (formData.value.type === 1) return 'property'
  126. if (formData.value.type === 2) return 'service'
  127. return 'event'
  128. }
  129. const tryFormatJson = (value?: string, indent = 2) => {
  130. if (!value) return ''
  131. const trimmed = value.trim()
  132. if (!trimmed) return ''
  133. return JSON.stringify(JSON.parse(trimmed), null, indent)
  134. }
  135. const formatJsonField = (field: 'property' | 'event' | 'service') => {
  136. try {
  137. formData.value[field] = tryFormatJson(formData.value[field], 2)
  138. message.success('JSON 已美化')
  139. } catch {
  140. message.warning('当前内容不是合法 JSON,无法美化')
  141. }
  142. }
  143. const compressJsonField = (field: 'property' | 'event' | 'service') => {
  144. try {
  145. formData.value[field] = tryFormatJson(formData.value[field], 0)
  146. message.success('JSON 已压缩')
  147. } catch {
  148. message.warning('当前内容不是合法 JSON,无法压缩')
  149. }
  150. }
  151. const validateJsonField = (field: 'property' | 'event' | 'service', label: string) => {
  152. return (_rule: any, value: string, callback: (error?: Error) => void) => {
  153. const currentType = formData.value.type
  154. const fieldTypeMap = {
  155. property: 1,
  156. service: 2,
  157. event: 3
  158. }
  159. if (currentType !== fieldTypeMap[field]) {
  160. callback()
  161. return
  162. }
  163. if (!value) {
  164. callback(new Error(`${label}不能为空`))
  165. return
  166. }
  167. try {
  168. JSON.parse(value)
  169. callback()
  170. } catch {
  171. callback(new Error(`${label}必须是合法的 JSON`))
  172. }
  173. }
  174. }
  175. const formRules = reactive({
  176. identifier: [{ required: true, message: '功能标识不能为空', trigger: 'blur' }],
  177. name: [{ required: true, message: '功能名称不能为空', trigger: 'blur' }],
  178. type: [{ required: true, message: '功能类型不能为空', trigger: 'change' }]
  179. // property: [{ validator: validateJsonField('property', '属性'), trigger: 'blur' }],
  180. // event: [{ validator: validateJsonField('event', '事件'), trigger: 'blur' }],
  181. // service: [{ validator: validateJsonField('service', '服务'), trigger: 'blur' }]
  182. })
  183. const open = async (type: string, id?: number) => {
  184. dialogVisible.value = true
  185. dialogTitle.value = t('action.' + type)
  186. formType.value = type
  187. resetForm()
  188. if (!id) return
  189. formLoading.value = true
  190. try {
  191. const data = await ThingModelCommon.getCommonThingModel(id)
  192. formData.value = {
  193. ...createDefaultFormData(),
  194. ...data
  195. }
  196. const activeField = getJsonFieldByType()
  197. if (formData.value[activeField]) {
  198. formData.value[activeField] = tryFormatJson(formData.value[activeField], 2)
  199. }
  200. } finally {
  201. formLoading.value = false
  202. }
  203. }
  204. defineExpose({ open, close: () => (dialogVisible.value = false) })
  205. const emit = defineEmits(['success'])
  206. const submitForm = async () => {
  207. await formRef.value.validate()
  208. formLoading.value = true
  209. try {
  210. const payload: CommonThingModelData = {
  211. ...formData.value,
  212. property: formData.value.type === 1 ? formData.value.property : '',
  213. service: formData.value.type === 2 ? formData.value.service : '',
  214. event: formData.value.type === 3 ? formData.value.event : ''
  215. }
  216. if (formType.value === 'create') {
  217. await ThingModelCommon.createCommonThingModel(payload)
  218. message.success(t('common.createSuccess'))
  219. } else {
  220. await ThingModelCommon.updateCommonThingModel(payload)
  221. message.success(t('common.updateSuccess'))
  222. }
  223. dialogVisible.value = false
  224. emit('success')
  225. } finally {
  226. formLoading.value = false
  227. }
  228. }
  229. const resetForm = () => {
  230. formData.value = createDefaultFormData()
  231. formRef.value?.resetFields()
  232. }
  233. watch(
  234. () => formData.value.type,
  235. () => {
  236. const activeField = getJsonFieldByType()
  237. if (formData.value[activeField]) {
  238. try {
  239. formData.value[activeField] = tryFormatJson(formData.value[activeField], 2)
  240. } catch {
  241. // Keep original input when JSON is incomplete during editing.
  242. }
  243. }
  244. }
  245. )
  246. </script>
  247. <style scoped>
  248. .json-editor-field {
  249. width: 100%;
  250. border: 1px solid var(--el-border-color);
  251. border-radius: 8px;
  252. overflow: hidden;
  253. background: #111827;
  254. }
  255. .json-editor-toolbar {
  256. display: flex;
  257. align-items: center;
  258. justify-content: space-between;
  259. gap: 12px;
  260. padding: 10px 12px;
  261. background: #1f2937;
  262. border-bottom: 1px solid rgb(255 255 255 / 0.08);
  263. }
  264. .json-editor-toolbar__tip {
  265. font-size: 12px;
  266. color: rgb(255 255 255 / 0.72);
  267. }
  268. .json-editor-toolbar__actions {
  269. display: flex;
  270. align-items: center;
  271. gap: 8px;
  272. }
  273. .json-editor {
  274. width: 100%;
  275. height: 320px;
  276. }
  277. </style>