Commit 1db0a60cb54020679a8f4507dc6425d48aea40f4

Authored by wuxw
1 parent d720526e

优化常用菜单 和搜索功能

src/api/system/menuUserManageApi.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +
  3 +/**
  4 + * 获取用户菜单列表
  5 + * @param {Object} params 查询参数
  6 + * @returns {Promise}
  7 + */
  8 +export function listMenuUser(params) {
  9 + return new Promise((resolve, reject) => {
  10 + request({
  11 + url: '/menuUser.listMenuUser',
  12 + method: 'get',
  13 + params
  14 + }).then(response => {
  15 + const res = response.data
  16 + resolve(res)
  17 + }).catch(error => {
  18 + reject(error)
  19 + })
  20 + })
  21 +}
  22 +
  23 +/**
  24 + * 添加用户菜单
  25 + * @param {Object} data 菜单数据
  26 + * @returns {Promise}
  27 + */
  28 +export function saveMenuUser(data) {
  29 + return new Promise((resolve, reject) => {
  30 + request({
  31 + url: '/menuUser.saveMenuUser',
  32 + method: 'post',
  33 + data
  34 + }).then(response => {
  35 + const res = response.data
  36 + resolve(res)
  37 + }).catch(error => {
  38 + reject(error)
  39 + })
  40 + })
  41 +}
  42 +
  43 +/**
  44 + * 删除用户菜单
  45 + * @param {Object} data 删除参数
  46 + * @returns {Promise}
  47 + */
  48 +export function deleteMenuUser(data) {
  49 + return new Promise((resolve, reject) => {
  50 + request({
  51 + url: '/menuUser.deleteMenuUser',
  52 + method: 'post',
  53 + data
  54 + }).then(response => {
  55 + const res = response.data
  56 + resolve(res)
  57 + }).catch(error => {
  58 + reject(error)
  59 + })
  60 + })
  61 +}
  62 +
  63 +/**
  64 + * 获取目录菜单列表
  65 + * @returns {Promise}
  66 + */
  67 +export function listCatalogMenus() {
  68 + return new Promise((resolve, reject) => {
  69 + request({
  70 + url: '/menu.listCatalogMenus',
  71 + method: 'get'
  72 + }).then(response => {
  73 + const res = response.data
  74 + resolve(res)
  75 + }).catch(error => {
  76 + reject(error)
  77 + })
  78 + })
  79 +}
0 80 \ No newline at end of file
... ...
src/api/system/searchCommunityDataApi.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +import { getCommunityId } from '@/api/community/communityApi'
  3 +
  4 +// 搜索社区数据
  5 +export function searchCommunityData(params) {
  6 + return new Promise((resolve, reject) => {
  7 + const communityId = getCommunityId()
  8 + request({
  9 + url: '/search.searchCommunityData',
  10 + method: 'get',
  11 + params: {
  12 + ...params,
  13 + communityId
  14 + }
  15 + }).then(response => {
  16 + const res = response.data
  17 + resolve(res)
  18 + }).catch(error => {
  19 + reject(error)
  20 + })
  21 + })
  22 +}
  23 +
  24 +// 获取电话机信息
  25 +export function getTelMachineInfo() {
  26 + return new Promise((resolve, reject) => {
  27 + const communityId = getCommunityId()
  28 + request({
  29 + url: '/iot.getOpenApi',
  30 + method: 'get',
  31 + params: {
  32 + page: 1,
  33 + row: 1,
  34 + communityId,
  35 + iotApiCode: 'queryTelMachineBmoImpl'
  36 + }
  37 + }).then(response => {
  38 + const res = response.data
  39 + resolve(res)
  40 + }).catch(error => {
  41 + reject(error)
  42 + })
  43 + })
  44 +}
  45 +
  46 +// 保存电话机消息
  47 +export function saveTelMachineMsg(data) {
  48 + return new Promise((resolve, reject) => {
  49 + const communityId = getCommunityId()
  50 + request({
  51 + url: '/iot.postOpenApi',
  52 + method: 'post',
  53 + data: {
  54 + ...data,
  55 + communityId,
  56 + iotApiCode: 'telMachineMsgBmoImpl'
  57 + }
  58 + }).then(response => {
  59 + const res = response.data
  60 + resolve(res)
  61 + }).catch(error => {
  62 + reject(error)
  63 + })
  64 + })
  65 +}
0 66 \ No newline at end of file
... ...
src/api/system/viewMenuUserApi.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +
  3 +/**
  4 + * 获取用户菜单列表
  5 + * @param {Object} params 查询参数
  6 + * @returns {Promise} Promise对象
  7 + */
  8 +export function listMenuUser(params) {
  9 + return new Promise((resolve, reject) => {
  10 + request({
  11 + url: '/menuUser.listMenuUser',
  12 + method: 'get',
  13 + params
  14 + }).then(response => {
  15 + const res = response.data
  16 + resolve(res)
  17 + }).catch(error => {
  18 + reject(error)
  19 + })
  20 + })
  21 +}
