feat:菜单名称添加英文适配/大屏管理-设备下拉框

main
黄伟杰 1 month ago
parent 862bf73668
commit ed2eae1afa

@ -103,7 +103,7 @@ export const DeviceApi = {
}, },
// 批量获取设备属性列表 // 批量获取设备属性列表
getDeviceAttributeBatchList: async (params: { deviceIds: string; orgId?: number | string }) => { getDeviceAttributeBatchList: async (params: { goviewId: number | string; orgId?: number | string }) => {
return await request.get({ url: `/iot/device/device-attribute/batchList`, params }) return await request.get({ url: `/iot/device/device-attribute/batchList`, params })
}, },
// 修改物联设备 // 修改物联设备

@ -3,6 +3,7 @@ import request from '@/config/axios'
export interface MenuVO { export interface MenuVO {
id: number id: number
name: string name: string
enName?: string
permission: string permission: string
type: number type: number
sort: number sort: number

@ -818,6 +818,7 @@ export default {
}, },
Menu: { Menu: {
name: 'Menu Name', name: 'Menu Name',
enName: 'Menu English Name',
parent: 'Parent Menu', parent: 'Parent Menu',
type: 'Menu Type', type: 'Menu Type',
icon: 'Menu Icon', icon: 'Menu Icon',
@ -834,6 +835,7 @@ export default {
searchNamePlaceholder: 'Please input menu name', searchNamePlaceholder: 'Please input menu name',
searchStatusPlaceholder: 'Please select menu status', searchStatusPlaceholder: 'Please select menu status',
namePlaceholder: 'Please input menu name', namePlaceholder: 'Please input menu name',
enNamePlaceholder: 'Please input menu English name',
pathPlaceholder: 'Please input route path', pathPlaceholder: 'Please input route path',
componentPlaceholder: 'For example: system/user/index', componentPlaceholder: 'For example: system/user/index',
componentNamePlaceholder: 'For example: SystemUser', componentNamePlaceholder: 'For example: SystemUser',

@ -822,6 +822,7 @@ export default {
}, },
Menu: { Menu: {
name: '菜单名称', name: '菜单名称',
enName: '菜单英文名称',
parent: '上级菜单', parent: '上级菜单',
type: '菜单类型', type: '菜单类型',
icon: '菜单图标', icon: '菜单图标',
@ -838,6 +839,7 @@ export default {
searchNamePlaceholder: '请输入菜单名称', searchNamePlaceholder: '请输入菜单名称',
searchStatusPlaceholder: '请选择菜单状态', searchStatusPlaceholder: '请选择菜单状态',
namePlaceholder: '请输入菜单名称', namePlaceholder: '请输入菜单名称',
enNamePlaceholder: '请输入菜单英文名称',
pathPlaceholder: '请输入路由地址', pathPlaceholder: '请输入路由地址',
componentPlaceholder: '例如说system/user/index', componentPlaceholder: '例如说system/user/index',
componentNamePlaceholder: '例如说SystemUser', componentNamePlaceholder: '例如说SystemUser',

@ -64,10 +64,13 @@ export const getRawRoute = (route: RouteLocationNormalized): RouteLocationNormal
export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => { export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {
const res: AppRouteRecordRaw[] = [] const res: AppRouteRecordRaw[] = []
const modulesRoutesKeys = Object.keys(modules) const modulesRoutesKeys = Object.keys(modules)
const { locale } = useI18n()
for (const route of routes) { for (const route of routes) {
const useEnTitle = String(locale.value).toLowerCase().startsWith('en')
const title = useEnTitle && (route as any).enName ? (route as any).enName : route.name
// 1. 生成 meta 菜单元数据 // 1. 生成 meta 菜单元数据
const meta = { const meta = {
title: route.name, title,
icon: route.icon, icon: route.icon,
hidden: !route.visible, hidden: !route.visible,
noCache: !route.keepAlive, noCache: !route.keepAlive,

@ -129,6 +129,7 @@
:title="dialogMode === 'create' ? '新增数据大屏' : '编辑数据大屏'" :title="dialogMode === 'create' ? '新增数据大屏' : '编辑数据大屏'"
width="600px" width="600px"
draggable draggable
@closed="handleCreateDialogClosed"
> >
<el-form :model="createForm" ref="createFormRef" label-width="80px" :rules="createFormRules"> <el-form :model="createForm" ref="createFormRef" label-width="80px" :rules="createFormRules">
<el-form-item label="名称" prop="name"> <el-form-item label="名称" prop="name">
@ -157,24 +158,66 @@
/> />
</el-form-item> </el-form-item>
<el-form-item label="设备" v-if="createForm.type === '1'"> <el-form-item label="设备" v-if="createForm.type === '1'">
<el-select <div class="dashboard-device-group-list">
v-model="selectedDeviceIds" <div
multiple v-for="(group, index) in deviceAttrSelections"
:multiple-limit="8" :key="index"
collapse-tags class="dashboard-device-group"
collapse-tags-tooltip >
placeholder="请选择设备必须选择8个" <el-select
clearable v-model="group.deviceId"
filterable placeholder="请选择设备"
class="!w-240px" clearable
> filterable
<el-option class="!w-160px mr-8px"
v-for="item in deviceList" @change="(val) => handleDeviceChange(val, index)"
:key="item.id" >
:label="item.deviceName" <el-option
:value="String(item.id)" v-for="item in deviceList"
/> :key="item.id"
</el-select> :label="item.deviceName"
:value="item.id"
/>
</el-select>
<el-select
v-model="group.attributeIds"
multiple
collapse-tags
collapse-tags-tooltip
:disabled="!group.deviceId"
placeholder="请选择点位"
clearable
filterable
class="!w-260px"
>
<el-option
v-for="attr in (deviceAttributeOptionsMap[String(group.deviceId)] || [])"
:key="attr.id"
:label="attr.attributeName"
:value="attr.id"
/>
</el-select>
<el-button
v-if="deviceAttrSelections.length > 1"
type="danger"
text
class="dashboard-device-remove-btn"
@click="removeDeviceAttrGroup(index)"
circle
>
<Icon icon="ep:minus" />
</el-button>
</div>
<el-button
type="primary"
text
@click="addDeviceAttrGroup"
:disabled="deviceAttrSelections.length >= 8"
class="mt-8px"
>
<Icon icon="ep:plus" class="mr-5px" /> 添加设备
</el-button>
</div>
</el-form-item> </el-form-item>
<el-form-item label="内容"> <el-form-item label="内容">
<el-input <el-input
@ -230,7 +273,8 @@ interface DashboardItem {
type?: string type?: string
orgId?: number | string orgId?: number | string
orgName?: string orgName?: string
deviceIds?: string deviceIds?: string | { deviceId: number; attributesIds: number[] }[]
deviceIdsList?: { deviceId: number; attributesIds: number[] }[]
} }
const loading = ref(false) const loading = ref(false)
@ -269,7 +313,7 @@ const createForm = reactive({
type: '', type: '',
orgId: undefined as number | string | undefined, orgId: undefined as number | string | undefined,
orgName: '', orgName: '',
deviceIds: '', deviceIdsList: [] as { deviceId: number; attributesIds: number[] }[],
indexImage: '', indexImage: '',
route: '', route: '',
content: '' content: ''
@ -289,7 +333,10 @@ const lineTreeProps = {
} }
const deviceList = ref<any[]>([]) const deviceList = ref<any[]>([])
const selectedDeviceIds = ref<string[]>([]) const deviceAttrSelections = ref<{ deviceId?: number | string; attributeIds: number[] }[]>([
{ deviceId: undefined, attributeIds: [] }
])
const deviceAttributeOptionsMap = ref<Record<string, any[]>>({})
const getList = async () => { const getList = async () => {
loading.value = true loading.value = true
@ -324,8 +371,27 @@ const handlePreview = (item: DashboardItem) => {
} }
const path = route.startsWith('/') ? route : `/${route}` const path = route.startsWith('/') ? route : `/${route}`
const queryParams = new URLSearchParams() const queryParams = new URLSearchParams()
if (item.id) queryParams.append('goviewId', String(item.id))
if (item.orgId) queryParams.append('orgId', String(item.orgId)) if (item.orgId) queryParams.append('orgId', String(item.orgId))
if (item.type === '1' && item.deviceIds) queryParams.append('deviceIds', item.deviceIds) if (item.type === '1') {
let deviceIdsStr = ''
if (Array.isArray(item.deviceIdsList) && item.deviceIdsList.length) {
deviceIdsStr = item.deviceIdsList
.map((g) => g.deviceId)
.filter((id) => id !== undefined && id !== null)
.join(',')
} else if (Array.isArray(item.deviceIds)) {
deviceIdsStr = item.deviceIds
.map((g: any) => g?.deviceId)
.filter((id: any) => id !== undefined && id !== null)
.join(',')
} else if (typeof item.deviceIds === 'string') {
deviceIdsStr = item.deviceIds
}
if (deviceIdsStr && deviceIdsStr.trim()) {
queryParams.append('deviceIds', deviceIdsStr.trim())
}
}
const queryString = queryParams.toString() const queryString = queryParams.toString()
const url = router.resolve(path + (queryString ? `?${queryString}` : '')).href const url = router.resolve(path + (queryString ? `?${queryString}` : '')).href
window.open(url, '_blank') window.open(url, '_blank')
@ -338,11 +404,16 @@ const resetCreateForm = () => {
createForm.type = '' createForm.type = ''
createForm.orgId = undefined createForm.orgId = undefined
createForm.orgName = '' createForm.orgName = ''
createForm.deviceIds = '' createForm.deviceIdsList = []
createForm.indexImage = '' createForm.indexImage = ''
createForm.route = '' createForm.route = ''
createForm.content = '' createForm.content = ''
selectedDeviceIds.value = [] deviceAttrSelections.value = [{ deviceId: undefined, attributeIds: [] }]
}
const handleCreateDialogClosed = () => {
resetCreateForm()
createFormRef.value?.resetFields?.()
} }
const openCreateDialog = () => { const openCreateDialog = () => {
@ -352,6 +423,38 @@ const openCreateDialog = () => {
createDialogVisible.value = true createDialogVisible.value = true
} }
const normalizeDeviceIdsList = (val: any): { deviceId: number; attributesIds: number[] }[] => {
if (!val) return []
if (Array.isArray(val)) {
return val
.map((v: any) => ({
deviceId: Number(v?.deviceId),
attributesIds: Array.isArray(v?.attributesIds)
? v.attributesIds.map((id: any) => Number(id)).filter((id: number) => !Number.isNaN(id))
: []
}))
.filter((v) => v.deviceId && !Number.isNaN(v.deviceId))
}
if (typeof val === 'string') {
const trimmed = val.trim()
if (!trimmed) return []
if (trimmed.startsWith('[')) {
try {
const parsed = JSON.parse(trimmed)
if (Array.isArray(parsed)) {
return normalizeDeviceIdsList(parsed)
}
} catch {}
}
const ids = trimmed
.split(',')
.map((s) => Number(s.trim()))
.filter((id) => !Number.isNaN(id))
return ids.map((id) => ({ deviceId: id, attributesIds: [] }))
}
return []
}
const openEditDialog = (item: DashboardItem) => { const openEditDialog = (item: DashboardItem) => {
dialogMode.value = 'edit' dialogMode.value = 'edit'
editingId.value = item.id editingId.value = item.id
@ -361,13 +464,26 @@ const openEditDialog = (item: DashboardItem) => {
createForm.type = item.type || '' createForm.type = item.type || ''
createForm.orgId = item.orgId || undefined createForm.orgId = item.orgId || undefined
createForm.orgName = item.orgName || '' createForm.orgName = item.orgName || ''
createForm.deviceIds = item.deviceIds || '' const fromNew = Array.isArray(item.deviceIdsList) && item.deviceIdsList.length
? normalizeDeviceIdsList(item.deviceIdsList)
: []
const fromLegacy = !fromNew.length ? normalizeDeviceIdsList(item.deviceIds) : []
createForm.deviceIdsList = fromNew.length ? fromNew : fromLegacy
createForm.indexImage = item.indexImage || '' createForm.indexImage = item.indexImage || ''
createForm.route = item.route || '' createForm.route = item.route || ''
createForm.content = item.content || '' createForm.content = item.content || ''
selectedDeviceIds.value = createForm.deviceIds deviceAttrSelections.value =
? createForm.deviceIds.split(',').filter((v) => v) createForm.deviceIdsList && createForm.deviceIdsList.length
: [] ? createForm.deviceIdsList.slice(0, 8).map((g) => ({
deviceId: g.deviceId,
attributeIds: Array.isArray(g.attributesIds) ? g.attributesIds.slice() : []
}))
: [{ deviceId: undefined, attributeIds: [] }]
deviceAttrSelections.value.forEach((g) => {
if (g.deviceId) {
loadDeviceAttributes(g.deviceId)
}
})
createDialogVisible.value = true createDialogVisible.value = true
} }
@ -384,17 +500,22 @@ const submitDialog = async () => {
return return
} }
if (createForm.type === '1') { if (createForm.type === '1') {
if (!selectedDeviceIds.value.length) { const groups = deviceAttrSelections.value
message.error('设备不能为空') .map((g) => ({
return deviceId: g.deviceId,
} attributesIds: (g.attributeIds || []).filter((v) => v !== undefined && v !== null)
if (selectedDeviceIds.value.length !== 8) { }))
message.error('设备必须选择8个') .filter((g) => g.deviceId && g.attributesIds.length)
if (!groups.length) {
message.error('请至少配置一组设备和点位')
return return
} }
createForm.deviceIds = selectedDeviceIds.value.join(',') createForm.deviceIdsList = groups.map((g) => ({
deviceId: Number(g.deviceId),
attributesIds: g.attributesIds.map((id: any) => Number(id))
}))
} else { } else {
createForm.deviceIds = '' createForm.deviceIdsList = []
} }
if (createForm.orgId) { if (createForm.orgId) {
const org = findOrgNode(organizationTree.value || [], createForm.orgId) const org = findOrgNode(organizationTree.value || [], createForm.orgId)
@ -475,6 +596,39 @@ const loadDeviceList = async () => {
deviceList.value = await DeviceApi.getDeviceList() deviceList.value = await DeviceApi.getDeviceList()
} }
const loadDeviceAttributes = async (deviceId: number | string) => {
const key = String(deviceId)
if (!key) return
if (deviceAttributeOptionsMap.value[key]) return
const data = await request.get({
url: '/iot/device/device-attribute/page',
params: {
pageNo: 1,
pageSize: 100,
deviceId
}
})
deviceAttributeOptionsMap.value[key] = data?.list || []
}
const handleDeviceChange = async (value: number | string, index: number) => {
const group = deviceAttrSelections.value[index]
if (!group) return
group.attributeIds = []
if (!value) return
await loadDeviceAttributes(value)
}
const addDeviceAttrGroup = () => {
if (deviceAttrSelections.value.length >= 8) return
deviceAttrSelections.value.push({ deviceId: undefined, attributeIds: [] })
}
const removeDeviceAttrGroup = (index: number) => {
if (deviceAttrSelections.value.length <= 1) return
deviceAttrSelections.value.splice(index, 1)
}
onMounted(() => { onMounted(() => {
getList() getList()
loadOrganizationTree() loadOrganizationTree()
@ -554,4 +708,21 @@ onMounted(() => {
.dashboard-card-menu { .dashboard-card-menu {
min-width: 120px; min-width: 120px;
} }
.dashboard-device-group-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.dashboard-device-group {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
}
.dashboard-device-remove-btn {
flex-shrink: 0;
}
</style> </style>

@ -98,7 +98,7 @@ import EnergyMonitor from './components/EnergyMonitor.vue'
import ProductionTrend from './components/ProductionTrend.vue' import ProductionTrend from './components/ProductionTrend.vue'
const route = useRoute() const route = useRoute()
const deviceIds = route.query.deviceIds as string const goviewId = route.query.goviewId as string
const orgId = route.query.orgId const orgId = route.query.orgId
interface DeviceAttribute { interface DeviceAttribute {
@ -128,13 +128,11 @@ const getBottomLeft = (index: number) => {
} }
const loadDeviceAttributes = async () => { const loadDeviceAttributes = async () => {
if (!deviceIds) return if (!goviewId) return
try { try {
const res = await DeviceApi.getDeviceAttributeBatchList({ deviceIds, orgId }) const res = await DeviceApi.getDeviceAttributeBatchList({ goviewId, orgId })
const list = (res && Array.isArray(res) ? res : []) as any[] const list = (res && Array.isArray(res) ? res : []) as any[]
// list deviceIds deviceId
// 4 top4 bottom
const cards: DeviceCardData[] = list.map((d: any) => { const cards: DeviceCardData[] = list.map((d: any) => {
return { return {
deviceName: d.deviceName || 'Device', deviceName: d.deviceName || 'Device',

@ -21,6 +21,9 @@
<el-form-item :label="t('SystemManagement.Menu.name')" prop="name"> <el-form-item :label="t('SystemManagement.Menu.name')" prop="name">
<el-input v-model="formData.name" clearable :placeholder="t('SystemManagement.Menu.namePlaceholder')" /> <el-input v-model="formData.name" clearable :placeholder="t('SystemManagement.Menu.namePlaceholder')" />
</el-form-item> </el-form-item>
<el-form-item :label="t('SystemManagement.Menu.enName')">
<el-input v-model="formData.enName" clearable :placeholder="t('SystemManagement.Menu.enNamePlaceholder')" />
</el-form-item>
<el-form-item :label="t('SystemManagement.Menu.type')" prop="type"> <el-form-item :label="t('SystemManagement.Menu.type')" prop="type">
<el-radio-group v-model="formData.type"> <el-radio-group v-model="formData.type">
<el-radio-button <el-radio-button
@ -133,6 +136,7 @@ const formType = ref('') // 表单的类型create - 新增update - 修改
const formData = ref({ const formData = ref({
id: undefined, id: undefined,
name: '', name: '',
enName: '',
permission: '', permission: '',
type: SystemMenuTypeEnum.DIR, type: SystemMenuTypeEnum.DIR,
sort: Number(undefined), sort: Number(undefined),
@ -234,6 +238,7 @@ const resetForm = () => {
formData.value = { formData.value = {
id: undefined, id: undefined,
name: '', name: '',
enName: '',
permission: '', permission: '',
type: SystemMenuTypeEnum.DIR, type: SystemMenuTypeEnum.DIR,
sort: Number(undefined), sort: Number(undefined),

Loading…
Cancel
Save