| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 |
- <!-- JSON参数输入组件 - 通用版本 -->
- <template>
- <!-- 参数配置 -->
- <div class="w-full space-y-12px">
- <!-- JSON 输入框 -->
- <div class="relative">
- <el-input
- v-model="paramsJson"
- type="textarea"
- :rows="4"
- :placeholder="placeholder"
- @input="handleParamsChange"
- :class="{ 'is-error': jsonError }"
- />
- <!-- 查看详细示例弹出层 -->
- <div class="absolute top-8px right-8px">
- <el-popover
- placement="left-start"
- :width="450"
- trigger="click"
- :show-arrow="true"
- :offset="8"
- popper-class="json-params-detail-popover"
- >
- <template #reference>
- <el-button
- type="info"
- :icon="InfoFilled"
- circle
- size="small"
- :title="JSON_PARAMS_INPUT_CONSTANTS.VIEW_EXAMPLE_TITLE"
- />
- </template>
- <!-- 弹出层内容 -->
- <div class="json-params-detail-content">
- <div class="flex items-center gap-8px mb-16px">
- <Icon :icon="titleIcon" class="text-[var(--el-color-primary)] text-18px" />
- <span class="text-16px font-600 text-[var(--el-text-color-primary)]">
- {{ title }}
- </span>
- </div>
- <div class="space-y-16px">
- <!-- 参数列表 -->
- <div v-if="paramsList.length > 0">
- <div class="flex items-center gap-8px mb-8px">
- <Icon :icon="paramsIcon" class="text-[var(--el-color-primary)] text-14px" />
- <span class="text-14px font-500 text-[var(--el-text-color-primary)]">
- {{ paramsLabel }}
- </span>
- </div>
- <div class="ml-22px space-y-8px">
- <div
- v-for="param in paramsList"
- :key="param.identifier"
- class="flex items-center justify-between p-8px bg-[var(--el-fill-color-lighter)] rounded-4px"
- >
- <div class="flex-1">
- <div class="text-12px font-500 text-[var(--el-text-color-primary)]">
- {{ param.name }}
- <el-tag v-if="param.required" size="small" type="danger" class="ml-4px">
- {{ JSON_PARAMS_INPUT_CONSTANTS.REQUIRED_TAG }}
- </el-tag>
- </div>
- <div class="text-11px text-[var(--el-text-color-secondary)]">
- {{ param.identifier }}
- </div>
- </div>
- <div class="flex items-center gap-8px">
- <el-tag :type="getParamTypeTag(param.dataType)" size="small">
- {{ getParamTypeName(param.dataType) }}
- </el-tag>
- <span class="text-11px text-[var(--el-text-color-secondary)]">
- {{ getExampleValue(param) }}
- </span>
- </div>
- </div>
- </div>
- <div class="mt-12px ml-22px">
- <div class="text-12px text-[var(--el-text-color-secondary)] mb-6px">
- {{ JSON_PARAMS_INPUT_CONSTANTS.COMPLETE_JSON_FORMAT }}
- </div>
- <pre
- class="p-12px bg-[var(--el-fill-color-light)] rounded-4px text-11px text-[var(--el-text-color-primary)] overflow-x-auto border-l-3px border-[var(--el-color-primary)]"
- >
- <code>{{ generateExampleJson() }}</code>
- </pre>
- </div>
- </div>
- <!-- 无参数提示 -->
- <div v-else>
- <div class="text-center py-16px">
- <p class="text-14px text-[var(--el-text-color-secondary)]">{{ emptyMessage }}</p>
- </div>
- </div>
- </div>
- </div>
- </el-popover>
- </div>
- </div>
- <!-- 验证状态和错误提示 -->
- <div class="flex items-center justify-between">
- <div class="flex items-center gap-8px">
- <Icon
- :icon="
- jsonError
- ? JSON_PARAMS_INPUT_ICONS.STATUS_ICONS.ERROR
- : JSON_PARAMS_INPUT_ICONS.STATUS_ICONS.SUCCESS
- "
- :class="jsonError ? 'text-[var(--el-color-danger)]' : 'text-[var(--el-color-success)]'"
- class="text-14px"
- />
- <span
- :class="jsonError ? 'text-[var(--el-color-danger)]' : 'text-[var(--el-color-success)]'"
- class="text-12px"
- >
- {{ jsonError || JSON_PARAMS_INPUT_CONSTANTS.JSON_FORMAT_CORRECT }}
- </span>
- </div>
- <!-- 快速填充按钮 -->
- <div v-if="paramsList.length > 0" class="flex items-center gap-8px">
- <span class="text-12px text-[var(--el-text-color-secondary)]">{{
- JSON_PARAMS_INPUT_CONSTANTS.QUICK_FILL_LABEL
- }}</span>
- <el-button size="small" type="primary" plain @click="fillExampleJson">
- {{ JSON_PARAMS_INPUT_CONSTANTS.EXAMPLE_DATA_BUTTON }}
- </el-button>
- <el-button size="small" type="danger" plain @click="clearParams">{{
- JSON_PARAMS_INPUT_CONSTANTS.CLEAR_BUTTON
- }}</el-button>
- </div>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { useVModel } from '@vueuse/core'
- import { InfoFilled } from '@element-plus/icons-vue'
- import {
- IoTDataSpecsDataTypeEnum,
- JSON_PARAMS_INPUT_CONSTANTS,
- JSON_PARAMS_INPUT_ICONS,
- JSON_PARAMS_EXAMPLE_VALUES,
- JsonParamsInputTypeEnum,
- type JsonParamsInputType
- } from '@/views/iot/utils/constants'
- /** JSON参数输入组件 - 通用版本 */
- defineOptions({ name: 'JsonParamsInput' })
- interface JsonParamsConfig {
- // 服务配置
- service?: {
- name: string
- inputParams?: any[]
- }
- // 事件配置
- event?: {
- name: string
- outputParams?: any[]
- }
- // 属性配置
- properties?: any[]
- // 自定义配置
- custom?: {
- name: string
- params: any[]
- }
- }
- interface Props {
- modelValue?: string
- config?: JsonParamsConfig
- type?: JsonParamsInputType
- placeholder?: string
- }
- interface Emits {
- (e: 'update:modelValue', value: string): void
- }
- const props = withDefaults(defineProps<Props>(), {
- type: JsonParamsInputTypeEnum.SERVICE,
- placeholder: JSON_PARAMS_INPUT_CONSTANTS.PLACEHOLDER
- })
- const emit = defineEmits<Emits>()
- const localValue = useVModel(props, 'modelValue', emit, {
- defaultValue: ''
- })
- const paramsJson = ref('') // JSON参数字符串
- const jsonError = ref('') // JSON验证错误信息
- // 计算属性:参数列表
- const paramsList = computed(() => {
- switch (props.type) {
- case JsonParamsInputTypeEnum.SERVICE:
- return props.config?.service?.inputParams || []
- case JsonParamsInputTypeEnum.EVENT:
- return props.config?.event?.outputParams || []
- case JsonParamsInputTypeEnum.PROPERTY:
- return props.config?.properties || []
- case JsonParamsInputTypeEnum.CUSTOM:
- return props.config?.custom?.params || []
- default:
- return []
- }
- })
- // 计算属性:标题
- const title = computed(() => {
- switch (props.type) {
- case JsonParamsInputTypeEnum.SERVICE:
- return JSON_PARAMS_INPUT_CONSTANTS.TITLES.SERVICE(props.config?.service?.name)
- case JsonParamsInputTypeEnum.EVENT:
- return JSON_PARAMS_INPUT_CONSTANTS.TITLES.EVENT(props.config?.event?.name)
- case JsonParamsInputTypeEnum.PROPERTY:
- return JSON_PARAMS_INPUT_CONSTANTS.TITLES.PROPERTY
- case JsonParamsInputTypeEnum.CUSTOM:
- return JSON_PARAMS_INPUT_CONSTANTS.TITLES.CUSTOM(props.config?.custom?.name)
- default:
- return JSON_PARAMS_INPUT_CONSTANTS.TITLES.DEFAULT
- }
- })
- // 计算属性:标题图标
- const titleIcon = computed(() => {
- switch (props.type) {
- case JsonParamsInputTypeEnum.SERVICE:
- return JSON_PARAMS_INPUT_ICONS.TITLE_ICONS.SERVICE
- case JsonParamsInputTypeEnum.EVENT:
- return JSON_PARAMS_INPUT_ICONS.TITLE_ICONS.EVENT
- case JsonParamsInputTypeEnum.PROPERTY:
- return JSON_PARAMS_INPUT_ICONS.TITLE_ICONS.PROPERTY
- case JsonParamsInputTypeEnum.CUSTOM:
- return JSON_PARAMS_INPUT_ICONS.TITLE_ICONS.CUSTOM
- default:
- return JSON_PARAMS_INPUT_ICONS.TITLE_ICONS.DEFAULT
- }
- })
- // 计算属性:参数图标
- const paramsIcon = computed(() => {
- switch (props.type) {
- case JsonParamsInputTypeEnum.SERVICE:
- return JSON_PARAMS_INPUT_ICONS.PARAMS_ICONS.SERVICE
- case JsonParamsInputTypeEnum.EVENT:
- return JSON_PARAMS_INPUT_ICONS.PARAMS_ICONS.EVENT
- case JsonParamsInputTypeEnum.PROPERTY:
- return JSON_PARAMS_INPUT_ICONS.PARAMS_ICONS.PROPERTY
- case JsonParamsInputTypeEnum.CUSTOM:
- return JSON_PARAMS_INPUT_ICONS.PARAMS_ICONS.CUSTOM
- default:
- return JSON_PARAMS_INPUT_ICONS.PARAMS_ICONS.DEFAULT
- }
- })
- // 计算属性:参数标签
- const paramsLabel = computed(() => {
- switch (props.type) {
- case JsonParamsInputTypeEnum.SERVICE:
- return JSON_PARAMS_INPUT_CONSTANTS.PARAMS_LABELS.SERVICE
- case JsonParamsInputTypeEnum.EVENT:
- return JSON_PARAMS_INPUT_CONSTANTS.PARAMS_LABELS.EVENT
- case JsonParamsInputTypeEnum.PROPERTY:
- return JSON_PARAMS_INPUT_CONSTANTS.PARAMS_LABELS.PROPERTY
- case JsonParamsInputTypeEnum.CUSTOM:
- return JSON_PARAMS_INPUT_CONSTANTS.PARAMS_LABELS.CUSTOM
- default:
- return JSON_PARAMS_INPUT_CONSTANTS.PARAMS_LABELS.DEFAULT
- }
- })
- // 计算属性:空状态消息
- const emptyMessage = computed(() => {
- switch (props.type) {
- case JsonParamsInputTypeEnum.SERVICE:
- return JSON_PARAMS_INPUT_CONSTANTS.EMPTY_MESSAGES.SERVICE
- case JsonParamsInputTypeEnum.EVENT:
- return JSON_PARAMS_INPUT_CONSTANTS.EMPTY_MESSAGES.EVENT
- case JsonParamsInputTypeEnum.PROPERTY:
- return JSON_PARAMS_INPUT_CONSTANTS.EMPTY_MESSAGES.PROPERTY
- case JsonParamsInputTypeEnum.CUSTOM:
- return JSON_PARAMS_INPUT_CONSTANTS.EMPTY_MESSAGES.CUSTOM
- default:
- return JSON_PARAMS_INPUT_CONSTANTS.EMPTY_MESSAGES.DEFAULT
- }
- })
- // 计算属性:无配置消息
- const noConfigMessage = computed(() => {
- switch (props.type) {
- case JsonParamsInputTypeEnum.SERVICE:
- return JSON_PARAMS_INPUT_CONSTANTS.NO_CONFIG_MESSAGES.SERVICE
- case JsonParamsInputTypeEnum.EVENT:
- return JSON_PARAMS_INPUT_CONSTANTS.NO_CONFIG_MESSAGES.EVENT
- case JsonParamsInputTypeEnum.PROPERTY:
- return JSON_PARAMS_INPUT_CONSTANTS.NO_CONFIG_MESSAGES.PROPERTY
- case JsonParamsInputTypeEnum.CUSTOM:
- return JSON_PARAMS_INPUT_CONSTANTS.NO_CONFIG_MESSAGES.CUSTOM
- default:
- return JSON_PARAMS_INPUT_CONSTANTS.NO_CONFIG_MESSAGES.DEFAULT
- }
- })
- /**
- * 处理参数变化事件
- */
- const handleParamsChange = () => {
- try {
- jsonError.value = '' // 清除之前的错误
- if (paramsJson.value.trim()) {
- const parsed = JSON.parse(paramsJson.value)
- localValue.value = paramsJson.value
- // 额外的参数验证
- if (typeof parsed !== 'object' || parsed === null) {
- jsonError.value = JSON_PARAMS_INPUT_CONSTANTS.PARAMS_MUST_BE_OBJECT
- return
- }
- // 验证必填参数
- for (const param of paramsList.value) {
- if (param.required && (!parsed[param.identifier] || parsed[param.identifier] === '')) {
- jsonError.value = JSON_PARAMS_INPUT_CONSTANTS.PARAM_REQUIRED_ERROR(param.name)
- return
- }
- }
- } else {
- localValue.value = ''
- }
- // 验证通过
- jsonError.value = ''
- } catch (error) {
- jsonError.value = JSON_PARAMS_INPUT_CONSTANTS.JSON_FORMAT_ERROR(
- error instanceof Error ? error.message : JSON_PARAMS_INPUT_CONSTANTS.UNKNOWN_ERROR
- )
- }
- }
- /**
- * 快速填充示例数据
- */
- const fillExampleJson = () => {
- paramsJson.value = generateExampleJson()
- handleParamsChange()
- }
- /**
- * 清空参数
- */
- const clearParams = () => {
- paramsJson.value = ''
- localValue.value = ''
- jsonError.value = ''
- }
- /**
- * 获取参数类型名称
- * @param dataType 数据类型
- * @returns 类型名称
- */
- const getParamTypeName = (dataType: string) => {
- // 使用 constants.ts 中已有的 getDataTypeName 函数逻辑
- const typeMap = {
- [IoTDataSpecsDataTypeEnum.INT]: '整数',
- [IoTDataSpecsDataTypeEnum.FLOAT]: '浮点数',
- [IoTDataSpecsDataTypeEnum.DOUBLE]: '双精度',
- [IoTDataSpecsDataTypeEnum.TEXT]: '字符串',
- [IoTDataSpecsDataTypeEnum.BOOL]: '布尔值',
- [IoTDataSpecsDataTypeEnum.ENUM]: '枚举',
- [IoTDataSpecsDataTypeEnum.DATE]: '日期',
- [IoTDataSpecsDataTypeEnum.STRUCT]: '结构体',
- [IoTDataSpecsDataTypeEnum.ARRAY]: '数组'
- }
- return typeMap[dataType] || dataType
- }
- /**
- * 获取参数类型标签样式
- * @param dataType 数据类型
- * @returns 标签样式
- */
- const getParamTypeTag = (dataType: string) => {
- const tagMap = {
- [IoTDataSpecsDataTypeEnum.INT]: 'primary',
- [IoTDataSpecsDataTypeEnum.FLOAT]: 'success',
- [IoTDataSpecsDataTypeEnum.DOUBLE]: 'success',
- [IoTDataSpecsDataTypeEnum.TEXT]: 'info',
- [IoTDataSpecsDataTypeEnum.BOOL]: 'warning',
- [IoTDataSpecsDataTypeEnum.ENUM]: 'danger',
- [IoTDataSpecsDataTypeEnum.DATE]: 'primary',
- [IoTDataSpecsDataTypeEnum.STRUCT]: 'info',
- [IoTDataSpecsDataTypeEnum.ARRAY]: 'warning'
- }
- return tagMap[dataType] || 'info'
- }
- /**
- * 获取示例值
- * @param param 参数对象
- * @returns 示例值
- */
- const getExampleValue = (param: any) => {
- const exampleConfig =
- JSON_PARAMS_EXAMPLE_VALUES[param.dataType] || JSON_PARAMS_EXAMPLE_VALUES.DEFAULT
- return exampleConfig.display
- }
- /**
- * 生成示例JSON
- * @returns JSON字符串
- */
- const generateExampleJson = () => {
- if (paramsList.value.length === 0) {
- return '{}'
- }
- const example = {}
- paramsList.value.forEach((param) => {
- const exampleConfig =
- JSON_PARAMS_EXAMPLE_VALUES[param.dataType] || JSON_PARAMS_EXAMPLE_VALUES.DEFAULT
- example[param.identifier] = exampleConfig.value
- })
- return JSON.stringify(example, null, 2)
- }
- /**
- * 处理数据回显
- * @param value 值字符串
- */
- const handleDataDisplay = (value: string) => {
- if (!value || !value.trim()) {
- paramsJson.value = ''
- jsonError.value = ''
- return
- }
- try {
- // 尝试解析JSON,如果成功则格式化
- const parsed = JSON.parse(value)
- paramsJson.value = JSON.stringify(parsed, null, 2)
- jsonError.value = ''
- } catch {
- // 如果不是有效的JSON,直接使用原字符串
- paramsJson.value = value
- jsonError.value = ''
- }
- }
- // 监听外部值变化(编辑模式数据回显)
- watch(
- () => localValue.value,
- async (newValue, oldValue) => {
- // 避免循环更新
- if (newValue === oldValue) return
- // 使用 nextTick 确保在下一个 tick 中处理数据
- await nextTick()
- handleDataDisplay(newValue || '')
- },
- { immediate: true }
- )
- // 组件挂载后也尝试处理一次数据回显
- onMounted(async () => {
- await nextTick()
- if (localValue.value) {
- handleDataDisplay(localValue.value)
- }
- })
- // 监听配置变化
- watch(
- () => props.config,
- (newConfig, oldConfig) => {
- // 只有在配置真正变化时才清空数据
- if (JSON.stringify(newConfig) !== JSON.stringify(oldConfig)) {
- // 如果没有外部传入的值,才清空数据
- if (!localValue.value) {
- paramsJson.value = ''
- jsonError.value = ''
- }
- }
- }
- )
- </script>
- <style scoped>
- /* 弹出层内容样式 */
- .json-params-detail-content {
- padding: 4px 0;
- }
- /* 弹出层自定义样式 */
- :global(.json-params-detail-popover) {
- max-width: 500px !important;
- }
- :global(.json-params-detail-popover .el-popover__content) {
- padding: 16px !important;
- }
- /* JSON 代码块样式 */
- .json-params-detail-content pre {
- max-height: 200px;
- overflow-y: auto;
- }
- </style>
|