0 22 \ No newline at end of file
... ...
src/components/system/addMenuUser.vue 0 → 100644
  1 +<template>
  2 + <el-dialog :title="$t('menuUserManage.add.title')" :visible.sync="visible" width="50%" @close="handleClose">
  3 + <el-form ref="form" :model="form" :rules="rules" label-width="120px">
  4 + <el-form-item :label="$t('menuUserManage.add.menu')" prop="mId">
  5 + <el-select v-model="form.mId" :placeholder="$t('menuUserManage.add.menuPlaceholder')" style="width:100%">
  6 + <el-option v-for="item in menus" :key="item.mId" :label="`${item.menuGroupName}-${item.name}`"
  7 + :value="item.mId" :disabled="item.isShow !== 'Y'" />
  8 + </el-select>
  9 + </el-form-item>
  10 +
  11 + <el-form-item :label="$t('menuUserManage.add.icon')" prop="icon">
  12 + <el-radio-group v-model="form.icon">
  13 + <el-radio label="fa fa-adjust">
  14 + <i class="fa fa-adjust"></i>
  15 + </el-radio>
  16 + <el-radio label="fa fa-bar-chart">
  17 + <i class="fa fa-bar-chart"></i>
  18 + </el-radio>
  19 + <el-radio label="fa fa-comment-o">
  20 + <i class="fa fa-comment-o"></i>
  21 + </el-radio>
  22 + <el-radio label="fa fa-folder-o">
  23 + <i class="fa fa-folder-o"></i>
  24 + </el-radio>
  25 + <el-radio label="fa fa-futbol-o">
  26 + <i class="fa fa-futbol-o"></i>
  27 + </el-radio>
  28 + <el-radio label="fa fa-globe">
  29 + <i class="fa fa-globe"></i>
  30 + </el-radio>
  31 + <el-radio label="fa fa-female">
  32 + <i class="fa fa-female"></i>
  33 + </el-radio>
  34 + </el-radio-group>
  35 + </el-form-item>
  36 +
  37 + <el-form-item :label="$t('menuUserManage.add.seq')" prop="seq">
  38 + <el-input-number v-model="form.seq" :placeholder="$t('menuUserManage.add.seqPlaceholder')" :min="1"
  39 + style="width:100%" />
  40 + </el-form-item>
  41 + </el-form>
  42 +
  43 + <div slot="footer" class="dialog-footer">
  44 + <el-button @click="visible = false">{{ $t('common.cancel') }}</el-button>
  45 + <el-button type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</el-button>
  46 + </div>
  47 + </el-dialog>
  48 +</template>
  49 +
  50 +<script>
  51 +import { saveMenuUser,listCatalogMenus } from '@/api/system/menuUserManageApi'
  52 +
  53 +export default {
  54 + name: 'AddMenuUser',
  55 + data() {
  56 + return {
  57 + visible: false,
  58 + form: {
  59 + mId: '',
  60 + icon: 'fa fa-adjust',
  61 + seq: 1
  62 + },
  63 + menus: [],
  64 + rules: {
  65 + mId: [
  66 + { required: true, message: this.$t('menuUserManage.validate.menuRequired'), trigger: 'blur' }
  67 + ],
  68 + icon: [
  69 + { required: true, message: this.$t('menuUserManage.validate.iconRequired'), trigger: 'blur' }
  70 + ],
  71 + seq: [
  72 + { required: true, message: this.$t('menuUserManage.validate.seqRequired'), trigger: 'blur' }
  73 + ]
  74 + }
  75 + }
  76 + },
  77 + methods: {
  78 + open() {
  79 + this.visible = true
  80 + this.loadMenus()
  81 + },
  82 + async loadMenus() {
  83 + try {
  84 + const { data } = await listCatalogMenus()
  85 + const newMenus = []
  86 + data.forEach(item => {
  87 + item.childs.forEach(child => {
  88 + child.menuGroupName = item.name
  89 + newMenus.push(child)
  90 + })
  91 + })
  92 + this.menus = newMenus
  93 + } catch (error) {
  94 + console.error('Failed to load menus:', error)
  95 + }
  96 + },
  97 + handleSubmit() {
  98 + this.$refs.form.validate(async valid => {
  99 + if (valid) {
  100 + try {
  101 + await saveMenuUser(this.form)
  102 + this.$message.success(this.$t('menuUserManage.add.success'))
  103 + this.$emit('success')
  104 + this.visible = false
  105 + } catch (error) {
  106 + this.$message.error(error.message || this.$t('menuUserManage.add.error'))
  107 + }
  108 + }
  109 + })
  110 + },
  111 + handleClose() {
  112 + this.$refs.form.resetFields()
  113 + this.form.icon = 'fa fa-adjust'
  114 + this.form.seq = 1
  115 + }
  116 + }
  117 +}
  118 +</script>
  119 +
  120 +<style scoped>
  121 +.el-radio-group {
  122 + display: flex;
  123 + flex-wrap: wrap;
  124 +}
  125 +
  126 +.el-radio {
  127 + margin-right: 15px;
  128 + margin-bottom: 10px;
  129 +}
  130 +</style>
0 131 \ No newline at end of file
... ...
src/components/system/deleteMenuUser.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('menuUserManage.delete.title')"
  4 + :visible.sync="visible"
  5 + width="30%"
  6 + @close="handleClose"
  7 + >
  8 + <div class="delete-content">
  9 + <i class="el-icon-warning" style="color:#E6A23C;font-size:24px;margin-right:10px;"></i>
  10 + <span>{{ $t('menuUserManage.delete.confirm') }}</span>
  11 + </div>
  12 + <div slot="footer" class="dialog-footer">
  13 + <el-button @click="visible = false">{{ $t('common.cancel') }}</el-button>
  14 + <el-button type="primary" @click="handleConfirm">{{ $t('common.confirm') }}</el-button>
  15 + </div>
  16 + </el-dialog>
  17 +</template>
  18 +
  19 +<script>
  20 +import { deleteMenuUser } from '@/api/system/menuUserManageApi'
  21 +
  22 +export default {
  23 + name: 'DeleteMenuUser',
  24 + data() {
  25 + return {
  26 + visible: false,
  27 + currentData: null
  28 + }
  29 + },
  30 + methods: {
  31 + open(data) {
  32 + this.currentData = data
  33 + this.visible = true
  34 + },
  35 + async handleConfirm() {
  36 + try {
  37 + await deleteMenuUser(this.currentData)
  38 + this.$message.success(this.$t('menuUserManage.delete.success'))
  39 + this.$emit('success')
  40 + this.visible = false
  41 + } catch (error) {
  42 + this.$message.error(error.message || this.$t('menuUserManage.delete.error'))
  43 + }
  44 + },
  45 + handleClose() {
  46 + this.currentData = null
  47 + }
  48 + }
  49 +}
  50 +</script>
  51 +
  52 +<style scoped>
  53 +.delete-content {
  54 + display: flex;
  55 + align-items: center;
  56 + justify-content: center;
  57 + font-size: 16px;
  58 + padding: 20px 0;
  59 +}
  60 +</style>
0 61 \ No newline at end of file
... ...
src/components/system/searchCommunityDataList.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :visible.sync="visible"
  4 + :title="$t('searchCommunityData.title')"
  5 + width="80%"
  6 + @close="handleClose"
  7 + >
  8 + <el-row class="margin-bottom">
  9 + <el-col :span="14" :offset="2">
  10 + <el-input
  11 + v-model="searchValue"
  12 + :placeholder="$t('searchCommunityData.placeholder')"
  13 + @keyup.enter.native="doSearch"
  14 + />
  15 + </el-col>
  16 + <el-col :span="3" :offset="1">
  17 + <el-button type="primary" @click="doSearch">
  18 + {{ $t('common.search') }}
  19 + </el-button>
  20 + </el-col>
  21 + <el-col :span="3">
  22 + <el-button @click="queryCallInTel">
  23 + {{ $t('searchCommunityData.callInNumber') }}
  24 + </el-button>
  25 + </el-col>
  26 + </el-row>
  27 + <el-divider />
  28 + <div class="margin-top margin-bottom" style="min-height: 200px;">
  29 + <template v-if="!noData">
  30 + <div class="vc-search-community-item" v-if="rooms.length > 0">
  31 + <div class="item-title">{{ $t('searchCommunityData.roomInfo') }}</div>
  32 + <div class="item-content">
  33 + <el-link
  34 + v-for="(item, index) in rooms"
  35 + :key="'room'+index"
  36 + @click="toSimplifyAcceptance(item)"
  37 + >
  38 + {{ item.roomName }}
  39 + </el-link>
  40 + </div>
  41 + </div>
  42 + <div class="vc-search-community-item" v-if="owners.length > 0">
  43 + <div class="item-title">{{ $t('searchCommunityData.ownerInfo') }}</div>
  44 + <div class="item-content">
  45 + <el-link
  46 + v-for="(item, index) in owners"
  47 + :key="'owner'+index"
  48 + :href="'/#/views/owner/ownerDetail?ownerId='+item.ownerId"
  49 + target="_blank"
  50 + >
  51 + {{ item.name }}
  52 + </el-link>
  53 + </div>
  54 + </div>
  55 + <div class="vc-search-community-item" v-if="ownerMembers.length > 0">
  56 + <div class="item-title">{{ $t('searchCommunityData.ownerMember') }}</div>
  57 + <div class="item-content">
  58 + <el-link
  59 + v-for="(item, index) in ownerMembers"
  60 + :key="'ownerMember'+index"
  61 + :href="'/#/views/owner/ownerDetail?ownerId='+item.ownerId"
  62 + target="_blank"
  63 + >
  64 + {{ item.name }}
  65 + </el-link>
  66 + </div>
  67 + </div>
  68 + <div class="vc-search-community-item" v-if="cars.length > 0">
  69 + <div class="item-title">{{ $t('searchCommunityData.communityCar') }}</div>
  70 + <div class="item-content">
  71 + <el-link
  72 + v-for="(item, index) in cars"
  73 + :key="'car'+index"
  74 + :href="'/#/views/car/carDetail?memberId='+item.carId"
  75 + target="_blank"
  76 + >
  77 + {{ item.carNum }}
  78 + </el-link>
  79 + </div>
  80 + </div>
  81 + <div class="vc-search-community-item" v-if="carMembers.length > 0">
  82 + <div class="item-title">{{ $t('searchCommunityData.memberCar') }}</div>
  83 + <div class="item-content">
  84 + <el-link
  85 + v-for="(item, index) in carMembers"
  86 + :key="'carMember'+index"
  87 + :href="'/#/views/car/carDetail?memberId='+item.carId"
  88 + target="_blank"
  89 + >
  90 + {{ item.carNum }}
  91 + </el-link>
  92 + </div>
  93 + </div>
  94 + <div class="vc-search-community-item" v-if="contracts.length > 0">
  95 + <div class="item-title">{{ $t('searchCommunityData.contract') }}</div>
  96 + <div class="item-content">
  97 + <el-link
  98 + v-for="(item, index) in contracts"
  99 + :key="'contract'+index"
  100 + :href="'/#/views/contract/contractDetail?contractId='+item.contractId"
  101 + target="_blank"
  102 + >
  103 + {{ item.contractName }}
  104 + </el-link>
  105 + </div>
  106 + </div>
  107 + <div class="vc-search-community-item" v-if="repairs.length > 0">
  108 + <div class="item-title">{{ $t('searchCommunityData.repairOrder') }}</div>
  109 + <div class="item-content">
  110 + <el-link
  111 + v-for="(item, index) in repairs"
  112 + :key="'repair'+index"
  113 + :href="'/#/pages/property/ownerRepairDetail?repairId='+item.repairId"
  114 + target="_blank"
  115 + >
  116 + {{ item.repairName }}
  117 + </el-link>
  118 + </div>
  119 + </div>
  120 + <div class="vc-search-community-item" v-if="visits.length > 0">
  121 + <div class="item-title">{{ $t('searchCommunityData.visitor') }}</div>
  122 + <div class="item-content">
  123 + <el-link
  124 + v-for="(item, index) in visits"
  125 + :key="'visit'+index"
  126 + :href="'/#/pages/property/visitDetail?vId='+item.vId+'&flowId='+item.flowId"
  127 + target="_blank"
  128 + >
  129 + {{ item.vName }}
  130 + </el-link>
  131 + </div>
  132 + </div>
  133 + <div class="vc-search-community-item" v-if="staffs.length > 0">
  134 + <div class="item-title">{{ $t('searchCommunityData.staff') }}</div>
  135 + <div class="item-content">
  136 + <el-link
  137 + v-for="(item, index) in staffs"
  138 + :key="'staff'+index"
  139 + :href="'/#/views/staff/staffDetail?staffId='+item.userId"
  140 + target="_blank"
  141 + >
  142 + {{ item.name }}
  143 + </el-link>
  144 + </div>
  145 + </div>
  146 + </template>
  147 + <div v-else class="text-center" style="font-size: 38px;color: #aaa;margin-top: 100px;">
  148 + {{ $t('searchCommunityData.noData') }}
  149 + </div>
  150 + </div>
  151 + <div slot="footer" class="dialog-footer">
  152 + <el-button @click="visible = false">{{ $t('common.close') }}</el-button>
  153 + </div>
  154 + </el-dialog>
  155 +</template>
  156 +
  157 +<script>
  158 +import { searchCommunityData, getTelMachine, postTelMachineMsg } from '@/api/system/searchCommunityDataApi'
  159 +import { getCommunityId } from '@/api/community/communityApi'
  160 +
  161 +export default {
  162 + name: 'SearchCommunityData',
  163 + data() {
  164 + return {
  165 + visible: false,
  166 + searchValue: '',
  167 + rooms: [],
  168 + owners: [],
  169 + ownerMembers: [],
  170 + cars: [],
  171 + carMembers: [],
  172 + contracts: [],
  173 + repairs: [],
  174 + visits: [],
  175 + staffs: [],
  176 + noData: true,
  177 + machine: {},
  178 + ws: null,
  179 + componentName: '',
  180 + communityId: ''
  181 + }
  182 + },
  183 + created() {
  184 + this.communityId = getCommunityId()
  185 + this.initTelMachine()
  186 + },
  187 + methods: {
  188 + open() {
  189 + this.visible = true
  190 + this.clearSearchData()
  191 + },
  192 + handleClose() {
  193 + this.clearSearchData()
  194 + },
  195 + initTelMachine() {
  196 + if (!this.communityId || this.communityId === '-1') {
  197 + return
  198 + }
  199 + getTelMachine({
  200 + page: 1,
  201 + row: 1,
  202 + communityId: this.communityId
  203 + }).then(res => {
  204 + if (res.code !== 0 || res.data.length < 1) {
  205 + return
  206 + }
  207 + this.machine = res.data[0]
  208 + this.connectTelMachine(this.machine)
  209 + }).catch(error => {
  210 + console.error('Failed to get tel machine:', error)
  211 + })
  212 + },
  213 + doSearch() {
  214 + if (!this.searchValue) {
  215 + this.$message.warning(this.$t('searchCommunityData.inputTip'))
  216 + return
  217 + }
  218 +
  219 + searchCommunityData({
  220 + searchValue: this.searchValue,
  221 + communityId: this.communityId
  222 + }).then(res => {
  223 + if (res.code !== 0) {
  224 + this.$message.error(res.msg)
  225 + return
  226 + }
  227 +
  228 + this.noData = false
  229 + const data = res.data
  230 + this.rooms = data.rooms || []
  231 + this.owners = data.owners || []
  232 + this.ownerMembers = data.ownerMembers || []
  233 + this.cars = data.cars || []
  234 + this.carMembers = data.carMembers || []
  235 + this.contracts = data.contracts || []
  236 + this.repairs = data.repairs || []
  237 + this.visits = data.visitDtos || []
  238 + this.staffs = data.staffs || []
  239 +
  240 + this.noData = !(this.rooms.length > 0 || this.owners.length > 0 ||
  241 + this.ownerMembers.length > 0 || this.cars.length > 0 ||
  242 + this.carMembers.length > 0 || this.contracts.length > 0 ||
  243 + this.repairs.length > 0 || this.visits.length > 0 ||
  244 + this.staffs.length > 0)
  245 + }).catch(error => {
  246 + console.error('Search failed:', error)
  247 + })
  248 + },
  249 + toSimplifyAcceptance(room) {
  250 + const date = new Date()
  251 + localStorage.setItem("JAVA110_IS_BACK", date.getTime())
  252 + localStorage.setItem('simplifyAcceptanceSearch', JSON.stringify({
  253 + searchType: '1',
  254 + searchValue: `${room.floorNum}-${room.unitNum}-${room.roomNum}`,
  255 + searchPlaceholder: this.$t('searchCommunityData.roomPlaceholder')
  256 + }))
  257 + window.open('/#/pages/property/simplifyAcceptance?tab=businessAcceptance')
  258 + },
  259 + clearSearchData() {
  260 + const ws = this.ws
  261 + const machine = this.machine
  262 +
  263 + this.searchValue = ''
  264 + this.noData = true
  265 + this.rooms = []
  266 + this.owners = []
  267 + this.ownerMembers = []
  268 + this.cars = []
  269 + this.carMembers = []
  270 + this.contracts = []
  271 + this.repairs = []
  272 + this.visits = []
  273 + this.staffs = []
  274 + this.machine = machine
  275 + this.ws = ws
  276 + },
  277 + queryCallInTel() {
  278 + if (!this.ws) {
  279 + this.$message.warning(this.$t('searchCommunityData.telNotConnected'))
  280 + return
  281 + }
  282 + this.ws.send(JSON.stringify({
  283 + action: 'Query',
  284 + type: 'Device',
  285 + cb: 'cb_data'
  286 + }))
  287 + },
  288 + connectTelMachine(machine) {
  289 + const protocol = window.location.protocol
  290 + let url = "ws://"
  291 + if (protocol.startsWith('https')) {
  292 + url = "wss://"
  293 + }
  294 + url = `${url}${machine.machineIp}:${machine.machinePort}/APP_2AD85C71-BEF8-463C-9B4B-B672F603542A_fast`
  295 +
  296 + const ws = new WebSocket(url)
  297 +
  298 + ws.onerror = (event) => {
  299 + console.log('WebSocket error:', event)
  300 + setTimeout(() => {
  301 + this.connectTelMachine(machine)
  302 + }, 2000)
  303 + }
  304 +
  305 + ws.onclose = () => {
  306 + console.log('WebSocket closed')
  307 + }
  308 +
  309 + ws.onopen = () => {
  310 + console.log('WebSocket connected')
  311 + }
  312 +
  313 + ws.onmessage = (event) => {
  314 + try {
  315 + const data = JSON.parse(event.data)
  316 + console.log('WebSocket message:', data)
  317 +
  318 + if (data.message === 'query') {
  319 + if (data.name === 'Device' && data.param.CallerId) {
  320 + this.clearSearchData()
  321 + this.searchValue = data.param.CallerId
  322 + this.doSearch()
  323 + return
  324 + }
  325 + if (data.name === 'Device' && Object.prototype.hasOwnProperty.call(data.param, 'CallerId')) {
  326 + this.$message.warning(this.$t('searchCommunityData.noCallInNumber'))
  327 + return
  328 + }
  329 + return
  330 + }
  331 +
  332 + const param = data.param
  333 +
  334 + if (param && param.status === 'CallIn') {
  335 + this.clearSearchData()
  336 + this.searchValue = param.number
  337 + this.visible = true
  338 + this.doSearch()
  339 + return
  340 + }
  341 +
  342 + if (param && param.status === 'TalkingStart') {
  343 + this.$emit('TalkingStart', param)
  344 + return
  345 + }
  346 +
  347 + if (param && param.status === 'TalkingEnd') {
  348 + this.$emit('TalkingEnd', param)
  349 + return
  350 + }
  351 +
  352 + this.saveTelMachineMsg(data)
  353 + } catch (error) {
  354 + console.error('Error parsing WebSocket message:', error)
  355 + }
  356 + }
  357 +
  358 + this.ws = ws
  359 + },
  360 + saveTelMachineMsg(data) {
  361 + data.communityId = this.communityId
  362 + data.machineId = this.machine.machineId
  363 +
  364 + postTelMachineMsg(data).then(res => {
  365 + if (res.code !== 0) {
  366 + console.error('Failed to save tel machine message:', res.msg)
  367 + return
  368 + }
  369 +
  370 + const param = res.data || {}
  371 + if (param.action === 'sms') {
  372 + this.$emit('sendTelMsg', {
  373 + tel: param.tel,
  374 + msgText: param.msgText
  375 + })
  376 + }
  377 + }).catch(error => {
  378 + console.error('Failed to post tel machine message:', error)
  379 + })
  380 + },
  381 + playTts(data) {
  382 + if (this.ws) {
  383 + this.ws.send(JSON.stringify({
  384 + action: 'PlayMusic',
  385 + url: data.url,
  386 + file_name: this.uuid() + '.wav',
  387 + volume: 100,
  388 + loop: true
  389 + }))
  390 + }
  391 + },
  392 + hangup() {
  393 + if (this.ws) {
  394 + this.ws.send(JSON.stringify({
  395 + action: 'Hangup'
  396 + }))
  397 + }
  398 + },
  399 + callTel(data) {
  400 + if (this.ws) {
  401 + this.ws.send(JSON.stringify({
  402 + action: 'CallOut',
  403 + number: data.tel,
  404 + cb: 'callout_cb'
  405 + }))
  406 + this.componentName = data.componentName
  407 + }
  408 + },
  409 + sendTelMsg(data) {
  410 + if (this.ws) {
  411 + this.ws.send(JSON.stringify({
  412 + action: 'SmsSend',
  413 + number: data.tel,
  414 + content: data.msgText
  415 + }))
  416 + }
  417 + },
  418 + uuid() {
  419 + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
  420 + const r = Math.random() * 16 | 0
  421 + const v = c === 'x' ? r : (r & 0x3 | 0x8)
  422 + return v.toString(16)
  423 + })
  424 + }
  425 + }
  426 +}
  427 +</script>
  428 +
  429 +<style scoped>
  430 +.vc-search-community-item {
  431 + margin-bottom: 20px;
  432 +}
  433 +
  434 +.item-title {
  435 + font-weight: bold;
  436 + margin-bottom: 10px;
  437 +}
  438 +
  439 +.item-content {
  440 + display: flex;
  441 + flex-wrap: wrap;
  442 + gap: 10px;
  443 +}
  444 +
  445 +.margin-top {
  446 + margin-top: 20px;
  447 +}
  448 +
  449 +.margin-bottom {
  450 + margin-bottom: 20px;
  451 +}
  452 +
  453 +.text-center {
  454 + text-align: center;
  455 +}
  456 +</style>
0 457 \ No newline at end of file
... ...
src/components/system/viewMenuUserList.vue 0 → 100644
  1 +<template>
  2 + <div class="view-menu-user-container">
  3 + <el-dialog :visible.sync="dialogVisible" width="80%" :before-close="handleClose"
  4 + custom-class="view-menu-user-dialog">
  5 + <el-row>
  6 + <el-col :span="22"></el-col>
  7 + <el-col :span="2" class="text-right">
  8 + <i class="el-icon-close close-icon" @click="handleClose"></i>
  9 + </el-col>
  10 + </el-row>
  11 +
  12 + <el-row class="menu-items" :gutter="20">
  13 + <el-col v-for="(item, index) in viewMenuUserInfo.menus" :key="index" :span="4"
  14 + class="text-center menu-item" @click.native="jumpToUserPage(item)">
  15 + <div>
  16 + <i :class="item.icon" class="menu-icon"></i>
  17 + </div>
  18 + <div class="menu-name">
  19 + {{ item.name }}
  20 + </div>
  21 + </el-col>
  22 + </el-row>
  23 +
  24 + <el-row class="menu-items" :gutter="20">
  25 + <el-col :span="4" class="text-center menu-item" @click.native="toUserMenu">
  26 + <div>
  27 + <i class="el-icon-plus menu-icon"></i>
  28 + </div>
  29 + <div class="menu-name">
  30 + {{ $t('viewMenuUser.custom') }}
  31 + </div>
  32 + </el-col>
  33 + </el-row>
  34 + </el-dialog>
  35 + </div>
  36 +</template>
  37 +
  38 +<script>
  39 +import { listMenuUser } from '@/api/system/viewMenuUserApi'
  40 +
  41 +export default {
  42 + name: 'ViewMenuUserList',
  43 + data() {
  44 + return {
  45 + dialogVisible: false,
  46 + viewMenuUserInfo: {
  47 + menus: []
  48 + }
  49 + }
  50 + },
  51 + methods: {
  52 + open() {
  53 + this.dialogVisible = true
  54 + this.listViewMenuUsers()
  55 + },
  56 + handleClose() {
  57 + this.dialogVisible = false
  58 + },
  59 + async listViewMenuUsers() {
  60 + try {
  61 + const params = {
  62 + page: 1,
  63 + row: 100
  64 + }
  65 + const { data } = await listMenuUser(params)
  66 + this.viewMenuUserInfo.menus = data
  67 + } catch (error) {
  68 + console.error('Failed to fetch menu data:', error)
  69 + }
  70 + },
  71 + toUserMenu() {
  72 + this.handleClose()
  73 + this.$router.push('/views/system/menuUserManage?tab=commonMenu')
  74 + },
  75 + jumpToUserPage(item) {
  76 + this.handleClose()
  77 + let href = item.url
  78 + if (href.indexOf('/#/') > -1) {
  79 + href = href.replace('/#/', '/')
  80 + }
  81 + if (href.indexOf('?') > -1) {
  82 + href += `&tab=${item.name}`
  83 + } else {
  84 + href += `?tab=${item.name}`
  85 + }
  86 + this.$router.push(href)
  87 + }
  88 + }
  89 +}
  90 +</script>
  91 +
  92 +<style lang="scss" scoped>
  93 +.view-menu-user-container {
  94 + ::v-deep .view-menu-user-dialog {
  95 + background: rgba(66, 66, 66, 0.9);
  96 + color: #fff;
  97 + box-shadow: 0 0px 0px rgba(0, 0, 0, 0.3);
  98 +
  99 + .el-dialog__header {
  100 + display: none;
  101 + }
  102 +
  103 + .el-dialog__body {
  104 + color: #fff;
  105 + padding: 20px;
  106 + }
  107 + }
  108 +
  109 + .close-icon {
  110 + font-size: 18px;
  111 + cursor: pointer;
  112 +
  113 + &:hover {
  114 + color: #409EFF;
  115 + }
  116 + }
  117 +
  118 + .menu-items {
  119 + margin-top: 40px;
  120 + cursor: pointer;
  121 + }
  122 +
  123 + .menu-item {
  124 + margin-bottom: 20px;
  125 +
  126 + &:hover {
  127 +
  128 + .menu-icon,
  129 + .menu-name {
  130 + color: #409EFF;
  131 + }
  132 + }
  133 + }
  134 +
  135 + .menu-icon {
  136 + font-size: 48px;
  137 + margin-bottom: 10px;
  138 + }
  139 +
  140 + .menu-name {
  141 + font-size: 14px;
  142 + }
  143 +}
  144 +</style>
0 145 \ No newline at end of file
... ...
src/i18n/systemI18n.js
... ... @@ -11,6 +11,7 @@ import { messages as downloadTempFileMessages } from &#39;../views/system/downloadTe
11 11 import { messages as assetImportLogMessages } from '../views/system/assetImportLogLang'
12 12 import { messages as assetImportLogDetailMessages } from '../views/system/assetImportLogDetailLang'
13 13 import { messages as payFeeQrcodeMessages } from '../views/fee/payFeeQrcodeLang'
  14 +import { messages as menuUserManageMessages } from '../views/system/menuUserManageLang'
14 15  
15 16 export const messages = {
16 17 en: {
... ... @@ -27,6 +28,7 @@ export const messages = {
27 28 ...assetImportLogMessages.en,
28 29 ...assetImportLogDetailMessages.en,
29 30 ...payFeeQrcodeMessages.en,
  31 + ...menuUserManageMessages.en,
30 32 },
31 33 zh: {
32 34 ...communitySettingManageMessages.zh,
... ... @@ -42,5 +44,6 @@ export const messages = {
42 44 ...assetImportLogMessages.zh,
43 45 ...assetImportLogDetailMessages.zh,
44 46 ...payFeeQrcodeMessages.zh,
  47 + ...menuUserManageMessages.zh,
45 48 }
46 49 }
47 50 \ No newline at end of file
... ...
src/router/systemRouter.js
... ... @@ -74,4 +74,9 @@ export default [
74 74 name: '/pages/fee/payFeeQrcode',
75 75 component: () => import('@/views/fee/payFeeQrcodeList.vue')
76 76 },
  77 + {
  78 + path: '/views/system/menuUserManage',
  79 + name: '/views/system/menuUserManage',
  80 + component: () => import('@/views/system/menuUserManageList.vue')
  81 + },
77 82 ]
78 83 \ No newline at end of file
... ...
src/views/layout/layout.vue
... ... @@ -71,6 +71,8 @@
71 71 </el-main>
72 72 </el-container>
73 73 <more-community ref="moreCommunity" />
  74 + <view-menu-user-list ref="viewMenuUserModel" />
  75 + <search-community-data-list ref="searchCommunityDataModel" />
74 76 </el-container>
75 77 </template>
76 78  
... ... @@ -80,6 +82,8 @@ import { getStoreInfo } from &quot;@/api/user/indexApi&quot;
80 82 import { deepCopy, setCurrentCommunity } from "@/utils/vc"
81 83 import { getCommunityName, _loadCommunityInfo } from '@/api/community/communityApi'
82 84 import moreCommunity from '@/components/community/moreCommunity.vue'
  85 +import viewMenuUserList from '@/components/system/viewMenuUserList.vue'
  86 +import searchCommunityDataList from '@/components/system/searchCommunityDataList.vue'
83 87  
84 88 export default {
85 89 name: 'Layout',
... ... @@ -113,7 +117,9 @@ export default {
113 117 this.loadCommunity()
114 118 },
115 119 components: {
116   - moreCommunity
  120 + moreCommunity,
  121 + viewMenuUserList,
  122 + searchCommunityDataList
117 123 },
118 124 methods: {
119 125 async loadCommunity() {
... ... @@ -152,7 +158,9 @@ export default {
152 158 },
153 159 _changeMenuCatalog(_catalog, _isJump) {
154 160 this.activeMenu = _catalog.caId;
155   -
  161 + if (this._showModelDiv(_catalog)) {
  162 + return;
  163 + }
156 164 if (_catalog.caId === '1') {
157 165 this.$router.push('/#/pages/mall/product');
158 166 }
... ... @@ -172,7 +180,7 @@ export default {
172 180 }
173 181 this.loadMenuTree(_catalog)
174 182 },
175   -
  183 +
176 184 handleCommand(command) {
177 185 if (command === 'logout') {
178 186 // 处理退出登录
... ... @@ -260,7 +268,7 @@ export default {
260 268 console.log(_href, _tabName)
261 269 // 子菜单默认选中
262 270 this._setSelectedMenusChild(_href);
263   - if(_href.indexOf('.html') > -1){
  271 + if (_href.indexOf('.html') > -1) {
264 272 window.open(_href, '_blank')
265 273 return
266 274 }
... ... @@ -275,7 +283,16 @@ export default {
275 283 }
276 284 })
277 285 },
278   -
  286 + _showModelDiv: function (_catalog) {
  287 + console.log(_catalog)
  288 + if (_catalog.url.startsWith('?')) {
  289 + let _modelName = _catalog.url.substring(1, _catalog.url.length);
  290 + this.$refs[_modelName].open()
  291 + return true;
  292 + }
  293 + return false;
  294 + }
  295 +
279 296 }
280 297 }
281 298 </script>
... ...
src/views/oa/attendanceClassesStaffManageList.vue
... ... @@ -185,7 +185,7 @@ export default {
185 185 this.$refs.viewImage.open(url)
186 186 },
187 187 openStaffDetail(staff) {
188   - this.$router.push(`/staff/staffDetail?staffId=${staff.staffId}`)
  188 + this.$router.push(`/views/staff/staffDetail?staffId=${staff.staffId}`)
189 189 },
190 190 handleSizeChange(val) {
191 191 this.page.size = val
... ...
src/views/system/menuUserManageLang.js 0 → 100644
  1 +export const messages = {
  2 + en: {
  3 + menuUserManage: {
  4 + search: {
  5 + title: 'Search Conditions',
  6 + muId: 'Please enter ID',
  7 + name: 'Please enter menu name',
  8 + seq: 'Please enter sequence'
  9 + },
  10 + list: {
  11 + title: 'Common Menu'
  12 + },
  13 + table: {
  14 + muId: 'ID',
  15 + name: 'Menu',
  16 + icon: 'Icon',
  17 + seq: 'Sequence'
  18 + },
  19 + add: {
  20 + title: 'Add Menu',
  21 + menu: 'Menu',
  22 + menuPlaceholder: 'Required, please select menu',
  23 + icon: 'Icon',
  24 + seq: 'Sequence',
  25 + seqPlaceholder: 'Required, please enter sequence',
  26 + success: 'Add success',
  27 + error: 'Add failed'
  28 + },
  29 + delete: {
  30 + title: 'Confirm Operation',
  31 + confirm: 'Are you sure to delete this menu?',
  32 + success: 'Delete success',
  33 + error: 'Delete failed'
  34 + },
  35 + validate: {
  36 + menuRequired: 'Menu is required',
  37 + iconRequired: 'Icon is required',
  38 + seqRequired: 'Sequence is required'
  39 + },
  40 + fetchError: 'Failed to fetch menu data'
  41 + }
  42 + },
  43 + zh: {
  44 + menuUserManage: {
  45 + search: {
  46 + title: '查询条件',
  47 + muId: '请输入编号',
  48 + name: '请输入菜单',
  49 + seq: '请输入列顺序'
  50 + },
  51 + list: {
  52 + title: '常用菜单'
  53 + },
  54 + table: {
  55 + muId: '编号',
  56 + name: '菜单',
  57 + icon: '图标',
  58 + seq: '顺序'
  59 + },
  60 + add: {
  61 + title: '添加菜单',
  62 + menu: '菜单',
  63 + menuPlaceholder: '必填,请选择菜单',
  64 + icon: '图标',
  65 + seq: '列顺序',
  66 + seqPlaceholder: '必填,请填写列顺序',
  67 + success: '添加成功',
  68 + error: '添加失败'
  69 + },
  70 + delete: {
  71 + title: '请确认您的操作',
  72 + confirm: '确定删除常用菜单?',
  73 + success: '删除成功',
  74 + error: '删除失败'
  75 + },
  76 + validate: {
  77 + menuRequired: '菜单不能为空',
  78 + iconRequired: '图标不能为空',
  79 + seqRequired: '列顺序不能为空'
  80 + },
  81 + fetchError: '获取菜单数据失败'
  82 + }
  83 + }
  84 +}
0 85 \ No newline at end of file
... ...
src/views/system/menuUserManageList.vue 0 → 100644
  1 +<template>
  2 + <div class="menu-user-manage-container">
  3 + <!-- 查询条件 -->
  4 + <el-card class="search-wrapper">
  5 + <div slot="header" class="flex justify-between">
  6 + <span>{{ $t('menuUserManage.search.title') }}</span>
  7 + </div>
  8 + <el-row :gutter="20">
  9 + <el-col :span="6">
  10 + <el-input v-model="searchForm.muId" :placeholder="$t('menuUserManage.search.muId')" clearable
  11 + @keyup.enter.native="handleSearch" />
  12 + </el-col>
  13 + <el-col :span="6">
  14 + <el-input v-model="searchForm.name" :placeholder="$t('menuUserManage.search.name')" clearable
  15 + @keyup.enter.native="handleSearch" />
  16 + </el-col>
  17 + <el-col :span="6">
  18 + <el-input v-model="searchForm.seq" :placeholder="$t('menuUserManage.search.seq')" clearable
  19 + @keyup.enter.native="handleSearch" />
  20 + </el-col>
  21 + <el-col :span="6">
  22 + <el-button type="primary" @click="handleSearch">{{ $t('common.search') }}</el-button>
  23 + <el-button @click="handleReset">{{ $t('common.reset') }}</el-button>
  24 + </el-col>
  25 + </el-row>
  26 + </el-card>
  27 +
  28 + <!-- 常用菜单列表 -->
  29 + <el-card class="list-wrapper">
  30 + <div slot="header" class="flex justify-between">
  31 + <span>{{ $t('menuUserManage.list.title') }}</span>
  32 + <el-button type="primary" size="small" class="float-right" @click="handleAdd">
  33 + <i class="el-icon-plus"></i> {{ $t('common.add') }}
  34 + </el-button>
  35 + </div>
  36 +
  37 + <el-table v-loading="loading" :data="tableData" border style="width: 100%">
  38 + <el-table-column prop="muId" :label="$t('menuUserManage.table.muId')" align="center" />
  39 + <el-table-column prop="name" :label="$t('menuUserManage.table.name')" align="center" />
  40 + <el-table-column :label="$t('menuUserManage.table.icon')" align="center">
  41 + <template slot-scope="scope">
  42 + <i :class="scope.row.icon"></i>
  43 + </template>
  44 + </el-table-column>
  45 + <el-table-column prop="seq" :label="$t('menuUserManage.table.seq')" align="center" />
  46 + <el-table-column :label="$t('common.operation')" align="center" width="150">
  47 + <template slot-scope="scope">
  48 + <el-button size="mini" type="danger" @click="handleDelete(scope.row)">
  49 + {{ $t('common.delete') }}
  50 + </el-button>
  51 + </template>
  52 + </el-table-column>
  53 + </el-table>
  54 +
  55 + <el-pagination :current-page.sync="page.current" :page-sizes="[10, 20, 30, 50]" :page-size="page.size"
  56 + :total="page.total" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange"
  57 + @current-change="handleCurrentChange" />
  58 + </el-card>
  59 +
  60 + <!-- 添加菜单弹窗 -->
  61 + <add-menu-user ref="addMenuUser" @success="handleSuccess" />
  62 +
  63 + <!-- 删除菜单确认弹窗 -->
  64 + <delete-menu-user ref="deleteMenuUser" @success="handleSuccess" />
  65 + </div>
  66 +</template>
  67 +
  68 +<script>
  69 +import { listMenuUser } from '@/api/system/menuUserManageApi'
  70 +import AddMenuUser from '@/components/system/addMenuUser'
  71 +import DeleteMenuUser from '@/components/system/deleteMenuUser'
  72 +
  73 +export default {
  74 + name: 'MenuUserManageList',
  75 + components: {
  76 + AddMenuUser,
  77 + DeleteMenuUser
  78 + },
  79 + data() {
  80 + return {
  81 + loading: false,
  82 + searchForm: {
  83 + muId: '',
  84 + name: '',
  85 + seq: ''
  86 + },
  87 + tableData: [],
  88 + page: {
  89 + current: 1,
  90 + size: 10,
  91 + total: 0
  92 + }
  93 + }
  94 + },
  95 + created() {
  96 + this.getList()
  97 + },
  98 + methods: {
  99 + async getList() {
  100 + try {
  101 + this.loading = true
  102 + const params = {
  103 + page: this.page.current,
  104 + row: this.page.size,
  105 + ...this.searchForm
  106 + }
  107 + const { data, total } = await listMenuUser(params)
  108 + this.tableData = data
  109 + this.page.total = total
  110 + } catch (error) {
  111 + this.$message.error(this.$t('menuUserManage.fetchError'))
  112 + } finally {
  113 + this.loading = false
  114 + }
  115 + },
  116 + handleSearch() {
  117 + this.page.current = 1
  118 + this.getList()
  119 + },
  120 + handleReset() {
  121 + this.searchForm = {
  122 + muId: '',
  123 + name: '',
  124 + seq: ''
  125 + }
  126 + this.handleSearch()
  127 + },
  128 + handleAdd() {
  129 + this.$refs.addMenuUser.open()
  130 + },
  131 + handleDelete(row) {
  132 + this.$refs.deleteMenuUser.open(row)
  133 + },
  134 + handleSuccess() {
  135 + this.getList()
  136 + },
  137 + handleSizeChange(val) {
  138 + this.page.size = val
  139 + this.getList()
  140 + },
  141 + handleCurrentChange(val) {
  142 + this.page.current = val
  143 + this.getList()
  144 + }
  145 + }
  146 +}
  147 +</script>
  148 +
  149 +<style lang="scss" scoped>
  150 +.menu-user-manage-container {
  151 + padding: 20px;
  152 +
  153 + .search-wrapper {
  154 + margin-bottom: 20px;
  155 +
  156 + .el-row {
  157 + margin-bottom: -20px;
  158 + }
  159 +
  160 + .el-col {
  161 + margin-bottom: 20px;
  162 + }
  163 + }
  164 +
  165 + .list-wrapper {
  166 + .el-pagination {
  167 + margin-top: 20px;
  168 + text-align: right;
  169 + }
  170 + }
  171 +}
  172 +</style>
0 173 \ No newline at end of file
... ...
src/views/system/systemInfoManageLang.js
... ... @@ -17,7 +17,27 @@ export const messages = {
17 17 required: '{field} is required',
18 18 maxLength: '{field} cannot exceed {length} characters',
19 19 fetchError: 'Failed to fetch system information'
  20 + },
  21 + searchCommunityData: {
  22 + title: 'Search',
  23 + placeholder: 'Please enter house number, owner name, car number or family member name, etc.',
  24 + callInNumber: 'Calling number',
  25 + noData: 'Sorry, no data available',
  26 + inputTip: 'Please enter search criteria',
  27 + telNotConnected: 'Fixed telephone not connected',
  28 + noCallInNumber: 'No calling number returned from the telephone',
  29 + roomInfo: 'Room Information',
  30 + ownerInfo: 'Owner Information',
  31 + ownerMember: 'Owner Member',
  32 + communityCar: 'Community Vehicle',
  33 + memberCar: 'Member Vehicle',
  34 + contract: 'Contract',
  35 + repairOrder: 'Repair Order',
  36 + visitor: 'Visitor',
  37 + staff: 'Staff',
  38 + roomPlaceholder: 'Please enter room number like building-unit-room, e.g. 1-1-1'
20 39 }
  40 +
21 41 },
22 42 zh: {
23 43 systemInfo: {
... ... @@ -37,6 +57,25 @@ export const messages = {
37 57 required: '{field}不能为空',
38 58 maxLength: '{field}不能超过{length}个字符',
39 59 fetchError: '获取系统信息失败'
  60 + },
  61 + searchCommunityData: {
  62 + title: '搜索',
  63 + placeholder: '请输入房屋编号,业主名称,车辆编号或者家庭成员名称等',
  64 + callInNumber: '通话中号码',
  65 + noData: '抱歉,没有数据',
  66 + inputTip: '请输入查询条件',
  67 + telNotConnected: '未连接固定电话',
  68 + noCallInNumber: '座机未返回通话中号码',
  69 + roomInfo: '房屋信息',
  70 + ownerInfo: '业主信息',
  71 + ownerMember: '业主成员',
  72 + communityCar: '小区车辆',
  73 + memberCar: '成员车辆',
  74 + contract: '合同',
  75 + repairOrder: '报修工单',
  76 + visitor: '访客',
  77 + staff: '员工',
  78 + roomPlaceholder: '请输入房屋编号 楼栋-单元-房屋 如1-1-1'
40 79 }
41 80 }
42 81 }
43 82 \ No newline at end of file
... ...