Compare commits

..

52 Commits

Author SHA1 Message Date
sjl
72c37c2759 微调 2025-11-10 13:58:23 +08:00
sjl
d1eb7f2dad 设备录入微调 2025-11-10 13:10:15 +08:00
stt
308ceb1a03 暂态电能质量分析添加时间组件 2025-11-07 11:28:24 +08:00
stt
ad7b77ff92 稳态治理效果分析添加时间组件 2025-11-07 10:32:55 +08:00
stt
0e76ab66f3 添加查询条件 2025-11-06 16:21:39 +08:00
stt
24afa84f29 稳态电能质量分析页面同步时间组件 2025-11-06 16:11:12 +08:00
stt
a5f3571906 时间组件缓存提取出来 2025-11-06 14:55:56 +08:00
stt
d16d262d1a Merge branch 'main' of http://192.168.1.22:3000/Web/admin-govern 2025-11-06 14:24:20 +08:00
stt
593f2e2c66 内部时间组件设置默认值 2025-11-06 14:24:15 +08:00
sjl
d1e7aab876 微调 2025-11-05 15:12:06 +08:00
sjl
8a3e0263d2 Merge branch 'main' of http://192.168.1.22:3000/Web/admin-govern 2025-11-05 15:10:50 +08:00
sjl
35ce7314b0 微调 2025-11-05 15:10:44 +08:00
guanj
5538d18127 Merge branch 'main' of http://192.168.1.22:3000/Web/admin-govern 2025-11-05 15:09:09 +08:00
guanj
00dd79e000 修改echart y轴计算方式 2025-11-05 15:09:00 +08:00
stt
b5aff1a837 样式修改 2025-11-05 14:45:56 +08:00
guanj
1ec3bd11a0 修改半月报导出功能 2025-11-04 09:48:23 +08:00
guanj
d553754847 修改组态展示地址 2025-11-03 10:39:06 +08:00
guanj
dc44e16d4d 历史趋势添加缺失数据功能 2025-10-31 13:41:48 +08:00
sjl
13c0a28c95 在线设备录入左侧树微调 2025-10-31 11:23:28 +08:00
sjl
443d5ab2bd 节点展开 2025-10-31 10:53:17 +08:00
sjl
288f4254b0 治理设备状态 2025-10-31 10:23:22 +08:00
sjl
130db82e41 事件补召,前置调整 2025-10-30 16:35:49 +08:00
stt
55a30a323d 稳态、暂态电能质量,稳态质量效果各个弹框 2025-10-28 11:23:17 +08:00
stt
608be23687 暂态电能质量分析页面添加各个列表详情弹框 2025-10-27 08:50:03 +08:00
stt
8f8f2aad6e Merge branch 'main' of http://192.168.1.22:3000/Web/admin-govern 2025-10-24 16:26:20 +08:00
stt
a9dcb54286 iframe弹框样式修改 2025-10-24 16:26:17 +08:00
guanj
dede918f34 修改驾驶舱页面 2025-10-24 16:17:40 +08:00
sjl
d113df832d 微调 2025-10-24 11:17:18 +08:00
stt
f41af33413 iframe点击弹框,样式修改 2025-10-24 10:54:37 +08:00
stt
f2e328826d 接线图角落弹框 2025-10-23 15:48:42 +08:00
stt
b3154e894a 接线图角落弹框 2025-10-23 15:47:56 +08:00
stt
f3252f8a15 Merge branch 'main' of http://192.168.1.22:3000/Web/admin-govern 2025-10-23 14:33:58 +08:00
stt
7f5296daee iframe路径中去掉display 2025-10-23 14:33:45 +08:00
sjl
ed0800359d 补召表格高度 2025-10-22 16:05:51 +08:00
sjl
e97aa2cd0e 补召日志 2025-10-22 15:32:18 +08:00
sjl
f0adfe65a5 补召接口 2025-10-22 09:24:41 +08:00
sjl
a7b0982527 Merge branch 'main' of http://192.168.1.22:3000/Web/admin-govern
# Conflicts:
#	pnpm-lock.yaml
2025-10-21 16:30:45 +08:00
sjl
bf22f7b312 微调 2025-10-21 16:28:36 +08:00
sjl
43b5e4e497 在线补召 2025-10-21 16:27:49 +08:00
guanj
269d941828 修改表格高度 2025-10-21 16:11:00 +08:00
guanj
29b5cfa4db 调整实时数据页面样式 2025-10-21 16:01:33 +08:00
stt
987441f2df 实时数据页面 2025-10-21 14:56:27 +08:00
stt
a236693cf6 实时数据页面 2025-10-21 13:45:11 +08:00
guanj
f252422f65 修改打包报错问题 2025-10-21 10:21:15 +08:00
guanj
4bd192dabc 微调 2025-10-20 13:44:59 +08:00
guanj
e7612c4083 新增实时数据页面 2025-10-20 13:41:00 +08:00
guanj
676bb37bbe 驾驶舱页面绘制
绘制2、稳态电能质量分析、稳态治理效果分析、暂态电能质量分析页面
2025-10-20 13:25:30 +08:00
sjl
177e50de75 mac地址,推送调整 2025-10-20 09:42:01 +08:00
sjl
448d450936 微调 2025-10-17 14:41:58 +08:00
sjl
6e5e289271 台账推送 2025-10-17 14:37:31 +08:00
sjl
e7f38519b4 前置管理选中背景,重启,更新进程号 2025-10-17 10:19:48 +08:00
sjl
1211277a0d 前置管理 2025-10-17 09:55:43 +08:00
153 changed files with 32127 additions and 13290 deletions

10908
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,8 +26,10 @@
"element-plus": "^2.7.5", "element-plus": "^2.7.5",
"exceljs": "v4.4.0", "exceljs": "v4.4.0",
"file-saver": "v2.0.5", "file-saver": "v2.0.5",
"grid-layout-plus": "^1.1.0",
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",
"jquery": "^3.7.1", "jquery": "^3.7.1",
"jsencrypt": "^3.3.2",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
@@ -38,28 +40,30 @@
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1", "pinia-plugin-persistedstate": "^3.2.1",
"qs": "^6.12.0",
"screenfull": "^6.0.2", "screenfull": "^6.0.2",
"splitpanes": "3.1.5", "splitpanes": "^3.1.5",
"use-element-plus-theme": "^0.0.5", "use-element-plus-theme": "^0.0.5",
"vue": "^3.3.11", "vue": "^3.3.11",
"vue-draggable-resizable": "3.0.0-beta.2", "vue-draggable-resizable": "^3.0.0-beta.2",
"vue-i18n": "9.10.2",
"vue-router": "4", "vue-router": "4",
"vue-types": "^5.1.1",
"vxe-table": "^4.5.17", "vxe-table": "^4.5.17",
"vxe-table-plugin-export-xlsx": "^4.0.7", "vxe-table-plugin-export-xlsx": "^4.0.7",
"grid-layout-plus": "^1.1.0", "web-storage-cache": "^1.1.1",
"vue-i18n": "9.10.2",
"xe-utils": "^3.5.14" "xe-utils": "^3.5.14"
}, },
"devDependencies": { "devDependencies": {
"@purge-icons/generated": "^0.9.0", "@purge-icons/generated": "^0.9.0",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/node": "^20.10.5", "@types/node": "^20.10.5",
"@types/splitpanes": "2.2.6", "@types/splitpanes": "2.2.6",
"@vitejs/plugin-vue": "^4.5.2", "@vitejs/plugin-vue": "^4.5.2",
"@vitejs/plugin-vue-jsx": "^3.1.0", "@vitejs/plugin-vue-jsx": "^3.1.0",
"unplugin-auto-import": "^0.16.7",
"sass": "^1.69.5", "sass": "^1.69.5",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"unplugin-auto-import": "^0.16.7",
"vite": "^5.0.8", "vite": "^5.0.8",
"vue-tsc": "^1.8.25" "vue-tsc": "^1.8.25"
} }

2651
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -54,3 +54,12 @@ export function downloadWave(params: string) {
method: 'get' method: 'get'
}) })
} }
//根据事件补召波形
export function getFileByEventId(eventId: string) {
return createAxios({
url: '/cs-device-boot/icd/bzFileByEventId?eventId=' + eventId,
method: 'POST'
})
}

View File

@@ -130,3 +130,20 @@ export function updateLine(data: any) {
}) })
} }
//推送日志台账信息
export function pushLog() {
return createAxios({
url: '/cs-device-boot/csTerminalLogs/pushCldInfo',
method: 'post',
})
}
//查询推送结果
export function queryPushResult() {
return createAxios({
url: '/cs-device-boot/csTerminalReply/queryData',
method: 'post',
})
}

View File

@@ -0,0 +1,59 @@
import createAxios from '@/utils/request'
// 新增前置机
export function addNode(data: any) {
return createAxios({
url: '/cs-device-boot/node/addNode',
method: 'post',
data: data
})
}
//修改前置机
export function updateNode(data: any) {
return createAxios({
url: '/cs-device-boot/node/updateNode',
method: 'put',
data: data
})
}
//删除前置机
export function delNode(data: any) {
return createAxios({
url: '/cs-device-boot/node/delNode',
method: 'post',
params: { id: data }
})
}
//查询前置-进程-设备-设备树
export function nodeDeviceTree(data: any) {
return createAxios({
url: '/cs-device-boot/node/getProcessNoAndDeviceById',
method: 'get',
params: data
})
}
//重启前置机进程
export function restartProcess(data: any) {
return createAxios({
url: '/cs-device-boot/node/restartProcess',
method: 'post',
params: data
})
}
//更新进程号
export function updateProcess(data:any) {
return createAxios({
url: '/cs-device-boot/node/updateDevNode',
method: 'post',
data: data
})
}

View File

@@ -0,0 +1,20 @@
import createAxios from '@/utils/request'
//补召事件
export function eventRecall(data: any) {
return createAxios({
url: '/cs-device-boot/icd/bzEvent',
method: 'post',
data: data
})
}
//补召波形
export function fileRecall(data: any) {
return createAxios({
url: '/cs-device-boot/icd/bzFile',
method: 'post',
data: data
})
}

View File

@@ -69,3 +69,19 @@ export const queryById = (params: any) => {
params params
}) })
} }
// 更具id 查询驾驶舱页面
export const queryByPagePath = (params: any) => {
return createAxios({
url: '/system-boot/dashboard/queryByPagePath',
method: 'post',
params
})
}
// 根据用户id查询用户驾驶舱
export const getDashboardPageByUserId = (params: any) => {
return createAxios({
url: '/system-boot/dashboard/getDashboardPageByUserId',
method: 'post',
params
})
}

View File

@@ -0,0 +1,72 @@
import createAxios from '@/utils/request'
const SYSTEM_PREFIX = '/system-boot'
/**
* 上传文件
* @param file
*/
export const uploadFile = (file: any, path: string) => {
let form = new FormData()
form.append('file', file)
form.append('path', path)
return createAxios({
url: SYSTEM_PREFIX + '/file/upload',
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data'
},
data: form
})
}
/**
* 删除文件
*/
export const deleteFile = (filePath: string) => {
let form = new FormData()
form.append('filePath', filePath)
return createAxios({
url: SYSTEM_PREFIX + '/file/delete',
method: 'POST',
data: form
})
}
/**
* 下载文件
*/
export const downloadFile = (filePath: any) => {
// let form = new FormData()
// form.append('filePath', filePath)
return createAxios({
url: SYSTEM_PREFIX + '/file/download',
method: 'GET',
params: filePath,
responseType: 'blob'
})
}
/**
* 获取文件的短期url展示
*/
export const getFileUrl = (params:any) => {
let form = new FormData()
// form.append('filePath', filePath)
return createAxios({
url: SYSTEM_PREFIX + '/file/getFileUrl',
method: 'get',
params
})
}
/**
* 根据获取文件的一个短期url及文件名
*/
export const getFileNameAndFilePath = (query: any) => {
return createAxios({
url: SYSTEM_PREFIX + '/file/getFileVO',
method: 'GET',
params: query
})
}

BIN
src/assets/img/amplify.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
src/assets/img/reduce.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
src/assets/imgs/1x1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 B

BIN
src/assets/imgs/2x2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

BIN
src/assets/imgs/2x3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

BIN
src/assets/imgs/3x3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

BIN
src/assets/imgs/amplify.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
src/assets/imgs/avatar.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
src/assets/imgs/avatar.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
src/assets/imgs/bg02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

BIN
src/assets/imgs/bg1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
src/assets/imgs/ditu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
src/assets/imgs/ditu1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

BIN
src/assets/imgs/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
src/assets/imgs/profile.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

BIN
src/assets/imgs/reduce.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
src/assets/imgs/t_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 B

BIN
src/assets/imgs/t_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

BIN
src/assets/imgs/t_7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

BIN
src/assets/imgs/wechat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,3 @@
import Backtop from './src/Backtop.vue'
export { Backtop }

View File

@@ -0,0 +1,17 @@
<script lang="ts" setup>
import { ElBacktop } from 'element-plus'
import { useDesign } from '@/hooks/web/useDesign'
defineOptions({ name: 'BackTop' })
const { getPrefixCls, variables } = useDesign()
const prefixCls = getPrefixCls('backtop')
</script>
<template>
<ElBacktop
:class="`${prefixCls}-backtop`"
:target="`.${variables.namespace}-layout-content-scrollbar .${variables.elNamespace}-scrollbar__wrap`"
/>
</template>

View File

@@ -0,0 +1,111 @@
<template>
<ElDialog v-if="isModal" v-model="showSearch" :show-close="false" title="菜单搜索">
<el-select
filterable
:reserve-keyword="false"
remote
placeholder="请输入菜单内容"
:remote-method="remoteMethod"
style="width: 100%"
@change="handleChange"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</ElDialog>
<div v-else class="custom-hover" @click.stop="showTopSearch = !showTopSearch">
<Icon icon="ep:search" />
<el-select
filterable
:reserve-keyword="false"
remote
placeholder="请输入菜单内容"
:remote-method="remoteMethod"
class="overflow-hidden transition-all-600"
:class="showTopSearch ? '!w-220px ml2' : '!w-0'"
@change="handleChange"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
</template>
<script lang="ts" setup>
defineProps({
isModal: {
type: Boolean,
default: true
}
})
const router = useRouter() // 路由对象
const showSearch = ref(false) // 是否显示弹框
const showTopSearch = ref(false) // 是否显示顶部搜索框
const value: Ref = ref('') // 用户输入的值
const routers = router.getRoutes() // 路由对象
const options = computed(() => {
// 提示选项
if (!value.value) {
return []
}
const list = routers.filter((item: any) => {
if (item.meta.title?.indexOf(value.value) > -1 || item.path.indexOf(value.value) > -1) {
return true
}
})
return list.map((item) => {
return {
label: `${item.meta.title}${item.path}`,
value: item.path
}
})
})
function remoteMethod(data) {
// 这里可以执行相应的操作(例如打开搜索框等)
value.value = data
}
function handleChange(path) {
router.push({ path })
hiddenTopSearch()
}
function hiddenTopSearch() {
showTopSearch.value = false
}
onMounted(() => {
window.addEventListener('keydown', listenKey)
window.addEventListener('click', hiddenTopSearch)
})
onUnmounted(() => {
window.removeEventListener('keydown', listenKey)
window.removeEventListener('click', hiddenTopSearch)
})
// 监听 ctrl + k
function listenKey(event) {
if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
showSearch.value = !showSearch.value
// 这里可以执行相应的操作(例如打开搜索框等)
}
}
defineExpose({
openSearch: () => {
showSearch.value = true
}
})
</script>

View File

@@ -1,282 +1,282 @@
<template> <template>
<div class="w100"> <div class="w100">
<!-- el-select 的远程下拉只在有搜索词时才会加载数据显示出 option 列表 --> <!-- el-select 的远程下拉只在有搜索词时才会加载数据显示出 option 列表 -->
<!-- 使用 el-popover 在无数据/无搜索词时显示一个无数据的提醒 --> <!-- 使用 el-popover 在无数据/无搜索词时显示一个无数据的提醒 -->
<el-popover width="100%" placement="bottom" popper-class="remote-select-popper" <el-popover width="100%" placement="bottom" popper-class="remote-select-popper"
:visible="state.focusStatus && !state.loading && !state.keyword && !state.options.length" :visible="state.focusStatus && !state.loading && !state.keyword && !state.options.length"
:teleported="false" :content="$t('utils.No data')"> :teleported="false" :content="$t('utils.No data')">
<template #reference> <template #reference>
<el-select ref="selectRef" class="w100" @focus="onFocus" @blur="onBlur" <el-select ref="selectRef" class="w100" @focus="onFocus" @blur="onBlur"
:loading="state.loading || state.accidentBlur" :filterable="true" :remote="true" clearable :loading="state.loading || state.accidentBlur" :filterable="true" :remote="true" clearable
remote-show-suffix :remote-method="onLogKeyword" v-model.trim="state.value" @change="onChangeSelect" remote-show-suffix :remote-method="onLogKeyword" v-model.trim="state.value" @change="onChangeSelect"
:multiple="multiple" :key="state.selectKey" @clear="onClear" @visible-change="onVisibleChange" :multiple="multiple" :key="state.selectKey" @clear="onClear" @visible-change="onVisibleChange"
v-bind="$attrs"> v-bind="$attrs">
<el-option class="remote-select-option" v-for="item in state.options" :label="item[field]" <el-option class="remote-select-option" v-for="item in state.options" :label="item[field]"
:value="item[state.primaryKey].toString()" :key="item[state.primaryKey]"> :value="item[state.primaryKey].toString()" :key="item[state.primaryKey]">
<el-tooltip placement="right" effect="light" v-if="!isEmpty(tooltipParams)"> <el-tooltip placement="right" effect="light" v-if="!isEmpty(tooltipParams)">
<template #content> <template #content>
<p v-for="(tooltipParam, key) in tooltipParams" :key="key">{{ key }}: {{ <p v-for="(tooltipParam, key) in tooltipParams" :key="key">{{ key }}: {{
item[tooltipParam] }}</p> item[tooltipParam] }}</p>
</template> </template>
<div>{{ item[field] }}</div> <div>{{ item[field] }}</div>
</el-tooltip> </el-tooltip>
</el-option> </el-option>
<el-pagination v-if="state.total" :currentPage="state.currentPage" :page-size="state.pageSize" <el-pagination v-if="state.total" :currentPage="state.currentPage" :page-size="state.pageSize"
class="select-pagination" layout="->, prev, next" :total="state.total" class="select-pagination" layout="->, prev, next" :total="state.total"
@current-change="onSelectCurrentPageChange" /> @current-change="onSelectCurrentPageChange" />
</el-select> </el-select>
</template> </template>
</el-popover> </el-popover>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, watch, onMounted, onUnmounted, ref, nextTick, getCurrentInstance, toRaw } from 'vue' import { reactive, watch, onMounted, onUnmounted, ref, nextTick, getCurrentInstance, toRaw } from 'vue'
import { getSelectData } from '@/api/common' // import { getSelectData } from '@/api/common'
import { uuid } from '@/utils/random' import { uuid } from '@/utils/random'
import type { ElSelect } from 'element-plus' import type { ElSelect } from 'element-plus'
import { isEmpty } from 'lodash-es' import { isEmpty } from 'lodash-es'
import { getArrayKey } from '@/utils/common' // import { getArrayKey } from '@/utils/common'
const selectRef = ref<InstanceType<typeof ElSelect> | undefined>() const selectRef = ref<InstanceType<typeof ElSelect> | undefined>()
type ElSelectProps = Partial<InstanceType<typeof ElSelect>['$props']> type ElSelectProps = Partial<InstanceType<typeof ElSelect>['$props']>
type valType = string | number | string[] | number[] type valType = string | number | string[] | number[]
interface Props extends /* @vue-ignore */ ElSelectProps { interface Props extends /* @vue-ignore */ ElSelectProps {
pk?: string pk?: string
field?: string field?: string
params?: anyObj params?: anyObj
multiple?: boolean multiple?: boolean
remoteUrl: string remoteUrl: string
modelValue: valType modelValue: valType
labelFormatter?: (optionData: anyObj, optionKey: string) => string labelFormatter?: (optionData: anyObj, optionKey: string) => string
tooltipParams?: anyObj tooltipParams?: anyObj
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
pk: 'id', pk: 'id',
field: 'name', field: 'name',
params: () => { params: () => {
return {} return {}
}, },
remoteUrl: '', remoteUrl: '',
modelValue: '', modelValue: '',
multiple: false, multiple: false,
tooltipParams: () => { tooltipParams: () => {
return {} return {}
}, },
}) })
const state: { const state: {
// 主表字段名(不带表别名) // 主表字段名(不带表别名)
primaryKey: string primaryKey: string
options: anyObj[] options: anyObj[]
loading: boolean loading: boolean
total: number total: number
currentPage: number currentPage: number
pageSize: number pageSize: number
params: anyObj params: anyObj
keyword: string keyword: string
value: valType value: valType
selectKey: string selectKey: string
initializeData: boolean initializeData: boolean
accidentBlur: boolean accidentBlur: boolean
focusStatus: boolean focusStatus: boolean
} = reactive({ } = reactive({
primaryKey: props.pk, primaryKey: props.pk,
options: [], options: [],
loading: false, loading: false,
total: 0, total: 0,
currentPage: 1, currentPage: 1,
pageSize: 10, pageSize: 10,
params: props.params, params: props.params,
keyword: '', keyword: '',
value: props.modelValue ? props.modelValue : '', value: props.modelValue ? props.modelValue : '',
selectKey: uuid(), selectKey: uuid(),
initializeData: false, initializeData: false,
accidentBlur: false, accidentBlur: false,
focusStatus: false, focusStatus: false,
}) })
let io: null | IntersectionObserver = null let io: null | IntersectionObserver = null
const instance = getCurrentInstance() const instance = getCurrentInstance()
const emits = defineEmits<{ const emits = defineEmits<{
(e: 'update:modelValue', value: valType): void (e: 'update:modelValue', value: valType): void
(e: 'row', value: any): void (e: 'row', value: any): void
}>() }>()
const onChangeSelect = (val: valType) => { const onChangeSelect = (val: valType) => {
emits('update:modelValue', val) emits('update:modelValue', val)
if (typeof instance?.vnode.props?.onRow == 'function') { if (typeof instance?.vnode.props?.onRow == 'function') {
let pkArr = props.pk.split('.') let pkArr = props.pk.split('.')
let pk = pkArr[pkArr.length - 1] let pk = pkArr[pkArr.length - 1]
if (typeof val == 'number' || typeof val == 'string') { if (typeof val == 'number' || typeof val == 'string') {
const dataKey = getArrayKey(state.options, pk, val.toString()) // const dataKey = getArrayKey(state.options, pk, val.toString())
emits('row', dataKey ? toRaw(state.options[dataKey]) : {}) // emits('row', dataKey ? toRaw(state.options[dataKey]) : {})
} else { } else {
const valueArr = [] // const valueArr = []
for (const key in val) { // for (const key in val) {
let dataKey = getArrayKey(state.options, pk, val[key].toString()) // let dataKey = getArrayKey(state.options, pk, val[key].toString())
if (dataKey) valueArr.push(toRaw(state.options[dataKey])) // if (dataKey) valueArr.push(toRaw(state.options[dataKey]))
} // }
emits('row', valueArr) // emits('row', valueArr)
} }
} }
} }
const onVisibleChange = (val: boolean) => { const onVisibleChange = (val: boolean) => {
// 保持面板状态和焦点状态一致 // 保持面板状态和焦点状态一致
if (!val) { if (!val) {
nextTick(() => { nextTick(() => {
selectRef.value?.blur() selectRef.value?.blur()
}) })
} }
} }
const onFocus = () => { const onFocus = () => {
state.focusStatus = true state.focusStatus = true
if (selectRef.value?.query != state.keyword) { if (selectRef.value?.query != state.keyword) {
state.keyword = '' state.keyword = ''
state.initializeData = false state.initializeData = false
// el-select 自动清理搜索词会产生意外的脱焦 // el-select 自动清理搜索词会产生意外的脱焦
state.accidentBlur = true state.accidentBlur = true
} }
if (!state.initializeData) { if (!state.initializeData) {
getData() getData()
} }
} }
const onBlur = () => { const onBlur = () => {
state.focusStatus = false state.focusStatus = false
} }
const onClear = () => { const onClear = () => {
state.keyword = '' state.keyword = ''
state.initializeData = false state.initializeData = false
} }
const onLogKeyword = (q: string) => { const onLogKeyword = (q: string) => {
if (state.keyword != q) { if (state.keyword != q) {
state.keyword = q state.keyword = q
getData() getData()
} }
} }
const getData = (initValue: valType = '') => { const getData = (initValue: valType = '') => {
state.loading = true state.loading = true
state.params.page = state.currentPage state.params.page = state.currentPage
state.params.initKey = props.pk state.params.initKey = props.pk
state.params.initValue = initValue state.params.initValue = initValue
getSelectData(props.remoteUrl, state.keyword, state.params) getSelectData(props.remoteUrl, state.keyword, state.params)
.then((res) => { .then((res) => {
let initializeData = true let initializeData = true
let opts = res.data.options ? res.data.options : res.data.list let opts = res.data.options ? res.data.options : res.data.list
if (typeof props.labelFormatter == 'function') { if (typeof props.labelFormatter == 'function') {
for (const key in opts) { for (const key in opts) {
opts[key][props.field] = props.labelFormatter(opts[key], key) opts[key][props.field] = props.labelFormatter(opts[key], key)
} }
} }
state.options = opts state.options = opts
state.total = res.data.total ?? 0 state.total = res.data.total ?? 0
if (initValue) { if (initValue) {
// 重新渲染组件,确保在赋值前,opts已加载到-兼容 modelValue 更新 // 重新渲染组件,确保在赋值前,opts已加载到-兼容 modelValue 更新
state.selectKey = uuid() state.selectKey = uuid()
initializeData = false initializeData = false
} }
state.loading = false state.loading = false
state.initializeData = initializeData state.initializeData = initializeData
if (state.accidentBlur) { if (state.accidentBlur) {
nextTick(() => { nextTick(() => {
const inputEl = selectRef.value?.$el.querySelector('.el-select__tags .el-select__input') const inputEl = selectRef.value?.$el.querySelector('.el-select__tags .el-select__input')
inputEl && inputEl.focus() inputEl && inputEl.focus()
state.accidentBlur = false state.accidentBlur = false
}) })
} }
}) })
.catch(() => { .catch(() => {
state.loading = false state.loading = false
}) })
} }
const onSelectCurrentPageChange = (val: number) => { const onSelectCurrentPageChange = (val: number) => {
state.currentPage = val state.currentPage = val
getData() getData()
} }
const initDefaultValue = () => { const initDefaultValue = () => {
if (state.value) { if (state.value) {
// number[]转string[]确保默认值能够选中 // number[]转string[]确保默认值能够选中
if (typeof state.value === 'object') { if (typeof state.value === 'object') {
for (const key in state.value as string[]) { for (const key in state.value as string[]) {
state.value[key] = state.value[key].toString() state.value[key] = state.value[key].toString()
} }
} else if (typeof state.value === 'number') { } else if (typeof state.value === 'number') {
state.value = state.value.toString() state.value = state.value.toString()
} }
getData(state.value) getData(state.value)
} }
} }
onMounted(() => { onMounted(() => {
if (props.pk.indexOf('.') > 0) { if (props.pk.indexOf('.') > 0) {
let pk = props.pk.split('.') let pk = props.pk.split('.')
state.primaryKey = pk[1] ? pk[1] : pk[0] state.primaryKey = pk[1] ? pk[1] : pk[0]
} }
initDefaultValue() initDefaultValue()
setTimeout(() => { setTimeout(() => {
if (window?.IntersectionObserver) { if (window?.IntersectionObserver) {
io = new IntersectionObserver((entries) => { io = new IntersectionObserver((entries) => {
for (const key in entries) { for (const key in entries) {
if (!entries[key].isIntersecting) selectRef.value?.blur() if (!entries[key].isIntersecting) selectRef.value?.blur()
} }
}) })
if (selectRef.value?.$el instanceof Element) { if (selectRef.value?.$el instanceof Element) {
io.observe(selectRef.value.$el) io.observe(selectRef.value.$el)
} }
} }
}, 500) }, 500)
}) })
onUnmounted(() => { onUnmounted(() => {
io?.disconnect() io?.disconnect()
}) })
watch( watch(
() => props.modelValue, () => props.modelValue,
(newVal) => { (newVal) => {
if (String(state.value) != String(newVal)) { if (String(state.value) != String(newVal)) {
state.value = newVal ? newVal : '' state.value = newVal ? newVal : ''
initDefaultValue() initDefaultValue()
} }
} }
) )
const getSelectRef = () => { const getSelectRef = () => {
return selectRef.value return selectRef.value
} }
const focus = () => { const focus = () => {
selectRef.value?.focus() selectRef.value?.focus()
} }
const blur = () => { const blur = () => {
selectRef.value?.blur() selectRef.value?.blur()
} }
defineExpose({ defineExpose({
blur, blur,
focus, focus,
getSelectRef, getSelectRef,
}) })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
:deep(.remote-select-popper) { :deep(.remote-select-popper) {
text-align: center; text-align: center;
} }
.remote-select-option { .remote-select-option {
white-space: pre; white-space: pre;
} }
</style> </style>

View File

@@ -1,414 +1,414 @@
<script lang="ts"> <script lang="ts">
import type { PropType, VNode } from 'vue' import type { PropType, VNode } from 'vue'
import type { modelValueTypes, InputAttr, InputData } from '@/components/baInput' import type { modelValueTypes, InputAttr, InputData } from '@/components/baInput'
import { createVNode, resolveComponent, defineComponent, computed, reactive } from 'vue' import { createVNode, resolveComponent, defineComponent, computed, reactive } from 'vue'
import { inputTypes } from '@/components/baInput' import { inputTypes } from '@/components/baInput'
import Array from '@/components/baInput/components/array.vue' import Array from '@/components/baInput/components/array.vue'
import RemoteSelect from '@/components/baInput/components/remoteSelect.vue' import RemoteSelect from '@/components/baInput/components/remoteSelect.vue'
import IconSelector from '@/components/baInput/components/iconSelector.vue' import IconSelector from '@/components/baInput/components/iconSelector.vue'
import Editor from '@/components/baInput/components/editor.vue' import Editor from '@/components/baInput/components/editor.vue'
import { getArea } from '@/api/common' // import { getArea } from '@/api/common'
export default defineComponent({ export default defineComponent({
name: 'baInput', name: 'baInput',
props: { props: {
// 输入框类型,支持的输入框见 inputTypes // 输入框类型,支持的输入框见 inputTypes
type: { type: {
type: String, type: String,
required: true, required: true,
validator: (value: string) => { validator: (value: string) => {
return inputTypes.includes(value) return inputTypes.includes(value)
}, },
}, },
// 双向绑定值 // 双向绑定值
modelValue: { modelValue: {
type: null, type: null,
required: true, required: true,
}, },
// 输入框的附加属性 // 输入框的附加属性
attr: { attr: {
type: Object as PropType<InputAttr>, type: Object as PropType<InputAttr>,
default: () => {}, default: () => {},
}, },
// 额外数据,radio、checkbox的选项等数据 // 额外数据,radio、checkbox的选项等数据
data: { data: {
type: Object as PropType<InputData>, type: Object as PropType<InputData>,
default: () => {}, default: () => {},
}, },
}, },
emits: ['update:modelValue'], emits: ['update:modelValue'],
setup(props, { emit }) { setup(props, { emit }) {
const onValueUpdate = (value: modelValueTypes) => { const onValueUpdate = (value: modelValueTypes) => {
emit('update:modelValue', value) emit('update:modelValue', value)
} }
// 子级元素属性 // 子级元素属性
let childrenAttr = props.data && props.data.childrenAttr ? props.data.childrenAttr : {} let childrenAttr = props.data && props.data.childrenAttr ? props.data.childrenAttr : {}
// string number textarea password // string number textarea password
const sntp = () => { const sntp = () => {
return () => return () =>
createVNode(resolveComponent('el-input'), { createVNode(resolveComponent('el-input'), {
type: props.type == 'string' ? 'text' : props.type, type: props.type == 'string' ? 'text' : props.type,
...props.attr, ...props.attr,
modelValue: props.modelValue, modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate, 'onUpdate:modelValue': onValueUpdate,
}) })
} }
// radio checkbox // radio checkbox
const rc = () => { const rc = () => {
if (!props.data || !props.data.content) { if (!props.data || !props.data.content) {
console.warn('请传递 ' + props.type + '的 content') console.warn('请传递 ' + props.type + '的 content')
} }
let vNode: VNode[] = [] let vNode: VNode[] = []
for (const key in props.data.content) { for (const key in props.data.content) {
vNode.push( vNode.push(
createVNode( createVNode(
resolveComponent('el-' + props.type), resolveComponent('el-' + props.type),
{ {
label: key, label: key,
...childrenAttr, ...childrenAttr,
}, },
() => props.data.content[key] () => props.data.content[key]
) )
) )
} }
return () => { return () => {
const valueComputed = computed(() => { const valueComputed = computed(() => {
if (props.type == 'radio') { if (props.type == 'radio') {
if (props.modelValue == undefined) return '' if (props.modelValue == undefined) return ''
return '' + props.modelValue return '' + props.modelValue
} else { } else {
let modelValueArr: anyObj = [] let modelValueArr: anyObj = []
for (const key in props.modelValue) { for (const key in props.modelValue) {
modelValueArr[key] = '' + props.modelValue[key] modelValueArr[key] = '' + props.modelValue[key]
} }
return modelValueArr return modelValueArr
} }
}) })
return createVNode( return createVNode(
resolveComponent('el-' + props.type + '-group'), resolveComponent('el-' + props.type + '-group'),
{ {
...props.attr, ...props.attr,
modelValue: valueComputed.value, modelValue: valueComputed.value,
'onUpdate:modelValue': onValueUpdate, 'onUpdate:modelValue': onValueUpdate,
}, },
() => vNode () => vNode
) )
} }
} }
// select selects // select selects
const select = () => { const select = () => {
let vNode: VNode[] = [] let vNode: VNode[] = []
if (!props.data || !props.data.content) { if (!props.data || !props.data.content) {
console.warn('请传递 ' + props.type + '的 content') console.warn('请传递 ' + props.type + '的 content')
} }
for (const key in props.data.content) { for (const key in props.data.content) {
vNode.push( vNode.push(
createVNode(resolveComponent('el-option'), { createVNode(resolveComponent('el-option'), {
key: key, key: key,
label: props.data.content[key], label: props.data.content[key],
value: key, value: key,
...childrenAttr, ...childrenAttr,
}) })
) )
} }
return () => { return () => {
const valueComputed = computed(() => { const valueComputed = computed(() => {
if (props.type == 'select') { if (props.type == 'select') {
if (props.modelValue == undefined) return '' if (props.modelValue == undefined) return ''
return '' + props.modelValue return '' + props.modelValue
} else { } else {
let modelValueArr: anyObj = [] let modelValueArr: anyObj = []
for (const key in props.modelValue) { for (const key in props.modelValue) {
modelValueArr[key] = '' + props.modelValue[key] modelValueArr[key] = '' + props.modelValue[key]
} }
return modelValueArr return modelValueArr
} }
}) })
return createVNode( return createVNode(
resolveComponent('el-select'), resolveComponent('el-select'),
{ {
class: 'w100', class: 'w100',
multiple: props.type == 'select' ? false : true, multiple: props.type == 'select' ? false : true,
clearable: true, clearable: true,
...props.attr, ...props.attr,
modelValue: valueComputed.value, modelValue: valueComputed.value,
'onUpdate:modelValue': onValueUpdate, 'onUpdate:modelValue': onValueUpdate,
}, },
() => vNode () => vNode
) )
} }
} }
// datetime // datetime
const datetime = () => { const datetime = () => {
let valueFormat = 'YYYY-MM-DD HH:mm:ss' let valueFormat = 'YYYY-MM-DD HH:mm:ss'
switch (props.type) { switch (props.type) {
case 'date': case 'date':
valueFormat = 'YYYY-MM-DD' valueFormat = 'YYYY-MM-DD'
break break
case 'year': case 'year':
valueFormat = 'YYYY' valueFormat = 'YYYY'
break break
} }
return () => return () =>
createVNode(resolveComponent('el-date-picker'), { createVNode(resolveComponent('el-date-picker'), {
class: 'w100', class: 'w100',
type: props.type, type: props.type,
'value-format': valueFormat, 'value-format': valueFormat,
...props.attr, ...props.attr,
modelValue: props.modelValue, modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate, 'onUpdate:modelValue': onValueUpdate,
}) })
} }
// remoteSelect remoteSelects // remoteSelect remoteSelects
const remoteSelect = () => { const remoteSelect = () => {
return () => return () =>
createVNode(RemoteSelect, { createVNode(RemoteSelect, {
modelValue: props.modelValue, modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate, 'onUpdate:modelValue': onValueUpdate,
multiple: props.type == 'remoteSelect' ? false : true, multiple: props.type == 'remoteSelect' ? false : true,
...props.attr, ...props.attr,
}) })
} }
const buildFun = new Map([ const buildFun = new Map([
['string', sntp], ['string', sntp],
['number', sntp], ['number', sntp],
['textarea', sntp], ['textarea', sntp],
['password', sntp], ['password', sntp],
['radio', rc], ['radio', rc],
['checkbox', rc], ['checkbox', rc],
[ [
'switch', 'switch',
() => { () => {
const valueType = computed(() => typeof props.modelValue) const valueType = computed(() => typeof props.modelValue)
const valueComputed = computed(() => { const valueComputed = computed(() => {
if (valueType.value === 'boolean') { if (valueType.value === 'boolean') {
return props.modelValue return props.modelValue
} else { } else {
let valueTmp = parseInt(props.modelValue as string) let valueTmp = parseInt(props.modelValue as string)
return isNaN(valueTmp) || valueTmp <= 0 ? false : true return isNaN(valueTmp) || valueTmp <= 0 ? false : true
} }
}) })
return () => return () =>
createVNode(resolveComponent('el-switch'), { createVNode(resolveComponent('el-switch'), {
...props.attr, ...props.attr,
modelValue: valueComputed.value, modelValue: valueComputed.value,
'onUpdate:modelValue': (value: boolean) => { 'onUpdate:modelValue': (value: boolean) => {
let newValue: boolean | string | number = value let newValue: boolean | string | number = value
switch (valueType.value) { switch (valueType.value) {
case 'string': case 'string':
newValue = value ? '1' : '0' newValue = value ? '1' : '0'
break break
case 'number': case 'number':
newValue = value ? 1 : 0 newValue = value ? 1 : 0
} }
emit('update:modelValue', newValue) emit('update:modelValue', newValue)
}, },
}) })
}, },
], ],
['datetime', datetime], ['datetime', datetime],
[ [
'year', 'year',
() => { () => {
return () => { return () => {
const valueComputed = computed(() => (!props.modelValue ? null : '' + props.modelValue)) const valueComputed = computed(() => (!props.modelValue ? null : '' + props.modelValue))
return createVNode(resolveComponent('el-date-picker'), { return createVNode(resolveComponent('el-date-picker'), {
class: 'w100', class: 'w100',
type: props.type, type: props.type,
'value-format': 'YYYY', 'value-format': 'YYYY',
...props.attr, ...props.attr,
modelValue: valueComputed.value, modelValue: valueComputed.value,
'onUpdate:modelValue': onValueUpdate, 'onUpdate:modelValue': onValueUpdate,
}) })
} }
}, },
], ],
['date', datetime], ['date', datetime],
[ [
'time', 'time',
() => { () => {
const valueComputed = computed(() => { const valueComputed = computed(() => {
if (props.modelValue instanceof Date) { if (props.modelValue instanceof Date) {
return props.modelValue return props.modelValue
} else if (!props.modelValue) { } else if (!props.modelValue) {
return '' return ''
} else { } else {
let date = new Date() let date = new Date()
return new Date(date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate() + ' ' + props.modelValue) return new Date(date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate() + ' ' + props.modelValue)
} }
}) })
return () => return () =>
createVNode(resolveComponent('el-time-picker'), { createVNode(resolveComponent('el-time-picker'), {
class: 'w100', class: 'w100',
clearable: true, clearable: true,
format: 'HH:mm:ss', format: 'HH:mm:ss',
...props.attr, ...props.attr,
modelValue: valueComputed.value, modelValue: valueComputed.value,
'onUpdate:modelValue': onValueUpdate, 'onUpdate:modelValue': onValueUpdate,
}) })
}, },
], ],
['select', select], ['select', select],
['selects', select], ['selects', select],
[ [
'array', 'array',
() => { () => {
return () => return () =>
createVNode(Array, { createVNode(Array, {
modelValue: props.modelValue, modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate, 'onUpdate:modelValue': onValueUpdate,
...props.attr, ...props.attr,
}) })
}, },
], ],
['remoteSelect', remoteSelect], ['remoteSelect', remoteSelect],
['remoteSelects', remoteSelect], ['remoteSelects', remoteSelect],
[ [
'city', 'city',
() => { () => {
type Node = { value?: number; label?: string; leaf?: boolean } type Node = { value?: number; label?: string; leaf?: boolean }
let maxLevel = props.data && props.data.level ? props.data.level - 1 : 2 let maxLevel = props.data && props.data.level ? props.data.level - 1 : 2
const lastLazyValue: { const lastLazyValue: {
value: string | number[] | unknown value: string | number[] | unknown
nodes: Node[] nodes: Node[]
key: string key: string
currentRequest: any currentRequest: any
} = reactive({ } = reactive({
value: 'ready', value: 'ready',
nodes: [], nodes: [],
key: '', key: '',
currentRequest: null, currentRequest: null,
}) })
// 请求到的node备份-s // 请求到的node备份-s
let nodeEbak: anyObj = {} let nodeEbak: anyObj = {}
const getNodes = (level: number, key: string) => { const getNodes = (level: number, key: string) => {
if (nodeEbak[level] && nodeEbak[level][key]) { if (nodeEbak[level] && nodeEbak[level][key]) {
return nodeEbak[level][key] return nodeEbak[level][key]
} }
return false return false
} }
const setNodes = (level: number, key: string, nodes: Node[] = []) => { const setNodes = (level: number, key: string, nodes: Node[] = []) => {
if (!nodeEbak[level]) { if (!nodeEbak[level]) {
nodeEbak[level] = {} nodeEbak[level] = {}
} }
nodeEbak[level][key] = nodes nodeEbak[level][key] = nodes
} }
// 请求到的node备份-e // 请求到的node备份-e
return () => return () =>
createVNode(resolveComponent('el-cascader'), { createVNode(resolveComponent('el-cascader'), {
modelValue: props.modelValue, modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate, 'onUpdate:modelValue': onValueUpdate,
class: 'w100', class: 'w100',
clearable: true, clearable: true,
props: { props: {
lazy: true, lazy: true,
lazyLoad(node: any, resolve: any) { lazyLoad(node: any, resolve: any) {
// lazyLoad会频繁触发,在本地存储请求结果,供重复触发时直接读取 // lazyLoad会频繁触发,在本地存储请求结果,供重复触发时直接读取
const { level, pathValues } = node const { level, pathValues } = node
let key = pathValues.join(',') let key = pathValues.join(',')
key = key ? key : 'init' key = key ? key : 'init'
let locaNode = getNodes(level, key) let locaNode = getNodes(level, key)
if (locaNode) { if (locaNode) {
return resolve(locaNode) return resolve(locaNode)
} }
if (lastLazyValue.key == key && lastLazyValue.value == props.modelValue) { if (lastLazyValue.key == key && lastLazyValue.value == props.modelValue) {
if (lastLazyValue.currentRequest) { if (lastLazyValue.currentRequest) {
return lastLazyValue.currentRequest return lastLazyValue.currentRequest
} }
return resolve(lastLazyValue.nodes) return resolve(lastLazyValue.nodes)
} }
let nodes: Node[] = [] let nodes: Node[] = []
lastLazyValue.key = key lastLazyValue.key = key
lastLazyValue.value = props.modelValue lastLazyValue.value = props.modelValue
lastLazyValue.currentRequest = getArea(pathValues).then((res) => { // lastLazyValue.currentRequest = getArea(pathValues).then((res) => {
let toStr = false // let toStr = false
if (props.modelValue && typeof (props.modelValue as anyObj)[0] === 'string') { // if (props.modelValue && typeof (props.modelValue as anyObj)[0] === 'string') {
toStr = true // toStr = true
} // }
for (const key in res.data) { // for (const key in res.data) {
if (toStr) { // if (toStr) {
res.data[key].value = res.data[key].value.toString() // res.data[key].value = res.data[key].value.toString()
} // }
res.data[key].leaf = level >= maxLevel // res.data[key].leaf = level >= maxLevel
nodes.push(res.data[key]) // nodes.push(res.data[key])
} // }
lastLazyValue.nodes = nodes // lastLazyValue.nodes = nodes
lastLazyValue.currentRequest = null // lastLazyValue.currentRequest = null
setNodes(level, key, nodes) // setNodes(level, key, nodes)
resolve(nodes) // resolve(nodes)
}) // })
}, },
}, },
...props.attr, ...props.attr,
}) })
}, },
], ],
['image', upload], ['image', upload],
['images', upload], ['images', upload],
['file', upload], ['file', upload],
['files', upload], ['files', upload],
[ [
'icon', 'icon',
() => { () => {
return () => return () =>
createVNode(IconSelector, { createVNode(IconSelector, {
modelValue: props.modelValue, modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate, 'onUpdate:modelValue': onValueUpdate,
...props.attr, ...props.attr,
}) })
}, },
], ],
[ [
'color', 'color',
() => { () => {
return () => return () =>
createVNode(resolveComponent('el-color-picker'), { createVNode(resolveComponent('el-color-picker'), {
modelValue: props.modelValue, modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate, 'onUpdate:modelValue': onValueUpdate,
...props.attr, ...props.attr,
}) })
}, },
], ],
[ [
'editor', 'editor',
() => { () => {
return () => return () =>
createVNode(Editor, { createVNode(Editor, {
modelValue: props.modelValue, modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate, 'onUpdate:modelValue': onValueUpdate,
...props.attr, ...props.attr,
}) })
}, },
], ],
[ [
'default', 'default',
() => { () => {
console.warn('暂不支持' + props.type + '的输入框类型,你可以自行在 BaInput 组件内添加逻辑') console.warn('暂不支持' + props.type + '的输入框类型,你可以自行在 BaInput 组件内添加逻辑')
}, },
], ],
]) ])
let action = buildFun.get(props.type) || buildFun.get('default') let action = buildFun.get(props.type) || buildFun.get('default')
return action!.call(this) return action!.call(this)
}, },
}) })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.ba-upload-image :deep(.el-upload--picture-card) { .ba-upload-image :deep(.el-upload--picture-card) {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.ba-upload-file :deep(.el-upload-list) { .ba-upload-file :deep(.el-upload-list) {
margin-left: -10px; margin-left: -10px;
} }
</style> </style>

View File

@@ -0,0 +1,463 @@
<template>
<div>
<!--F47曲线 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader>
<el-descriptions class="mt2" direction="vertical" :column="4" border>
<el-descriptions-item align="center" label="名称">{{ data.name }}</el-descriptions-item>
<el-descriptions-item align="center" label="事件总数">{{ data.gs }}</el-descriptions-item>
<el-descriptions-item align="center" label="可容忍">{{ data.krr }}</el-descriptions-item>
<el-descriptions-item align="center" label="不可容忍">{{ data.bkrr }}</el-descriptions-item>
</el-descriptions>
<my-echart
ref="chartRef"
class="tall"
:options="echartList"
:style="{ width: prop.width, height: `calc(${prop.height} - 80px - ${headerHeight}px + ${fullscreen ? 0 : 56}px)` }"
@chart-click="handleChartClick"
/>
<el-dialog v-model="isWaveCharts" draggable title="瞬时/RMS波形" append-to-body width="70%">
<waveFormAnalysis v-loading="loading" v-if="isWaveCharts" ref="waveFormAnalysisRef"
@handleHideCharts="isWaveCharts = false" :wp="wp" />
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import waveFormAnalysis from '@/views/govern/device/control/tabs/components/waveFormAnalysis.vue';
import TableHeader from '@/components/table/header/index.vue'
import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({
w: { type: String },
h: { type: String },
width: { type: String },
height: { type: String },
timeKey: { type: String },
timeValue: { type: Object }
})
const headerHeight = ref(57)
const route = useRoute()
const timeCacheStore = useTimeCacheStore()
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height
// 如果有传入 datePicker 的值
if (datePickerValue) {
// 更新表格参数
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
}
}
// 计算是否全屏展示
const fullscreen = computed(() => {
const w = Number(prop.w)
const h = Number(prop.h)
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
// 执行相应逻辑
return true
} else {
return false
}
})
const echartList = ref({})
const chartRef = ref()
// 波形
const isWaveCharts = ref(false)
const loading = ref(false)
const wp = ref({})
const OverLimitDetailsRef = ref()
const data = reactive({
name: '事件个数',
gs: 0,
krr: 0,
bkrr: 0
})
const tableStore: any = new TableStore({
url: '/user-boot/dept/deptTree',
method: 'POST',
showPage: false,
column: [],
beforeSearchFun: () => {
// 尝试从缓存获取时间值
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
},
loadCallback: () => {
let res = {
data: { totalNumberOfEvents: 0, voltageToleranceCurveDataList: [] }
}
const gongData = gongfunction(res.data.voltageToleranceCurveDataList)
data.gs = res.data.voltageToleranceCurveDataList.length
data.krr = gongData.pointI.length
data.bkrr = gongData.pointIun.length
console.log(gongData)
echartList.value = {
// backgroundColor: "#f9f9f9", //地图背景色深蓝
title: {
text: `F47曲线`
},
legend: {
// data: ['上限', '下限', '可容忍事件', '不可容忍事件'],
data: ['可容忍事件', '不可容忍事件'],
itemWidth: 10,
itemHeight: 10,
itemGap: 15
},
tooltip: {
trigger: 'item',
show: true,
axisPointer: {
type: 'shadow',
label: {
color: '#fff',
fontSize: 16
}
},
textStyle: {
color: '#fff',
fontStyle: 'normal',
opacity: 0.35,
fontSize: 14
},
backgroundColor: 'rgba(0,0,0,0.55)',
borderWidth: 0,
formatter: function (a: any) {
var relVal = ''
relVal = "<font style='color:" + "'>发生时间:" + a.value[2] + '</font><br/>'
relVal += "<font style='color:" + "'>持续时间:" + a.value[0] + 's</font><br/>'
relVal += "<font style='color:" + "'>特征幅值:" + a.value[1].toFixed(2) + '%</font>'
return relVal
}
},
xAxis: [
{
type: 'log',
min: 0.001,
max: 1000,
splitLine: {
show: false
},
name: 's'
}
],
yAxis: [
{
type: 'value',
max: function (value: any) {
return value.max + 20
},
splitNumber: 10,
minInterval: 0.1,
name: '%'
}
],
color: ['#DAA520', 'green', 'red'],
options: {
dataZoom: null,
series: [
{
name: '边界线',
type: 'line',
data: [
[0.05, 0],
[0.05, 50],
[0.2, 50],
[0.2, 70],
[0.5, 70],
[0.5, 80],
[10, 80],
[1000, 80]
],
showSymbol: false,
tooltips: {
show: false
}
},
{
name: '可容忍事件',
type: 'scatter',
symbol: 'circle',
symbolSize: 8,
// data: gongData.pointF,
data: [
[0.2, 10, '2023-01-01 10:00:00'],
[0.4, 50, '2023-01-01 11:00:00']
],
legendSymbol: 'circle',
emphasis: {
focus: 'series',
itemStyle: {
borderColor: '#fff',
borderWidth: 2,
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
tooltip: {
show: true,
trigger: 'item',
formatter: function (params: any) {
return `<strong>可容忍事件</strong><br/>
持续时间: ${params.value[0]}s<br/>
特征幅值: ${params.value[1].toFixed(2)}%<br/>
发生时间: ${params.value[2] || 'N/A'}`
}
}
},
{
name: '不可容忍事件',
type: 'scatter',
symbol: 'rect',
symbolSize: 8,
data: gongData.pointFun,
legendSymbol: 'rect'
}
]
}
}
}
})
const tableRef = ref()
provide('tableRef', tableRef)
provide('tableStore', tableStore)
function gongfunction(arr: any) {
let standI = 0
let unstandI = 0
let standF = 0
let unstandF = 0
let total = 0
let pointIun = []
let pointI = []
let pointF = []
let pointFun = []
total = arr.length
if (total == 0) {
} else {
for (let i = 0; i < arr.length; i++) {
let point = []
let xx = arr[i].persistTime
let yy = arr[i].eventValue
let time = arr[i].time
let eventId = arr[i].eventId
// let index =arr[i].eventDetailIndex;
point = [xx, yy, time, eventId]
if (xx <= 0.003) {
let line = 0
line = 250 - 30000 * xx
if (yy > line) {
unstandI++
pointIun.push({
value: point,
itemStyle: { normal: { color: 'red' } }
})
} else {
standI++
pointI.push({
value: point,
itemStyle: { normal: { color: 'green' } }
})
}
} else if (xx <= 0.02) {
if (yy > 120) {
unstandI++
pointIun.push({
value: point,
itemStyle: { normal: { color: 'red' } }
})
} else {
standI++
pointI.push({
value: point,
itemStyle: { normal: { color: 'green' } }
})
}
} else if (xx <= 0.5) {
if (yy > 120 || yy < 70) {
unstandI++
pointIun.push({
value: point,
itemStyle: { normal: { color: 'red' } }
})
} else {
standI++
pointI.push({
value: point,
itemStyle: { normal: { color: 'green' } }
})
}
} else if (xx <= 10) {
if (yy > 110 || yy < 80) {
unstandI++
pointIun.push({
value: point,
itemStyle: { normal: { color: 'red' } }
})
} else {
standI++
pointI.push({
value: point,
itemStyle: { normal: { color: 'green' } }
})
}
} else {
if (yy > 110 || yy < 90) {
unstandI++
pointIun.push({
value: point,
itemStyle: { normal: { color: 'red' } }
})
} else {
standI++
pointI.push({
value: point,
itemStyle: { normal: { color: 'green' } }
})
}
}
if (xx < 0.05) {
standF++
pointF.push({
value: point,
itemStyle: { normal: { color: 'green' } }
})
} else if (xx < 0.2) {
if (yy > 50) {
standF++
pointF.push({
value: point,
itemStyle: { normal: { color: 'green' } }
})
} else {
unstandF++
pointFun.push({
value: point,
itemStyle: { normal: { color: 'red' } }
})
}
} else if (xx < 0.5) {
if (yy > 70) {
standF++
pointF.push({
value: point,
itemStyle: { normal: { color: 'green' } }
})
} else {
unstandF++
pointFun.push({
value: point,
itemStyle: { normal: { color: 'red' } }
})
}
} else {
if (yy > 80) {
standF++
pointF.push({
value: point,
itemStyle: { normal: { color: 'green' } }
})
} else {
unstandF++
pointFun.push({
value: point,
itemStyle: { normal: { color: 'red' } }
})
}
}
}
}
return {
standI,
unstandI,
pointI,
pointIun,
standF,
unstandF,
pointF,
pointFun
}
}
onMounted(() => {
setTimeout(() => {
tableStore.index()
}, 100)
})
// 点击事件处理函数
const handleChartClick = (params: any) => {
if (params.seriesName === '可容忍事件') {
// 处理可容忍事件点击
ElMessage.info(`点击了可容忍事件: 持续时间${params.value[0]}s, 幅值${params.value[1].toFixed(2)}%`)
handleTolerableEventClick(params)
} else if (params.seriesName === '不可容忍事件') {
// 处理不可容忍事件点击
ElMessage.info(`点击了不可容忍事件: 持续时间${params.value[0]}s, 幅值${params.value[1].toFixed(2)}%`)
handleIntolerableEventClick(params)
}
}
// 可容忍事件点击处理函数
const handleTolerableEventClick = (params: any) => {
console.log('可容忍事件详情:', params)
isWaveCharts.value = true
}
// 不可容忍事件点击处理函数
const handleIntolerableEventClick = (params: any) => {
console.log('不可容忍事件详情:', params)
}
watch(
() => prop.timeKey,
val => {
tableStore.index()
}
)
watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告)
(newVal, oldVal) => {
tableStore.index()
},
{
deep: true // 若 timeValue 是对象/数组,需开启深度监听
}
)
const addMenu = () => {}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,185 @@
<template>
<div>
<!--暂降方向统计 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader>
<my-echart class="tall" :options="echartList" :style="{ width: prop.width, height: `calc(${prop.height} )` }" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { useDictData } from '@/stores/dictData'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import TableHeader from '@/components/table/header/index.vue'
import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({
w: { type: String },
h: { type: String },
width: { type: String },
height: { type: String },
timeKey: { type: String },
timeValue: { type: Object }
})
const headerHeight = ref(57)
const route = useRoute()
const timeCacheStore = useTimeCacheStore()
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height
// 如果有传入 datePicker 的值
if (datePickerValue) {
// 更新表格参数
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
}
}
// 计算是否全屏展示
const fullscreen = computed(() => {
const w = Number(prop.w)
const h = Number(prop.h)
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
// 执行相应逻辑
return true
} else {
return false
}
})
const data = [
{
name: '来自电网',
value: 4
},
{
name: '来自负荷',
value: 41
}
]
const echartList = ref({
title: {},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
top: 'center',
right: '5%',
formatter: function (e: any) {
return e + ' ' + data.filter(item => item.name == e)[0].value + '次'
}
},
xAxis: {
show: false
},
yAxis: {
show: false
},
grid: {
left: '10px',
right: '20px'
},
options: {
dataZoom: null,
title: [
{
text: '暂降方向统计',
left: 'center'
},
{
text: data[0].value + data[1].value + '次',
left: 'center',
top: 'center'
}
],
series: [
{
type: 'pie',
center: 'center',
radius: ['55%', '75%'],
label: {
show: false,
position: 'outside',
textStyle: {
//数值样式
}
},
name: '事件统计',
data: data
}
]
}
})
const OverLimitDetailsRef = ref()
const tableStore: any = new TableStore({
url: '/user-boot/dept/deptTree',
method: 'POST',
showPage: false,
column: [],
beforeSearchFun: () => {
// 尝试从缓存获取时间值
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
},
loadCallback: () => {}
})
const tableRef = ref()
provide('tableRef', tableRef)
provide('tableStore', tableStore)
// 点击行
const cellClickEvent = ({ row, column }: any) => {
if (column.field != 'name') {
console.log(row)
OverLimitDetailsRef.value.open(row)
}
}
onMounted(() => {
tableStore.index()
})
watch(
() => prop.timeKey,
val => {
tableStore.index()
}
)
watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告)
(newVal, oldVal) => {
tableStore.index()
},
{
deep: true // 若 timeValue 是对象/数组,需开启深度监听
}
)
const addMenu = () => {}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,151 @@
<template>
<!-- 指标日趋势图 -->
<el-dialog draggable title="指标日趋势图" v-model="dialogVisible" append-to-body width="70%">
<my-echart
class="tall"
:options="echartList"
style="width: 98%; height: 320px"
/>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore'
import TableHeader from '@/components/table/header/index.vue'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { useConfig } from '@/stores/config'
const prop = defineProps({
width: { type: String },
height: { type: String },
timeKey: { type: String },
timeValue: { type: Object }
})
const dialogVisible: any = ref(false)
const config = useConfig()
const echartList = ref({
title: {
text: '35kV进线谐波含有率',
},
xAxis: {
type: 'time',
axisLabel: {
formatter: {
day: '{MM}-{dd}',
month: '{MM}',
year: '{yyyy}'
}
}
},
yAxis: [{}, {}],
grid: {
left: '10px',
right: '20px'
},
options: {
series: [
{
// name: '暂降次数',
type: 'bar',
name: '有功功率',
data: [
['2025-10-16 07:00:00', 10],
['2025-10-16 07:15:00', 10],
['2025-10-16 07:30:00', 10],
['2025-10-16 07:45:00', 10],
['2025-10-16 08:00:00', 30],
['2025-10-16 08:15:00', 50],
['2025-10-16 08:30:00', 60],
['2025-10-16 08:45:00', 70],
['2025-10-16 09:00:00', 100],
['2025-10-16 09:15:00', 120],
['2025-10-16 09:30:00', 130],
['2025-10-16 09:45:00', 140],
['2025-10-16 10:00:00', 160],
['2025-10-16 10:15:00', 160],
['2025-10-16 10:30:00', 130],
['2025-10-16 10:45:00', 120],
['2025-10-16 11:00:00', 140],
['2025-10-16 11:15:00', 80],
['2025-10-16 11:30:00', 70],
['2025-10-16 11:45:00', 90],
['2025-10-16 12:00:00', 60],
['2025-10-16 12:15:00', 60],
['2025-10-16 12:30:00', 60],
['2025-10-16 12:45:00', 60]
],
itemStyle: {
normal: {
//这里是颜色
color: function (params: any) {
if (params.value[1] == 0 || params.value[1] == 3.14159) {
return '#ccc'
} else {
return config.layout.elementUiPrimary[0]
}
}
}
},
yAxisIndex: 0
},
{
name: '谐波总畸变率',
type: 'line',
showSymbol: false,
smooth: true,
data: [
['2025-10-16 07:00:00', 0],
['2025-10-16 07:15:00', 0],
['2025-10-16 07:30:00', 0],
['2025-10-16 07:45:00', 0],
['2025-10-16 08:00:00', 0],
['2025-10-16 08:15:00', 0.1],
['2025-10-16 08:30:00', 0.1],
['2025-10-16 08:45:00', 0.1],
['2025-10-16 09:00:00', 1],
['2025-10-16 09:15:00', 1],
['2025-10-16 09:30:00', 1],
['2025-10-16 09:45:00', 1],
['2025-10-16 10:00:00', 0.8],
['2025-10-16 10:15:00', 0.8],
['2025-10-16 10:30:00', 0.8],
['2025-10-16 10:45:00', 0.8],
['2025-10-16 11:00:00', 0.8],
['2025-10-16 11:15:00', 0.1],
['2025-10-16 11:30:00', 0.1],
['2025-10-16 11:45:00', 0.1],
['2025-10-16 12:00:00', 0],
['2025-10-16 12:15:00', 0],
['2025-10-16 12:30:00', 0],
['2025-10-16 12:45:00', 0]
],
yAxisIndex: 1
}
]
}
})
onMounted(() => {
})
const open = async (row: any) => {
dialogVisible.value = true
}
defineExpose({ open })
</script>
<style lang="scss" scoped>
:deep(.el-select) {
min-width: 80px;
}
</style>

View File

@@ -0,0 +1,252 @@
<template>
<div>
<!--指标越限程度 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader>
<my-echart
class="tall"
:options="echartList"
:style="{ width: prop.width, height: `calc(${prop.height} / 2 )` }"
/>
<Table ref="tableRef" @cell-click="cellClickEvent" :height="`calc(${prop.height} / 2 - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`" isGroup></Table>
<!-- 指标日趋势图 -->
<DailyTrendChart ref="dailyTrendChartRef" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import DailyTrendChart from '@/components/cockpit/exceedanceLevel/components/dailyTrendChart.vue'
import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({
w: { type: String },
h: { type: String },
width: { type: String },
height: { type: String },
timeKey: { type: String },
timeValue: { type: Object }
})
const headerHeight = ref(57)
const route = useRoute()
const timeCacheStore = useTimeCacheStore()
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height
// 如果有传入 datePicker 的值
if (datePickerValue) {
// 更新表格参数
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
}
}
// 计算是否全屏展示
const fullscreen = computed(() => {
const w = Number(prop.w)
const h = Number(prop.h)
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
// 执行相应逻辑
return true
} else {
return false
}
})
const echartList = ref({
title: {
text: '指标越限严重度'
},
xAxis: {
// name: '(区域)',
data: ['闪变', '谐波电压', '谐波电流', '电压偏差', '三相不平衡']
},
yAxis: {
name: '%', // 给X轴加单位
interval: 20
},
grid: {
left: '10px',
right: '20px'
},
options: {
series: [
{
// name: '暂降次数',
type: 'bar',
name: '越限占比',
data: [0, 7.5, 36, 0, 80],
barMaxWidth: 30
// label: {
// show: true,
// position: 'top',
// textStyle: {
// //数值样式
// color: '#000'
// },
// fontSize: 12
// }
}
]
}
})
const dailyTrendChartRef = ref()
const tableStore: any = new TableStore({
url: '/user-boot/dept/deptTree',
method: 'POST',
showPage: false,
column: [
{
field: 'index',
title: '序号',
width: '80',
formatter: (row: any) => {
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{
title: '指标名称',
field: 'name',
minWidth: '90'
},
{
title: '越限最大值',
field: 'type',
minWidth: '60',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type}</span>`
}
},
{
title: '国标限值',
field: 'type1',
minWidth: '60'
},
{
title: '越限程度(%)',
field: 'type2',
minWidth: '60'
},
{
title: '发生日期',
field: 'type3',
minWidth: '100'
},
{
title: '越限最高监测点',
field: 'type4',
minWidth: '90'
}
],
beforeSearchFun: () => {
// 尝试从缓存获取时间值
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
},
loadCallback: () => {
tableStore.table.data = [
{
name: '闪变',
type: '0.0',
type1: '2.0',
type2: '0.0',
type3: '/',
type4: '/'
},
{
name: '谐波电压',
type: '1.72',
type1: '1.6',
type2: '7.5',
type3: '2025-03-09',
type4: '10kV1#电动机'
},
{
name: '谐波电流',
type: '27.2',
type1: '20.0',
type2: '36.0',
type3: '2025-03-16',
type4: '380V电焊机(治理前)'
},
{
name: '电压偏差',
type: '0.0',
type1: '2.0',
type2: '0.0',
type3: '/',
type4: '/'
},
{
name: '三相不平衡',
type: '3.6',
type1: '2.0',
type2: '80.0',
type3: '2025-03-01',
type4: '380V电焊机(治理前)'
}
]
}
})
const tableRef = ref()
provide('tableRef', tableRef)
provide('tableStore', tableStore)
// 点击行
const cellClickEvent = ({ row, column }: any) => {
if (column.field != 'name') {
console.log(row)
dailyTrendChartRef.value.open(row)
}
}
onMounted(() => {
tableStore.index()
})
watch(
() => prop.timeKey,
val => {
tableStore.index()
}
)
watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告)
(newVal, oldVal) => {
tableStore.index()
},
{
deep: true // 若 timeValue 是对象/数组,需开启深度监听
}
)
const addMenu = () => {}
</script>
<style lang="scss" scoped></style>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,163 @@
<template>
<div>
<!--治理效果报表 -->
<TableHeader :showReset="false" datePicker @selectChange="selectChange" v-if="fullscreen">
<template v-slot:select>
<el-form-item label="治理对象">
<el-select
v-model="tableStore.table.params.power"
placeholder="请选择治理对象"
clearable
style="width: 130px"
>
<el-option
v-for="item in powerList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</template>
<template v-slot:operation>
<el-button @click="downloadExcel" class="" type="primary" icon="el-icon-Download">导出excel</el-button>
</template>
</TableHeader>
<div style="display: flex">
<div
id="luckysheet"
:style="{
width: `calc(${prop.width} )`,
height: `calc(${prop.height} - 57px + ${fullscreen ? 0 : 56}px)`
}"
></div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h, computed } from 'vue'
import TableStore from '@/utils/tableStore'
import { exportExcel } from '@/views/govern/reportForms/export.js'
import TableHeader from '@/components/table/header/index.vue'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import { useConfig } from '@/stores/config'
import Json from './index.json'
import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({
w: { type: String },
h: { type: String },
width: { type: String },
height: { type: String },
timeKey: { type: String },
timeValue: { type: Object }
})
const config = useConfig()
const powerList: any = ref([
{
label: '1#变压器',
value: '1'
},
{
label: '2#变压器',
value: '2'
}
])
const route = useRoute()
const timeCacheStore = useTimeCacheStore()
const tableStore: any = new TableStore({
url: '/user-boot/role/selectRoleDetail?id=0',
method: 'POST',
showPage: false,
exportName: '主要监测点列表',
column: [],
beforeSearchFun: () => {
// 尝试从缓存获取时间值
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
},
loadCallback: () => {}
})
const tableRef = ref()
provide('tableRef', tableRef)
tableStore.table.params.power = '1'
provide('tableStore', tableStore)
// 下载表格
const downloadExcel = () => {
exportExcel(luckysheet.getAllSheets(), '治理效果报表')
}
onMounted(() => {
luckysheet.create({
container: 'luckysheet',
title: '', // 表 头名
lang: 'zh', // 中文
showtoolbar: false, // 是否显示工具栏
showinfobar: false, // 是否显示顶部信息栏
showsheetbar: true, // 是否显示底部sheet按钮
allowEdit: false, // 禁止所有编辑操作(必填)
data: Json
})
tableStore.index()
})
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
// 如果有传入 datePicker 的值
if (datePickerValue) {
// 更新表格参数
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
}
}
// 计算是否全屏展示
const fullscreen = computed(() => {
const w = Number(prop.w)
const h = Number(prop.h)
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
// 执行相应逻辑
return true
} else {
return false
}
})
watch(
() => prop.timeKey,
val => {
tableStore.index()
}
)
watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告)
(newVal, oldVal) => {
tableStore.index()
},
{
deep: true // 若 timeValue 是对象/数组,需开启深度监听
}
)
const addMenu = () => {}
</script>
<style lang="scss" scoped>
:deep(.el-select) {
min-width: 80px;
}
</style>

View File

@@ -0,0 +1,249 @@
<template>
<div>
<!--电网侧指标越限统计 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader>
<my-echart
class="tall"
:options="echartList"
:style="{
width: prop.width,
height: `calc(${prop.height} / 2 - ${headerHeight / 2}px + ${fullscreen ? 0 : 28}px )`
}"
/>
<Table
ref="tableRef"
@cell-click="cellClickEvent"
:height="`calc(${prop.height} / 2 - ${headerHeight / 2}px + ${fullscreen ? 0 : 28}px )`"
isGroup
></Table>
<!-- 指标越限详情 -->
<OverLimitDetails ref="OverLimitDetailsRef" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import OverLimitDetails from '@/components/cockpit/listOfMainMonitoringPoints/components/overLimitDetails.vue'
import TableHeader from '@/components/table/header/index.vue'
import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({
w: { type: String },
h: { type: String },
width: { type: String },
height: { type: String },
timeKey: { type: String },
timeValue: { type: Object }
})
const headerHeight = ref(57)
const route = useRoute()
const timeCacheStore = useTimeCacheStore()
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height
// 如果有传入 datePicker 的值
if (datePickerValue) {
// 更新表格参数
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
}
}
// 计算是否全屏展示
const fullscreen = computed(() => {
const w = Number(prop.w)
const h = Number(prop.h)
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
// 执行相应逻辑
return true
} else {
return false
}
})
const echartList = ref({
title: {
text: '指标越限占比'
},
xAxis: {
// name: '(区域)',
data: ['闪变', '谐波电压', '谐波电流', '电压偏差', '三相不平衡']
},
yAxis: {
name: '%', // 给X轴加单位
interval: 20
},
grid: {
left: '10px',
right: '20px'
},
options: {
series: [
{
// name: '暂降次数',
type: 'bar',
name: '越限占比',
data: [0, 45, 22, 0, 70],
barMaxWidth: 30
// label: {
// show: true,
// position: 'top',
// textStyle: {
// //数值样式
// color: '#000'
// },
// fontSize: 12
// }
}
]
}
})
const OverLimitDetailsRef = ref()
const tableStore: any = new TableStore({
url: '/user-boot/dept/deptTree',
method: 'POST',
showPage: false,
column: [
{
field: 'index',
title: '序号',
width: '80',
formatter: (row: any) => {
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{
title: '名称',
field: 'name',
minWidth: '90'
},
{
title: '越限占比(%)',
children: [
{
title: '闪变',
field: 'type',
minWidth: '70',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type}</span>`
}
},
{
title: '谐波电压',
field: 'type1',
minWidth: '80',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type1}</span>`
}
},
{
title: '谐波电流',
field: 'type2',
minWidth: '80',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type2}</span>`
}
},
{
title: '电压偏差',
field: 'type3',
minWidth: '80',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type3}</span>`
}
},
{
title: '三相不平衡',
field: 'type4',
minWidth: '90',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type4}</span>`
}
}
]
}
],
beforeSearchFun: () => {
// 尝试从缓存获取时间值
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
},
loadCallback: () => {
tableStore.table.data = [
{
name: '1#变压器电网侧',
type: '0',
type1: '45',
type2: '22',
type3: '0',
type4: '70'
}
]
tableStore.table.height = `calc(${prop.height} - 80px)`
}
})
const tableRef = ref()
provide('tableRef', tableRef)
provide('tableStore', tableStore)
// 点击行
const cellClickEvent = ({ row, column }: any) => {
if (column.field != 'name') {
console.log(row)
OverLimitDetailsRef.value.open(row)
}
}
onMounted(() => {
tableStore.index()
})
watch(
() => prop.timeKey,
val => {
tableStore.index()
}
)
watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告)
(newVal, oldVal) => {
tableStore.index()
},
{
deep: true // 若 timeValue 是对象/数组,需开启深度监听
}
)
const addMenu = () => {}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,230 @@
<template>
<div>
<!--指标越限明细 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader>
<el-calendar v-model="value" :style="{ height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`, overflow: 'auto' }">
<template #date-cell="{ data }">
<div style="height: 100%; padding: 8px" :style="{ background: setBackground(data.day) }">
<p :class="data.isSelected ? 'is-selected' : ''">
{{ data.day.split('-').slice(2).join('-') }}
</p>
<el-tooltip
effect="dark"
placement="top"
:hide-after="0"
v-if="list?.filter(item => item.time == data.day)[0]?.text || false"
>
<template #content>
<span v-html="list?.filter(item => item.time == data.day)[0]?.text || ''"></span>
</template>
<div
:style="{ height: `calc(${prop.height} / 5 - 47px)`, overflow: 'auto' }"
v-html="list?.filter(item => item.time == data.day)[0]?.text || ''"
></div>
</el-tooltip>
</div>
</template>
</el-calendar>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore'
import TableHeader from '@/components/table/header/index.vue'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import { dayjs } from 'element-plus'
import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({
w: { type: String },
h: { type: String },
width: { type: String },
height: { type: String },
timeKey: { type: String },
timeValue: { type: Object }
})
const headerHeight = ref(57)
const route = useRoute()
const timeCacheStore = useTimeCacheStore()
dayjs.en.weekStart = 1 //设置日历的周起始日为星期一
const value = ref(new Date())
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height
// 如果有传入 datePicker 的值
if (datePickerValue) {
// 更新表格参数
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
}
}
// 计算是否全屏展示
const fullscreen = computed(() => {
const w = Number(prop.w)
const h = Number(prop.h)
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
// 执行相应逻辑
return true
} else {
return false
}
})
const list = ref([
{
time: '2025-10-01',
key: 81,
text: '3次谐波越限<br/>5次谐波越限<br/>三相不平衡越限'
},
{
time: '2025-10-31',
key: 81,
text: '3次谐波越限<br/>5次谐波越限<br/>三相不平衡越限'
},
{
time: '2025-10-08',
key: 20,
text: '3次谐波越限<br/>5次谐波越限<br/>三相不平衡越限'
},
{
time: '2025-10-16',
key: 20,
text: '3次谐波越限<br/>5次谐波越限<br/>三相不平衡越限'
},
{
time: '2025-10-23',
key: 20,
text: '3次谐波越限<br/>5次谐波越限<br/>三相不平衡越限'
},
{
time: '2025-10-04',
key: 0,
text: ''
},
{
time: '2025-10-05',
key: 0,
text: ''
}
])
const tableStore: any = new TableStore({
url: '/user-boot/dept/deptTree',
method: 'POST',
showPage: false,
column: [],
beforeSearchFun: () => {
// 尝试从缓存获取时间值
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
},
loadCallback: () => {
tableStore.table.data = []
}
})
const tableRef = ref([])
const setBackground = (value: string) => {
let data = []
data = list.value?.filter(item => item.time == value)
if (data && data?.length > 0) {
if (data[0].key < 10) {
return '#33996690'
} else if (data[0].key < 80) {
return '#FFCC3390'
} else if (data[0].key <= 100) {
return '#Ff660090'
}
}
return '#fff'
}
provide('tableRef', tableRef)
provide('tableStore', tableStore)
onMounted(() => {
tableStore.index()
})
watch(
() => prop.timeKey,
val => {
tableStore.index()
}
)
watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告)
(newVal, oldVal) => {
tableStore.index()
},
{
deep: true // 若 timeValue 是对象/数组,需开启深度监听
}
)
const addMenu = () => {}
</script>
<style lang="scss" scoped>
:deep(.el-calendar) {
.el-calendar__header {
display: none;
}
.el-calendar__body {
padding: 0px !important;
height: 100%;
.el-calendar-table {
height: 100%;
}
}
.el-calendar-day {
height: 100%;
padding: 0px;
overflow: hidden;
}
.el-calendar-table__row {
.next {
pointer-events: none;
}
.prev {
pointer-events: none;
}
}
.el-calendar-table .el-calendar-day:hover {
background-color: #ffffff00;
}
.el-calendar-table td.is-selected {
background-color: #ffffff00;
}
}
// /*calendar_class 是el-calendar所在父标签的css*/
// .calendar_class >>> .el-calendar-table:not(.is-range) td.next {
// pointer-events: none;
// }
// .calendar_class >>> .el-calendar-table:not(.is-range) td.prev {
// pointer-events: none;
// }
</style>

View File

@@ -0,0 +1,462 @@
<template>
<div>
<!--指标越限概率分布 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen">
<template v-slot:select>
<el-form-item>
<el-select size="small" v-model="tableStore.table.params.searchValue">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</template>
</TableHeader>
<my-echart
class="tall"
:options="echartList"
:style="{
width: prop.width,
height: `calc(${prop.height} / 2 - ${headerHeight / 2}px + ${fullscreen ? 0 : 28}px )`
}"
/>
<my-echart
class="mt10"
:options="echartList1"
:style="{
width: prop.width,
height: `calc(${prop.height} / 2 - ${headerHeight / 2}px + ${fullscreen ? 0 : 28}px )`
}"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore'
import TableHeader from '@/components/table/header/index.vue'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({
w: { type: String },
h: { type: String },
width: { type: String },
height: { type: String },
timeKey: { type: String },
timeValue: { type: Object }
})
const options = [
{
value: '1',
label: '35V进线',
},
]
const headerHeight = ref(57)
const route = useRoute()
const timeCacheStore = useTimeCacheStore()
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height
// 如果有传入 datePicker 的值
if (datePickerValue) {
// 更新表格参数
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
}
}
// 计算是否全屏展示
const fullscreen = computed(() => {
const w = Number(prop.w)
const h = Number(prop.h)
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
// 执行相应逻辑
return true
} else {
return false
}
})
const echartList = ref({
options: {
xAxis: null,
yAxis: null,
dataZoom: null,
backgroundColor: '#fff',
tooltip: {
// trigger: 'axis'
textStyle: {
color: '#fff',
fontStyle: 'normal',
opacity: 0.35,
fontSize: 14
},
backgroundColor: 'rgba(0,0,0,0.55)',
borderWidth: 0,
formatter: function (params: any) {
console.log(params)
var tips = ''
for (var i = 0; i < params.length; i++) {
tips += params[i].name + '</br/>'
tips += '监测点数' + ':' + '&nbsp' + '&nbsp' + params[i].value + '</br/>'
}
return tips
}
},
title: {
text: '指标越限概率分布',
x: 'center'
},
visualMap: {
max: 20,
show: false,
inRange: {
color: ['#313695', '#00BB00', '#ff8000', '#a50026']
}
},
xAxis3D: {
type: 'category',
name: '指标越限',
data: ['0-10', '10-20', '20-30', '30-40', '40-50'],
axisLine: {
lineStyle: {
color: '#111'
}
},
axisLabel: {
color: '#111'
}
},
yAxis3D: {
type: 'category',
name: '指标类型',
data: ['闪变', '谐波电压', '谐波电流', '电压偏差', '三相不平衡'],
nameTextStyle: {
color: '#111'
},
axisLine: {
show: true,
lineStyle: {
color: '#111'
}
},
axisLabel: {
color: '#111'
},
splitLine: {
lineStyle: {
// 使用深浅的间隔色
color: ['#111'],
type: 'dashed',
opacity: 0.5
}
}
},
zAxis3D: {
type: 'value',
splitNumber: 10,
minInterval: 10,
name: '越限占比'
},
grid3D: {
viewControl: {
projection: 'perspective',
distance: 250
},
boxWidth: 200,
boxDepth: 80,
light: {
main: {
intensity: 1.2
},
ambient: {
intensity: 0.3
}
}
},
series: [
{
type: 'bar3D',
data: [
[0, 0, 1],
[0, 1, 1],
[0.2, 1]
],
shading: 'realistic',
label: {
show: false,
textStyle: {
fontSize: 16,
borderWidth: 1
}
},
itemStyle: {
opacity: 1
},
emphasis: {
label: {
textStyle: {
fontSize: 20,
color: '#900'
}
},
itemStyle: {
color: '#900'
}
}
}
]
}
})
const echartList1 = ref({
title: {
text: '越限时间概率分布'
},
xAxis: {
// name: '时间',
// data: ['闪变', '谐波电压', '谐波电流', '电压偏差', '三相不平衡']
type: 'time',
axisLabel: {
formatter: {
day: '{MM}-{dd}',
month: '{MM}',
year: '{yyyy}'
}
}
},
yAxis: {
name: '次' // 给X轴加单位
},
grid: {
left: '10px',
right: '20px'
},
options: {
series: [
{
type: 'line',
showSymbol: false,
// smooth: true,
name: '闪变',
data: [
['2025-10-16 07:00:00', 10],
['2025-10-16 07:15:00', 10],
['2025-10-16 07:30:00', 10],
['2025-10-16 07:45:00', 10],
['2025-10-16 08:00:00', 30],
['2025-10-16 08:15:00', 50],
['2025-10-16 08:30:00', 60],
['2025-10-16 08:45:00', 70],
['2025-10-16 09:00:00', 100],
['2025-10-16 09:15:00', 120],
['2025-10-16 09:30:00', 130],
['2025-10-16 09:45:00', 140],
['2025-10-16 10:00:00', 160],
['2025-10-16 10:15:00', 160],
['2025-10-16 10:30:00', 130],
['2025-10-16 10:45:00', 120],
['2025-10-16 11:00:00', 140],
['2025-10-16 11:15:00', 80],
['2025-10-16 11:30:00', 70],
['2025-10-16 11:45:00', 90],
['2025-10-16 12:00:00', 60],
['2025-10-16 12:15:00', 60],
['2025-10-16 12:30:00', 60],
['2025-10-16 12:45:00', 60]
]
},
{
type: 'line',
showSymbol: false,
// smooth: true,
name: '谐波电压',
data: [
['2025-10-16 07:00:00', 1],
['2025-10-16 07:15:00', 1],
['2025-10-16 07:30:00', 1],
['2025-10-16 07:45:00', 1],
['2025-10-16 08:00:00', 3],
['2025-10-16 08:15:00', 5],
['2025-10-16 08:30:00', 6],
['2025-10-16 08:45:00', 7],
['2025-10-16 09:00:00', 10],
['2025-10-16 09:15:00', 12],
['2025-10-16 09:30:00', 13],
['2025-10-16 09:45:00', 14],
['2025-10-16 10:00:00', 16],
['2025-10-16 10:15:00', 16],
['2025-10-16 10:30:00', 13],
['2025-10-16 10:45:00', 12],
['2025-10-16 11:00:00', 14],
['2025-10-16 11:15:00', 8],
['2025-10-16 11:30:00', 7],
['2025-10-16 11:45:00', 9],
['2025-10-16 12:00:00', 6],
['2025-10-16 12:15:00', 6],
['2025-10-16 12:30:00', 6],
['2025-10-16 12:45:00', 6]
]
},
{
type: 'line',
showSymbol: false,
// smooth: true,
name: '谐波电流',
data: [
['2025-10-16 07:00:00', 19],
['2025-10-16 07:15:00', 19],
['2025-10-16 07:30:00', 19],
['2025-10-16 07:45:00', 19],
['2025-10-16 08:00:00', 39],
['2025-10-16 08:15:00', 59],
['2025-10-16 08:30:00', 69],
['2025-10-16 08:45:00', 79],
['2025-10-16 09:00:00', 109],
['2025-10-16 09:15:00', 129],
['2025-10-16 09:30:00', 139],
['2025-10-16 09:45:00', 149],
['2025-10-16 10:00:00', 169],
['2025-10-16 10:15:00', 169],
['2025-10-16 10:30:00', 139],
['2025-10-16 10:45:00', 129],
['2025-10-16 11:00:00', 149],
['2025-10-16 11:15:00', 89],
['2025-10-16 11:30:00', 79],
['2025-10-16 11:45:00', 99],
['2025-10-16 12:00:00', 69],
['2025-10-16 12:15:00', 69],
['2025-10-16 12:30:00', 69],
['2025-10-16 12:45:00', 69]
]
},
{
type: 'line',
showSymbol: false,
// smooth: true,
name: '电压偏差',
data: [
['2025-10-16 07:00:00', 12],
['2025-10-16 07:15:00', 12],
['2025-10-16 07:30:00', 12],
['2025-10-16 07:45:00', 12],
['2025-10-16 08:00:00', 32],
['2025-10-16 08:15:00', 52],
['2025-10-16 08:30:00', 62],
['2025-10-16 08:45:00', 72],
['2025-10-16 09:00:00', 112],
['2025-10-16 09:15:00', 122],
['2025-10-16 09:30:00', 122],
['2025-10-16 09:45:00', 152],
['2025-10-16 10:00:00', 122],
['2025-10-16 10:15:00', 112],
['2025-10-16 10:30:00', 132],
['2025-10-16 10:45:00', 122],
['2025-10-16 11:00:00', 142],
['2025-10-16 11:15:00', 82],
['2025-10-16 11:30:00', 72],
['2025-10-16 11:45:00', 92],
['2025-10-16 12:00:00', 62],
['2025-10-16 12:15:00', 62],
['2025-10-16 12:30:00', 62],
['2025-10-16 12:45:00', 62]
]
},
{
type: 'line',
showSymbol: false,
// smooth: true,
name: '三相不平衡',
data: [
['2025-10-16 07:00:00', 10],
['2025-10-16 07:15:00', 10],
['2025-10-16 07:30:00', 10],
['2025-10-16 07:45:00', 10],
['2025-10-16 08:00:00', 30],
['2025-10-16 08:15:00', 50],
['2025-10-16 08:30:00', 60],
['2025-10-16 08:45:00', 70],
['2025-10-16 09:00:00', 100],
['2025-10-16 09:15:00', 120],
['2025-10-16 09:30:00', 130],
['2025-10-16 09:45:00', 140],
['2025-10-16 10:00:00', 160],
['2025-10-16 10:15:00', 160],
['2025-10-16 10:30:00', 130],
['2025-10-16 10:45:00', 120],
['2025-10-16 11:00:00', 140],
['2025-10-16 11:15:00', 80],
['2025-10-16 11:30:00', 70],
['2025-10-16 11:45:00', 90],
['2025-10-16 12:00:00', 60],
['2025-10-16 12:15:00', 60],
['2025-10-16 12:30:00', 60],
['2025-10-16 12:45:00', 60]
]
}
]
}
})
const tableStore: any = new TableStore({
url: '/user-boot/dept/deptTree',
method: 'POST',
showPage: false,
column: [],
beforeSearchFun: () => {
// 尝试从缓存获取时间值
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
},
loadCallback: () => {
tableStore.table.data = []
}
})
const tableRef = ref()
tableStore.table.params.searchValue = '1'
provide('tableRef', tableRef)
provide('tableStore', tableStore)
onMounted(() => {
tableStore.index()
})
watch(
() => prop.timeKey,
val => {
tableStore.index()
}
)
watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告)
(newVal, oldVal) => {
tableStore.index()
},
{
deep: true // 若 timeValue 是对象/数组,需开启深度监听
}
)
const addMenu = () => {}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,223 @@
<template>
<div>
<!--主要监测点列表 -->
<TableHeader
:showReset="false"
@selectChange="selectChange"
v-if="fullscreen"
datePicker
:initialInterval="getInitialInterval()"
:initialTimeValue="getInitialTimeValue()"
>
<template v-slot:select>
<el-form-item label="关键词">
<el-input v-model="tableStore.table.params.searchValue" clearable placeholder="请输关键字" />
</el-form-item>
</template>
</TableHeader>
<Table
ref="tableRef"
@cell-click="cellClickEvent"
:height="`calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`"
></Table>
<!-- 指标越限详情 -->
<OverLimitDetails ref="OverLimitDetailsRef" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue'
import { useDictData } from '@/stores/dictData'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import { ElTag } from 'element-plus'
import OverLimitDetails from '@/components/cockpit/listOfMainMonitoringPoints/components/overLimitDetails.vue'
import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({
w: { type: String },
h: { type: String },
width: { type: String },
height: { type: String },
timeKey: { type: String },
timeValue: { type: Object }
})
const OverLimitDetailsRef = ref()
const headerHeight = ref(57)
const route = useRoute()
const timeCacheStore = useTimeCacheStore()
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height
// 如果有传入 datePicker 的值
if (datePickerValue) {
// 更新表格参数
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
}
}
// 计算是否全屏展示
const fullscreen = computed(() => {
const w = Number(prop.w)
const h = Number(prop.h)
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
// 执行相应逻辑
return true
} else {
return false
}
})
const tableStore: any = new TableStore({
url: '/system-boot/dashboard/queryPage',
method: 'POST',
showPage: false,
exportName: '主要监测点列表',
column: [
{
field: 'index',
title: '序号',
width: '80',
formatter: (row: any) => {
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{
title: '监测点名称',
field: 'name',
minWidth: '90',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.name}</span>`
}
},
{
title: '监测对象类型',
field: 'type',
minWidth: '90'
},
{
title: '是否治理',
field: 'whetherToGovern',
minWidth: '70'
},
{ title: '主要存在的电能质量问题', field: 'question', minWidth: '150' }
],
beforeSearchFun: () => {
// 尝试从缓存获取时间值
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
},
loadCallback: () => {
tableStore.table.data = [
{
name: '10kV1#电动机',
type: '电动机',
whetherToGovern: '否',
question: '3次谐波电压、5次谐波电流、电压不平衡度超标'
},
{
name: '10kV2#(治理后)',
type: '电焊机',
whetherToGovern: '100A APF',
question: '所有指标均合格'
},
{
name: '380V电焊机(治理前)',
type: '电焊机',
whetherToGovern: '100A APF',
question: '5次谐波电流、电压不平衡度超标'
},
{
name: '380V水泵机',
type: '电动机',
whetherToGovern: '否',
question: '所有指标均合格'
}
]
tableStore.table.height = `calc(${prop.height} - 80px)`
}
})
const tableRef = ref()
provide('tableRef', tableRef)
tableStore.table.params.type = ''
tableStore.table.params.searchValue = ''
provide('tableStore', tableStore)
// 点击行
const cellClickEvent = ({ row, column }: any) => {
if (column.field == 'name') {
console.log(row)
OverLimitDetailsRef.value.open(row)
}
}
// 获取缓存的初始值
const getInitialInterval = (): 3 => {
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path);
if (cached && cached.interval !== undefined) {
return cached.interval as 3; // 强制断言为 3 或根据实际类型调整
}
}
return 3; // 明确返回字面量类型 3
};
// 外部总的时间值
const getInitialTimeValue = () => {
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
return cached.timeValue
}
}
return prop.timeValue // 使用传入的默认值
}
// 在组件挂载时设置缓存值到 DatePicker
onMounted(() => {
tableStore.index()
})
watch(
() => prop.timeKey,
val => {
tableStore.index()
}
)
watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告)
(newVal, oldVal) => {
tableStore.index()
},
{
deep: true // 若 timeValue 是对象/数组,需开启深度监听
}
)
const addMenu = () => {}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,254 @@
<template>
<!-- 总体指标占比详情谐波含有率 -->
<el-dialog draggable title="谐波电压/电流含有率" v-model="dialogVisible" append-to-body width="70%">
<TableHeader datePicker showExport :showReset="false" @selectChange="selectChange">
<template v-slot:select>
<el-form-item label="谐波次数">
<el-select
v-model="tableStore.table.params.searchValue"
placeholder="请选择谐波次数"
style="width: 240px"
>
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="指标类型">
<el-select
v-model="tableStore.table.params.searchValue"
placeholder="请选择指标类型"
style="width: 240px"
>
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</template>
</TableHeader>
<my-echart
class="tall"
:options="echartList"
style="width: 98%; height: 320px"
/>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore'
import TableHeader from '@/components/table/header/index.vue'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { useConfig } from '@/stores/config'
const prop = defineProps({
width: { type: String },
height: { type: String },
timeKey: { type: String },
timeValue: { type: Object }
})
const dialogVisible: any = ref(false)
const options = [
{
value: '35kV进线',
label: '35kV进线'
}
]
const config = useConfig()
const powerList: any = ref([
{
label: '三相总有功功率',
value: '1'
},
{
label: '三相总无功功率',
value: '2'
}
])
const exceedingTheLimitList: any = ref([
{
label: '越限',
value: '1'
},
{
label: '不越限',
value: '0'
}
])
const indicatorList: any = ref([
{
label: '谐波电压总畸变率',
value: '1'
},
{
label: '各次谐波电压',
value: '2'
},
{
label: '各次谐波电压',
value: '3'
},
{
label: '三相电压不平衡',
value: '4'
}
])
const echartList = ref({
title: {
text: '35kV进线谐波含有率',
},
xAxis: {
type: 'time',
axisLabel: {
formatter: {
day: '{MM}-{dd}',
month: '{MM}',
year: '{yyyy}'
}
}
},
yAxis: [{}, {}],
grid: {
left: '10px',
right: '20px'
},
options: {
series: [
{
// name: '暂降次数',
type: 'bar',
name: '有功功率',
data: [
['2025-10-16 07:00:00', 10],
['2025-10-16 07:15:00', 10],
['2025-10-16 07:30:00', 10],
['2025-10-16 07:45:00', 10],
['2025-10-16 08:00:00', 30],
['2025-10-16 08:15:00', 50],
['2025-10-16 08:30:00', 60],
['2025-10-16 08:45:00', 70],
['2025-10-16 09:00:00', 100],
['2025-10-16 09:15:00', 120],
['2025-10-16 09:30:00', 130],
['2025-10-16 09:45:00', 140],
['2025-10-16 10:00:00', 160],
['2025-10-16 10:15:00', 160],
['2025-10-16 10:30:00', 130],
['2025-10-16 10:45:00', 120],
['2025-10-16 11:00:00', 140],
['2025-10-16 11:15:00', 80],
['2025-10-16 11:30:00', 70],
['2025-10-16 11:45:00', 90],
['2025-10-16 12:00:00', 60],
['2025-10-16 12:15:00', 60],
['2025-10-16 12:30:00', 60],
['2025-10-16 12:45:00', 60]
],
itemStyle: {
normal: {
//这里是颜色
color: function (params: any) {
if (params.value[1] == 0 || params.value[1] == 3.14159) {
return '#ccc'
} else {
return config.layout.elementUiPrimary[0]
}
}
}
},
yAxisIndex: 0
},
{
name: '谐波总畸变率',
type: 'line',
showSymbol: false,
smooth: true,
data: [
['2025-10-16 07:00:00', 0],
['2025-10-16 07:15:00', 0],
['2025-10-16 07:30:00', 0],
['2025-10-16 07:45:00', 0],
['2025-10-16 08:00:00', 0],
['2025-10-16 08:15:00', 0.1],
['2025-10-16 08:30:00', 0.1],
['2025-10-16 08:45:00', 0.1],
['2025-10-16 09:00:00', 1],
['2025-10-16 09:15:00', 1],
['2025-10-16 09:30:00', 1],
['2025-10-16 09:45:00', 1],
['2025-10-16 10:00:00', 0.8],
['2025-10-16 10:15:00', 0.8],
['2025-10-16 10:30:00', 0.8],
['2025-10-16 10:45:00', 0.8],
['2025-10-16 11:00:00', 0.8],
['2025-10-16 11:15:00', 0.1],
['2025-10-16 11:30:00', 0.1],
['2025-10-16 11:45:00', 0.1],
['2025-10-16 12:00:00', 0],
['2025-10-16 12:15:00', 0],
['2025-10-16 12:30:00', 0],
['2025-10-16 12:45:00', 0]
],
yAxisIndex: 1
}
]
}
})
const headerHeight = ref(57)
const selectChange = (showSelect: any, height: any) => {
headerHeight.value = height
}
const tableStore: any = new TableStore({
url: '/user-boot/role/selectRoleDetail?id=0',
method: 'POST',
showPage: false,
exportName: '主要监测点列表',
column: [],
beforeSearchFun: () => {},
loadCallback: () => {
tableStore.table.height = `calc(${prop.height} - 80px)`
}
})
const tableRef = ref()
provide('tableRef', tableRef)
tableStore.table.params.power = '1'
tableStore.table.params.indicator = '1'
tableStore.table.params.exceedingTheLimit = '1'
tableStore.table.params.searchValue = ''
provide('tableStore', tableStore)
onMounted(() => {
tableStore.index()
})
watch(
() => prop.timeKey,
val => {
tableStore.index()
}
)
watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告)
(newVal, oldVal) => {
tableStore.index()
},
{
deep: true // 若 timeValue 是对象/数组,需开启深度监听
}
)
const openDialog = async (row: any) => {
dialogVisible.value = true
tableStore.index()
}
defineExpose({ openDialog })
</script>
<style lang="scss" scoped>
:deep(.el-select) {
min-width: 80px;
}
</style>

View File

@@ -0,0 +1,134 @@
<template>
<!-- 综合评估详情 -->
<el-dialog draggable title="指标合格率统计" v-model="dialogVisible" append-to-body width="70%">
<TableHeader datePicker showExport :showReset="false">
<template v-slot:select>
<el-form-item label="监测点名称">
<el-select
v-model="tableStore.table.params.searchValue"
placeholder="请选择监测点名称"
style="width: 240px"
>
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</template>
</TableHeader>
<Table ref="tableRef" isGroup :height="height"></Table>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, provide } from 'vue'
import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue'
import TableStore from '@/utils/tableStore'
import { mainHeight } from '@/utils/layout'
const dialogVisible: any = ref(false)
const options = [
{
value: '35kV进线',
label: '35kV进线'
}
]
const height = mainHeight(0, 2).height as any
const loop50 = (key: string) => {
let list: any[] = []
for (let i = 2; i < 51; i++) {
list.push({
title: i + '次',
// field: key + i,
field: 'flicker',
width: '80'
})
}
return list
}
const tableStore: any = new TableStore({
url: '/user-boot/role/selectRoleDetail?id=0',
method: 'POST',
publicHeight: 30,
showPage: false,
exportName: '主要监测点列表',
column: [
{
field: 'index',
title: '序号',
width: '80',
formatter: (row: any) => {
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{
title: '日期',
field: 'time',
width: '150'
},
{
title: '名称',
field: 'name',
width: '150'
},
{
title: '闪变越限(分钟)',
field: 'flicker',
width: '80'
},
{
title: '谐波电压越限(分钟)',
children: loop50('voltage')
},
{
title: '谐波电流越限(分钟)',
children: loop50('harmonicCurrent')
},
{
title: '三相不平衡度越限(分钟)',
field: 'flicker',
width: '100'
},
{
title: '电压偏差越限(分钟)',
field: 'flicker',
width: '100'
},
{
title: '频率偏差越限(分钟)',
field: 'flicker',
width: '100'
}
],
beforeSearchFun: () => {},
loadCallback: () => {
tableStore.table.data = [
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0',
},
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0',
},
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0',
},
]
}
})
tableStore.table.params.searchValue = ''
provide('tableStore', tableStore)
const open = async (row: any) => {
dialogVisible.value = true
tableStore.index()
}
defineExpose({ open })
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,158 @@
<template>
<div>
<!-- 综合评估详情 -->
<el-dialog draggable title="指标合格率统计" v-model="dialogVisible" append-to-body width="70%">
<TableHeader datePicker showExport :showReset="false">
<template v-slot:select>
<el-form-item label="监测点名称">
<el-select
v-model="tableStore.table.params.searchValue"
placeholder="请选择监测点名称"
style="width: 240px"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</template>
</TableHeader>
<Table ref="tableRef" @cell-click="cellClickEvent" isGroup :height="height"></Table>
</el-dialog>
<!-- 谐波电流谐波电压占有率 -->
<HarmonicRatio ref="harmonicRatioRef" />
</div>
</template>
<script setup lang="ts">
import { ref, provide } from 'vue'
import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue'
import TableStore from '@/utils/tableStore'
import { mainHeight } from '@/utils/layout'
import HarmonicRatio from '@/components/cockpit/listOfMainMonitoringPoints/components/harmonicRatio.vue'
const dialogVisible: any = ref(false)
const harmonicRatioRef: any = ref(null)
const options = [
{
value: '35kV进线',
label: '35kV进线'
}
]
const height = mainHeight(0, 2).height as any
const loop50 = (key: string) => {
let list: any[] = []
for (let i = 2; i < 51; i++) {
list.push({
title: i + '次',
// field: key + i,
field: 'flicker',
width: '80',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.flicker}</span>`
}
})
}
return list
}
const tableStore: any = new TableStore({
url: '/user-boot/role/selectRoleDetail?id=0',
method: 'POST',
publicHeight: 30,
showPage: false,
exportName: '主要监测点列表',
column: [
{
field: 'index',
title: '序号',
width: '80',
formatter: (row: any) => {
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{
title: '日期',
field: 'time',
width: '150'
},
{
title: '名称',
field: 'name',
width: '150'
},
{
title: '闪变越限(分钟)',
field: 'flicker',
width: '80',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.flicker}</span>`
}
},
{
title: '谐波电压越限(分钟)',
children: loop50('voltage')
},
{
title: '谐波电流越限(分钟)',
children: loop50('harmonicCurrent')
},
{
title: '三相不平衡度越限(分钟)',
field: 'flicker',
width: '100'
},
{
title: '电压偏差越限(分钟)',
field: 'flicker',
width: '100'
},
{
title: '频率偏差越限(分钟)',
field: 'flicker',
width: '100'
}
],
beforeSearchFun: () => {},
loadCallback: () => {
tableStore.table.data = [
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0'
},
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0'
},
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0'
}
]
}
})
tableStore.table.params.searchValue = ''
provide('tableStore', tableStore)
const open = async (row: any) => {
dialogVisible.value = true
tableStore.index()
}
// 点击行
const cellClickEvent = ({ row, column }: any) => {
if (column.field != 'name' && column.field != 'time') {
harmonicRatioRef.value.openDialog(row)
}
}
defineExpose({ open })
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,307 @@
<template>
<div>
<!--指标拟合图 -->
<TableHeader :showReset="false" datePicker @selectChange="selectChange" v-if="fullscreen">
<template v-slot:select>
<el-form-item label="用户功率">
<el-select
v-model="tableStore.table.params.power"
placeholder="请选择用户功率"
clearable
style="width: 130px"
>
<el-option
v-for="item in powerList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="电能质量指标">
<el-select
v-model="tableStore.table.params.indicator"
placeholder="请选择电能质量指标"
clearable
style="width: 130px"
>
<el-option
v-for="item in indicatorList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="越限情况">
<el-select
v-model="tableStore.table.params.exceedingTheLimit"
placeholder="请选择越限情况"
clearable
style="width: 90px"
>
<el-option
v-for="item in exceedingTheLimitList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</template>
</TableHeader>
<my-echart
class="tall"
:options="echartList"
:style="{
width: prop.width,
height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`
}"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h, computed } from 'vue'
import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue'
import { useDictData } from '@/stores/dictData'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { useConfig } from '@/stores/config'
import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({
w: { type: String },
h: { type: String },
width: { type: String },
height: { type: String },
timeKey: { type: String },
timeValue: { type: Object }
})
const route = useRoute()
const timeCacheStore = useTimeCacheStore()
const config = useConfig()
const powerList: any = ref([
{
label: '三相总有功功率',
value: '1'
},
{
label: '三相总无功功率',
value: '2'
}
])
// 计算是否全屏展示
const fullscreen = computed(() => {
const w = Number(prop.w)
const h = Number(prop.h)
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
// 执行相应逻辑
return true
} else {
return false
}
})
const exceedingTheLimitList: any = ref([
{
label: '越限',
value: '1'
},
{
label: '不越限',
value: '0'
}
])
const indicatorList: any = ref([
{
label: '谐波电压总畸变率',
value: '1'
},
{
label: '各次谐波电压',
value: '2'
},
{
label: '各次谐波电压',
value: '3'
},
{
label: '三相电压不平衡',
value: '4'
}
])
const echartList = ref({
title: {
text: '谐波电压总畸变率越限与功率负荷曲线拟合图'
},
xAxis: {
type: 'time',
axisLabel: {
formatter: {
day: '{MM}-{dd}',
month: '{MM}',
year: '{yyyy}'
}
}
},
yAxis: [{}, {}],
grid: {
left: '10px',
right: '20px'
},
options: {
series: [
{
// name: '暂降次数',
type: 'bar',
name: '有功功率',
data: [
['2025-10-16 07:00:00', 10],
['2025-10-16 07:15:00', 10],
['2025-10-16 07:30:00', 10],
['2025-10-16 07:45:00', 10],
['2025-10-16 08:00:00', 30],
['2025-10-16 08:15:00', 50],
['2025-10-16 08:30:00', 60],
['2025-10-16 08:45:00', 70],
['2025-10-16 09:00:00', 100],
['2025-10-16 09:15:00', 120],
['2025-10-16 09:30:00', 130],
['2025-10-16 09:45:00', 140],
['2025-10-16 10:00:00', 160],
['2025-10-16 10:15:00', 160],
['2025-10-16 10:30:00', 130],
['2025-10-16 10:45:00', 120],
['2025-10-16 11:00:00', 140],
['2025-10-16 11:15:00', 80],
['2025-10-16 11:30:00', 70],
['2025-10-16 11:45:00', 90],
['2025-10-16 12:00:00', 60],
['2025-10-16 12:15:00', 60],
['2025-10-16 12:30:00', 60],
['2025-10-16 12:45:00', 60]
],
itemStyle: {
normal: {
//这里是颜色
color: function (params: any) {
if (params.value[1] == 0 || params.value[1] == 3.14159) {
return '#ccc'
} else {
return config.layout.elementUiPrimary[0]
}
}
}
},
yAxisIndex: 0
},
{
name: '谐波总畸变率',
type: 'line',
showSymbol: false,
smooth: true,
data: [
['2025-10-16 07:00:00', 0],
['2025-10-16 07:15:00', 0],
['2025-10-16 07:30:00', 0],
['2025-10-16 07:45:00', 0],
['2025-10-16 08:00:00', 0],
['2025-10-16 08:15:00', 0.1],
['2025-10-16 08:30:00', 0.1],
['2025-10-16 08:45:00', 0.1],
['2025-10-16 09:00:00', 1],
['2025-10-16 09:15:00', 1],
['2025-10-16 09:30:00', 1],
['2025-10-16 09:45:00', 1],
['2025-10-16 10:00:00', 0.8],
['2025-10-16 10:15:00', 0.8],
['2025-10-16 10:30:00', 0.8],
['2025-10-16 10:45:00', 0.8],
['2025-10-16 11:00:00', 0.8],
['2025-10-16 11:15:00', 0.1],
['2025-10-16 11:30:00', 0.1],
['2025-10-16 11:45:00', 0.1],
['2025-10-16 12:00:00', 0],
['2025-10-16 12:15:00', 0],
['2025-10-16 12:30:00', 0],
['2025-10-16 12:45:00', 0]
],
yAxisIndex: 1
}
]
}
})
const headerHeight = ref(57)
const selectChange = (showSelect: any, height: any) => {
headerHeight.value = height
}
const tableStore: any = new TableStore({
url: '/user-boot/role/selectRoleDetail?id=0',
method: 'POST',
showPage: false,
exportName: '主要监测点列表',
column: [],
beforeSearchFun: () => {
// 尝试从缓存获取时间值
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
},
loadCallback: () => {
tableStore.table.height = `calc(${prop.height} - 80px)`
}
})
const tableRef = ref()
provide('tableRef', tableRef)
tableStore.table.params.power = '1'
tableStore.table.params.indicator = '1'
tableStore.table.params.exceedingTheLimit = '1'
tableStore.table.params.searchValue = ''
provide('tableStore', tableStore)
onMounted(() => {
tableStore.index()
})
watch(
() => prop.timeKey,
val => {
tableStore.index()
}
)
watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告)
(newVal, oldVal) => {
tableStore.index()
},
{
deep: true // 若 timeValue 是对象/数组,需开启深度监听
}
)
const addMenu = () => {}
</script>
<style lang="scss" scoped>
:deep(.el-select) {
min-width: 80px;
}
</style>

View File

@@ -0,0 +1,238 @@
<template>
<div>
<!--监测点列表 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader>
<Table ref="tableRef" @cell-click="cellClickEvent" :height="`calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`"></Table>
<!-- 指标越限详情 -->
<OverLimitDetails ref="OverLimitDetailsRef" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import { ElTag } from 'element-plus'
import OverLimitDetails from '@/components/cockpit/listOfMainMonitoringPoints/components/overLimitDetails.vue'
import TableHeader from '@/components/table/header/index.vue'
import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({
w: { type: String },
h: { type: String },
width: { type: String },
height: { type: String },
timeKey: { type: String },
timeValue: { type: Object }
})
const headerHeight = ref(57)
const route = useRoute()
const timeCacheStore = useTimeCacheStore()
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height
// 如果有传入 datePicker 的值
if (datePickerValue) {
// 更新表格参数
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
}
}
// 计算是否全屏展示
const fullscreen = computed(() => {
const w = Number(prop.w)
const h = Number(prop.h)
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
// 执行相应逻辑
return true
} else {
return false
}
})
const OverLimitDetailsRef = ref()
const tableStore: any = new TableStore({
url: '/user-boot/role/selectRoleDetail?id=0',
method: 'POST',
showPage: false,
exportName: '主要监测点列表',
column: [
{
field: 'index',
title: '序号',
width: '80',
formatter: (row: any) => {
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{
title: '治理对象名称',
field: 'name',
minWidth: '80'
},
{
title: '电压等级',
field: 'type',
minWidth: '70'
},
{
title: '治理设备详情',
field: 'type1',
minWidth: '70'
},
{
title: '治理前报告',
field: 'type2',
minWidth: '80',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;' onclick="handleReportClick('${row.name}')">${row.type2}</span>`
}
},
{ title: '监测点名称', field: 'type3', minWidth: '70' },
{ title: '监测类型', field: 'type4', minWidth: '60' },
{
title: '监测点状态',
field: 'type5',
minWidth: '60',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='color: ${row.type5 === '中断' ? '#FF0000' : ''}'>${row.type5}</span>`
}
},
{ title: '最新数据时间', field: 'type6', minWidth: '140' }
],
beforeSearchFun: () => {
// 尝试从缓存获取时间值
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
},
loadCallback: () => {
tableStore.table.data = [
{
name: '1#变压器',
type: '0.38kV',
type1: '250A APF',
type2: '报告.doc',
type3: '1#变压器 电网侧',
type4: '电网侧',
type5: '运行',
type6: '2025-04-11 18:16:00'
},
{
name: '1#变压器',
type: '0.38kV',
type1: '250A APF',
type2: '报告.doc',
type3: '1#变压器 负载侧',
type4: '负载侧',
type5: '运行',
type6: '2025-04-11 18:16:00'
},
{
name: '2#变压器',
type: '0.38kV',
type1: '100A SVG',
type2: '/',
type3: '1#变压器 电网侧',
type4: '电网侧',
type5: '运行',
type6: '2025-04-11 18:16:00'
},
{
name: '2#变压器',
type: '0.38kV',
type1: '100A SVG',
type2: '/',
type3: '1#变压器 负载侧',
type4: '负载侧',
type5: '中断',
type6: '2025-04-11 18:16:00'
}
]
tableStore.table.height = `calc(${prop.height} - 80px)`
}
})
const tableRef = ref()
provide('tableRef', tableRef)
tableStore.table.params.type = ''
tableStore.table.params.searchValue = ''
provide('tableStore', tableStore)
// 点击行
const cellClickEvent = ({ row, column }: any) => {
if (column.field == 'name') {
console.log(row)
OverLimitDetailsRef.value.open(row)
}
}
onMounted(() => {
tableStore.index()
})
watch(
() => prop.timeKey,
val => {
tableStore.index()
}
)
watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告)
(newVal, oldVal) => {
tableStore.index()
},
{
deep: true // 若 timeValue 是对象/数组,需开启深度监听
}
)
const handleReportClick = (id: string) => {
const row = tableStore.table.data.find((item: any) => item.id === id)
if (row && row.type2 !== '/') {
// 示例:触发下载逻辑(根据你注释掉的代码)
// getFileZip({ eventId: id }).then(res => {
// let blob = new Blob([res], { type: 'application/zip' })
// const url = window.URL.createObjectURL(blob)
// const link = document.createElement('a')
// link.href = url
// link.download = row.wavePath?.split('/')[2] || '报告文件.zip'
// link.click()
// })
console.log('点击了报告:', row.type2)
} else {
ElMessage.warning('暂无报告可下载')
}
}
// 挂载到 window 供 HTML 调用
window.handleReportClick = handleReportClick
// 组件销毁时清理全局方法
onBeforeUnmount(() => {
delete window.handleReportClick
})
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,249 @@
<template>
<div>
<!--总体指标越限统计 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader>
<my-echart
class="tall"
:options="echartList"
:style="{
width: prop.width,
height: `calc(${prop.height} / 2 )`
}"
/>
<Table
ref="tableRef"
@cell-click="cellClickEvent"
:height="`calc(${prop.height} / 2 - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`"
isGroup
></Table>
<!-- 指标越限详情 -->
<OverLimitDetails ref="OverLimitDetailsRef" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import OverLimitDetails from '@/components/cockpit/listOfMainMonitoringPoints/components/overLimitDetails.vue'
import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({
w: { type: String },
h: { type: String },
width: { type: String },
height: { type: String },
timeKey: { type: String },
timeValue: { type: Object }
})
const headerHeight = ref(57)
const route = useRoute()
const timeCacheStore = useTimeCacheStore()
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height
// 如果有传入 datePicker 的值
if (datePickerValue) {
// 更新表格参数
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
}
}
// 计算是否全屏展示
const fullscreen = computed(() => {
const w = Number(prop.w)
const h = Number(prop.h)
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
// 执行相应逻辑
return true
} else {
return false
}
})
const echartList = ref({
title: {
text: '指标越限占比'
},
xAxis: {
// name: '(区域)',
data: ['闪变', '谐波电压', '谐波电流', '电压偏差', '三相不平衡']
},
yAxis: {
name: '%', // 给X轴加单位
interval: 20
},
grid: {
left: '10px',
right: '20px'
},
options: {
series: [
{
// name: '暂降次数',
type: 'bar',
name: '越限占比',
data: [0, 45, 22, 0, 70],
barMaxWidth: 30
// label: {
// show: true,
// position: 'top',
// textStyle: {
// //数值样式
// color: '#000'
// },
// fontSize: 12
// }
}
]
}
})
const OverLimitDetailsRef = ref()
const tableStore: any = new TableStore({
url: '/user-boot/dept/deptTree',
method: 'POST',
showPage: false,
column: [
{
field: 'index',
title: '序号',
width: '80',
formatter: (row: any) => {
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{
title: '名称',
field: 'name',
minWidth: '90'
},
{
title: '越限占比(%)',
children: [
{
title: '闪变',
field: 'type',
minWidth: '70',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type}</span>`
}
},
{
title: '谐波电压',
field: 'type1',
minWidth: '80',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type1}</span>`
}
},
{
title: '谐波电流',
field: 'type2',
minWidth: '80',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type2}</span>`
}
},
{
title: '电压偏差',
field: 'type3',
minWidth: '80',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type3}</span>`
}
},
{
title: '三相不平衡',
field: 'type4',
minWidth: '90',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type4}</span>`
}
}
]
}
],
beforeSearchFun: () => {
// 尝试从缓存获取时间值
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
},
loadCallback: () => {
tableStore.table.data = [
{
name: '10kV1#电动机',
type: '0',
type1: '45',
type2: '22',
type3: '0',
type4: '70'
}
]
tableStore.table.height = `calc(${prop.height} - 80px)`
}
})
const tableRef = ref()
provide('tableRef', tableRef)
provide('tableStore', tableStore)
// 点击行
const cellClickEvent = ({ row, column }: any) => {
if (column.field != 'name') {
console.log(row)
OverLimitDetailsRef.value.open(row)
}
}
onMounted(() => {
tableStore.index()
})
watch(
() => prop.timeKey,
val => {
tableStore.index()
}
)
watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告)
(newVal, oldVal) => {
tableStore.index()
},
{
deep: true // 若 timeValue 是对象/数组,需开启深度监听
}
)
const addMenu = () => {}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,164 @@
<template>
<div>
<!--敏感负荷列表 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader>
<Table ref="tableRef" @cell-click="cellClickEvent" :height="`calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`" isGroup></Table>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import TableHeader from '@/components/table/header/index.vue'
import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({
w: { type: String },
h: { type: String },
width: { type: String },
height: { type: String },
timeKey: { type: String },
timeValue: { type: Object }
})
const headerHeight = ref(57)
const route = useRoute()
const timeCacheStore = useTimeCacheStore()
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height
// 如果有传入 datePicker 的值
if (datePickerValue) {
// 更新表格参数
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
}
}
// 计算是否全屏展示
const fullscreen = computed(() => {
const w = Number(prop.w)
const h = Number(prop.h)
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
// 执行相应逻辑
return true
} else {
return false
}
})
const OverLimitDetailsRef = ref()
const tableStore: any = new TableStore({
url: '/user-boot/dept/deptTree',
method: 'POST',
showPage: false,
column: [
{
field: 'index',
title: '序号',
width: '80',
formatter: (row: any) => {
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{
title: '敏感负荷名称',
field: 'name',
minWidth: '90'
},
{
title: '敏感负荷类型',
field: 'type',
minWidth: '70'
},
{
title: '是否监测',
field: 'type1',
minWidth: '80'
},
{
title: '是否治理',
field: 'type2',
minWidth: '80'
}
],
beforeSearchFun: () => {
// 尝试从缓存获取时间值
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
},
loadCallback: () => {
tableStore.table.data = [
{
name: '10kV1#变压器',
type: '机房',
type1: '是',
type2: '100A APF'
},
{
name: '380kV1#母线',
type: 'PLC',
type1: '是',
type2: 'UPS'
}
]
}
})
const tableRef = ref()
provide('tableRef', tableRef)
provide('tableStore', tableStore)
// 点击行
const cellClickEvent = ({ row, column }: any) => {
if (column.field != 'name') {
console.log(row)
OverLimitDetailsRef.value.open(row)
}
}
onMounted(() => {
setTimeout(() => {
tableStore.index()
}, 500)
})
watch(
() => prop.timeKey,
val => {
tableStore.index()
}
)
watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告)
(newVal, oldVal) => {
tableStore.index()
},
{
deep: true // 若 timeValue 是对象/数组,需开启深度监听
}
)
const addMenu = () => {}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,131 @@
<template>
<div>
<!-- 暂态事件列表 -->
<el-dialog draggable title="暂态事件列表" v-model="dialogVisible" append-to-body width="70%">
<!-- <TableHeader datePicker showExport :showReset="false">
<template v-slot:select>
<el-form-item label="监测点名称">
<el-select
v-model="tableStore.table.params.searchValue"
placeholder="请选择监测点名称"
style="width: 240px"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</template>
</TableHeader> -->
<Table ref="tableRef" isGroup :height="height"></Table>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, provide } from 'vue'
import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue'
import TableStore from '@/utils/tableStore'
import { mainHeight } from '@/utils/layout'
const dialogVisible: any = ref(false)
const harmonicRatioRef: any = ref(null)
const options = [
{
value: '35kV进线',
label: '35kV进线'
}
]
const height = mainHeight(0, 2).height as any
const tableStore: any = new TableStore({
url: '/user-boot/role/selectRoleDetail?id=0',
method: 'POST',
publicHeight: 30,
showPage: false,
exportName: '主要监测点列表',
column: [
{
field: 'index',
title: '序号',
width: '80',
formatter: (row: any) => {
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{
title: '暂态时间',
field: 'time',
},
{
title: '测点名称',
field: 'name',
width: '150'
},
{
title: '暂态类型',
field: 'flicker',
width: '100',
},
{
title: '特征幅值(%)',
field: 'flicker',
width: '100'
},
{
title: '暂降深度(%)',
field: 'flicker',
width: '100'
},
{
title: '持续时间(S)',
field: 'flicker',
width: '100'
},
{
title: '严重度',
field: 'flicker',
width: '80'
},
],
beforeSearchFun: () => {},
loadCallback: () => {
tableStore.table.data = [
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0'
},
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0'
},
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0'
}
]
}
})
tableStore.table.params.searchValue = ''
provide('tableStore', tableStore)
const open = async (row: any) => {
dialogVisible.value = true
tableStore.index()
}
// 点击行
const cellClickEvent = ({ row, column }: any) => {
if (column.field != 'name') {
harmonicRatioRef.value.openDialog(row)
}
}
defineExpose({ open })
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,252 @@
<template>
<div>
<!--暂态事件明细 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader>
<el-calendar v-model="value" :style="{ height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`, overflow: 'auto' }">
<template #date-cell="{ data }">
<div style="height: 100%; padding: 8px" :style="{ background: setBackground(data.day) }">
<p :class="data.isSelected ? 'is-selected' : ''">
{{ data.day.split('-').slice(2).join('-') }}
</p>
<el-tooltip
effect="dark"
placement="top"
:hide-after="0"
v-if="list?.filter(item => item.time == data.day)[0]?.type || false"
>
<template #content>
<!-- <span v-html="list?.filter(item => item.time == data.day)[0]?.type || ''"></span> -->
<div v-for="item in list?.filter(item => item.time == data.day)">
<div>电压暂降{{ item.type || '' }}</div>
<div>电压中断{{ item.type1 || '' }}</div>
<div>电压暂升{{ item.type2 || '' }}</div>
</div>
</template>
<div
style="text-decoration: underline"
:style="{ height: `calc(${prop.height} / 5 - 47px)`, overflow: 'auto' }"
v-for="item in list?.filter(item => item.time == data.day)"
>
<div @click="descentClick">电压暂降:{{ item.type || '' }}</div>
<div>电压中断:{{ item.type1 || '' }}</div>
<div>电压暂升:{{ item.type2 || '' }}</div>
</div>
</el-tooltip>
</div>
</template>
</el-calendar>
<!-- 暂态事件列表 -->
<TransientList ref="transientListRef" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import { dayjs } from 'element-plus'
import TransientList from './components/transientList.vue'
import TableHeader from '@/components/table/header/index.vue'
import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({
w: { type: String },
h: { type: String },
width: { type: String },
height: { type: String },
timeKey: { type: String },
timeValue: { type: Object }
})
const headerHeight = ref(57)
const route = useRoute()
const timeCacheStore = useTimeCacheStore()
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height
// 如果有传入 datePicker 的值
if (datePickerValue) {
// 更新表格参数
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
}
}
// 计算是否全屏展示
const fullscreen = computed(() => {
const w = Number(prop.w)
const h = Number(prop.h)
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
// 执行相应逻辑
return true
} else {
return false
}
})
dayjs.en.weekStart = 1 //设置日历的周起始日为星期一
const value = ref(new Date())
const transientListRef = ref()
const list = ref([
{
time: '2025-10-01',
key: 81,
type: 1,
type1: 1,
type2: 1
},
{
time: '2025-10-31',
key: 81,
type: 1,
type1: 1,
type2: 1
},
{
time: '2025-10-08',
key: 20,
type: 1,
type1: 1,
type2: 1
},
{
time: '2025-10-16',
key: 20,
type: 1,
type1: 1,
type2: 1
},
{
time: '2025-10-23',
key: 20,
type: 1,
type1: 1,
type2: 1
},
{
time: '2025-10-04',
key: 0
},
{
time: '2025-10-05',
key: 0
}
])
const tableStore: any = new TableStore({
url: '/user-boot/dept/deptTree',
method: 'POST',
showPage: false,
column: [],
beforeSearchFun: () => {
// 尝试从缓存获取时间值
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
},
loadCallback: () => {
tableStore.table.data = []
}
})
const tableRef = ref([])
const setBackground = (value: string) => {
let data = []
data = list.value?.filter(item => item.time == value)
if (data && data?.length > 0) {
if (data[0].key > 0) {
return '#Ff660090'
}
}
return '#fff'
}
provide('tableRef', tableRef)
provide('tableStore', tableStore)
onMounted(() => {
tableStore.index()
})
watch(
() => prop.timeKey,
val => {
tableStore.index()
}
)
watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告)
(newVal, oldVal) => {
tableStore.index()
},
{
deep: true // 若 timeValue 是对象/数组,需开启深度监听
}
)
// 电压暂降点击事件
const descentClick = () => {
transientListRef.value.open()
}
</script>
<style lang="scss" scoped>
:deep(.el-calendar) {
.el-calendar__header {
display: none;
}
.el-calendar__body {
padding: 0px !important;
height: 100%;
.el-calendar-table {
height: 100%;
}
}
.el-calendar-day {
height: 100%;
padding: 0px;
overflow: hidden;
}
.el-calendar-table__row {
.next {
pointer-events: none;
}
.prev {
pointer-events: none;
}
}
.el-calendar-table .el-calendar-day:hover {
background-color: #ffffff00;
}
.el-calendar-table td.is-selected {
background-color: #ffffff00;
}
}
// /*calendar_class 是el-calendar所在父标签的css*/
// .calendar_class >>> .el-calendar-table:not(.is-range) td.next {
// pointer-events: none;
// }
// .calendar_class >>> .el-calendar-table:not(.is-range) td.prev {
// pointer-events: none;
// }
</style>

View File

@@ -0,0 +1,392 @@
<template>
<div>
<!--暂态事件概率分布 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader>
<my-echart
class="tall"
:options="echartList"
:style="{ width: prop.width, height: `calc(${prop.height} / 2 - ${headerHeight / 2}px + ${fullscreen ? 0 : 28}px )` }"
/>
<my-echart
class="mt10"
:options="echartList1"
:style="{ width: prop.width, height: `calc(${prop.height} / 2 - ${headerHeight / 2}px + ${fullscreen ? 0 : 28}px )` }"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch } from 'vue'
import TableStore from '@/utils/tableStore'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import { useConfig } from '@/stores/config'
import TableHeader from '@/components/table/header/index.vue'
import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({
w: { type: String },
h: { type: String },
width: { type: String },
height: { type: String },
timeKey: { type: String },
timeValue: { type: Object }
})
const headerHeight = ref(57)
const route = useRoute()
const timeCacheStore = useTimeCacheStore()
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height
// 如果有传入 datePicker 的值
if (datePickerValue) {
// 更新表格参数
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
}
}
// 计算是否全屏展示
const fullscreen = computed(() => {
const w = Number(prop.w)
const h = Number(prop.h)
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
// 执行相应逻辑
return true
} else {
return false
}
})
const config = useConfig()
const echartList = ref({
options: {
xAxis: null,
yAxis: null,
dataZoom: null,
backgroundColor: '#fff',
tooltip: {
// trigger: 'axis'
textStyle: {
color: '#fff',
fontStyle: 'normal',
opacity: 0.35,
fontSize: 14
},
backgroundColor: 'rgba(0,0,0,0.55)',
borderWidth: 0,
formatter: function (params: any) {
console.log(params)
var tips = ''
for (var i = 0; i < params.length; i++) {
tips += params[i].name + '</br/>'
tips += '监测点数' + ':' + '&nbsp' + '&nbsp' + params[i].value + '</br/>'
}
return tips
}
},
title: {
text: '暂态事件概率分布',
x: 'center'
},
visualMap: {
max: 20,
show: false,
inRange: {
color: ['#313695', '#00BB00', '#ff8000', '#a50026']
}
},
xAxis3D: {
type: 'category',
name: '特征幅值',
data: [
'0-10%',
'10%-20%',
'20%-30%',
'30%-40%',
'40%-50%',
'50%-60%',
'60%-70%',
'70%-80%',
'80%-90%',
'90%-100%'
],
axisLine: {
lineStyle: {
color: '#111'
}
},
axisLabel: {
color: '#111'
}
},
yAxis3D: {
type: 'category',
name: '持续时间',
data: ['0-0.01s', '0.01s-0.1s', '0.1s-1s', '1s-10s', '10s'],
nameTextStyle: {
color: '#111'
},
axisLine: {
show: true,
lineStyle: {
color: '#111'
}
},
axisLabel: {
color: '#111'
},
splitLine: {
lineStyle: {
// 使用深浅的间隔色
color: ['#111'],
type: 'dashed',
opacity: 0.5
}
}
},
zAxis3D: {
type: 'value',
splitNumber: 10,
minInterval: 10,
name: '暂态事件次数'
},
grid3D: {
viewControl: {
projection: 'perspective',
distance: 250
},
boxWidth: 200,
boxDepth: 80,
light: {
main: {
intensity: 1.2
},
ambient: {
intensity: 0.3
}
}
},
series: [
{
type: 'bar3D',
data: [
[0, 0, 1],
[0, 1, 1],
[0.2, 1]
],
shading: 'realistic',
label: {
show: false,
textStyle: {
fontSize: 16,
borderWidth: 1
}
},
itemStyle: {
opacity: 1
},
emphasis: {
label: {
textStyle: {
fontSize: 20,
color: '#900'
}
},
itemStyle: {
color: '#900'
}
}
}
]
}
})
const echartList1 = ref({
title: {
text: '越限时间概率分布'
},
xAxis: {
// name: '时间',
// data: ['闪变', '谐波电压', '谐波电流', '电压偏差', '三相不平衡']
type: 'time',
axisLabel: {
formatter: {
day: '{MM}-{dd}',
month: '{MM}',
year: '{yyyy}'
}
}
},
yAxis: {
name: '次' // 给X轴加单位
},
grid: {
left: '10px',
right: '20px'
},
options: {
series: [
{
type: 'line',
showSymbol: false,
// smooth: true,
name: '电压中断',
color: '#FF9100',
data: [
['2025-10-16 07:00:00', 10],
['2025-10-16 07:15:00', 10],
['2025-10-16 07:30:00', 10],
['2025-10-16 07:45:00', 10],
['2025-10-16 08:00:00', 30],
['2025-10-16 08:15:00', 50],
['2025-10-16 08:30:00', 60],
['2025-10-16 08:45:00', 70],
['2025-10-16 09:00:00', 100],
['2025-10-16 09:15:00', 120],
['2025-10-16 09:30:00', 130],
['2025-10-16 09:45:00', 140],
['2025-10-16 10:00:00', 160],
['2025-10-16 10:15:00', 160],
['2025-10-16 10:30:00', 130],
['2025-10-16 10:45:00', 120],
['2025-10-16 11:00:00', 140],
['2025-10-16 11:15:00', 80],
['2025-10-16 11:30:00', 70],
['2025-10-16 11:45:00', 90],
['2025-10-16 12:00:00', 60],
['2025-10-16 12:15:00', 60],
['2025-10-16 12:30:00', 60],
['2025-10-16 12:45:00', 60]
]
},
{
type: 'line',
showSymbol: false,
// smooth: true,
color: '#FFBF00',
name: '电压暂降',
data: [
['2025-10-16 07:00:00', 1],
['2025-10-16 07:15:00', 1],
['2025-10-16 07:30:00', 1],
['2025-10-16 07:45:00', 1],
['2025-10-16 08:00:00', 3],
['2025-10-16 08:15:00', 5],
['2025-10-16 08:30:00', 6],
['2025-10-16 08:45:00', 7],
['2025-10-16 09:00:00', 10],
['2025-10-16 09:15:00', 12],
['2025-10-16 09:30:00', 13],
['2025-10-16 09:45:00', 14],
['2025-10-16 10:00:00', 16],
['2025-10-16 10:15:00', 16],
['2025-10-16 10:30:00', 13],
['2025-10-16 10:45:00', 12],
['2025-10-16 11:00:00', 14],
['2025-10-16 11:15:00', 8],
['2025-10-16 11:30:00', 7],
['2025-10-16 11:45:00', 9],
['2025-10-16 12:00:00', 6],
['2025-10-16 12:15:00', 6],
['2025-10-16 12:30:00', 6],
['2025-10-16 12:45:00', 6]
]
},
{
type: 'line',
showSymbol: false,
// smooth: true,
name: '电压暂升',
color: config.layout.elementUiPrimary[0],
data: [
['2025-10-16 07:00:00', 19],
['2025-10-16 07:15:00', 19],
['2025-10-16 07:30:00', 19],
['2025-10-16 07:45:00', 19],
['2025-10-16 08:00:00', 39],
['2025-10-16 08:15:00', 59],
['2025-10-16 08:30:00', 69],
['2025-10-16 08:45:00', 79],
['2025-10-16 09:00:00', 109],
['2025-10-16 09:15:00', 129],
['2025-10-16 09:30:00', 139],
['2025-10-16 09:45:00', 149],
['2025-10-16 10:00:00', 169],
['2025-10-16 10:15:00', 169],
['2025-10-16 10:30:00', 139],
['2025-10-16 10:45:00', 129],
['2025-10-16 11:00:00', 149],
['2025-10-16 11:15:00', 89],
['2025-10-16 11:30:00', 79],
['2025-10-16 11:45:00', 99],
['2025-10-16 12:00:00', 69],
['2025-10-16 12:15:00', 69],
['2025-10-16 12:30:00', 69],
['2025-10-16 12:45:00', 69]
]
}
]
}
})
const tableStore: any = new TableStore({
url: '/user-boot/dept/deptTree',
method: 'POST',
showPage: false,
column: [],
beforeSearchFun: () => {
// 尝试从缓存获取时间值
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
},
loadCallback: () => {
tableStore.table.data = []
}
})
const tableRef = ref()
provide('tableRef', tableRef)
provide('tableStore', tableStore)
onMounted(() => {
tableStore.index()
})
watch(
() => prop.timeKey,
val => {
tableStore.index()
}
)
watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告)
(newVal, oldVal) => {
tableStore.index()
},
{
deep: true // 若 timeValue 是对象/数组,需开启深度监听
}
)
const addMenu = () => {}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,150 @@
<template>
<div>
<!-- 暂态事件详情 -->
<el-dialog draggable title="暂态事件详情 " v-model="dialogVisible" append-to-body width="70%">
<TableHeader datePicker showExport :showReset="false">
<template v-slot:select>
<el-form-item label="监测点名称">
<el-select
v-model="tableStore.table.params.searchValue"
placeholder="请选择监测点名称"
style="width: 240px"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</template>
</TableHeader>
<Table ref="tableRef" @cell-click="cellClickEvent" isGroup :height="height"></Table>
</el-dialog>
<!-- 查看波形 -->
<HarmonicRatio ref="harmonicRatioRef" />
</div>
</template>
<script setup lang="ts">
import { ref, provide } from 'vue'
import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue'
import TableStore from '@/utils/tableStore'
import { mainHeight } from '@/utils/layout'
import HarmonicRatio from '@/components/cockpit/listOfMainMonitoringPoints/components/harmonicRatio.vue'
const dialogVisible: any = ref(false)
const harmonicRatioRef: any = ref(null)
const options = [
{
value: '35kV进线',
label: '35kV进线'
}
]
const height = mainHeight(0, 2).height as any
const tableStore: any = new TableStore({
url: '/user-boot/role/selectRoleDetail?id=0',
method: 'POST',
publicHeight: 30,
showPage: false,
exportName: '主要监测点列表',
column: [
{
field: 'index',
title: '序号',
width: '80',
formatter: (row: any) => {
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{
title: '暂态时间',
field: 'time',
},
{
title: '测点名称',
field: 'name',
width: '150'
},
{
title: '暂态类型',
field: 'flicker',
width: '100',
},
{
title: '特征幅值(%)',
field: 'flicker',
width: '100'
},
{
title: '暂降深度(%)',
field: 'flicker',
width: '100'
},
{
title: '持续时间(S)',
field: 'flicker',
width: '100'
},
{
title: '严重度',
field: 'flicker',
width: '80'
},
{
title: '波形',
width: '100',
render: 'buttons',
buttons: [
{
name: 'check',
title: '查看波形',
type: 'primary',
icon: 'el-icon-EditPen',
render: 'basicButton',
click: row => {
}
}
]
}
],
beforeSearchFun: () => {},
loadCallback: () => {
tableStore.table.data = [
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0'
},
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0'
},
{
time: '2024-01-01 00:00:00',
name: '35kV进线',
flicker: '0'
}
]
}
})
tableStore.table.params.searchValue = ''
provide('tableStore', tableStore)
const open = async (row: any) => {
dialogVisible.value = true
tableStore.index()
}
// 点击行
const cellClickEvent = ({ row, column }: any) => {
if (column.field != 'name') {
harmonicRatioRef.value.openDialog(row)
}
}
defineExpose({ open })
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,273 @@
<template>
<div>
<!--暂态事件统计 -->
<TableHeader :showReset="false" @selectChange="selectChange" datePicker v-if="fullscreen"></TableHeader>
<my-echart
class="tall"
:options="echartList"
:style="{
width: prop.width,
height: `calc(${prop.height} / 2 - ${headerHeight / 2}px + ${fullscreen ? 0 : 28}px )`
}"
/>
<Table
ref="tableRef"
@cell-click="cellClickEvent"
:height="`calc(${prop.height} / 2 - ${headerHeight / 2}px + ${fullscreen ? 0 : 28}px )`"
isGroup
></Table>
<TransientStatisticsDetail ref="transientStatisticsDetailRef"></TransientStatisticsDetail>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h } from 'vue'
import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import { useConfig } from '@/stores/config'
import TransientStatisticsDetail from './components/transientStatisticsDetail.vue'
import TableHeader from '@/components/table/header/index.vue'
import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({
w: { type: String },
h: { type: String },
width: { type: String },
height: { type: String },
timeKey: { type: String },
timeValue: { type: Object }
})
const headerHeight = ref(57)
const route = useRoute()
const timeCacheStore = useTimeCacheStore()
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height
// 如果有传入 datePicker 的值
if (datePickerValue) {
// 更新表格参数
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
}
}
// 计算是否全屏展示
const fullscreen = computed(() => {
const w = Number(prop.w)
const h = Number(prop.h)
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
// 执行相应逻辑
return true
} else {
return false
}
})
const config = useConfig()
const data = [
{
name: '电压中断',
value: 4
},
{
name: '电压暂降',
value: 41
},
{
name: '电压暂升',
value: 46
}
]
const echartList = ref({
title: {},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
top: 'center',
right: '5%',
formatter: function (e: any) {
return e + ' ' + data.filter(item => item.name == e)[0].value + '次'
}
},
xAxis: {
show: false
},
yAxis: {
show: false
},
grid: {
left: '10px',
right: '20px'
},
color: ['#FF9100', '#FFBF00', config.layout.elementUiPrimary[0]],
options: {
dataZoom: null,
title: [
{
text: '暂态事件统计',
left: 'center'
},
{
text: data[0].value + data[1].value + data[2].value + '次',
left: 'center',
top: 'center'
}
],
series: [
{
type: 'pie',
center: 'center',
radius: ['50%', '70%'],
label: {
show: false,
position: 'outside',
textStyle: {
//数值样式
}
},
name: '事件统计',
data: data
}
]
}
})
const transientStatisticsDetailRef = ref()
const tableStore: any = new TableStore({
url: '/user-boot/dept/deptTree',
method: 'POST',
showPage: false,
column: [
{
field: 'index',
title: '序号',
width: '80',
formatter: (row: any) => {
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{
title: '名称',
field: 'name',
minWidth: '90'
},
{
title: '电压中断(次)',
field: 'type',
minWidth: '70',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type}</span>`
}
},
{
title: '电压暂降(次)',
field: 'type1',
minWidth: '80',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type1}</span>`
}
},
{
title: '电压暂升(次)',
field: 'type2',
minWidth: '80',
render: 'customTemplate',
customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type2}</span>`
}
}
],
beforeSearchFun: () => {
// 尝试从缓存获取时间值
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
},
loadCallback: () => {
tableStore.table.data = [
{
name: '35kV1进线',
type: '2',
type1: '38',
type2: '35'
},
{
name: '35kV1变压器',
type: '2',
type1: '1',
type2: '3'
},
{
name: '35kV1母线',
type: '0',
type1: '1',
type2: '4'
},
{
name: '35kV2母线',
type: '0',
type1: '1',
type2: '4'
}
]
}
})
const tableRef = ref()
provide('tableRef', tableRef)
provide('tableStore', tableStore)
// 点击行
const cellClickEvent = ({ row, column }: any) => {
if (column.field != 'name') {
transientStatisticsDetailRef.value.open(row)
}
}
onMounted(() => {
setTimeout(() => {
tableStore.index()
}, 200)
})
watch(
() => prop.timeKey,
val => {
tableStore.index()
}
)
watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告)
(newVal, oldVal) => {
tableStore.index()
},
{
deep: true // 若 timeValue 是对象/数组,需开启深度监听
}
)
const addMenu = () => {}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,318 @@
<template>
<div>
<!--趋势对比 -->
<TableHeader :showReset="false" datePicker @selectChange="selectChange" v-if="fullscreen">
<template v-slot:select>
<el-form-item label="监测点名称">
<el-select
v-model="tableStore.table.params.power"
placeholder="请选择监测点名称"
clearable
style="width: 130px"
>
<el-option
v-for="item in powerList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="电能质量指标">
<el-select
v-model="tableStore.table.params.indicator"
placeholder="请选择电能质量指标"
clearable
style="width: 130px"
>
<el-option
v-for="item in indicatorList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="谐波次数">
<el-select
v-model="tableStore.table.params.exceedingTheLimit"
placeholder="请选择谐波次数"
clearable
style="width: 90px"
>
<el-option
v-for="item in exceedingTheLimitList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</template>
</TableHeader>
<my-echart
class="tall"
:options="echartList"
:style="{ width: prop.width, height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px)` }"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h, computed } from 'vue'
import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue'
import { useDictData } from '@/stores/dictData'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getTimeOfTheMonth } from '@/utils/formatTime'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { useConfig } from '@/stores/config'
import { useRoute } from 'vue-router'
import { useTimeCacheStore } from '@/stores/timeCache'
const prop = defineProps({
w: { type: String },
h: { type: String },
width: { type: String },
height: { type: String },
timeKey: { type: String },
timeValue: { type: Object }
})
const route = useRoute()
const timeCacheStore = useTimeCacheStore()
const config = useConfig()
const powerList: any = ref([
{
label: '1#变压器',
value: '1'
},
{
label: '2#变压器',
value: '2'
}
])
// 计算是否全屏展示
const fullscreen = computed(() => {
const w = Number(prop.w)
const h = Number(prop.h)
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
// 执行相应逻辑
return true
} else {
return false
}
})
const exceedingTheLimitList: any = ref([
{
label: '2次',
value: '2'
},
{
label: '3次',
value: '3'
},
{
label: '4次',
value: '4'
},
{
label: '5次',
value: '5'
}
])
const indicatorList: any = ref([
{
label: '谐波电压总畸变率',
value: '1'
},
{
label: '各次谐波电压',
value: '2'
},
{
label: '各次谐波电压',
value: '3'
},
{
label: '三相电压不平衡',
value: '4'
}
])
const echartList = ref({
title: {
text: '趋势对比'
},
xAxis: {
type: 'time',
axisLabel: {
formatter: {
day: '{MM}-{dd}',
month: '{MM}',
year: '{yyyy}'
}
}
},
yAxis: {},
grid: {
left: '10px',
right: '20px'
},
options: {
series: [
{
// name: '暂降次数',
type: 'line',
name: '治理前',
showSymbol: false,
smooth: true,
data: [
['2025-10-16 07:00:00', 1],
['2025-10-16 07:15:00', 1],
['2025-10-16 07:30:00', 1],
['2025-10-16 07:45:00', 1],
['2025-10-16 08:00:00', 3],
['2025-10-16 08:15:00', 5],
['2025-10-16 08:30:00', 6],
['2025-10-16 08:45:00', 7],
['2025-10-16 09:00:00', 10],
['2025-10-16 09:15:00', 12],
['2025-10-16 09:30:00', 13],
['2025-10-16 09:45:00', 14],
['2025-10-16 10:00:00', 16],
['2025-10-16 10:15:00', 16],
['2025-10-16 10:30:00', 13],
['2025-10-16 10:45:00', 12],
['2025-10-16 11:00:00', 14],
['2025-10-16 11:15:00', 8],
['2025-10-16 11:30:00', 7],
['2025-10-16 11:45:00', 9],
['2025-10-16 12:00:00', 6],
['2025-10-16 12:15:00', 6],
['2025-10-16 12:30:00', 6],
['2025-10-16 12:45:00', 6]
],
itemStyle: {
normal: {
//这里是颜色
color: function (params: any) {
if (params.value[1] == 0 || params.value[1] == 3.14159) {
return '#ccc'
} else {
return config.layout.elementUiPrimary[0]
}
}
}
},
yAxisIndex: 0
},
{
name: '治理后',
type: 'line',
showSymbol: false,
smooth: true,
data: [
['2025-10-16 07:00:00', 10],
['2025-10-16 07:15:00', 10],
['2025-10-16 07:30:00', 10],
['2025-10-16 07:45:00', 10],
['2025-10-16 08:00:00', 30],
['2025-10-16 08:15:00', 50],
['2025-10-16 08:30:00', 60],
['2025-10-16 08:45:00', 70],
['2025-10-16 09:00:00', 100],
['2025-10-16 09:15:00', 120],
['2025-10-16 09:30:00', 130],
['2025-10-16 09:45:00', 140],
['2025-10-16 10:00:00', 160],
['2025-10-16 10:15:00', 160],
['2025-10-16 10:30:00', 130],
['2025-10-16 10:45:00', 120],
['2025-10-16 11:00:00', 140],
['2025-10-16 11:15:00', 80],
['2025-10-16 11:30:00', 70],
['2025-10-16 11:45:00', 90],
['2025-10-16 12:00:00', 60],
['2025-10-16 12:15:00', 60],
['2025-10-16 12:30:00', 60],
['2025-10-16 12:45:00', 60]
]
}
]
}
})
const headerHeight = ref(57)
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height
// 如果有传入 datePicker 的值
if (datePickerValue) {
// 更新表格参数
tableStore.table.params.searchBeginTime = datePickerValue.timeValue?.[0]
tableStore.table.params.searchEndTime = datePickerValue.timeValue?.[1]
}
}
const tableStore: any = new TableStore({
url: '/user-boot/role/selectRoleDetail?id=0',
method: 'POST',
showPage: false,
exportName: '主要监测点列表',
column: [],
beforeSearchFun: () => {
// 尝试从缓存获取时间值
let beginTime, endTime
if (fullscreen.value) {
const cached = timeCacheStore.getCache(route.path)
if (cached && cached.timeValue) {
beginTime = cached.timeValue[0]
endTime = cached.timeValue[1]
}
}
// 如果缓存中没有则使用默认值
tableStore.table.params.searchBeginTime = beginTime || prop.timeValue?.[0] || getTimeOfTheMonth(prop.timeKey)[0]
tableStore.table.params.searchEndTime = endTime || prop.timeValue?.[1] || getTimeOfTheMonth(prop.timeKey)[1]
},
loadCallback: () => {
tableStore.table.height = `calc(${prop.height} - 80px)`
}
})
const tableRef = ref()
provide('tableRef', tableRef)
tableStore.table.params.power = '1'
tableStore.table.params.indicator = '1'
tableStore.table.params.exceedingTheLimit = '1'
tableStore.table.params.searchValue = ''
provide('tableStore', tableStore)
onMounted(() => {
tableStore.index()
})
watch(
() => prop.timeKey,
val => {
tableStore.index()
}
)
watch(
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告)
(newVal, oldVal) => {
tableStore.index()
},
{
deep: true // 若 timeValue 是对象/数组,需开启深度监听
}
)
const addMenu = () => {}
</script>
<style lang="scss" scoped>
:deep(.el-select) {
min-width: 80px;
}
</style>

View File

@@ -1,288 +1,293 @@
<template> <template>
<div class="chart"> <div class="chart">
<div ref="chartRef" class="my-chart" /> <div ref="chartRef" class="my-chart" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, defineExpose, watch, nextTick } from 'vue' import { onBeforeUnmount, onMounted, ref, defineExpose, watch, nextTick } from 'vue'
// import echarts from './echarts' // import echarts from './echarts'
import * as echarts from 'echarts' // 全引入 import * as echarts from 'echarts' // 全引入
import 'echarts-gl' import 'echarts-gl'
import 'echarts-liquidfill' import 'echarts-liquidfill'
import 'echarts/lib/component/dataZoom' import 'echarts/lib/component/dataZoom'
import { color, gradeColor3 } from './color' import { color, gradeColor3 } from './color'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
// import { nextTick } from 'process' // import { nextTick } from 'process'
const config = useConfig() const emit = defineEmits(['chartClick'])
color[0] = config.layout.elementUiPrimary[0] const config = useConfig()
const chartRef = ref<HTMLDivElement>() color[0] = config.layout.elementUiPrimary[0]
const chartRef = ref<HTMLDivElement>()
const props = defineProps(['options', 'isInterVal', 'pieInterVal'])
let chart: echarts.ECharts | any = null const props = defineProps(['options', 'isInterVal', 'pieInterVal'])
const resizeHandler = () => { let chart: echarts.ECharts | any = null
// 不在视野中的时候不进行resize const resizeHandler = () => {
if (!chartRef.value) return // 不在视野中的时候不进行resize
if (chartRef.value.offsetHeight == 0) return if (!chartRef.value) return
chart.getZr().painter.getViewportRoot().style.display = 'none' if (chartRef.value.offsetHeight == 0) return
requestAnimationFrame(() => { chart.getZr().painter.getViewportRoot().style.display = 'none'
chart.resize() requestAnimationFrame(() => {
chart.getZr().painter.getViewportRoot().style.display = '' chart.resize()
}) chart.getZr().painter.getViewportRoot().style.display = ''
} })
const initChart = () => { }
if (!props.isInterVal && !props.pieInterVal) { const initChart = () => {
chart?.dispose() if (!props.isInterVal && !props.pieInterVal) {
} chart?.dispose()
// chart?.dispose() }
chart = echarts.init(chartRef.value as HTMLDivElement) // chart?.dispose()
const options = { chart = echarts.init(chartRef.value as HTMLDivElement)
title: { const options = {
left: 'center', title: {
// textStyle: { left: 'center',
color: '#000', // textStyle: {
textStyle: { color: '#000',
color: '#000', textStyle: {
fontSize: '18' color: '#000',
}, fontSize: '18'
// }, },
...(props.options?.title || null) // },
}, ...(props.options?.title || null)
tooltip: { },
trigger: 'axis', tooltip: {
trigger: 'axis',
textStyle: {
color: '#fff', textStyle: {
fontStyle: 'normal', color: '#fff',
opacity: 0.35, fontStyle: 'normal',
fontSize: 14 opacity: 0.35,
}, fontSize: 14
backgroundColor: 'rgba(0,0,0,0.55)', },
borderWidth: 0, backgroundColor: 'rgba(0,0,0,0.55)',
// confine: true, borderWidth: 0,
...(props.options?.tooltip || null) // confine: true,
}, ...(props.options?.tooltip || null)
toolbox: { },
right: 10, toolbox: {
top: 0, right: 20,
feature: { top: 15,
saveAsImage: { feature: {
title: '保存图片' saveAsImage: {
}, title: '保存图片'
...(props.options?.toolbox?.featureProps || null) },
}, ...(props.options?.toolbox?.featureProps || null)
// }, },
...(props.options?.toolbox || null) // },
}, ...(props.options?.toolbox || null)
legend: { },
right: 40, legend: {
top: 10, right: 50,
itemGap: 10, top: 25,
itemStyle: {}, itemGap: 10,
// textStyle: { itemStyle: {},
fontSize: 12, // textStyle: {
// padding: [2, 0, 0, 0], //[上、右、下、左] fontSize: 12,
// }, // padding: [2, 0, 0, 0], //[上、右、下、左]
itemWidth: 15, // },
itemHeight: 10, itemWidth: 15,
...(props.options?.legend || null) itemHeight: 10,
}, ...(props.options?.legend || null)
grid: { },
top: '60px', grid: {
left: '30px', top: '50px',
right: '70px', left: '30px',
bottom: props.options?.options?.dataZoom === null ? '10px' : '40px', right: '70px',
containLabel: true, bottom: props.options?.options?.dataZoom === null ? '10px' : '40px',
...(props.options?.grid || null) containLabel: true,
}, ...(props.options?.grid || null)
xAxis: props.options?.xAxis ? handlerXAxis() : null, },
yAxis: props.options?.yAxis ? handlerYAxis() : null, xAxis: props.options?.xAxis ? handlerXAxis() : null,
dataZoom: props.options?.dataZoom || [ yAxis: props.options?.yAxis ? handlerYAxis() : null,
{ dataZoom: props.options?.dataZoom || [
type: 'inside', {
height: 13, type: 'inside',
start: 0, height: 13,
start: 0,
bottom: '20px',
end: 100 bottom: '20px',
}, end: 100
{ },
start: 0, {
height: 13, start: 0,
bottom: '20px', height: 13,
end: 100 bottom: '20px',
} end: 100
// { }
// show: true, // {
// yAxisIndex: 0, // show: true,
// width: 12, // yAxisIndex: 0,
// handleSize: 8, // width: 12,
// showDataShadow: false, // handleSize: 8,
// right: 12 // showDataShadow: false,
// } // right: 12
], // }
color: props.options?.color || color, ],
series: props.options?.series, color: props.options?.color || color,
...props.options?.options series: props.options?.series,
} ...props.options?.options
// console.log(options.series,"获取x轴"); }
handlerBar(options) // console.log(options.series,"获取x轴");
// 处理柱状图 handlerBar(options)
chart.setOption(options, true) // 处理柱状图
chart.group = 'group' chart.setOption(options, true)
setTimeout(() => { chart.group = 'group'
chart.resize() // 添加点击事件
}, 0) chart.on('click', function (params: any) {
} emit('chartClick', params)
const handlerBar = (options: any) => { })
if (Array.isArray(options.series)) { setTimeout(() => {
options.series.forEach((item: any) => { chart.resize()
if (item.type === 'bar') { }, 0)
item.barMinHeight = 10 }
item.barMaxWidth = 20 const handlerBar = (options: any) => {
item.itemStyle = Object.assign( if (Array.isArray(options.series)) {
{ options.series.forEach((item: any) => {
color: (params: any) => { if (item.type === 'bar') {
if (params.value == 0 || params.value == 3.14159) { item.barMinHeight = 5
return '#ccc' item.barMaxWidth = 20
} else { item.itemStyle = Object.assign(
return props.options?.color {
? props.options?.color[params.seriesIndex] color: (params: any) => {
: color[params.seriesIndex] if (params.value == 0 || params.value == 3.14159) {
} return '#ccc'
} } else {
}, return props.options?.color
item.itemStyle ? props.options?.color[params.seriesIndex]
) : color[params.seriesIndex]
} }
}) }
} },
} item.itemStyle
const handlerYAxis = () => { )
let temp = { }
type: 'value', })
nameGap: 15, }
nameTextStyle: { }
color: '#000' const handlerYAxis = () => {
}, let temp = {
splitNumber: 5, type: 'value',
minInterval: 1, nameGap: 15,
axisLine: { nameTextStyle: {
show: true, color: '#000'
lineStyle: { },
color: '#000' splitNumber: 5,
} minInterval: 1,
}, axisLine: {
axisLabel: { show: true,
color: '#000', lineStyle: {
fontSize: 14, color: '#000'
formatter: function (value) { }
return parseFloat(value.toFixed(1)) // 格式化显示为一位小数 },
} axisLabel: {
}, color: '#000',
splitLine: { fontSize: 14,
lineStyle: { formatter: function (value) {
// 使用深浅的间隔色 return parseFloat(value.toFixed(1)) // 格式化显示为一位小数
color: ['#ccc'], }
type: 'dashed', },
opacity: 0.5 splitLine: {
} lineStyle: {
} // 使用深浅的间隔色
} color: ['#ccc'],
// props.options?.xAxis 是数组还是对象 type: 'dashed',
if (Array.isArray(props.options?.yAxis)) { opacity: 0.5
return props.options?.yAxis.map((item: any) => { }
return { }
...temp, }
...item // props.options?.xAxis 是数组还是对象
} if (Array.isArray(props.options?.yAxis)) {
}) return props.options?.yAxis.map((item: any) => {
} else { return {
return { ...temp,
...temp, ...item
...props.options?.yAxis }
} })
} } else {
} return {
const handlerXAxis = () => { ...temp,
let temp = { ...props.options?.yAxis
type: 'category', }
axisTick: { show: false }, }
axisLine: { }
// lineStyle: { const handlerXAxis = () => {
color: '#000' let temp = {
// } type: 'category',
}, axisTick: { show: false },
axisLabel: { axisLine: {
// textStyle: { // lineStyle: {
fontFamily: 'dinproRegular', color: '#000'
color: '#000', // }
fontSize: '12' },
// } axisLabel: {
} // textStyle: {
// boundaryGap: false, fontFamily: 'dinproRegular',
} color: '#000',
// props.options?.xAxis 是数组还是对象 fontSize: '12'
if (Array.isArray(props.options?.xAxis)) { // }
return props.options?.xAxis.map((item: any) => { }
return { // boundaryGap: false,
...temp, }
...item // props.options?.xAxis 是数组还是对象
} if (Array.isArray(props.options?.xAxis)) {
}) return props.options?.xAxis.map((item: any) => {
} else { return {
return { ...temp,
...temp, ...item
...props.options?.xAxis }
} })
} } else {
} return {
...temp,
let throttle: ReturnType<typeof setTimeout> ...props.options?.xAxis
// 动态计算table高度 }
const resizeObserver = new ResizeObserver(entries => { }
for (const entry of entries) { }
if (throttle) {
clearTimeout(throttle) let throttle: ReturnType<typeof setTimeout>
} // 动态计算table高度
throttle = setTimeout(() => { const resizeObserver = new ResizeObserver(entries => {
resizeHandler() for (const entry of entries) {
}, 100) if (throttle) {
} clearTimeout(throttle)
}) }
onMounted(() => { throttle = setTimeout(() => {
initChart() resizeHandler()
resizeObserver.observe(chartRef.value!) }, 100)
}) }
defineExpose({ initChart }) })
onBeforeUnmount(() => { onMounted(() => {
resizeObserver.unobserve(chartRef.value!) initChart()
chart?.dispose() resizeObserver.observe(chartRef.value!)
}) })
watch( defineExpose({ initChart })
() => props.options, onBeforeUnmount(() => {
(newVal, oldVal) => { resizeObserver.unobserve(chartRef.value!)
initChart() chart?.dispose()
} })
) watch(
</script> () => props.options,
(newVal, oldVal) => {
<style lang="scss" scoped> initChart()
.chart { }
width: 100%; )
height: 100%; </script>
position: relative;
<style lang="scss" scoped>
.el-button { .chart {
position: absolute; width: 100%;
right: 0px; height: 100%;
top: -60px; position: relative;
}
.el-button {
.my-chart { position: absolute;
height: 100%; right: 0px;
width: 100%; top: -60px;
} }
}
</style> .my-chart {
height: 100%;
width: 100%;
}
}
</style>

View File

@@ -1,4 +1,4 @@
export let color = [ '#07CCCA','#00BFF5', '#FFBF00', '#77DA63', '#D5FF6B', '#Ff6600', '#FF9100', '#5B6E96', '#66FFCC', '#B3B3B3'] export let color = [ '#07CCCA','#00BFF5', '#FFBF00', '#77DA63', '#Ff6600', '#FF9100', '#5B6E96', '#66FFCC', '#B3B3B3']
export const gradeColor3 = ['#339966', '#FFCC33', '#A52a2a'] export const gradeColor3 = ['#339966', '#FFCC33', '#A52a2a']
export const gradeColor5 = ['#00CC00', '#99CC99', '#FF9900','#996600','#A52a2a'] export const gradeColor5 = ['#00CC00', '#99CC99', '#FF9900','#996600','#A52a2a']

View File

@@ -1,13 +1,26 @@
<template> <template>
<div style="width: 540px"> <div style="width: 540px">
<el-select v-model.trim="interval" style="min-width: 90px; width: 90px; margin-right: 10px" <el-select
@change="timeChange"> v-model.trim="interval"
style="min-width: 90px; width: 90px; margin-right: 10px"
@change="timeChange"
>
<el-option v-for="item in timeOptions" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in timeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
<el-date-picker v-model.trim="timeValue" type="daterange" :disabled="disabledPicker" <el-date-picker
style="width: 220px; margin-right: 10px" unlink-panels :clearable="false" range-separator="" v-model.trim="timeValue"
start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD" :shortcuts="shortcuts" /> type="daterange"
:disabled="disabledPicker"
style="width: 220px; margin-right: 10px"
unlink-panels
:clearable="false"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
:shortcuts="shortcuts"
/>
<el-button :disabled="backDisabled" type="primary" :icon="DArrowLeft" @click="preClick"></el-button> <el-button :disabled="backDisabled" type="primary" :icon="DArrowLeft" @click="preClick"></el-button>
<el-button type="primary" :icon="VideoPause" @click="nowTime">当前</el-button> <el-button type="primary" :icon="VideoPause" @click="nowTime">当前</el-button>
<el-button :disabled="preDisabled" type="primary" :icon="DArrowRight" @click="next"></el-button> <el-button :disabled="preDisabled" type="primary" :icon="DArrowRight" @click="next"></el-button>
@@ -21,13 +34,19 @@ import { ref, onMounted, nextTick, watch } from 'vue'
interface Props { interface Props {
nextFlag?: boolean nextFlag?: boolean
theCurrentTime?: boolean theCurrentTime?: boolean
initialInterval?: number
initialTimeValue?: any
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
nextFlag: false, nextFlag: false,
theCurrentTime: true theCurrentTime: true,
initialInterval: 3,
initialTimeValue: undefined
}) })
const emit = defineEmits(['change'])
const interval = ref(3) const interval = ref(3)
const timeFlag = ref(1) const timeFlag = ref(1)
const count = ref(0) const count = ref(0)
@@ -72,8 +91,27 @@ const shortcuts = [
} }
] ]
onMounted(() => { onMounted(() => {
// 使用传入的初始值
if (props.initialInterval !== undefined) {
interval.value = props.initialInterval
}
if (props.initialTimeValue) {
timeValue.value = props.initialTimeValue
}
timeChange(3) timeChange(3)
}) })
// 添加统一的事件触发方法
const emitChange = () => {
nextTick(() => {
emit('change', {
interval: interval.value,
timeValue: timeValue.value,
timeFlag: timeFlag.value
})
})
}
// 选择时间范围 // 选择时间范围
const timeChange = (e: number) => { const timeChange = (e: number) => {
backDisabled.value = false backDisabled.value = false
@@ -110,6 +148,8 @@ const timeChange = (e: number) => {
} else { } else {
timeFlag.value = 1 timeFlag.value = 1
} }
// 触发 change 事件
emitChange()
} }
// 当前 // 当前
@@ -178,6 +218,9 @@ const preClick = () => {
// 判断向后键的状态 // 判断向后键的状态
// var temp = NowgetEndTime() // var temp = NowgetEndTime()
// timeStatus(temp, endTime) // timeStatus(temp, endTime)
// 触发 change 事件
emitChange()
} }
//下一个 //下一个
const next = () => { const next = () => {
@@ -383,7 +426,6 @@ const next = () => {
if (year >= presentY && !props.nextFlag) { if (year >= presentY && !props.nextFlag) {
startTime = presentY + '-01-01' startTime = presentY + '-01-01'
if (presentM < 10) { if (presentM < 10) {
if (presentD < 10) { if (presentD < 10) {
endTime = presentY + '-0' + presentM + '-0' + presentD endTime = presentY + '-0' + presentM + '-0' + presentD
} else { } else {
@@ -400,18 +442,31 @@ const next = () => {
startTime = year + '-01-01' startTime = year + '-01-01'
endTime = year + '-12-31' endTime = year + '-12-31'
} }
} }
if (!props.nextFlag) { if (!props.nextFlag) {
if (new Date(endTime + ' 00:00:00').getTime() >= new Date(window.XEUtils.toDateString(new Date(), 'yyyy-MM-dd ') + ' 00:00:00').getTime()) { if (
new Date(endTime + ' 00:00:00').getTime() >=
new Date(window.XEUtils.toDateString(new Date(), 'yyyy-MM-dd ') + ' 00:00:00').getTime()
) {
preDisabled.value = true preDisabled.value = true
} }
} }
timeValue.value = [startTime, endTime] timeValue.value = [startTime, endTime]
// 触发 change 事件
emitChange()
} }
// 监听值变化并触发事件
watch(
[interval, timeValue],
() => {
emitChange()
},
{ deep: true }
)
const setTime = (flag = 0, e = 0) => { const setTime = (flag = 0, e = 0) => {
let dd = window.XEUtils.toDateString(new Date().getTime() - e * 3600 * 1000 * 24, 'dd') let dd = window.XEUtils.toDateString(new Date().getTime() - e * 3600 * 1000 * 24, 'dd')

View File

@@ -1,25 +1,22 @@
<template> <template>
<div class="mac-address-input" :class="{ disabled: disabled }"> <div class="mac-address-input" :class="{ disabled: disabled }">
<template v-for="n in 6" :key="n"> <el-input
<el-input ref="inputRef"
ref="inputRefs" v-model="macValue"
v-model="segments[n - 1]" type="text"
type="text" maxlength="17"
maxlength="2" :disabled="disabled"
:disabled="disabled" @input="handleInput"
@input="handleInput(n - 1)" @keydown="handleKeydown"
@keydown="handleKeydown(n - 1,$event)" @focus="handleFocus"
@focus="handleFocus(n - 1)" @blur="handleBlur"
@blur="handleBlur" @paste="handlePaste"
@paste="handlePaste(n - 1, $event)" />
/>
<span v-if="n < 6" class="separator">:</span>
</template>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch, nextTick } from 'vue' import { ref, watch } from 'vue'
interface Props { interface Props {
modelValue?: string modelValue?: string
@@ -37,117 +34,85 @@ const props = withDefaults(defineProps<Props>(), {
const emit = defineEmits<Emits>() const emit = defineEmits<Emits>()
// 创建6个输入框的引用 // 创建个输入框的引用
const inputRefs = ref<InstanceType<typeof import('element-plus').ElInput>[]>([]) const inputRef = ref<InstanceType<typeof import('element-plus').ElInput> | null>(null)
// MAC地址的6个段 // MAC地址
const segments = ref<string[]>(Array(6).fill('')) const macValue = ref<string>('')
// 解析传入的MAC地址 // 解析传入的MAC地址
const parseMacAddress = (mac: string): string[] => { const parseMacAddress = (mac: string): string => {
if (!mac) return Array(6).fill('') if (!mac) return ''
// 移除非十六进制字符并转为大写 // 移除非十六进制字符并转为大写
const cleanMac = mac.replace(/[^0-9a-fA-F]/g, '').toUpperCase() const cleanMac = mac.replace(/[^0-9a-fA-F]/g, '').toUpperCase()
// 分割成6段每段2个字符 // 按每2个字符分割并用冒号连接
const result: string[] = [] let result = ''
for (let i = 0; i < 6; i++) { for (let i = 0; i < cleanMac.length; i += 2) {
result.push(cleanMac.substr(i * 2, 2)) if (i > 0) result += ':'
result += cleanMac.substr(i, 2)
} }
return result return result.substring(0, 17) // 最多17个字符 (12个数字+5个冒号)
} }
// 格式化MAC地址 // 格式化MAC地址 - 改进版
const formatSegment = (value: string): string => { const formatMac = (value: string): string => {
// 只保留前两个十六进制字符并转为大写 // 移除所有冒号
return value.replace(/[^0-9a-fA-F]/g, '').toUpperCase().substr(0, 2) const cleanValue = value.replace(/:/g, '')
// 只保留十六进制字符并转为大写
const hexOnly = cleanValue.replace(/[^0-9a-fA-F]/g, '').toUpperCase()
// 按每两个字符添加冒号最多6段
let formatted = ''
for (let i = 0; i < Math.min(hexOnly.length, 12); i += 2) {
if (i > 0) formatted += ':'
formatted += hexOnly.substr(i, 2)
}
return formatted
} }
// 当前聚焦的输入框索引 // 当前聚焦的输入框索引
const focusedIndex = ref<number | null>(null) const focusedIndex = ref<number | null>(null)
// 处理输入事件 // 处理输入事件
const handleInput = (index: number) => { const handleInput = (value: string) => {
// 格式化输入值 const formatted = formatMac(value)
const formatted = formatSegment(segments.value[index]) macValue.value = formatted
segments.value[index] = formatted // 发出不带冒号的纯净值
emit('update:modelValue', formatted.replace(/:/g, ''))
// 如果当前段已满2个字符且不是最后一段自动跳转到下一个输入框
if (formatted.length === 2 && index < 5) {
// 获取下一个输入框的DOM元素
setTimeout(() => {
const nextInput = inputRefs.value[index + 1]
if (nextInput) {
const inputEl = nextInput.$el.querySelector('input')
if (inputEl) {
inputEl.focus()
inputEl.select()
}
}
}, 0)
}
// 更新v-model值
updateModelValue()
} }
// 处理键盘事件 // 处理键盘事件
const handleKeydown = (index: number, event: KeyboardEvent) => { const handleKeydown = (event: KeyboardEvent) => {
const target = event.target as HTMLInputElement const target = event.target as HTMLInputElement
// 处理退格键 // 处理退格键
if (event.key === 'Backspace' && target.selectionStart === 0 && target.selectionEnd === 0) { if (event.key === 'Backspace') {
if (segments.value[index] === '' && index > 0) { // 处理在冒号前删除的情况
// 如果当前段为空,跳转到前一个输入框 const cursorPos = target.selectionStart || 0
if (cursorPos > 0 && macValue.value[cursorPos - 1] === ':' &&
target.selectionStart === target.selectionEnd) {
event.preventDefault() event.preventDefault()
const prevInput = inputRefs.value[index - 1] // 删除冒号前的两个字符
if (prevInput) { const newValue = macValue.value.substring(0, cursorPos - 3) +
const inputEl = prevInput.$el.querySelector('input') macValue.value.substring(cursorPos)
if (inputEl) { macValue.value = newValue
inputEl.focus() // 设置光标位置
// 将光标移到末尾 setTimeout(() => {
setTimeout(() => { if (target.setSelectionRange) {
inputEl.selectionStart = inputEl.value.length target.setSelectionRange(cursorPos - 3, cursorPos - 3)
inputEl.selectionEnd = inputEl.value.length
}, 0)
} }
} }, 0)
} emit('update:modelValue', newValue.replace(/:/g, ''))
}
// 处理左箭头键
if (event.key === 'ArrowLeft' && target.selectionStart === 0) {
if (index > 0) {
event.preventDefault()
const prevInput = inputRefs.value[index - 1]
if (prevInput) {
const inputEl = prevInput.$el.querySelector('input')
if (inputEl) {
inputEl.focus()
}
}
}
}
// 处理右箭头键和制表符
if ((event.key === 'ArrowRight' && target.selectionStart === target.value.length) || event.key === 'Tab') {
if (index < 5) {
event.preventDefault()
const nextInput = inputRefs.value[index + 1]
if (nextInput) {
const inputEl = nextInput.$el.querySelector('input')
if (inputEl) {
inputEl.focus()
}
}
} }
} }
} }
// 处理焦点事件 // 处理焦点事件
const handleFocus = (index: number) => { const handleFocus = () => {
focusedIndex.value = index focusedIndex.value = 0
} }
// 处理失焦事件 // 处理失焦事件
@@ -156,56 +121,26 @@ const handleBlur = () => {
} }
// 处理粘贴事件 // 处理粘贴事件
const handlePaste = (index: number, event: ClipboardEvent) => { const handlePaste = (event: ClipboardEvent) => {
event.preventDefault() event.preventDefault()
const pastedText = event.clipboardData?.getData('text') || '' const pastedText = event.clipboardData?.getData('text') || ''
// 清理粘贴的文本 // 清理粘贴的文本
const cleanText = parseMacAddress(pastedText) const cleanPastedText = pastedText.replace(/[^0-9a-fA-F]/g, '').toUpperCase()
const formatted = formatMac(cleanPastedText)
// 填充各段 macValue.value = formatted
for (let i = 0; i < 6; i++) { emit('update:modelValue', formatted.replace(/:/g, ''))
segments.value[i] = cleanText[i] || ''
}
// 更新v-model值
updateModelValue()
// 聚焦到最后一个非空段或最后一个段
setTimeout(() => {
let focusIndex = 5
for (let i = 5; i >= 0; i--) {
if (segments.value[i] !== '') {
focusIndex = i
break
}
}
const inputToFocus = inputRefs.value[focusIndex]
if (inputToFocus) {
const inputEl = inputToFocus.$el.querySelector('input')
if (inputEl) {
inputEl.focus()
inputEl.select()
}
}
}, 0)
}
// 更新v-model值
const updateModelValue = () => {
// 过滤掉空段,然后用冒号连接
const nonEmptySegments = segments.value.filter(s => s !== '')
const mac = nonEmptySegments.join(':')
emit('update:modelValue', mac)
} }
// 监听modelValue变化 // 监听modelValue变化
watch( watch(
() => props.modelValue, () => props.modelValue,
(newVal) => { (newVal) => {
if (newVal !== segments.value.filter(s => s !== '').join(':')) { const cleanNewVal = (newVal || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase()
segments.value = parseMacAddress(newVal || '') const currentCleanValue = macValue.value.replace(/:/g, '')
if (cleanNewVal !== currentCleanValue) {
macValue.value = parseMacAddress(cleanNewVal)
} }
}, },
{ immediate: true } { immediate: true }
@@ -214,37 +149,17 @@ watch(
<style scoped lang="scss"> <style scoped lang="scss">
.mac-address-input { .mac-address-input {
display: flex; width: 100%;
align-items: center;
gap: 4px;
&.disabled { &.disabled {
opacity: 0.7; opacity: 0.7;
} }
input { :deep(.el-input__wrapper) {
width: 40px; input {
height: 32px; text-transform: uppercase;
text-align: center; font-family: inherit; // 使用继承的字体而不是等宽字体
border: 1px solid #dcdfe6;
border-radius: 4px;
outline: none;
font-size: 14px;
text-transform: uppercase;
&:focus {
border-color: #409eff;
} }
&:disabled {
background-color: #f5f7fa;
cursor: not-allowed;
}
}
.separator {
user-select: none;
font-weight: 500;
} }
} }
</style> </style>

View File

@@ -1,3 +1,9 @@
<template>
<div>
</div>
</template>
<script lang="ts"> <script lang="ts">
import { createVNode, resolveComponent, defineComponent, computed, type CSSProperties } from 'vue' import { createVNode, resolveComponent, defineComponent, computed, type CSSProperties } from 'vue'
import svg from '@/components/icon/svg/index.vue' import svg from '@/components/icon/svg/index.vue'
@@ -27,7 +33,7 @@ export default defineComponent({
color: color color: color
} }
}) })
if(props.name){ if (props.name) {
if (props.name.indexOf('el-icon-') === 0) { if (props.name.indexOf('el-icon-') === 0) {
return () => return () =>
createVNode('el-icon', { class: 'icon el-icon', style: iconStyle.value }, [ createVNode('el-icon', { class: 'icon el-icon', style: iconStyle.value }, [

View File

@@ -1,295 +1,303 @@
<template> <template>
<!-- Icon --> <!-- Icon -->
<Icon class="ba-icon-dark" v-if="field.render == 'icon'" :name="fieldValue ? fieldValue : field.default ?? ''" /> <Icon class="ba-icon-dark" v-if="field.render == 'icon'" :name="fieldValue ? fieldValue : field.default ?? ''" />
<!-- switch --> <!-- switch -->
<div v-if="field.render == 'switch' && fieldValue != undefined"> <div v-if="field.render == 'switch' && fieldValue != undefined">
<el-switch <el-switch
@change="onChangeField(field, $event)" @change="onChangeField(field, $event)"
:model-value="fieldValue.toString()" :model-value="fieldValue.toString()"
:loading="row.loading" :loading="row.loading"
inline-prompt inline-prompt
:active-value="field.activeValue" :active-value="field.activeValue"
:active-text="field.activeText" :active-text="field.activeText"
:inactive-value="field.inactiveValue" :inactive-value="field.inactiveValue"
:inactive-text="field.inactiveText" :inactive-text="field.inactiveText"
/> />
</div> </div>
<!-- image --> <!-- image -->
<div v-if="field.render == 'image' && fieldValue" class="ba-render-image"> <div v-if="field.render == 'image' && fieldValue" class="ba-render-image">
<el-image <el-image
:hide-on-click-modal="true" :hide-on-click-modal="true"
:preview-teleported="true" :preview-teleported="true"
:preview-src-list="[fullUrl(fieldValue)]" :preview-src-list="[fieldValue]"
:src="fieldValue.length > 100 ? fieldValue : fullUrl(fieldValue)" :src="fieldValue.length > 100 ? fieldValue : getUrl(fieldValue)"
></el-image> ></el-image>
</div> </div>
<!-- tag --> <!-- tag -->
<div v-if="field.render == 'tag' && fieldValue !== ''"> <div v-if="field.render == 'tag' && fieldValue !== ''">
<el-tag :type="getTagType(fieldValue, field.custom)" size="small"> <el-tag :type="getTagType(fieldValue, field.custom)" size="small">
{{ field.replaceValue ? field.replaceValue[fieldValue] : fieldValue }} {{ field.replaceValue ? field.replaceValue[fieldValue] : fieldValue }}
</el-tag> </el-tag>
</div> </div>
<!-- datetime --> <!-- datetime -->
<div v-if="field.render == 'datetime'"> <div v-if="field.render == 'datetime'">
{{ !fieldValue ? '/' : timeFormat(fieldValue, field.timeFormat ?? undefined) }} {{ !fieldValue ? '/' : timeFormat(fieldValue, field.timeFormat ?? undefined) }}
</div> </div>
<!-- color --> <!-- color -->
<div v-if="field.render == 'color'"> <div v-if="field.render == 'color'">
<div :style="{ background: fieldValue }" class="ba-render-color"></div> <div :style="{ background: fieldValue }" class="ba-render-color"></div>
</div> </div>
<!-- customTemplate 自定义模板 --> <!-- customTemplate 自定义模板 -->
<div <div
v-if="field.render == 'customTemplate'" v-if="field.render == 'customTemplate'"
v-html="field.customTemplate ? field.customTemplate(row, field, fieldValue, column, index) : ''" v-html="field.customTemplate ? field.customTemplate(row, field, fieldValue, column, index) : ''"
></div> ></div>
<!-- 自定义组件/函数渲染 --> <!-- 自定义组件/函数渲染 -->
<component <component
v-if="field.render == 'customRender'" v-if="field.render == 'customRender'"
:is="field.customRender" :is="field.customRender"
:renderRow="row" :renderRow="row"
:renderField="field" :renderField="field"
:renderValue="fieldValue" :renderValue="fieldValue"
:renderColumn="column" :renderColumn="column"
:renderIndex="index" :renderIndex="index"
/> />
<!-- 按钮组 --> <!-- 按钮组 -->
<div v-if="field.render == 'buttons' && buttons" class="cn-render-buttons"> <div v-if="field.render == 'buttons' && buttons" class="cn-render-buttons">
<template v-for="(btn, idx) in buttons" :key="idx"> <template v-for="(btn, idx) in buttons" :key="idx">
<!-- 常规按钮 --> <!-- 常规按钮 -->
<el-button <el-button
link link
v-if="btn.render == 'basicButton'" v-if="btn.render == 'basicButton'"
@click="onButtonClick(btn)" @click="onButtonClick(btn)"
:class="btn.class" :class="btn.class"
class="table-operate" class="table-operate"
:type="btn.type" :type="btn.type"
:loading="props.row[btn.loading] || props.row.loading || false" :loading="props.row[btn.loading] || props.row.loading || false"
v-bind="btn.attr" v-bind="btn.attr"
> >
<div v-if="btn.text || btn.title" class="table-operate-text">{{ btn.text || btn.title }}</div> <div v-if="btn.text || btn.title" class="table-operate-text">{{ btn.text || btn.title }}</div>
</el-button> </el-button>
<!-- 带提示信息的按钮 --> <!-- 带提示信息的按钮 -->
<el-tooltip <el-tooltip
v-if="btn.render == 'tipButton'" v-if="btn.render == 'tipButton'"
:disabled="btn.title && !btn.disabledTip ? false : true" :disabled="btn.title && !btn.disabledTip ? false : true"
:content="btn.title" :content="btn.title"
placement="top" placement="top"
> >
<el-button <el-button
link link
@click="onButtonClick(btn)" @click="onButtonClick(btn)"
:class="btn.class" :class="btn.class"
class="table-operate" class="table-operate"
:type="btn.type" :type="btn.type"
v-bind="btn.attr" v-bind="btn.attr"
> >
<div v-if="btn.text || btn.title" class="table-operate-text"> <div v-if="btn.text || btn.title" class="table-operate-text">
{{ btn.text || btn.title }} {{ btn.text || btn.title }}
</div> </div>
</el-button> </el-button>
</el-tooltip> </el-tooltip>
<!-- 带确认框的按钮 --> <!-- 带确认框的按钮 -->
<el-popconfirm <el-popconfirm
v-if="btn.render == 'confirmButton'" v-if="btn.render == 'confirmButton'"
:disabled="btn.disabled && btn.disabled(row, field)" :disabled="btn.disabled && btn.disabled(row, field)"
v-bind="btn.popconfirm" v-bind="btn.popconfirm"
@confirm="onButtonClick(btn)" @confirm="onButtonClick(btn)"
> >
<template #reference> <template #reference>
<div style="display: inline-block"> <div style="display: inline-block">
<el-button link :class="btn.class" class="table-operate" :type="btn.type" v-bind="btn.attr"> <el-button link :class="btn.class" class="table-operate" :type="btn.type" v-bind="btn.attr">
<div v-if="btn.text || btn.title" class="table-operate-text"> <div v-if="btn.text || btn.title" class="table-operate-text">
{{ btn.text || btn.title }} {{ btn.text || btn.title }}
</div> </div>
</el-button> </el-button>
</div> </div>
</template> </template>
</el-popconfirm> </el-popconfirm>
<el-dropdown v-if="btn.render == 'dropdown'" trigger="click" @command="handlerCommand"> <el-dropdown v-if="btn.render == 'dropdown'" trigger="click" @command="handlerCommand">
<el-button link type="primary" class="table-operate"> <el-button link type="primary" class="table-operate">
<div class="table-operate-text">更多</div> <div class="table-operate-text">更多</div>
</el-button> </el-button>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item <el-dropdown-item
v-for="item in btn.buttons" v-for="item in btn.buttons"
:key="item.text" :key="item.text"
:command="item" :command="item"
:style="{ :style="{
color: item.type === 'primary' ? 'var(--el-color-primary)' : 'var(--el-color-danger' color: item.type === 'primary' ? 'var(--el-color-primary)' : 'var(--el-color-danger'
}" }"
> >
{{ item.text }} {{ item.text }}
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
</template> </template>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, inject } from 'vue' import { ref, inject } from 'vue'
import { ElMessageBox, type TagProps } from 'element-plus' import { ElMessageBox, type TagProps } from 'element-plus'
import type TableStoreClass from '@/utils/tableStore' import type TableStoreClass from '@/utils/tableStore'
import { fullUrl, timeFormat } from '@/utils/common' import { fullUrl, timeFormat } from '@/utils/common'
import type { VxeColumnProps } from 'vxe-table' import type { VxeColumnProps } from 'vxe-table'
import { getFileUrl } from '@/api/system-boot/file'
const TableStore = inject('tableStore') as TableStoreClass const TableStore = inject('tableStore') as TableStoreClass
interface Props { interface Props {
row: TableRow row: TableRow
field: TableColumn field: TableColumn
column: VxeColumnProps column: VxeColumnProps
index: number index: number
} }
const props = defineProps<Props>() const props = defineProps<Props>()
// 字段值(单元格值) // 字段值(单元格值)
const fieldName = ref(props.field.field) const fieldName = ref(props.field.field)
const fieldValue = ref(fieldName.value ? props.row[fieldName.value] : '') const fieldValue = ref(fieldName.value ? props.row[fieldName.value] : '')
if (fieldName.value && fieldName.value.indexOf('.') > -1) { if (fieldName.value && fieldName.value.indexOf('.') > -1) {
let fieldNameArr = fieldName.value.split('.') let fieldNameArr = fieldName.value.split('.')
let val: any = ref(props.row[fieldNameArr[0]]) let val: any = ref(props.row[fieldNameArr[0]])
for (let index = 1; index < fieldNameArr.length; index++) { for (let index = 1; index < fieldNameArr.length; index++) {
val.value = val.value ? val.value[fieldNameArr[index]] ?? '' : '' val.value = val.value ? val.value[fieldNameArr[index]] ?? '' : ''
} }
fieldValue.value = val.value fieldValue.value = val.value
} }
if (props.field.renderFormatter && typeof props.field.renderFormatter == 'function') { if (props.field.renderFormatter && typeof props.field.renderFormatter == 'function') {
fieldValue.value = props.field.renderFormatter(props.row, props.field, fieldValue.value, props.column, props.index) fieldValue.value = props.field.renderFormatter(props.row, props.field, fieldValue.value, props.column, props.index)
} }
const onChangeField = (row: any, value: any) => { const onChangeField = (row: any, value: any) => {
row.onChangeField && row.onChangeField(props.row, value) row.onChangeField && row.onChangeField(props.row, value)
// TableStore.onTableAction('field-change', { value: value, ...props }) // TableStore.onTableAction('field-change', { value: value, ...props })
} }
const onButtonClick = (btn: OptButton) => { const onButtonClick = (btn: OptButton) => {
btn.click && btn.click(props.row, props.field) btn.click && btn.click(props.row, props.field)
} }
const getTagType = (value: string, custom: any): TagProps['type'] => { const getTagType = (value: string, custom: any): TagProps['type'] => {
return custom && custom[value] ? custom[value] : '' return custom && custom[value] ? custom[value] : ''
} }
// 按钮组处理 最多显示三个按钮 多余的显示为下拉 // 按钮组处理 最多显示三个按钮 多余的显示为下拉
const buttonsFilter = props.field.buttons?.filter(btn => !(btn.disabled && btn.disabled(props.row, props.field))) || [] const buttonsFilter = props.field.buttons?.filter(btn => !(btn.disabled && btn.disabled(props.row, props.field))) || []
const buttons = ref<any[]>([]) const buttons = ref<any[]>([])
if (buttonsFilter.length > 3) { if (buttonsFilter.length > 3) {
buttonsFilter?.forEach((btn, index) => { buttonsFilter?.forEach((btn, index) => {
btn.text = btn.text || btn.title btn.text = btn.text || btn.title
if (index < 2) { if (index < 2) {
buttons.value.push(btn) buttons.value.push(btn)
} else { } else {
if (buttons.value.length > 2) { if (buttons.value.length > 2) {
buttons.value[buttons.value.length - 1].buttons.push(btn) buttons.value[buttons.value.length - 1].buttons.push(btn)
} else { } else {
buttons.value.push({ buttons.value.push({
render: 'dropdown', render: 'dropdown',
buttons: [btn] buttons: [btn]
}) })
} }
} }
}) })
} else { } else {
buttons.value = buttonsFilter buttons.value = buttonsFilter
} }
const handlerCommand = (item: OptButton) => { const handlerCommand = (item: OptButton) => {
switch (item.render) { switch (item.render) {
case 'basicButton': case 'basicButton':
onButtonClick(item) onButtonClick(item)
break break
case 'confirmButton': case 'confirmButton':
ElMessageBox.confirm(item.popconfirm?.title || '提示', { ElMessageBox.confirm(item.popconfirm?.title || '提示', {
confirmButtonText: item.popconfirm?.confirmButtonText || '确认', confirmButtonText: item.popconfirm?.confirmButtonText || '确认',
cancelButtonText: item.popconfirm?.cancelButtonText || '取消', cancelButtonText: item.popconfirm?.cancelButtonText || '取消',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
onButtonClick(item) onButtonClick(item)
}) })
default: default:
break break
} }
} }
</script> const getUrl = (url: string) => {
getFileUrl({ filePath: url }).then(res => {
<style scoped lang="scss"> return res.data
.m-10 { })
margin: 4px; }
} </script>
.ba-render-image { <style scoped lang="scss">
text-align: center; .m-10 {
} margin: 4px;
}
.images-item {
width: 50px; .ba-render-image {
margin: 0 5px; text-align: center;
} }
.el-image { .images-item {
height: 36px; width: 50px;
// width: 36px; margin: 0 5px;
} }
.table-operate-text { .el-image {
padding-left: 5px; height: 36px;
font-size: 12px; // width: 36px;
} }
.table-operate { .table-operate-text {
padding: 4px 5px; padding-left: 5px;
height: auto; font-size: 12px;
} }
.cn-render-buttons { .table-operate {
display: flex; padding: 4px 5px;
align-items: center; height: auto;
justify-content: center; }
.icon { .cn-render-buttons {
font-size: 12px !important; display: flex;
// color: var(--ba-bg-color-overlay) !important; align-items: center;
} justify-content: center;
}
.icon {
.move-button { font-size: 12px !important;
cursor: move; // color: var(--ba-bg-color-overlay) !important;
} }
}
.ml-6 {
display: inline-flex; .move-button {
vertical-align: middle; cursor: move;
margin-left: 6px; }
}
.ml-6 {
.ml-6 + .el-button { display: inline-flex;
margin-left: 6px; vertical-align: middle;
} margin-left: 6px;
}
.ba-render-color {
height: 25px; .ml-6 + .el-button {
width: 100%; margin-left: 6px;
} }
.cn-render-buttons { .ba-render-color {
:deep(.el-button) { height: 25px;
margin-left: 0; width: 100%;
} }
}
</style> .cn-render-buttons {
:deep(.el-button) {
margin-left: 0;
}
}
.ba-render-image {
text-align: center;
}
</style>

View File

@@ -1,306 +1,377 @@
<template> <template>
<div ref="tableHeader" class="cn-table-header"> <div ref="tableHeader" class="cn-table-header">
<div class="table-header ba-scroll-style" :key="num"> <div class="table-header ba-scroll-style" :key="num">
<el-form
<el-form style="flex: 1; height: 34px; margin-right: 20px; display: flex; flex-wrap: wrap" ref="headerForm" style="flex: 1; height: 34px; margin-right: 20px; display: flex; flex-wrap: wrap"
@submit.prevent="" @keyup.enter="onComSearch" label-position="left" :inline="true"> ref="headerForm"
<el-form-item label="日期" v-if="datePicker" style="grid-column: span 2; max-width: 570px"> @submit.prevent=""
<DatePicker ref="datePickerRef" :nextFlag="nextFlag" :theCurrentTime="theCurrentTime"></DatePicker> @keyup.enter="onComSearch"
</el-form-item> label-position="left"
:inline="true"
<el-form-item label="区域" v-if="area"> >
<Area ref="areaRef" v-model.trim="tableStore.table.params.deptIndex" /> <el-form-item label="日期" v-if="datePicker" style="grid-column: span 2; max-width: 570px">
</el-form-item> <DatePicker
<slot name="select"></slot> ref="datePickerRef"
</el-form> :nextFlag="nextFlag"
<template v-if="$slots.select || datePicker || showSearch"> :theCurrentTime="theCurrentTime"
<el-button type="primary" @click="showSelectChange" v-if="showUnfoldButton"> :initialInterval="initialInterval"
<Icon size="14" name="el-icon-ArrowUp" style="color: #fff" v-if="showSelect" /> :initialTimeValue="initialTimeValue"
<Icon size="14" name="el-icon-ArrowDown" style="color: #fff" v-else /> @change="handleDatePickerChange"
</el-button> ></DatePicker>
<el-button @click="onComSearch" v-if="showSearch" type="primary" :icon="Search">查询</el-button> </el-form-item>
<el-button @click="onResetForm" v-if="showSearch && showReset" :icon="RefreshLeft">重置</el-button>
<el-button @click="onExport" v-if="showExport" :loading="tableStore.table.loading" type="primary" <el-form-item label="区域" v-if="area">
icon="el-icon-Download">导出</el-button> <Area ref="areaRef" v-model.trim="tableStore.table.params.deptIndex" />
</template> </el-form-item>
<slot name="operation"></slot> <slot name="select"></slot>
</div> </el-form>
<el-form :style="showSelect && showUnfoldButton ? headerFormSecondStyleOpen : headerFormSecondStyleClose" <template v-if="$slots.select || datePicker || showSearch">
ref="headerFormSecond" @submit.prevent="" @keyup.enter="onComSearch" label-position="left" <el-button type="primary" @click="showSelectChange" v-if="showUnfoldButton">
:inline="true"></el-form> <Icon size="14" name="el-icon-ArrowUp" style="color: #fff" v-if="showSelect" />
</div> <Icon size="14" name="el-icon-ArrowDown" style="color: #fff" v-else />
</template> </el-button>
<el-button @click="onComSearch" v-if="showSearch" type="primary" :icon="Search">查询</el-button>
<script setup lang="ts"> <el-button @click="onResetForm" v-if="showSearch && showReset" :icon="RefreshLeft">重置</el-button>
import { inject, ref, onMounted, nextTick, onUnmounted } from 'vue' <el-button
import type TableStore from '@/utils/tableStore' @click="onExport"
import DatePicker from '@/components/form/datePicker/index.vue' v-if="showExport"
import Area from '@/components/form/area/index.vue' :loading="tableStore.table.loading"
import { mainHeight } from '@/utils/layout' type="primary"
import { useDictData } from '@/stores/dictData' icon="el-icon-Download"
import { Search, RefreshLeft } from '@element-plus/icons-vue' >
import { defineProps } from 'vue' 导出
const emit = defineEmits(['selectChange',]) </el-button>
</template>
const tableStore = inject('tableStore') as TableStore <slot name="operation"></slot>
const tableHeader = ref() </div>
const datePickerRef = ref() <el-form
const dictData = useDictData() :style="showSelect && showUnfoldButton ? headerFormSecondStyleOpen : headerFormSecondStyleClose"
const areaRef = ref() ref="headerFormSecond"
const headerForm = ref() @submit.prevent=""
const headerFormSecond = ref() @keyup.enter="onComSearch"
const num = ref(0) label-position="left"
interface Props { :inline="true"
datePicker?: boolean ></el-form>
area?: boolean </div>
showSearch?: boolean </template>
nextFlag?: boolean //控制时间是否可以往后推
theCurrentTime?: boolean //控制时间前3天展示上个月时间 <script setup lang="ts">
showReset?: boolean //是否显示重置 import { inject, ref, onMounted, nextTick, onUnmounted } from 'vue'
showExport?: boolean //导出控制 import type TableStore from '@/utils/tableStore'
} import DatePicker from '@/components/form/datePicker/index.vue'
import Area from '@/components/form/area/index.vue'
const props = withDefaults(defineProps<Props>(), { import { mainHeight } from '@/utils/layout'
datePicker: false, import { useDictData } from '@/stores/dictData'
area: false, import { Search, RefreshLeft } from '@element-plus/icons-vue'
showSearch: true, import { defineProps } from 'vue'
nextFlag: false, import { useTimeCacheStore } from '@/stores/timeCache'
theCurrentTime: true, import { useRoute } from 'vue-router'
showReset: true,
showExport:false const emit = defineEmits(['selectChange'])
})
// 动态计算table高度 const tableStore = inject('tableStore') as TableStore
let resizeObserver = new ResizeObserver(entries => { const tableHeader = ref()
for (const entry of entries) { const datePickerRef = ref()
handlerHeight() const dictData = useDictData()
computedSearchRow() const areaRef = ref()
} const headerForm = ref()
}) const headerFormSecond = ref()
const showUnfoldButton = ref(false) const num = ref(0)
const headerFormSecondStyleOpen = {
opacity: 1, // 获取路由和缓存 store
height: 'auto', const route = useRoute()
padding: '0 15px 0px 15px' const timeCacheStore = useTimeCacheStore()
}
const headerFormSecondStyleClose = { interface Props {
opacity: 0, datePicker?: boolean
height: '0', area?: boolean
padding: '0' showSearch?: boolean
} nextFlag?: boolean //控制时间是否可以往后推
onMounted(() => { theCurrentTime?: boolean //控制时间前3天展示上个月时间
if (props.datePicker && tableStore) { showReset?: boolean //是否显示重置
tableStore.table.params.searchBeginTime = datePickerRef.value?.timeValue[0] showExport?: boolean //导出控制
tableStore.table.params.searchEndTime = datePickerRef.value?.timeValue[1] initialInterval?: 3
tableStore.table.params.startTime = datePickerRef.value?.timeValue[0] initialTimeValue?: undefined
tableStore.table.params.endTime = datePickerRef.value?.timeValue[1] }
tableStore.table.params.timeFlag = datePickerRef.value?.timeFlag
} const props = withDefaults(defineProps<Props>(), {
if (props.area) { datePicker: false,
tableStore.table.params.deptIndex = dictData.state.area[0].id area: false,
} showSearch: true,
nextTick(() => { nextFlag: false,
resizeObserver.observe(tableHeader.value) theCurrentTime: true,
computedSearchRow() showReset: true,
}) showExport: false,
}) initialInterval: 3,
onUnmounted(() => { initialTimeValue: undefined
resizeObserver.disconnect() })
})
// 处理 DatePicker 值变化事件
const handlerHeight = () => { const handleDatePickerChange = (value: any) => {
if (tableStore && tableStore.table) {
tableStore.table.height = mainHeight( // 将值缓存到 timeCache
tableStore.table.publicHeight + tableHeader.value.offsetHeight + (tableStore.showPage ? 58 : 0) + 20 if (value) {
).height as string timeCacheStore.setCache(route.path, value.interval, value.timeValue)
} }
}
// 刷新页面handler高度出下拉 // 将 datePicker 的变化传递给父组件
const computedSearchRow = () => { emit('selectChange', true, tableHeader.value.offsetHeight, value)
}
if (!headerForm.value.$el) return
// 动态计算table高度
let resizeObserver = new ResizeObserver(entries => {
// 清空headerFormSecond.value.$el下的元素 for (const entry of entries) {
while (headerFormSecond.value.$el.firstChild) { handlerHeight()
headerForm.value.$el.appendChild(headerFormSecond.value.$el.firstChild) computedSearchRow()
} }
})
// 获取第一行放了几个表单 const showUnfoldButton = ref(false)
const elFormItem = headerForm.value.$el.querySelectorAll('.el-form-item') const headerFormSecondStyleOpen = {
opacity: 1,
// 把第一行放不下的复制一份放到headerFormSecond.value.$el height: 'auto',
let width = 0 padding: '0 15px 0px 15px'
for (let i = 0; i < elFormItem.length; i++) { }
width += elFormItem[i].offsetWidth + 32 const headerFormSecondStyleClose = {
if (width > headerForm.value.$el.offsetWidth) { opacity: 0,
headerFormSecond.value.$el.appendChild(elFormItem[i]) height: '0',
} padding: '0'
}
} onMounted(() => {
// 设置初始值到 DatePicker
// 判断是否需要折叠 if (props.datePicker && datePickerRef.value) {
if (headerFormSecond.value.$el.scrollHeight > 0) { // 如果有传入的初始值,则设置到 DatePicker
showUnfoldButton.value = true if (props.initialInterval !== undefined) {
} else { datePickerRef.value.setInterval(props.initialInterval)
showUnfoldButton.value = false }
}
} if (props.initialTimeValue) {
datePickerRef.value.timeValue = props.initialTimeValue
const showSelect = ref(false) }
const showSelectChange = () => {
showSelect.value = !showSelect.value // 从缓存中获取值并设置
emit('selectChange', showSelect.value) const cached = timeCacheStore.getCache(route.path)
} if (cached) {
const onComSearch = async () => { if (cached.interval !== undefined) {
if (props.datePicker) { datePickerRef.value.setInterval(cached.interval)
tableStore.table.params.searchBeginTime = datePickerRef.value.timeValue[0] }
tableStore.table.params.searchEndTime = datePickerRef.value.timeValue[1] if (cached.timeValue) {
tableStore.table.params.startTime = datePickerRef.value.timeValue[0] datePickerRef.value.timeValue = cached.timeValue
tableStore.table.params.endTime = datePickerRef.value.timeValue[1] }
tableStore.table.params.timeFlag = datePickerRef.value.timeFlag }
}
// 更新 tableStore 参数
await tableStore.onTableAction('search', {}) tableStore.table.params.searchBeginTime = datePickerRef.value?.timeValue?.[0]
} tableStore.table.params.searchEndTime = datePickerRef.value?.timeValue?.[1]
const setDatePicker = (list: any) => { tableStore.table.params.startTime = datePickerRef.value?.timeValue?.[0]
datePickerRef.value.setTimeOptions(list) tableStore.table.params.endTime = datePickerRef.value?.timeValue?.[1]
} tableStore.table.params.timeFlag = datePickerRef.value?.timeFlag
const onResetForm = () => { }
//时间重置成默认值
datePickerRef.value?.setInterval(3) if (props.area) {
tableStore.onTableAction('reset', {}) tableStore.table.params.deptIndex = dictData.state.area[0].id
} }
const setInterval = (val: any) => {
datePickerRef.value.setInterval(val) nextTick(() => {
} resizeObserver.observe(tableHeader.value)
// 导出 computedSearchRow()
const onExport = () => { })
tableStore.onTableAction('export', {showAllFlag:true}) })
} onUnmounted(() => {
resizeObserver.disconnect()
defineExpose({ onComSearch, areaRef, setDatePicker, setInterval, datePickerRef, showSelectChange, computedSearchRow }) })
</script>
const handlerHeight = () => {
<style scoped lang="scss"> if (tableStore && tableStore.table) {
.cn-table-header { tableStore.table.height = mainHeight(
border: 1px solid var(--el-border-color); tableStore.table.publicHeight + tableHeader.value.offsetHeight + (tableStore.showPage ? 58 : 0) + 20
} ).height as string
}
.table-header { }
position: relative; // 刷新页面handler高度出下拉
overflow-x: auto; const computedSearchRow = () => {
box-sizing: border-box; if (!headerForm.value.$el) return
display: flex;
align-items: center; // 清空headerFormSecond.value.$el下的元素
width: 100%; while (headerFormSecond.value.$el.firstChild) {
max-width: 100%; headerForm.value.$el.appendChild(headerFormSecond.value.$el.firstChild)
background-color: var(--ba-bg-color-overlay); }
border-bottom: none; // 获取第一行放了几个表单
padding: 13px 15px 9px; const elFormItem = headerForm.value.$el.querySelectorAll('.el-form-item')
font-size: 14px;
// 把第一行放不下的复制一份放到headerFormSecond.value.$el
.table-header-operate-text { let width = 0
margin-left: 6px; for (let i = 0; i < elFormItem.length; i++) {
} width += elFormItem[i].offsetWidth + 32
} if (width > headerForm.value.$el.offsetWidth) {
headerFormSecond.value.$el.appendChild(elFormItem[i])
.table-com-search { }
box-sizing: border-box; }
width: 100%;
max-width: 100%; // 判断是否需要折叠
background-color: var(--ba-bg-color-overlay); if (headerFormSecond.value.$el.scrollHeight > 0) {
border: 1px solid var(--ba-border-color); showUnfoldButton.value = true
border-bottom: none; } else {
padding: 13px 15px 20px 15px; showUnfoldButton.value = false
font-size: 14px; }
} }
#header-form-second, const showSelect = ref(false)
#header-form { const showSelectChange = () => {
// display: flex; showSelect.value = !showSelect.value
// flex-wrap: wrap; setTimeout(() => {
transition: all 0.3s; emit('selectChange', showSelect.value, tableHeader.value.offsetHeight)
} }, 20)
}
.mlr-12 { const onComSearch = async () => {
margin-left: 12px; if (props.datePicker) {
} tableStore.table.params.searchBeginTime = datePickerRef.value.timeValue[0]
tableStore.table.params.searchEndTime = datePickerRef.value.timeValue[1]
.mlr-12+.el-button { tableStore.table.params.startTime = datePickerRef.value.timeValue[0]
margin-left: 12px; tableStore.table.params.endTime = datePickerRef.value.timeValue[1]
} tableStore.table.params.timeFlag = datePickerRef.value.timeFlag
}
.table-search {
display: flex; await tableStore.onTableAction('search', {})
margin-left: auto; }
const setDatePicker = (list: any) => {
.quick-search { datePickerRef.value.setTimeOptions(list)
width: auto; }
} const onResetForm = () => {
} //时间重置成默认值
datePickerRef.value?.setInterval(3)
.table-search-button-group { tableStore.onTableAction('reset', {})
display: flex; }
margin-left: 12px; const setInterval = (val: any) => {
border: 1px solid var(--el-border-color); datePickerRef.value.setInterval(val)
border-radius: var(--el-border-radius-base); }
overflow: hidden; // 导出
const onExport = () => {
button:focus, tableStore.onTableAction('export', { showAllFlag: true })
button:active { }
background-color: var(--ba-bg-color-overlay);
} defineExpose({ onComSearch, areaRef, setDatePicker, setInterval, datePickerRef, showSelectChange, computedSearchRow })
</script>
button:hover {
background-color: var(--el-color-info-light-7); <style scoped lang="scss">
} .cn-table-header {
border: 1px solid var(--el-border-color);
.table-search-button-item { }
height: 30px;
border: none; .table-header {
border-radius: 0; position: relative;
} overflow-x: auto;
box-sizing: border-box;
.el-button+.el-button { display: flex;
margin: 0; align-items: center;
} width: 100%;
max-width: 100%;
.right-border { background-color: var(--ba-bg-color-overlay);
border-right: 1px solid var(--el-border-color);
} border-bottom: none;
} padding: 13px 15px 9px;
font-size: 14px;
html.dark {
.table-search-button-group { .table-header-operate-text {
margin-left: 6px;
button:focus, }
button:active { }
background-color: var(--el-color-info-dark-2);
} .table-com-search {
box-sizing: border-box;
button:hover { width: 100%;
background-color: var(--el-color-info-light-7); max-width: 100%;
} background-color: var(--ba-bg-color-overlay);
border: 1px solid var(--ba-border-color);
button { border-bottom: none;
background-color: var(--ba-bg-color-overlay); padding: 13px 15px 20px 15px;
font-size: 14px;
el-icon { }
color: white !important;
} #header-form-second,
} #header-form {
} // display: flex;
} // flex-wrap: wrap;
transition: all 0.3s;
#header-form, }
#header-form-second {
:deep(.el-select) { .mlr-12 {
--el-select-width: 220px; margin-left: 12px;
} }
:deep(.el-input) { .mlr-12 + .el-button {
--el-input-width: 220px; margin-left: 12px;
} }
}
</style> .table-search {
display: flex;
margin-left: auto;
.quick-search {
width: auto;
}
}
.table-search-button-group {
display: flex;
margin-left: 12px;
border: 1px solid var(--el-border-color);
border-radius: var(--el-border-radius-base);
overflow: hidden;
button:focus,
button:active {
background-color: var(--ba-bg-color-overlay);
}
button:hover {
background-color: var(--el-color-info-light-7);
}
.table-search-button-item {
height: 30px;
border: none;
border-radius: 0;
}
.el-button + .el-button {
margin: 0;
}
.right-border {
border-right: 1px solid var(--el-border-color);
}
}
html.dark {
.table-search-button-group {
button:focus,
button:active {
background-color: var(--el-color-info-dark-2);
}
button:hover {
background-color: var(--el-color-info-light-7);
}
button {
background-color: var(--ba-bg-color-overlay);
el-icon {
color: white !important;
}
}
}
}
#header-form,
#header-form-second {
:deep(.el-select) {
--el-select-width: 220px;
}
:deep(.el-input) {
--el-input-width: 220px;
}
}
</style>

View File

@@ -1,44 +1,24 @@
<template> <template>
<div :style="{ height: tableStore.table.height }"> <div :style="{ height: typeof props.height === 'string' ? props.height : tableStore.table.height }">
<vxe-table <vxe-table ref="tableRef" height="auto" :key="key" :data="tableStore.table.data"
ref="tableRef" v-loading="tableStore.table.loading" v-bind="Object.assign({}, defaultAttribute, $attrs)"
height="auto" @checkbox-all="selectChangeEvent" @checkbox-change="selectChangeEvent" :showOverflow="showOverflow"
:key="key" :sort-config="{ remote: true }" @sort-change="handleSortChange">
:data="tableStore.table.data"
v-loading="tableStore.table.loading"
v-bind="Object.assign({}, defaultAttribute, $attrs)"
@checkbox-all="selectChangeEvent"
@checkbox-change="selectChangeEvent"
:showOverflow="showOverflow"
:sort-config="{ remote: true }"
@sort-change="handleSortChange"
>
<!-- Column 组件内部是 el-table-column --> <!-- Column 组件内部是 el-table-column -->
<template v-if="isGroup"> <template v-if="isGroup">
<GroupColumn :column="tableStore.table.column" /> <GroupColumn :column="tableStore.table.column" />
</template> </template>
<template v-else> <template v-else>
<Column <Column :attr="item" :key="key + '-column'" v-for="(item, key) in tableStore.table.column"
:attr="item" :tree-node="item.treeNode">
:key="key + '-column'"
v-for="(item, key) in tableStore.table.column"
:tree-node="item.treeNode"
>
<!-- tableStore 预设的列 render 方案 --> <!-- tableStore 预设的列 render 方案 -->
<template v-if="item.render" #default="scope"> <template v-if="item.render" #default="scope">
<FieldRender <FieldRender :field="item" :row="scope.row" :column="scope.column" :index="scope.rowIndex" :key="key +
:field="item" '-' +
:row="scope.row" item.render +
:column="scope.column" '-' +
:index="scope.rowIndex" (item.field ? '-' + item.field + '-' + scope.row[item.field] : '')
:key=" " />
key +
'-' +
item.render +
'-' +
(item.field ? '-' + item.field + '-' + scope.row[item.field] : '')
"
/>
</template> </template>
</Column> </Column>
</template> </template>
@@ -47,16 +27,11 @@
</div> </div>
<div v-if="tableStore.showPage" class="table-pagination"> <div v-if="tableStore.showPage" class="table-pagination">
<el-pagination <el-pagination :currentPage="tableStore.table.params!.pageNum" :page-size="tableStore.table.params!.pageSize"
:currentPage="tableStore.table.params!.pageNum" :page-sizes="pageSizes" background
:page-size="tableStore.table.params!.pageSize"
:page-sizes="pageSizes"
background
:layout="config.layout.shrink ? 'prev, next, jumper' : 'sizes,total, ->, prev, pager, next, jumper'" :layout="config.layout.shrink ? 'prev, next, jumper' : 'sizes,total, ->, prev, pager, next, jumper'"
:total="tableStore.table.total" :total="tableStore.table.total" @size-change="onTableSizeChange"
@size-change="onTableSizeChange" @current-change="onTableCurrentChange"></el-pagination>
@current-change="onTableCurrentChange"
></el-pagination>
</div> </div>
<slot name="footer"></slot> <slot name="footer"></slot>
</template> </template>
@@ -81,11 +56,13 @@ const key = ref(0)
interface Props extends /* @vue-ignore */ Partial<InstanceType<typeof ElTable>> { interface Props extends /* @vue-ignore */ Partial<InstanceType<typeof ElTable>> {
isGroup?: boolean isGroup?: boolean
showOverflow?: boolean showOverflow?: boolean
height?: string | number
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
isGroup: false, isGroup: false,
showOverflow: true showOverflow: true,
height: undefined
}) })
onMounted(() => { onMounted(() => {
tableStore.table.ref = tableRef.value as VxeTableInstance tableStore.table.ref = tableRef.value as VxeTableInstance
@@ -119,7 +96,7 @@ const handleSortChange = ({ field, order }: any) => {
return a[field] < b[field] ? 1 : -1 return a[field] < b[field] ? 1 : -1
} }
}) })
if (tableStore.isWebPaging) { if (tableStore.isWebPaging) {
tableStore.table.data = JSON.parse( tableStore.table.data = JSON.parse(
JSON.stringify( JSON.stringify(
@@ -148,6 +125,7 @@ watch(
() => tableStore.table.allFlag, () => tableStore.table.allFlag,
newVal => { newVal => {
if (tableStore.table.allFlag) { if (tableStore.table.allFlag) {
tableRef.value?.exportData({ tableRef.value?.exportData({
filename: tableStore.exportName || document.querySelectorAll('.ba-nav-tab.active')[0].textContent || '', // 文件名字 filename: tableStore.exportName || document.querySelectorAll('.ba-nav-tab.active')[0].textContent || '', // 文件名字
sheetName: 'Sheet1', sheetName: 'Sheet1',

View File

@@ -15,7 +15,7 @@
style='cursor: pointer' v-if='props.canExpand' /> --> style='cursor: pointer' v-if='props.canExpand' /> -->
</div> </div>
<el-tree :style="{ height: 'calc(100vh - 200px)' }" <el-tree :style="{ height: 'calc(100vh - 200px)' }"
style=' overflow: auto;' ref='treeRef' :props='defaultProps' highlight-current style=' overflow: auto;' ref='treeRef' :props='defaultProps' highlight-current :default-expand-all="false"
@check-change="checkTreeNodeChange" :filter-node-method='filterNode' node-key='id' v-bind='$attrs'> @check-change="checkTreeNodeChange" :filter-node-method='filterNode' node-key='id' v-bind='$attrs'>
<template #default='{ node, data }'> <template #default='{ node, data }'>
<span class='custom-tree-node'> <span class='custom-tree-node'>
@@ -75,7 +75,6 @@ const filterNode = (value: string, data: any, node: any) => {
// 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配则返回该节点以及其下的所有子节点如果参数是子节点则返回该节点的父节点。name是中文字符enName是英文字符. // 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配则返回该节点以及其下的所有子节点如果参数是子节点则返回该节点的父节点。name是中文字符enName是英文字符.
const chooseNode = (value: string, data: any, node: any) => { const chooseNode = (value: string, data: any, node: any) => {
if (data.name.indexOf(value) !== -1) { if (data.name.indexOf(value) !== -1) {
return true return true
} }

View File

@@ -31,7 +31,7 @@
<el-tree <el-tree
:style="{ height: bxsDeviceData.length != 0 ? 'calc(100vh - 340px)' : 'calc(100vh - 278px)' }" :style="{ height: bxsDeviceData.length != 0 ? 'calc(100vh - 340px)' : 'calc(100vh - 278px)' }"
ref="treeRef1" :props="defaultProps" highlight-current :filter-node-method="filterNode" ref="treeRef1" :props="defaultProps" highlight-current :filter-node-method="filterNode"
node-key="id" default-expand-all v-bind="$attrs" :data="zlDevList" style="overflow: auto"> node-key="id" :default-expand-all="false" v-bind="$attrs" :data="zlDevList" style="overflow: auto">
<template #default="{ node, data }"> <template #default="{ node, data }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<Icon :name="data.icon" style="font-size: 16px" :style="{ color: data.color }" <Icon :name="data.icon" style="font-size: 16px" :style="{ color: data.color }"
@@ -44,7 +44,7 @@
<el-collapse-item title="便携式设备" name="1" v-if="bxsDeviceData.length != 0"> <el-collapse-item title="便携式设备" name="1" v-if="bxsDeviceData.length != 0">
<el-tree <el-tree
:style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 280px)' : 'calc(100vh - 238px)' }" :style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 280px)' : 'calc(100vh - 238px)' }"
ref="treeRef2" :props="defaultProps" highlight-current default-expand-all ref="treeRef2" :props="defaultProps" highlight-current :default-expand-all="false"
:filter-node-method="filterNode" node-key="id" :data="bxsDeviceData" v-bind="$attrs" :filter-node-method="filterNode" node-key="id" :data="bxsDeviceData" v-bind="$attrs"
style="overflow: auto"> style="overflow: auto">
<template #default="{ node, data }"> <template #default="{ node, data }">
@@ -59,7 +59,7 @@
<el-collapse-item title="在线设备" name="2" v-if="frontDeviceData.length != 0"> <el-collapse-item title="在线设备" name="2" v-if="frontDeviceData.length != 0">
<el-tree <el-tree
:style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 280px)' : 'calc(100vh - 238px)' }" :style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 280px)' : 'calc(100vh - 238px)' }"
ref="treeRef3" :props="defaultProps" highlight-current default-expand-all ref="treeRef3" :props="defaultProps" highlight-current :default-expand-all="false"
:filter-node-method="filterNode" node-key="id" :data="frontDeviceData" v-bind="$attrs" :filter-node-method="filterNode" node-key="id" :data="frontDeviceData" v-bind="$attrs"
style="overflow: auto"> style="overflow: auto">
<template #default="{ node, data }"> <template #default="{ node, data }">
@@ -124,6 +124,7 @@ watch(
item.children.map((vv: any) => { item.children.map((vv: any) => {
zlDeviceData.value.push(vv) zlDeviceData.value.push(vv)
}) })
zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value))
} else if (item.name == '便携式设备') { } else if (item.name == '便携式设备') {
bxsDeviceData.value = [] bxsDeviceData.value = []
item.children.map((vv: any) => { item.children.map((vv: any) => {
@@ -156,8 +157,6 @@ watch(filterText, val => {
} }
}) })
watch(process, val => { watch(process, val => {
if (val == '') { if (val == '') {
zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value)) zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value))
} else { } else {
@@ -177,10 +176,10 @@ function filterProcess(nodes: any) {
const children = node.children ? filterProcess(node.children) : [] const children = node.children ? filterProcess(node.children) : []
// 如果当前节点的process=4或者有子节点满足条件则保留当前节点 // 如果当前节点的process=4或者有子节点满足条件则保留当前节点
if (node.process == process.value || children.length > 0) { if ( node.process == process.value || children.length > 0) {
return { return {
...node, ...node,
children: node.children children: children
} }
} }

View File

@@ -1,5 +1,5 @@
<template> <template>
<Tree ref="treRef" :width="width" :data="tree" default-expand-all @changePointType="changePointType" /> <Tree ref="treRef" :width="width" :data="tree" default-expand-all @changePointType="changePointType" @onAdd="onAdd"/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@@ -20,7 +20,7 @@ defineOptions({
name: 'govern/deviceTree' name: 'govern/deviceTree'
}) })
const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy']) const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy','onAdd'])
const config = useConfig() const config = useConfig()
const tree = ref() const tree = ref()
const dictData = useDictData() const dictData = useDictData()
@@ -29,7 +29,6 @@ const width = ref('')
const info = (selectedNodeId?: string) => { const info = (selectedNodeId?: string) => {
console.log('tree.value:', selectedNodeId);
tree.value = [] tree.value = []
let arr1: any[] = [] let arr1: any[] = []
getCldTree().then(res => { getCldTree().then(res => {
@@ -149,6 +148,10 @@ const info = (selectedNodeId?: string) => {
const changePointType = (val: any, obj: any) => { const changePointType = (val: any, obj: any) => {
emit('pointTypeChange', val, obj) emit('pointTypeChange', val, obj)
} }
const onAdd = () => {
emit('onAdd')
}
if (props.template) { if (props.template) {
getTemplateByDept({ id: dictData.state.area[0].id }) getTemplateByDept({ id: dictData.state.area[0].id })
.then((res: any) => { .then((res: any) => {

View File

@@ -50,6 +50,7 @@ getDeviceTree().then(res => {
item2.color = config.getColorVal('elementUiPrimary') item2.color = config.getColorVal('elementUiPrimary')
item2.children.forEach((item3: any) => { item2.children.forEach((item3: any) => {
item3.icon = 'el-icon-Platform' item3.icon = 'el-icon-Platform'
item3.level = 2
item3.color = config.getColorVal('elementUiPrimary') item3.color = config.getColorVal('elementUiPrimary')
if (item3.comFlag === 1) { if (item3.comFlag === 1) {
item3.color = '#e26257 !important' item3.color = '#e26257 !important'

View File

@@ -46,7 +46,7 @@ const info = () => {
item2.color = config.getColorVal('elementUiPrimary') item2.color = config.getColorVal('elementUiPrimary')
item2.children.forEach((item3: any) => { item2.children.forEach((item3: any) => {
item3.icon = 'el-icon-Platform' item3.icon = 'el-icon-Platform'
item3.level = 1 item3.level = 2
item3.color = item3.color =
item3.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important' item3.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
item3.children.forEach((item4: any) => { item3.children.forEach((item4: any) => {

View File

@@ -0,0 +1,105 @@
<template>
<Tree ref="treRef" :width="width" :data="tree" default-expand-all @checkedNodesChange="handleCheckedNodesChange"/>
</template>
<script lang="ts" setup>
import { ref, nextTick, onMounted, defineProps } from 'vue'
import Tree from '../select.vue'
import { getLineTree } from '@/api/cs-device-boot/csLedger'
import { useConfig } from '@/stores/config'
import { getTemplateByDept } from '@/api/harmonic-boot/luckyexcel'
import { useDictData } from '@/stores/dictData'
// const props = defineProps(['template'])
interface Props {
template?: boolean
}
const props = withDefaults(defineProps<Props>(), {
template: false
})
defineOptions({
name: 'govern/deviceTree'
})
const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy'])
const config = useConfig()
const tree = ref()
const dictData = useDictData()
const treRef = ref()
const width = ref('')
const info = () => {
tree.value = []
let arr3: any[] = []
getLineTree().then(res => {
//治理设备
res.data.map((item: any) => {
if (item.name == '在线设备') {
item.children.forEach((item: any) => {
item.icon = 'el-icon-HomeFilled'
item.level = 1
item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-List'
item2.level = 1
item2.color = config.getColorVal('elementUiPrimary')
item2.children.forEach((item3: any) => {
item3.icon = 'el-icon-Platform'
item3.level = 1
item3.color =
item3.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
item3.children.forEach((item4: any) => {
item4.icon = 'el-icon-Platform'
item4.color =
item4.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
// item4.color = '#e26257 !important'
arr3.push(item4)
})
})
})
})
}
})
tree.value = res.data.filter(item => item.name == '在线设备')
nextTick(() => {
if (arr3.length) {
//初始化选中
treRef.value.treeRef.setCurrentKey(arr3[0].id)
// 注册父组件事件
emit('init', {
level: 2,
...arr3[0]
})
return
}
else {
emit('init')
return
}
})
})
}
// 处理子组件传递的勾选节点变化,并转发给父组件
const handleCheckedNodesChange = (nodes: any[]) => {
// 先给父组件
emit('checkChange', nodes)
}
if (props.template) {
getTemplateByDept({ id: dictData.state.area[0].id })
.then((res: any) => {
emit('Policy', res.data)
info()
})
.catch(err => {
info()
})
} else {
info()
}
onMounted(() => {})
</script>

View File

@@ -10,12 +10,26 @@
<Icon name='el-icon-Search' style='font-size: 16px' /> <Icon name='el-icon-Search' style='font-size: 16px' />
</template> </template>
</el-input> </el-input>
<el-tooltip placement="bottom" :hide-after="0">
<template #content>
<span>台账推送</span>
</template>
<Icon
name="el-icon-Promotion"
size="20"
class="fold ml10 menu-collapse"
style="cursor: pointer;"
:style="{ color: config.getColorVal('elementUiPrimary') }"
@click="onAdd" />
</el-tooltip>
<!-- <Icon @click='onMenuCollapse' :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" <!-- <Icon @click='onMenuCollapse' :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
:class="menuCollapse ? 'unfold' : ''" size='18' class='fold ml10 menu-collapse' :class="menuCollapse ? 'unfold' : ''" size='18' class='fold ml10 menu-collapse'
style='cursor: pointer' v-if='props.canExpand' /> --> style='cursor: pointer' v-if='props.canExpand' /> -->
</div> </div>
<el-tree :style="{ height: 'calc(100vh - 200px)' }" <el-tree :style="{ height: 'calc(100vh - 200px)' }"
style=' overflow: auto;' ref='treeRef' :props='defaultProps' highlight-current style=' overflow: auto;' ref='treeRef' :props='defaultProps' highlight-current :default-expand-all="false"
@check-change="checkTreeNodeChange" :filter-node-method='filterNode' node-key='id' v-bind='$attrs'> @check-change="checkTreeNodeChange" :filter-node-method='filterNode' node-key='id' v-bind='$attrs'>
<template #default='{ node, data }'> <template #default='{ node, data }'>
<span class='custom-tree-node'> <span class='custom-tree-node'>
@@ -35,6 +49,7 @@ import { ElTree } from 'element-plus'
import { emit } from 'process'; import { emit } from 'process';
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { t } from 'vxe-table'; import { t } from 'vxe-table';
import { useConfig } from '@/stores/config'
defineOptions({ defineOptions({
name: 'govern/tree' name: 'govern/tree'
@@ -49,6 +64,7 @@ const props = withDefaults(defineProps<Props>(), {
width: '280px', width: '280px',
canExpand: true canExpand: true
}) })
const config = useConfig()
const { proxy } = useCurrentInstance() const { proxy } = useCurrentInstance()
const menuCollapse = ref(false) const menuCollapse = ref(false)
const filterText = ref('') const filterText = ref('')
@@ -56,7 +72,7 @@ const defaultProps = {
label: 'name', label: 'name',
value: 'id' value: 'id'
} }
const emit = defineEmits(['checkTreeNodeChange']) const emit = defineEmits(['checkTreeNodeChange','onAdd'])
watch(filterText, val => { watch(filterText, val => {
treeRef.value!.filter(val) treeRef.value!.filter(val)
}) })
@@ -105,6 +121,11 @@ const checkTreeNodeChange = () => {
// console.log(treeRef.value?.getCheckedNodes(), "ikkkkkiisiiisis"); // console.log(treeRef.value?.getCheckedNodes(), "ikkkkkiisiiisis");
emit('checkTreeNodeChange', treeRef.value?.getCheckedNodes()) emit('checkTreeNodeChange', treeRef.value?.getCheckedNodes())
} }
const onAdd = () => {
emit('onAdd')
}
const treeRef = ref<InstanceType<typeof ElTree>>() const treeRef = ref<InstanceType<typeof ElTree>>()
defineExpose({ treeRef }) defineExpose({ treeRef })
</script> </script>

View File

@@ -28,7 +28,8 @@
<el-tree <el-tree
:style="{ height: bxsDeviceData.length != 0 ? 'calc(100vh - 340px)' : 'calc(100vh - 278px)' }" :style="{ height: bxsDeviceData.length != 0 ? 'calc(100vh - 340px)' : 'calc(100vh - 278px)' }"
ref="treeRef1" :props="defaultProps" highlight-current :filter-node-method="filterNode" ref="treeRef1" :props="defaultProps" highlight-current :filter-node-method="filterNode"
node-key="id" default-expand-all v-bind="$attrs" :data="zlDevList" style="overflow: auto"> node-key="id" v-bind="$attrs" :data="zlDevList" style="overflow: auto"
:default-expand-all="false">
<template #default="{ node, data }"> <template #default="{ node, data }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<Icon :name="data.icon" style="font-size: 16px" :style="{ color: data.color }" <Icon :name="data.icon" style="font-size: 16px" :style="{ color: data.color }"
@@ -41,9 +42,9 @@
<el-collapse-item title="便携式设备" name="1" v-if="bxsDeviceData.length != 0"> <el-collapse-item title="便携式设备" name="1" v-if="bxsDeviceData.length != 0">
<el-tree <el-tree
:style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 330px)' : 'calc(100vh - 238px)' }" :style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 330px)' : 'calc(100vh - 238px)' }"
ref="treeRef2" :props="defaultProps" highlight-current default-expand-all ref="treeRef2" :props="defaultProps" highlight-current :default-expand-all="false"
:filter-node-method="filterNode" node-key="id" :data="bxsDeviceData" v-bind="$attrs" :filter-node-method="filterNode" node-key="id" :data="bxsDeviceData" v-bind="$attrs"
style="overflow: auto"> style="overflow: auto" >
<template #default="{ node, data }"> <template #default="{ node, data }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<Icon :name="data.icon" style="font-size: 16px" :style="{ color: data.color }" <Icon :name="data.icon" style="font-size: 16px" :style="{ color: data.color }"
@@ -56,7 +57,7 @@
<el-collapse-item title="在线设备" name="2" v-if="yqfDeviceData.length != 0"> <el-collapse-item title="在线设备" name="2" v-if="yqfDeviceData.length != 0">
<el-tree <el-tree
:style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 330px)' : 'calc(100vh - 238px)' }" :style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 330px)' : 'calc(100vh - 238px)' }"
ref="treeRef3" :props="defaultProps" highlight-current default-expand-all ref="treeRef3" :props="defaultProps" highlight-current :default-expand-all="false"
:filter-node-method="filterNode" node-key="id" :data="yqfDeviceData" v-bind="$attrs" :filter-node-method="filterNode" node-key="id" :data="yqfDeviceData" v-bind="$attrs"
style="overflow: auto"> style="overflow: auto">
<template #default="{ node, data }"> <template #default="{ node, data }">
@@ -76,7 +77,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import useCurrentInstance from '@/utils/useCurrentInstance' import useCurrentInstance from '@/utils/useCurrentInstance'
import { ElTree } from 'element-plus' import { ElTree } from 'element-plus'
import { el } from 'element-plus/es/locale' import { el, fa } from 'element-plus/es/locale'
import { ref, watch, defineEmits, onMounted, nextTick, computed } from 'vue' import { ref, watch, defineEmits, onMounted, nextTick, computed } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
defineOptions({ defineOptions({
@@ -117,13 +118,14 @@ watch(
() => props.data, () => props.data,
(val, oldVal) => { (val, oldVal) => {
if (val && val.length != 0) { if (val && val.length != 0) {
val.map((item: any) => { val.map((item: any) => {
if (item.name == '治理设备') { if (item.name == '治理设备') {
zlDeviceData.value = [] zlDeviceData.value = []
item.children.map((vv: any) => { item.children.map((vv: any) => {
zlDeviceData.value.push(vv) zlDeviceData.value.push(vv)
}) })
// console.log('🚀 ~ item.children.map ~ zlDeviceData.value:', zlDeviceData.value) zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value))
} else if (item.name == '便携式设备') { } else if (item.name == '便携式设备') {
bxsDeviceData.value = [] bxsDeviceData.value = []
item.children.map((vv: any) => { item.children.map((vv: any) => {
@@ -155,18 +157,23 @@ watch(filterText, val => {
} }
}) })
watch(process, val => { watch(process, val => {
if (val == '') { if (val == '') {
zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value)) zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value))
} else { } else {
zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value))) zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
} }
setTimeout(() => { setTimeout(() => {
changeDevice(activeName.value) changeDevice(activeName.value)
}, 0) }, 0)
}) })
const changeDevice = (val: any) => { const changeDevice = (val: any) => {
console.log('changeDevice', val)
let arr1: any = [] let arr1: any = []
//zlDeviceData //zlDeviceData
zlDevList.value.forEach((item: any) => { zlDevList.value.forEach((item: any) => {
@@ -231,6 +238,7 @@ const filterNode = (value: string, data: any, node: any) => {
return chooseNode(value, data, node) return chooseNode(value, data, node)
} }
} }
function filterProcess(nodes: any) { function filterProcess(nodes: any) {
if (process.value == '') { if (process.value == '') {
return nodes return nodes
@@ -240,20 +248,58 @@ function filterProcess(nodes: any) {
// 递归处理子节点 // 递归处理子节点
const children = node.children ? filterProcess(node.children) : [] const children = node.children ? filterProcess(node.children) : []
// 如果当前节点的process=4或者有子节点满足条件则保留当前节点 // 对于装置层级level=2只保留 process 值匹配的节点
if (node.level === 2) {
if (node.process == process.value) {
return {
...node,
children: children
}
}
return null
}
if (node.process == process.value || children.length > 0) { // 对于其他节点:
// 1. 如果有满足条件的子节点则保留
// 2. 如果本身 process 值匹配则保留
// 3. 如果是叶子节点也保留(监测点通常没有子节点)
if (children.length > 0 || node.process == process.value ||
(!node.children || node.children.length === 0)) {
return { return {
...node, ...node,
children: node.children children: children
} }
} }
// 否则过滤掉当前节点
return null return null
}) })
.filter(Boolean) // 移除null节点 .filter(Boolean) // 移除null节点
} }
// function filterProcess(nodes: any) {
// if (process.value == '') {
// return nodes
// }
// return nodes
// .map(node => {
// // 递归处理子节点
// const children = node.children ? filterProcess(node.children) : []
// // 如果当前节点的process=4或者有子节点满足条件则保留当前节点
// if (node.process == process.value || children.length > 0) {
// return {
// ...node,
// children: node.children
// }
// }
// // 否则过滤掉当前节点
// return null
// })
// .filter(Boolean) // 移除null节点
// }
// 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配则返回该节点以及其下的所有子节点如果参数是子节点则返回该节点的父节点。name是中文字符enName是英文字符. // 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配则返回该节点以及其下的所有子节点如果参数是子节点则返回该节点的父节点。name是中文字符enName是英文字符.
const chooseNode = (value: string, data: any, node: any) => { const chooseNode = (value: string, data: any, node: any) => {
if (data.name.indexOf(value) !== -1) { if (data.name.indexOf(value) !== -1) {
@@ -288,7 +334,9 @@ const treeRef2 = ref<InstanceType<typeof ElTree>>()
const treeRef3 = ref<InstanceType<typeof ElTree>>() const treeRef3 = ref<InstanceType<typeof ElTree>>()
defineExpose({ treeRef1, treeRef2 }) defineExpose({ treeRef1, treeRef2 })
onMounted(() => { onMounted(() => {
setTimeout(() => { setTimeout(() => {
if (zlDeviceData.value.length != 0) { if (zlDeviceData.value.length != 0) {
zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value))) zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
activeName.value = '0' activeName.value = '0'

View File

@@ -0,0 +1,263 @@
<template>
<div :style="{ width: menuCollapse ? '40px' : props.width }" style='transition: all 0.3s; overflow: hidden;'>
<Icon v-show='menuCollapse' @click='onMenuCollapse' :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
:class="menuCollapse ? 'unfold' : ''" size='18' class='fold ml10 mt20 menu-collapse'
style='cursor: pointer' />
<div class='cn-tree' :style='{ opacity: menuCollapse ? 0 : 1 }'>
<div style='display: flex; align-items: center' class='mb10'>
<el-input maxlength="32" show-word-limit v-model.trim='filterText' placeholder='请输入内容' clearable>
<template #prefix>
<Icon name='el-icon-Search' style='font-size: 16px' />
</template>
</el-input>
</div>
<el-tree
:style="{ height: 'calc(100vh)' }"
style='overflow: auto;'
ref='treeRef'
:props='defaultProps'
highlight-current
:filter-node-method='filterNode'
node-key='id'
show-checkbox
@check="handleCheckChange"
@node-click="handleNodeClick"
:default-checked-keys="defaultCheckedKeys"
v-bind='$attrs'
:default-expand-all="false"
>
<template #default='{ node, data }'>
<span class='custom-tree-node'>
<Icon :name='data.icon' style='font-size: 16px' :style='{ color: data.color }'
v-if='data.icon' />
<span style='margin-left: 4px'>{{ node.label }}</span>
</span>
</template>
</el-tree>
</div>
</div>
</template>
<script lang='ts' setup>
import useCurrentInstance from '@/utils/useCurrentInstance'
import { ElMessage, ElTree } from 'element-plus'
import { emit } from 'process';
import { ref, watch } from 'vue'
defineOptions({
name: 'govern/tree'
})
interface Props {
width?: string
canExpand?: boolean
}
const props = withDefaults(defineProps<Props>(), {
width: '280px',
canExpand: true
})
const { proxy } = useCurrentInstance()
const menuCollapse = ref(false)
const filterText = ref('')
const defaultProps = {
label: 'name',
value: 'id'
}
const emit = defineEmits(['changePointType', 'checkedNodesChange'])
watch(filterText, val => {
treeRef.value!.filter(val)
})
const onMenuCollapse = () => {
menuCollapse.value = !menuCollapse.value
proxy.eventBus.emit('cnTreeCollapse', menuCollapse)
}
const filterNode = (value: string, data: any, node: any) => {
if (!value) return true
// return data.name.includes(value)
if (data.name) {
return chooseNode(value, data, node)
}
}
// 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配则返回该节点以及其下的所有子节点如果参数是子节点则返回该节点的父节点。name是中文字符enName是英文字符.
const chooseNode = (value: string, data: any, node: any) => {
if (data.name.indexOf(value) !== -1) {
return true
}
const level = node.level
// 如果传入的节点本身就是一级节点就不用校验了
if (level === 1) {
return false
}
// 先取当前节点的父节点
let parentData = node.parent
// 遍历当前节点的父节点
let index = 0
while (index < level - 1) {
// 如果匹配到直接返回此处name值是中文字符enName是英文字符。判断匹配中英文过滤
if (parentData.data.name.indexOf(value) !== -1) {
return true
}
// 否则的话再往上一层做匹配
parentData = parentData.parent
index++
}
// 没匹配到返回false
return false
}
// 处理节点点击事件
const handleNodeClick = (data: any, node: any, event: any) => {
}
// 存储所有勾选的节点
const checkedNodes = ref<any[]>([])
const defaultCheckedKeys = ref<string[]>([])
// 处理节点勾选变化
const handleCheckChange = (data: any, checkInfo: any) => {
const { checkedNodes: nodes } = checkInfo
// 过滤出监测点层级(level=3)的节点
const monitoringPointNodes = nodes.filter((node: any) => {
return node.level === 3
})
// 限制最多只能勾选5个监测点
if (monitoringPointNodes.length > 5) {
// 获取之前选中的节点
const previousCheckedNodes = checkedNodes.value || []
// 计算新增的节点
const newNodes = monitoringPointNodes.filter(
(node: any) => !previousCheckedNodes.some((prev: any) => prev.id === node.id)
)
// 如果是从父级勾选导致超过限制,保留前几个直到达到限制数量
if (newNodes.length > 0) {
const allowedNewCount = 5 - previousCheckedNodes.length
if (allowedNewCount > 0) {
// 允许添加allowedNewCount个新节点
const allowedNewNodes = newNodes.slice(0, allowedNewCount)
const finalNodes = [...previousCheckedNodes, ...allowedNewNodes]
checkedNodes.value = finalNodes
// 设置树的勾选状态为正确的节点
treeRef.value?.setCheckedNodes(finalNodes)
// 将勾选的监测点节点暴露出去
emit('checkedNodesChange', finalNodes)
// 更新节点的可勾选状态
updateNodeCheckStatus(finalNodes.length)
// 只有在真正超过5个时才提示警告
if (monitoringPointNodes.length > 5) {
ElMessage.warning('最多只能选择5个监测点')
}
return
}
}
// 其他情况回滚到之前的状态
ElMessage.warning('最多只能选择5个监测点')
treeRef.value?.setCheckedNodes(checkedNodes.value)
return
}
checkedNodes.value = monitoringPointNodes
// 将勾选的监测点节点暴露出去
emit('checkedNodesChange', monitoringPointNodes)
// 更新节点的可勾选状态
updateNodeCheckStatus(monitoringPointNodes.length)
}
// 处理节点勾选变化
const handleCheckChange2 = (data: any, checkInfo: any) => {
const { checkedNodes: nodes } = checkInfo
// 过滤出监测点层级(level=3)的节点
const monitoringPointNodes = nodes.filter((node: any) => {
// 监测点节点通常具有 comFlag 属性或其他标识
return node.level === 3
})
// 限制最多只能勾选5个监测点
if (monitoringPointNodes.length > 5) {
ElMessage.warning('最多只能选择5个监测点')
// 保持之前勾选的状态
treeRef.value?.setCheckedNodes(checkedNodes.value)
return
}
checkedNodes.value = monitoringPointNodes
// 将勾选的监测点节点暴露出去
emit('checkedNodesChange', monitoringPointNodes)
// 更新节点的可勾选状态
updateNodeCheckStatus(monitoringPointNodes.length)
}
// 更新节点的可勾选状态
const updateNodeCheckStatus = (currentCount: number) => {
if (!treeRef.value) return
// 如果已经选了5个则禁用其他未选中的监测点节点
const isMaxSelected = currentCount >= 5
// 获取所有节点并更新状态
const allNodes = treeRef.value.store._getAllNodes()
allNodes.forEach((node: any) => {
if (node.level === 3) { // 监测点层级
// 如果已达到最大数量且该节点未被选中,则禁用勾选
if (isMaxSelected && !node.checked) {
node.disabled = true
} else {
node.disabled = false
}
}
})
}
const treeRef = ref<InstanceType<typeof ElTree>>()
defineExpose({ treeRef })
</script>
<style lang='scss' scoped>
.cn-tree {
flex-shrink: 0;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 10px;
height: 100%;
width: 100%;
:deep(.el-tree) {
border: 1px solid var(--el-border-color);
}
:deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content) {
background-color: var(--el-color-primary-light-7);
}
.menu-collapse {
color: var(--el-color-primary);
}
}
.custom-tree-node {
display: flex;
align-items: center;
}
</style>

View File

@@ -1,104 +1,104 @@
<template> <template>
<el-main class="layout-main" :style="mainHeight()"> <el-main class="layout-main" :style="mainHeight()">
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<transition :name="config.layout.mainAnimation" mode="out-in"> <transition :name="config.layout.mainAnimation" mode="out-in">
<keep-alive :include="state.keepAliveComponentNameList"> <keep-alive :include="state.keepAliveComponentNameList">
<component :is="Component" :key="state.componentKey" /> <component :is="Component" :key="state.componentKey" />
</keep-alive> </keep-alive>
</transition> </transition>
</router-view> </router-view>
</el-main> </el-main>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted, watch, onBeforeMount, onUnmounted, nextTick, provide } from 'vue' import { ref, reactive, onMounted, watch, onBeforeMount, onUnmounted, nextTick, provide } from 'vue'
import { useRoute, type RouteLocationNormalized } from 'vue-router' import { useRoute, type RouteLocationNormalized } from 'vue-router'
import { mainHeight } from '@/utils/layout' import { mainHeight } from '@/utils/layout'
import useCurrentInstance from '@/utils/useCurrentInstance' import useCurrentInstance from '@/utils/useCurrentInstance'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { useNavTabs } from '@/stores/navTabs' import { useNavTabs } from '@/stores/navTabs'
defineOptions({ defineOptions({
name: 'layout/main' name: 'layout/main'
}) })
const { proxy } = useCurrentInstance() const { proxy } = useCurrentInstance()
const route = useRoute() const route = useRoute()
const config = useConfig() const config = useConfig()
const navTabs = useNavTabs() const navTabs = useNavTabs()
const state: { const state: {
componentKey: string componentKey: string
keepAliveComponentNameList: string[] keepAliveComponentNameList: string[]
} = reactive({ } = reactive({
componentKey: route.path, componentKey: route.path,
keepAliveComponentNameList: [] keepAliveComponentNameList: []
}) })
const addKeepAliveComponentName = function (keepAliveName: string | undefined) { const addKeepAliveComponentName = function (keepAliveName: string | undefined) {
if (keepAliveName) { if (keepAliveName) {
let exist = state.keepAliveComponentNameList.find((name: string) => { let exist = state.keepAliveComponentNameList.find((name: string) => {
return name === keepAliveName return name === keepAliveName
}) })
if (exist) return if (exist) return
state.keepAliveComponentNameList.push(keepAliveName) state.keepAliveComponentNameList.push(keepAliveName)
} }
} }
onBeforeMount(() => { onBeforeMount(() => {
proxy.eventBus.on('onTabViewRefresh', (menu: RouteLocationNormalized) => { proxy.eventBus.on('onTabViewRefresh', (menu: RouteLocationNormalized) => {
state.keepAliveComponentNameList = state.keepAliveComponentNameList.filter( state.keepAliveComponentNameList = state.keepAliveComponentNameList.filter(
(name: string) => menu.meta.keepalive !== name (name: string) => menu.meta.keepalive !== name
) )
state.componentKey = '' state.componentKey = ''
nextTick(() => { nextTick(() => {
state.componentKey = menu.path state.componentKey = menu.path
addKeepAliveComponentName(menu.meta.keepalive as string) addKeepAliveComponentName(menu.meta.keepalive as string)
}) })
}) })
proxy.eventBus.on('onTabViewClose', (menu: RouteLocationNormalized) => { proxy.eventBus.on('onTabViewClose', (menu: RouteLocationNormalized) => {
state.keepAliveComponentNameList = state.keepAliveComponentNameList.filter( state.keepAliveComponentNameList = state.keepAliveComponentNameList.filter(
(name: string) => menu.meta.keepalive !== name (name: string) => menu.meta.keepalive !== name
) )
}) })
}) })
onUnmounted(() => { onUnmounted(() => {
proxy.eventBus.off('onTabViewRefresh') proxy.eventBus.off('onTabViewRefresh')
proxy.eventBus.off('onTabViewClose') proxy.eventBus.off('onTabViewClose')
}) })
onMounted(() => { onMounted(() => {
// 确保刷新页面时也能正确取得当前路由 keepalive 参数 // 确保刷新页面时也能正确取得当前路由 keepalive 参数
if (typeof navTabs.state.activeRoute?.meta.keepalive == 'string') { if (typeof navTabs.state.activeRoute?.meta.keepalive == 'string') {
addKeepAliveComponentName(navTabs.state.activeRoute?.meta.keepalive) addKeepAliveComponentName(navTabs.state.activeRoute?.meta.keepalive)
} }
}) })
watch( watch(
() => route.path, () => route.path,
() => { () => {
state.componentKey = route.path state.componentKey = route.path
if (typeof navTabs.state.activeRoute?.meta.keepalive == 'string') { if (typeof navTabs.state.activeRoute?.meta.keepalive == 'string') {
addKeepAliveComponentName(navTabs.state.activeRoute?.meta.keepalive) addKeepAliveComponentName(navTabs.state.activeRoute?.meta.keepalive)
} }
} }
) )
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.layout-container .layout-main { .layout-container .layout-main {
padding: 0 !important; padding: 0 !important;
overflow: hidden; overflow: hidden;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.layout-main-scrollbar { .layout-main-scrollbar {
width: 100%; width: 100%;
position: relative; position: relative;
//overflow: hidden; //overflow: hidden;
} }
</style> </style>

View File

@@ -0,0 +1,55 @@
import type { App } from 'vue'
// 👇使用 form-create 需额外全局引入 element plus 组件
import {
ElAlert,
ElAside,
ElContainer,
ElDivider,
ElHeader,
ElMain,
ElPopconfirm,
ElTable,
ElTableColumn,
ElTabPane,
ElTabs,
ElTransfer
} from 'element-plus'
import FcDesigner from '@form-create/designer'
import formCreate from '@form-create/element-ui'
import install from '@form-create/element-ui/auto-import'
//======================= 自定义组件 =======================
import { UploadFile, UploadImg, UploadImgs } from '@/components/UploadFile'
import { DictSelect } from '@/components/DictSelect'
import UserSelect from '@/views/system/auth/userList/UserSelect.vue'
import { Editor } from '@/components/Editor'
const components = [
ElAside,
ElPopconfirm,
ElHeader,
ElMain,
ElContainer,
ElDivider,
ElTransfer,
ElAlert,
ElTabs,
ElTable,
ElTableColumn,
ElTabPane,
UploadImg,
UploadImgs,
UploadFile,
DictSelect,
UserSelect,
Editor
]
// 参考 http://www.form-create.com/v3/element-ui/auto-import.html 文档
export const setupFormCreate = (app: App<Element>) => {
components.forEach((component) => {
app.component(component.name, component)
})
formCreate.use(install)
app.use(formCreate)
app.use(FcDesigner)
}

View File

@@ -0,0 +1,3 @@
export const setHtmlPageLang = (locale: LocaleType) => {
document.querySelector('html')?.setAttribute('lang', locale)
}

View File

@@ -0,0 +1,42 @@
import type { App } from 'vue'
import { createI18n } from 'vue-i18n'
import { useLocaleStoreWithOut } from '@/stores/modules/locale'
import type { I18n, I18nOptions } from 'vue-i18n'
import { setHtmlPageLang } from './helper'
export let i18n: ReturnType<typeof createI18n>
const createI18nOptions = async (): Promise<I18nOptions> => {
const localeStore = useLocaleStoreWithOut()
const locale = localeStore.getCurrentLocale
const localeMap = localeStore.getLocaleMap
const defaultLocal = await import(`../../locales/${locale.lang}.ts`)
const message = defaultLocal.default ?? {}
setHtmlPageLang(locale.lang)
localeStore.setCurrentLocale({
lang: locale.lang
// elLocale: elLocal
})
return {
legacy: false,
locale: locale.lang,
fallbackLocale: locale.lang,
messages: {
[locale.lang]: message
},
availableLocales: localeMap.map((v) => v.lang),
sync: true,
silentTranslationWarn: true,
missingWarn: false,
silentFallbackWarn: true
}
}
export const setupI18n = async (app: App<Element>) => {
const options = await createI18nOptions()
i18n = createI18n(options) as I18n
app.use(i18n)
}

12
src/stores/indexs.ts Normal file
View File

@@ -0,0 +1,12 @@
import type { App } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const store = createPinia()
store.use(piniaPluginPersistedstate)
export const setupStore = (app: App<Element>) => {
app.use(store)
}
export { store }

277
src/stores/modules/app.ts Normal file
View File

@@ -0,0 +1,277 @@
import { defineStore } from 'pinia'
import { store } from '../indexs'
import { setCssVar, humpToUnderline } from '@/utils'
import { ElMessage } from 'element-plus'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
import { ElementPlusSize } from '@/types/elementPlus'
import { LayoutType } from '@/types/layout'
import { ThemeTypes } from '@/types/theme'
const { wsCache } = useCache()
interface AppState {
breadcrumb: boolean
breadcrumbIcon: boolean
collapse: boolean
uniqueOpened: boolean
hamburger: boolean
screenfull: boolean
search: boolean
size: boolean
locale: boolean
message: boolean
tagsView: boolean
tagsViewIcon: boolean
logo: boolean
fixedHeader: boolean
greyMode: boolean
pageLoading: boolean
layout: LayoutType
title: string
userInfo: string
isDark: boolean
currentSize: ElementPlusSize
sizeMap: ElementPlusSize[]
mobile: boolean
footer: boolean
theme: ThemeTypes
fixedMenu: boolean
}
export const useAppStore = defineStore('app', {
state: (): AppState => {
return {
userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其他项目冲突
sizeMap: ['default', 'large', 'small'],
mobile: false, // 是否是移动端
title: import.meta.env.VITE_APP_TITLE, // 标题
pageLoading: false, // 路由跳转loading
breadcrumb: true, // 面包屑
breadcrumbIcon: true, // 面包屑图标
collapse: false, // 折叠菜单
uniqueOpened: true, // 是否只保持一个子菜单的展开
hamburger: true, // 折叠图标
screenfull: true, // 全屏图标
search: true, // 搜索图标
size: true, // 尺寸图标
locale: true, // 多语言图标
message: true, // 消息图标
tagsView: true, // 标签页
tagsViewIcon: true, // 是否显示标签图标
logo: true, // logo
fixedHeader: true, // 固定toolheader
footer: true, // 显示页脚
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
fixedMenu: wsCache.get('fixedMenu') || false, // 是否固定菜单
layout: wsCache.get(CACHE_KEY.LAYOUT) || 'classic', // layout布局
isDark: wsCache.get(CACHE_KEY.IS_DARK) || false, // 是否是暗黑模式
currentSize: wsCache.get('default') || 'default', // 组件尺寸
theme: wsCache.get(CACHE_KEY.THEME) || {
// 主题色
elColorPrimary: '#409eff',
// 左侧菜单边框颜色
leftMenuBorderColor: 'inherit',
// 左侧菜单背景颜色
leftMenuBgColor: '#001529',
// 左侧菜单浅色背景颜色
leftMenuBgLightColor: '#0f2438',
// 左侧菜单选中背景颜色
leftMenuBgActiveColor: 'var(--el-color-primary)',
// 左侧菜单收起选中背景颜色
leftMenuCollapseBgActiveColor: 'var(--el-color-primary)',
// 左侧菜单字体颜色
leftMenuTextColor: '#bfcbd9',
// 左侧菜单选中字体颜色
leftMenuTextActiveColor: '#fff',
// logo字体颜色
logoTitleTextColor: '#fff',
// logo边框颜色
logoBorderColor: 'inherit',
// 头部背景颜色
topHeaderBgColor: '#fff',
// 头部字体颜色
topHeaderTextColor: 'inherit',
// 头部悬停颜色
topHeaderHoverColor: '#f6f6f6',
// 头部边框颜色
topToolBorderColor: '#eee'
}
}
},
getters: {
getBreadcrumb(): boolean {
return this.breadcrumb
},
getBreadcrumbIcon(): boolean {
return this.breadcrumbIcon
},
getCollapse(): boolean {
return this.collapse
},
getUniqueOpened(): boolean {
return this.uniqueOpened
},
getHamburger(): boolean {
return this.hamburger
},
getScreenfull(): boolean {
return this.screenfull
},
getSize(): boolean {
return this.size
},
getLocale(): boolean {
return this.locale
},
getMessage(): boolean {
return this.message
},
getTagsView(): boolean {
return this.tagsView
},
getTagsViewIcon(): boolean {
return this.tagsViewIcon
},
getLogo(): boolean {
return this.logo
},
getFixedHeader(): boolean {
return this.fixedHeader
},
getGreyMode(): boolean {
return this.greyMode
},
getFixedMenu(): boolean {
return this.fixedMenu
},
getPageLoading(): boolean {
return this.pageLoading
},
getLayout(): LayoutType {
return this.layout
},
getTitle(): string {
return this.title
},
getUserInfo(): string {
return this.userInfo
},
getIsDark(): boolean {
return this.isDark
},
getCurrentSize(): ElementPlusSize {
return this.currentSize
},
getSizeMap(): ElementPlusSize[] {
return this.sizeMap
},
getMobile(): boolean {
return this.mobile
},
getTheme(): ThemeTypes {
return this.theme
},
getFooter(): boolean {
return this.footer
}
},
actions: {
setBreadcrumb(breadcrumb: boolean) {
this.breadcrumb = breadcrumb
},
setBreadcrumbIcon(breadcrumbIcon: boolean) {
this.breadcrumbIcon = breadcrumbIcon
},
setCollapse(collapse: boolean) {
this.collapse = collapse
},
setUniqueOpened(uniqueOpened: boolean) {
this.uniqueOpened = uniqueOpened
},
setHamburger(hamburger: boolean) {
this.hamburger = hamburger
},
setScreenfull(screenfull: boolean) {
this.screenfull = screenfull
},
setSize(size: boolean) {
this.size = size
},
setLocale(locale: boolean) {
this.locale = locale
},
setMessage(message: boolean) {
this.message = message
},
setTagsView(tagsView: boolean) {
this.tagsView = tagsView
},
setTagsViewIcon(tagsViewIcon: boolean) {
this.tagsViewIcon = tagsViewIcon
},
setLogo(logo: boolean) {
this.logo = logo
},
setFixedHeader(fixedHeader: boolean) {
this.fixedHeader = fixedHeader
},
setGreyMode(greyMode: boolean) {
this.greyMode = greyMode
},
setFixedMenu(fixedMenu: boolean) {
wsCache.set('fixedMenu', fixedMenu)
this.fixedMenu = fixedMenu
},
setPageLoading(pageLoading: boolean) {
this.pageLoading = pageLoading
},
setLayout(layout: LayoutType) {
if (this.mobile && layout !== 'classic') {
ElMessage.warning('移动端模式下不支持切换其他布局')
return
}
this.layout = layout
wsCache.set(CACHE_KEY.LAYOUT, this.layout)
},
setTitle(title: string) {
this.title = title
},
setIsDark(isDark: boolean) {
this.isDark = isDark
if (this.isDark) {
document.documentElement.classList.add('dark')
document.documentElement.classList.remove('light')
} else {
document.documentElement.classList.add('light')
document.documentElement.classList.remove('dark')
}
wsCache.set(CACHE_KEY.IS_DARK, this.isDark)
},
setCurrentSize(currentSize: ElementPlusSize) {
this.currentSize = currentSize
wsCache.set('currentSize', this.currentSize)
},
setMobile(mobile: boolean) {
this.mobile = mobile
},
setTheme(theme: ThemeTypes) {
this.theme = Object.assign(this.theme, theme)
wsCache.set(CACHE_KEY.THEME, this.theme)
},
setCssVarTheme() {
for (const key in this.theme) {
setCssVar(`--${humpToUnderline(key)}`, this.theme[key])
}
},
setFooter(footer: boolean) {
this.footer = footer
}
},
persist: false
})
export const useAppStoreWithOut = () => {
return useAppStore(store)
}

104
src/stores/modules/dict.ts Normal file
View File

@@ -0,0 +1,104 @@
import { defineStore } from 'pinia'
import { store } from '../indexs'
// @ts-ignore
import { DictDataVO } from '@/api/system/dict/types'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache('sessionStorage')
// import { getSimpleDictDataList } from '@/api/system/dict/dict.data'
export interface DictValueType {
value: any
label: string
clorType?: string
cssClass?: string
}
export interface DictTypeType {
dictType: string
dictValue: DictValueType[]
}
export interface DictState {
dictMap: Map<string, any>
isSetDict: boolean
}
export const useDictStore = defineStore('dict', {
state: (): DictState => ({
dictMap: new Map<string, any>(),
isSetDict: false
}),
getters: {
getDictMap(): Recordable {
const dictMap = wsCache.get(CACHE_KEY.DICT_CACHE)
if (dictMap) {
this.dictMap = dictMap
}
return this.dictMap
},
getIsSetDict(): boolean {
return this.isSetDict
}
},
actions: {
async setDictMap() {
const dictMap = wsCache.get(CACHE_KEY.DICT_CACHE)
if (dictMap) {
this.dictMap = dictMap
this.isSetDict = true
} else {
// const res = await getSimpleDictDataList()
// 设置数据
const dictDataMap = new Map<string, any>()
// res.forEach((dictData: DictDataVO) => {
// // 获得 dictType 层级
// const enumValueObj = dictDataMap[dictData.dictType]
// if (!enumValueObj) {
// dictDataMap[dictData.dictType] = []
// }
// // 处理 dictValue 层级
// dictDataMap[dictData.dictType].push({
// value: dictData.value,
// label: dictData.label,
// colorType: dictData.colorType,
// cssClass: dictData.cssClass
// })
// })
this.dictMap = dictDataMap
this.isSetDict = true
wsCache.set(CACHE_KEY.DICT_CACHE, dictDataMap, { exp: 60 }) // 60 秒 过期
}
},
getDictByType(type: string) {
if (!this.isSetDict) {
this.setDictMap()
}
return this.dictMap[type]
},
async resetDict() {
wsCache.delete(CACHE_KEY.DICT_CACHE)
// const res = await getSimpleDictDataList()
// 设置数据
const dictDataMap = new Map<string, any>()
// res.forEach((dictData: DictDataVO) => {
// // 获得 dictType 层级
// const enumValueObj = dictDataMap[dictData.dictType]
// if (!enumValueObj) {
// dictDataMap[dictData.dictType] = []
// }
// // 处理 dictValue 层级
// dictDataMap[dictData.dictType].push({
// value: dictData.value,
// label: dictData.label,
// colorType: dictData.colorType,
// cssClass: dictData.cssClass
// })
// })
this.dictMap = dictDataMap
this.isSetDict = true
wsCache.set(CACHE_KEY.DICT_CACHE, dictDataMap, { exp: 60 }) // 60 秒 过期
}
}
})
export const useDictStoreWithOut = () => {
return useDictStore(store)
}

View File

@@ -0,0 +1,59 @@
import { defineStore } from 'pinia'
import { store } from '../indexs'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import en from 'element-plus/es/locale/lang/en'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
import { LocaleDropdownType } from '@/types/localeDropdown'
const { wsCache } = useCache()
const elLocaleMap = {
'zh-CN': zhCn,
en: en
}
interface LocaleState {
currentLocale: LocaleDropdownType
localeMap: LocaleDropdownType[]
}
export const useLocaleStore = defineStore('locales', {
state: (): LocaleState => {
return {
currentLocale: {
lang: wsCache.get(CACHE_KEY.LANG) || 'zh-CN',
elLocale: elLocaleMap[wsCache.get(CACHE_KEY.LANG) || 'zh-CN']
},
// 多语言
localeMap: [
{
lang: 'zh-CN',
name: '简体中文'
},
{
lang: 'en',
name: 'English'
}
]
}
},
getters: {
getCurrentLocale(): LocaleDropdownType {
return this.currentLocale
},
getLocaleMap(): LocaleDropdownType[] {
return this.localeMap
}
},
actions: {
setCurrentLocale(localeMap: LocaleDropdownType) {
// this.locale = Object.assign(this.locale, localeMap)
this.currentLocale.lang = localeMap?.lang
this.currentLocale.elLocale = elLocaleMap[localeMap?.lang]
wsCache.set(CACHE_KEY.LANG, localeMap?.lang)
}
}
})
export const useLocaleStoreWithOut = () => {
return useLocaleStore(store)
}

View File

@@ -0,0 +1,48 @@
import { defineStore } from 'pinia'
import { store } from '@/stores/indexs'
interface lockInfo {
isLock?: boolean
password?: string
}
interface LockState {
lockInfo: lockInfo
}
export const useLockStore = defineStore('lock', {
state: (): LockState => {
return {
lockInfo: {
// isLock: false, // 是否锁定屏幕
// password: '' // 锁屏密码
}
}
},
getters: {
getLockInfo(): lockInfo {
return this.lockInfo
}
},
actions: {
setLockInfo(lockInfo: lockInfo) {
this.lockInfo = lockInfo
},
resetLockInfo() {
this.lockInfo = {}
},
unLock(password: string) {
if (this.lockInfo?.password === password) {
this.resetLockInfo()
return true
} else {
return false
}
}
},
persist: true
})
export const useLockStoreWithOut = () => {
return useLockStore(store)
}

View File

@@ -0,0 +1,66 @@
import { defineStore } from 'pinia'
import { store } from '@/stores/indexs'
import { cloneDeep } from 'lodash-es'
import { flatMultiLevelRoutes, generateRoute } from '@/utils/routerHelper'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache()
export interface PermissionState {
routers: AppRouteRecordRaw[]
addRouters: AppRouteRecordRaw[]
menuTabRouters: AppRouteRecordRaw[]
}
export const usePermissionStore = defineStore('permission', {
state: (): PermissionState => ({
routers: [],
addRouters: [],
menuTabRouters: []
}),
getters: {
getRouters(): AppRouteRecordRaw[] {
return this.routers
},
getAddRouters(): AppRouteRecordRaw[] {
return flatMultiLevelRoutes(cloneDeep(this.addRouters))
},
getMenuTabRouters(): AppRouteRecordRaw[] {
return this.menuTabRouters
}
},
actions: {
async generateRoutes(): Promise<unknown> {
return new Promise<void>(async (resolve) => {
// 获得菜单列表它在登录的时候setUserInfoAction 方法中已经进行获取
let res: AppCustomRouteRecordRaw[] = []
if (wsCache.get(CACHE_KEY.ROLE_ROUTERS)) {
res = wsCache.get(CACHE_KEY.ROLE_ROUTERS) as AppCustomRouteRecordRaw[]
}
const routerMap: AppRouteRecordRaw[] = generateRoute(res)
// 动态路由404一定要放到最后面
this.addRouters = routerMap.concat([
{
path: '/:path(.*)*',
redirect: '/404',
name: '404Page',
meta: {
hidden: true,
breadcrumb: false
}
}
])
// 渲染菜单的所有路由
resolve()
})
},
setMenuTabRouters(routers: AppRouteRecordRaw[]): void {
this.menuTabRouters = routers
}
},
persist: false
})
export const usePermissionStoreWithOut = () => {
return usePermissionStore(store)
}

View File

@@ -0,0 +1,55 @@
import { store } from '../index'
import { defineStore } from 'pinia'
export const useWorkFlowStore = defineStore('simpleWorkflow', {
state: () => ({
tableId: '',
isTried: false,
promoterDrawer: false,
flowPermission1: {},
approverDrawer: false,
approverConfig1: {},
copyerDrawer: false,
copyerConfig1: {},
conditionDrawer: false,
conditionsConfig1: {
conditionNodes: []
}
}),
actions: {
setTableId(payload) {
this.tableId = payload
},
setIsTried(payload) {
this.isTried = payload
},
setPromoter(payload) {
this.promoterDrawer = payload
},
setFlowPermission(payload) {
this.flowPermission1 = payload
},
setApprover(payload) {
this.approverDrawer = payload
},
setApproverConfig(payload) {
this.approverConfig1 = payload
},
setCopyer(payload) {
this.copyerDrawer = payload
},
setCopyerConfig(payload) {
this.copyerConfig1 = payload
},
setCondition(payload) {
this.conditionDrawer = payload
},
setConditionsConfig(payload) {
this.conditionsConfig1 = payload
}
}
})
export const useWorkFlowStoreWithOut = () => {
return useWorkFlowStore(store)
}

View File

@@ -0,0 +1,141 @@
import router from '@/router'
import type { RouteLocationNormalizedLoaded } from 'vue-router'
import { getRawRoute } from '@/utils/routerHelper'
import { defineStore } from 'pinia'
import { store } from '../indexs'
import { findIndex } from '@/utils'
export interface TagsViewState {
visitedViews: RouteLocationNormalizedLoaded[]
cachedViews: Set<string>
}
export const useTagsViewStore = defineStore('tagsView', {
state: (): TagsViewState => ({
visitedViews: [],
cachedViews: new Set()
}),
getters: {
getVisitedViews(): RouteLocationNormalizedLoaded[] {
return this.visitedViews
},
getCachedViews(): string[] {
return Array.from(this.cachedViews)
}
},
actions: {
// 新增缓存和tag
addView(view: RouteLocationNormalizedLoaded): void {
this.addVisitedView(view)
this.addCachedView()
},
// 新增tag
addVisitedView(view: RouteLocationNormalizedLoaded) {
if (this.visitedViews.some((v) => v.path === view.path)) return
if (view.meta?.noTagsView) return
this.visitedViews.push(
Object.assign({}, view, {
title: view.meta?.title || 'no-name'
})
)
},
// 新增缓存
addCachedView() {
const cacheMap: Set<string> = new Set()
for (const v of this.visitedViews) {
const item = getRawRoute(v)
const needCache = !item.meta?.noCache
if (!needCache) {
continue
}
const name = item.name as string
cacheMap.add(name)
}
if (Array.from(this.cachedViews).sort().toString() === Array.from(cacheMap).sort().toString())
return
this.cachedViews = cacheMap
},
// 删除某个
delView(view: RouteLocationNormalizedLoaded) {
this.delVisitedView(view)
this.delCachedView()
},
// 删除tag
delVisitedView(view: RouteLocationNormalizedLoaded) {
for (const [i, v] of this.visitedViews.entries()) {
if (v.path === view.path) {
this.visitedViews.splice(i, 1)
break
}
}
},
// 删除缓存
delCachedView() {
const route = router.currentRoute.value
const index = findIndex<string>(this.getCachedViews, (v) => v === route.name)
if (index > -1) {
this.cachedViews.delete(this.getCachedViews[index])
}
},
// 删除所有缓存和tag
delAllViews() {
this.delAllVisitedViews()
this.delCachedView()
},
// 删除所有tag
delAllVisitedViews() {
// const affixTags = this.visitedViews.filter((tag) => tag.meta.affix)
this.visitedViews = []
},
// 删除其他
delOthersViews(view: RouteLocationNormalizedLoaded) {
this.delOthersVisitedViews(view)
this.addCachedView()
},
// 删除其他tag
delOthersVisitedViews(view: RouteLocationNormalizedLoaded) {
this.visitedViews = this.visitedViews.filter((v) => {
return v?.meta?.affix || v.path === view.path
})
},
// 删除左侧
delLeftViews(view: RouteLocationNormalizedLoaded) {
const index = findIndex<RouteLocationNormalizedLoaded>(
this.visitedViews,
(v) => v.path === view.path
)
if (index > -1) {
this.visitedViews = this.visitedViews.filter((v, i) => {
return v?.meta?.affix || v.path === view.path || i > index
})
this.addCachedView()
}
},
// 删除右侧
delRightViews(view: RouteLocationNormalizedLoaded) {
const index = findIndex<RouteLocationNormalizedLoaded>(
this.visitedViews,
(v) => v.path === view.path
)
if (index > -1) {
this.visitedViews = this.visitedViews.filter((v, i) => {
return v?.meta?.affix || v.path === view.path || i < index
})
this.addCachedView()
}
},
updateVisitedView(view: RouteLocationNormalizedLoaded) {
for (let v of this.visitedViews) {
if (v.path === view.path) {
v = Object.assign(v, view)
break
}
}
}
},
persist: false
})
export const useTagsViewStoreWithOut = () => {
return useTagsViewStore(store)
}

103
src/stores/modules/user.ts Normal file
View File

@@ -0,0 +1,103 @@
import { store } from '@/stores/indexs'
import { defineStore } from 'pinia'
import { getAccessToken, removeToken } from '@/utils/auth'
import { CACHE_KEY, useCache, deleteUserCache } from '@/hooks/web/useCache'
// import { getInfo, loginOut } from '@/api/login'
const { wsCache } = useCache()
interface UserVO {
id: number
avatar: string
nickname: string
deptId: number
}
interface UserInfoVO {
// USER 缓存
permissions: string[]
roles: string[]
isSetUser: boolean
user: UserVO
}
export const useUserStore = defineStore('admin-user', {
state: (): UserInfoVO => ({
permissions: [],
roles: [],
isSetUser: false,
user: {
id: 0,
avatar: '',
nickname: '',
deptId: 0
}
}),
getters: {
getPermissions(): string[] {
return this.permissions
},
getRoles(): string[] {
return this.roles
},
getIsSetUser(): boolean {
return this.isSetUser
},
getUser(): UserVO {
return this.user
}
},
actions: {
async setUserInfoAction() {
if (!getAccessToken()) {
this.resetState()
return null
}
let userInfo = wsCache.get(CACHE_KEY.USER)
if (!userInfo) {
// userInfo = await getInfo()
}
this.permissions = userInfo.permissions
this.roles = userInfo.roles
this.user = userInfo.user
this.isSetUser = true
wsCache.set(CACHE_KEY.USER, userInfo)
wsCache.set(CACHE_KEY.ROLE_ROUTERS, userInfo.menus)
},
async setUserAvatarAction(avatar: string) {
const userInfo = wsCache.get(CACHE_KEY.USER)
// NOTE: 是否需要像`setUserInfoAction`一样判断`userInfo != null`
this.user.avatar = avatar
userInfo.user.avatar = avatar
wsCache.set(CACHE_KEY.USER, userInfo)
},
async setUserNicknameAction(nickname: string) {
const userInfo = wsCache.get(CACHE_KEY.USER)
// NOTE: 是否需要像`setUserInfoAction`一样判断`userInfo != null`
this.user.nickname = nickname
userInfo.user.nickname = nickname
wsCache.set(CACHE_KEY.USER, userInfo)
},
async loginOut() {
// await loginOut()
removeToken()
deleteUserCache() // 删除用户缓存
this.resetState()
},
resetState() {
this.permissions = []
this.roles = []
this.isSetUser = false
this.user = {
id: 0,
avatar: '',
nickname: '',
deptId: 0
}
}
}
})
export const useUserStoreWithOut = () => {
return useUserStore(store)
}

34
src/stores/timeCache.ts Normal file
View File

@@ -0,0 +1,34 @@
// src/stores/timeCache.ts
import { defineStore } from 'pinia'
import { RouteLocationNormalizedLoaded } from 'vue-router'
// 时间组件的缓存值 用于驾驶舱放大的时候和内部的时间组件同步
interface TimeCacheState {
cache: Map<string, {
interval: number | undefined // 时间组件的月份、年份、时间、时间格式的缓存值
timeValue: any // 时间组件的值
}>
}
export const useTimeCacheStore = defineStore('timeCache', {
state: (): TimeCacheState => ({
cache: new Map()
}),
actions: {
setCache(routePath: string, interval: number | undefined, timeValue: any) {
this.cache.set(routePath, {
interval,
timeValue
})
},
getCache(routePath: string) {
return this.cache.get(routePath)
},
hasCache(routePath: string) {
return this.cache.has(routePath)
}
}
})

355
src/styles/app.css Normal file
View File

@@ -0,0 +1,355 @@
@charset "UTF-8";
/* 基本样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
outline: none !important;
}
html,
body,
#app {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
font-weight: 400;
-webkit-font-smoothing: antialiased;
-webkit-tap-highlight-color: transparent;
background-color: var(--ba-bg-color);
font-size: 14px;
overflow: hidden;
position: relative;
}
/* 修改纵向滚动条轨道 */
::-webkit-scrollbar {
width: 8px;
}
/* 修改纵向滚动条拇指thumb */
::-webkit-scrollbar-thumb {
background-color: var(--el-color-primary);
border-radius: 5px;
cursor: pointer !important;
}
/* 修改横向滚动条轨道 */
::-webkit-scrollbar:horizontal {
height: 8px;
}
/* 修改横向滚动条拇指thumb */
::-webkit-scrollbar-thumb:horizontal {
background-color: var(--el-color-primary);
border-radius: 5px;
}
.iconfont-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
.w100 {
width: 100% !important;
}
.h100 {
height: 100% !important;
}
.ba-center {
display: flex;
align-items: center;
justify-content: center;
}
.default-main {
position: relative;
background: #fff;
margin: var(--ba-main-space) var(--ba-main-space) 0px var(--ba-main-space);
}
.form-one {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.form-one .el-form-item {
display: flex;
width: 98%;
margin-bottom: 15px !important;
}
.form-one .el-form-item .el-form-item__content {
flex: 1;
}
.form-one .el-form-item .el-form-item__content .el-select,
.form-one .el-form-item .el-form-item__content .el-cascader,
.form-one .el-form-item .el-form-item__content .el-input__inner,
.form-one .el-form-item .el-form-item__content .el-date-editor {
width: 100%;
}
.form-two {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.form-two .el-form-item {
display: flex;
width: 48%;
}
.form-two .el-form-item .el-form-item__content {
flex: 1;
}
.form-two .el-form-item .el-form-item__content .el-select,
.form-two .el-form-item .el-form-item__content .el-cascader,
.form-two .el-form-item .el-form-item__content .el-input__inner,
.form-two .el-form-item .el-form-item__content .el-date-editor {
width: 100%;
}
.uploadFile .el-form-item__content {
line-height: 20px !important;
}
.uploadFile .el-form-item__content div {
display: flex !important;
align-items: center !important;
}
.uploadFile .el-form-item__content .el-upload-list__item-name {
width: 150px !important;
}
.uploadFile .el-form-item__content .el-upload-list--text {
margin: 0px 20px 0 !important;
}
.zoom-handle {
position: absolute;
width: 20px;
height: 20px;
bottom: -10px;
right: -10px;
cursor: se-resize;
}
.block-help {
display: block;
width: 100%;
color: #909399;
font-size: 13px;
line-height: 16px;
padding-top: 5px;
}
/* 表格顶部菜单-s */
.table-header .table-header-operate .icon {
font-size: 14px !important;
color: var(--el-color-white) !important;
}
.table-header .el-button.is-disabled .icon {
color: var(--el-button-disabled-text-color) !important;
}
/* 表格顶部菜单-e */
/* 鼠标置入浮动效果-s */
.suspension {
transition: all 0.3s ease;
}
.suspension:hover {
-webkit-transform: translateY(-4px) scale(1.02);
-moz-transform: translateY(-4px) scale(1.02);
-ms-transform: translateY(-4px) scale(1.02);
-o-transform: translateY(-4px) scale(1.02);
transform: translateY(-4px) scale(1.02);
-webkit-box-shadow: 0 14px 24px rgba(0, 0, 0, 0.2);
box-shadow: 0 14px 24px rgba(0, 0, 0, 0.2);
z-index: 999;
border-radius: 6px;
}
/* 鼠标置入浮动效果-e */
/* 表格-s */
.ba-table-box {
border-radius: var(--el-border-radius-round);
}
.ba-table-alert {
background-color: var(--el-fill-color-darker) !important;
border: 1px solid var(--ba-boder-color);
border-bottom: 0;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
/* 表格-e */
.ba-operate-form {
padding-top: 20px;
}
/* 新增/编辑表单-e */
/* 全局遮罩-s */
.ba-layout-shade {
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 100vw;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9999990;
}
/* 全局遮罩-e */
/* 图片上传预览-s */
.img-preview-dialog .el-dialog__body {
display: flex;
align-items: center;
justify-content: center;
}
.img-preview-dialog .el-dialog__body img {
max-width: 100%;
}
/* 图片上传预览-e */
/* 页面切换动画-s */
.slide-right-enter-active,
.slide-right-leave-active,
.slide-left-enter-active,
.slide-left-leave-active {
will-change: transform;
transition: all 0.3s ease;
}
.slide-right-enter-from, .slide-left-leave-to {
opacity: 0;
transform: translateX(-20px);
}
.slide-right-leave-to, .slide-left-enter-from {
opacity: 0;
transform: translateX(20px);
}
/* 页面切换动画-e */
/* 布局相关-s */
.frontend-footer-brother {
min-height: calc(100vh - 120px);
}
.user-views {
padding-left: 15px;
}
.user-views .user-views-card {
margin-bottom: 15px;
}
.ba-aside-drawer .el-drawer__body {
padding: 0;
}
/* 布局相关-e */
/* 暗黑模式公共样式-s */
.ba-icon-dark {
color: var(--el-text-color-primary) !important;
}
/* 暗黑模式公共样式-e */
/* NProgress-s */
#nprogress .bar,
#nprogress .spinner {
z-index: 999999;
}
/* NProgress-e */
/* 自适应-s */
@media screen and (max-width: 768px) {
.xs-hidden {
display: none;
}
}
@media screen and (max-width: 1024px) {
.cn-operate-dialog {
width: 96%;
}
}
@media screen and (max-width: 991px) {
.user-views {
padding: 0;
}
}
/* 自适应-e */
.custom-table-header {
box-sizing: border-box;
display: flex;
padding: 13px 15px;
align-items: center;
border: 1px solid var(--el-border-color);
}
.custom-table-header .title {
flex: 1;
font-weight: 700;
}
.custom-table-header .el-form-item {
margin-bottom: 0 !important;
}
.child-router {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background: #fff;
z-index: 10;
}
.bjs-powered-by {
display: none;
}
.el-select__wrapper {
height: 32px !important;
}
@font-face {
font-family: 'AlimamaFangYuanTiVF';
src: url("../assets/font/ali/AlimamaFangYuanTiVF-Thin.woff") format("woff"), url("../assets/font/ali/AlimamaFangYuanTiVF-Thin.woff2") format("woff2");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'nablaRegular';
src: url("../assets/font/ali/Nabla_Regular.woff") format("woff"), url("../assets/font/ali/Nabla_Regular.woff2") format("woff2");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'AlimamaDongFangDaKai';
src: url("../assets/font/ali/AlimamaDongFangDaKai-Regular.woff") format("woff"), url("../assets/font/ali/AlimamaDongFangDaKai-Regular.woff2") format("woff2");
font-weight: normal;
font-style: normal;
}

1
src/styles/app.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,415 +1,416 @@
/* 基本样式 */ /* 基本样式 */
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
box-sizing: border-box; box-sizing: border-box;
outline: none !important; outline: none !important;
} }
html, html,
body, body,
#app { #app {
margin: 0; margin: 0;
padding: 0; padding: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif; font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
font-weight: 400; font-weight: 400;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
background-color: var(--ba-bg-color); background-color: var(--ba-bg-color);
font-size: 14px; font-size: 14px;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
} }
/* 修改纵向滚动条轨道 */ /* 修改纵向滚动条轨道 */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 8px; width: 8px;
} }
/* 修改纵向滚动条拇指thumb */ /* 修改纵向滚动条拇指thumb */
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background-color: var(--el-color-primary); background-color: var(--el-color-primary);
border-radius: 5px; border-radius: 5px;
} cursor: pointer !important;
/* 修改横向滚动条轨道 */ }
::-webkit-scrollbar:horizontal { /* 修改横向滚动条轨道 */
height: 8px; ::-webkit-scrollbar:horizontal {
} height: 8px;
/* 修改横向滚动条拇指thumb */ }
::-webkit-scrollbar-thumb:horizontal { /* 修改横向滚动条拇指thumb */
background-color: var(--el-color-primary); ::-webkit-scrollbar-thumb:horizontal {
border-radius: 5px; background-color: var(--el-color-primary);
} border-radius: 5px;
// 阿里 iconfont Symbol引用css }
.iconfont-icon { // 阿里 iconfont Symbol引用css
width: 1em; .iconfont-icon {
height: 1em; width: 1em;
vertical-align: -0.15em; height: 1em;
fill: currentColor; vertical-align: -0.15em;
overflow: hidden; fill: currentColor;
} overflow: hidden;
}
.w100 {
width: 100% !important; .w100 {
} width: 100% !important;
}
.h100 {
height: 100% !important; .h100 {
} height: 100% !important;
}
.ba-center {
display: flex; .ba-center {
align-items: center; display: flex;
justify-content: center; align-items: center;
} justify-content: center;
}
.default-main {
position: relative; .default-main {
background: #fff; position: relative;
margin: var(--ba-main-space) var(--ba-main-space) 0px var(--ba-main-space); background: #fff;
} margin: var(--ba-main-space) var(--ba-main-space) 0px var(--ba-main-space);
}
.form-one {
display: flex; .form-one {
flex-wrap: wrap; display: flex;
justify-content: space-between; flex-wrap: wrap;
.el-form-item { justify-content: space-between;
display: flex; .el-form-item {
width: 98%; display: flex;
margin-bottom: 15px !important; width: 98%;
.el-form-item__content { margin-bottom: 15px !important;
flex: 1; .el-form-item__content {
.el-select, flex: 1;
.el-cascader, .el-select,
.el-input__inner, .el-cascader,
.el-date-editor { .el-input__inner,
width: 100%; .el-date-editor {
} width: 100%;
} }
} }
} }
.form-two { }
display: flex; .form-two {
flex-wrap: wrap; display: flex;
justify-content: space-between; flex-wrap: wrap;
.el-form-item { justify-content: space-between;
display: flex; .el-form-item {
width: 48%; display: flex;
.el-form-item__content { width: 48%;
flex: 1; .el-form-item__content {
.el-select, flex: 1;
.el-cascader, .el-select,
.el-input__inner, .el-cascader,
.el-date-editor { .el-input__inner,
width: 100%; .el-date-editor {
} width: 100%;
} }
} }
} }
//修改上传文件filelist位置 }
.uploadFile { //修改上传文件filelist位置
.el-form-item__content { .uploadFile {
line-height: 20px !important; .el-form-item__content {
div { line-height: 20px !important;
display: flex !important; div {
align-items: center !important; display: flex !important;
} align-items: center !important;
.el-upload-list__item-name { }
width: 150px !important; .el-upload-list__item-name {
} width: 150px !important;
.el-upload-list--text { }
margin: 0px 20px 0 !important; .el-upload-list--text {
} margin: 0px 20px 0 !important;
} }
} }
.zoom-handle { }
position: absolute; .zoom-handle {
width: 20px; position: absolute;
height: 20px; width: 20px;
bottom: -10px; height: 20px;
right: -10px; bottom: -10px;
cursor: se-resize; right: -10px;
} cursor: se-resize;
}
.block-help {
display: block; .block-help {
width: 100%; display: block;
color: #909399; width: 100%;
font-size: 13px; color: #909399;
line-height: 16px; font-size: 13px;
padding-top: 5px; line-height: 16px;
} padding-top: 5px;
}
/* 表格顶部菜单-s */
.table-header { /* 表格顶部菜单-s */
.table-header-operate .icon { .table-header {
font-size: 14px !important; .table-header-operate .icon {
color: var(--el-color-white) !important; font-size: 14px !important;
} color: var(--el-color-white) !important;
}
.el-button.is-disabled .icon {
color: var(--el-button-disabled-text-color) !important; .el-button.is-disabled .icon {
} color: var(--el-button-disabled-text-color) !important;
} }
}
/* 表格顶部菜单-e */
/* 表格顶部菜单-e */
/* 鼠标置入浮动效果-s */
.suspension { /* 鼠标置入浮动效果-s */
transition: all 0.3s ease; .suspension {
} transition: all 0.3s ease;
}
.suspension:hover {
-webkit-transform: translateY(-4px) scale(1.02); .suspension:hover {
-moz-transform: translateY(-4px) scale(1.02); -webkit-transform: translateY(-4px) scale(1.02);
-ms-transform: translateY(-4px) scale(1.02); -moz-transform: translateY(-4px) scale(1.02);
-o-transform: translateY(-4px) scale(1.02); -ms-transform: translateY(-4px) scale(1.02);
transform: translateY(-4px) scale(1.02); -o-transform: translateY(-4px) scale(1.02);
-webkit-box-shadow: 0 14px 24px rgba(0, 0, 0, 0.2); transform: translateY(-4px) scale(1.02);
box-shadow: 0 14px 24px rgba(0, 0, 0, 0.2); -webkit-box-shadow: 0 14px 24px rgba(0, 0, 0, 0.2);
z-index: 999; box-shadow: 0 14px 24px rgba(0, 0, 0, 0.2);
border-radius: 6px; z-index: 999;
} border-radius: 6px;
}
/* 鼠标置入浮动效果-e */
/* 鼠标置入浮动效果-e */
/* 表格-s */
.ba-table-box { /* 表格-s */
border-radius: var(--el-border-radius-round); .ba-table-box {
} border-radius: var(--el-border-radius-round);
}
.ba-table-alert {
background-color: var(--el-fill-color-darker) !important; .ba-table-alert {
border: 1px solid var(--ba-boder-color); background-color: var(--el-fill-color-darker) !important;
border-bottom: 0; border: 1px solid var(--ba-boder-color);
border-bottom-left-radius: 0; border-bottom: 0;
border-bottom-right-radius: 0; border-bottom-left-radius: 0;
} border-bottom-right-radius: 0;
}
/* 表格-e */
/* 表格-e */
// /* 新增/编辑表单-s */
// .cn-operate-dialog { // /* 新增/编辑表单-s */
// overflow: hidden; // .cn-operate-dialog {
// border-radius: var(--el-border-radius-base); // overflow: hidden;
// } // border-radius: var(--el-border-radius-base);
// }
// .cn-operate-dialog .el-dialog__header {
// padding-bottom: 16px; // .cn-operate-dialog .el-dialog__header {
// border-bottom: 1px solid var(--ba-bg-color); // padding-bottom: 16px;
// } // border-bottom: 1px solid var(--ba-bg-color);
// }
// .cn-operate-dialog .el-dialog__body {
// max-height: 60vh; // .cn-operate-dialog .el-dialog__body {
// padding-top: 20px; // max-height: 60vh;
// padding-bottom: 52px; // padding-top: 20px;
// } // padding-bottom: 52px;
// }
// .cn-operate-dialog .el-dialog__body .el-scrollbar {
// // padding-right: 60px; // .cn-operate-dialog .el-dialog__body .el-scrollbar {
// } // // padding-right: 60px;
// }
// .cn-operate-dialog .el-dialog__footer {
// padding: 15px; // .cn-operate-dialog .el-dialog__footer {
// box-shadow: var(--el-box-shadow); // padding: 15px;
// position: absolute; // box-shadow: var(--el-box-shadow);
// width: 100%; // position: absolute;
// bottom: 0; // width: 100%;
// } // bottom: 0;
// }
// .cn-operate-dialog .el-form {
// width: calc(100% - 60px); // .cn-operate-dialog .el-form {
// } // width: calc(100% - 60px);
// }
// .cn-operate-dialog .el-form--inline {
// display: grid; // .cn-operate-dialog .el-form--inline {
// grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); // display: grid;
// grid-gap: 20px 10px; // grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
// grid-gap: 20px 10px;
// .el-form-item {
// margin-right: 0; // .el-form-item {
// margin-bottom: 0; // margin-right: 0;
// } // margin-bottom: 0;
// } // }
// }
// .cn-operate-dialog .el-form--inline .el-select {
// width: 100%; // .cn-operate-dialog .el-form--inline .el-select {
// } // width: 100%;
// }
.ba-operate-form {
padding-top: 20px; .ba-operate-form {
} padding-top: 20px;
}
/* 新增/编辑表单-e */
/* 新增/编辑表单-e */
/* 全局遮罩-s */
.ba-layout-shade { /* 全局遮罩-s */
position: fixed; .ba-layout-shade {
top: 0; position: fixed;
left: 0; top: 0;
height: 100vh; left: 0;
width: 100vw; height: 100vh;
background-color: rgba(0, 0, 0, 0.5); width: 100vw;
z-index: 9999990; background-color: rgba(0, 0, 0, 0.5);
} z-index: 9999990;
}
/* 全局遮罩-e */
/* 全局遮罩-e */
/* 图片上传预览-s */
.img-preview-dialog .el-dialog__body { /* 图片上传预览-s */
display: flex; .img-preview-dialog .el-dialog__body {
align-items: center; display: flex;
justify-content: center; align-items: center;
justify-content: center;
img {
max-width: 100%; img {
} max-width: 100%;
} }
}
/* 图片上传预览-e */
/* 图片上传预览-e */
/* 页面切换动画-s */
.slide-right-enter-active, /* 页面切换动画-s */
.slide-right-leave-active, .slide-right-enter-active,
.slide-left-enter-active, .slide-right-leave-active,
.slide-left-leave-active { .slide-left-enter-active,
will-change: transform; .slide-left-leave-active {
transition: all 0.3s ease; will-change: transform;
} transition: all 0.3s ease;
}
// slide-right
.slide-right-enter-from { // slide-right
opacity: 0; .slide-right-enter-from {
transform: translateX(-20px); opacity: 0;
} transform: translateX(-20px);
}
.slide-right-leave-to {
opacity: 0; .slide-right-leave-to {
transform: translateX(20px); opacity: 0;
} transform: translateX(20px);
}
// slide-left
.slide-left-enter-from { // slide-left
@extend .slide-right-leave-to; .slide-left-enter-from {
} @extend .slide-right-leave-to;
}
.slide-left-leave-to {
@extend .slide-right-enter-from; .slide-left-leave-to {
} @extend .slide-right-enter-from;
}
/* 页面切换动画-e */
/* 页面切换动画-e */
/* 布局相关-s */
.frontend-footer-brother { /* 布局相关-s */
min-height: calc(100vh - 120px); .frontend-footer-brother {
} min-height: calc(100vh - 120px);
}
.user-views {
padding-left: 15px; .user-views {
padding-left: 15px;
.user-views-card {
margin-bottom: 15px; .user-views-card {
} margin-bottom: 15px;
} }
}
.ba-aside-drawer {
.el-drawer__body { .ba-aside-drawer {
padding: 0; .el-drawer__body {
} padding: 0;
} }
}
/* 布局相关-e */
/* 布局相关-e */
/* 暗黑模式公共样式-s */
.ba-icon-dark { /* 暗黑模式公共样式-s */
color: var(--el-text-color-primary) !important; .ba-icon-dark {
} color: var(--el-text-color-primary) !important;
}
/* 暗黑模式公共样式-e */
/* 暗黑模式公共样式-e */
/* NProgress-s */
#nprogress { /* NProgress-s */
.bar, #nprogress {
.spinner { .bar,
z-index: 999999; .spinner {
} z-index: 999999;
} }
}
/* NProgress-e */
/* NProgress-e */
/* 自适应-s */
@media screen and (max-width: 768px) { /* 自适应-s */
.xs-hidden { @media screen and (max-width: 768px) {
display: none; .xs-hidden {
} display: none;
} }
}
@media screen and (max-width: 1024px) {
.cn-operate-dialog { @media screen and (max-width: 1024px) {
width: 96%; .cn-operate-dialog {
} width: 96%;
} }
}
@media screen and (max-width: 991px) {
.user-views { @media screen and (max-width: 991px) {
padding: 0; .user-views {
} padding: 0;
} }
}
/* 自适应-e */
/* 自适应-e */
.custom-table-header {
box-sizing: border-box; .custom-table-header {
display: flex; box-sizing: border-box;
padding: 13px 15px; display: flex;
align-items: center; padding: 13px 15px;
border: 1px solid var(--el-border-color); align-items: center;
.title { border: 1px solid var(--el-border-color);
flex: 1; .title {
font-weight: 700; flex: 1;
} font-weight: 700;
.el-form-item { }
margin-bottom: 0 !important; .el-form-item {
} margin-bottom: 0 !important;
} }
}
.child-router {
position: absolute; .child-router {
top: 0; position: absolute;
left: 0; top: 0;
height: 100%; left: 0;
width: 100%; height: 100%;
background: #fff; width: 100%;
z-index: 10; background: #fff;
} z-index: 10;
.bjs-powered-by { }
display: none; .bjs-powered-by {
} display: none;
.el-select__wrapper{ }
height: 32px !important; .el-select__wrapper{
} height: 32px !important;
@font-face { }
font-family: 'AlimamaFangYuanTiVF'; @font-face {
src: url('../assets/font/ali/AlimamaFangYuanTiVF-Thin.woff') format('woff'), font-family: 'AlimamaFangYuanTiVF';
url('../assets/font/ali/AlimamaFangYuanTiVF-Thin.woff2') format('woff2'); src: url('../assets/font/ali/AlimamaFangYuanTiVF-Thin.woff') format('woff'),
font-weight: normal; url('../assets/font/ali/AlimamaFangYuanTiVF-Thin.woff2') format('woff2');
font-style: normal; font-weight: normal;
} font-style: normal;
}
//阿里变形字体
@font-face { //阿里变形字体
font-family: 'nablaRegular'; @font-face {
src: url('../assets/font/ali/Nabla_Regular.woff') format('woff'), font-family: 'nablaRegular';
url('../assets/font/ali/Nabla_Regular.woff2') format('woff2'); src: url('../assets/font/ali/Nabla_Regular.woff') format('woff'),
font-weight: normal; url('../assets/font/ali/Nabla_Regular.woff2') format('woff2');
font-style: normal; font-weight: normal;
} font-style: normal;
}
@font-face {
font-family: 'AlimamaDongFangDaKai'; @font-face {
src: url('../assets/font/ali/AlimamaDongFangDaKai-Regular.woff') format('woff'), font-family: 'AlimamaDongFangDaKai';
url('../assets/font/ali/AlimamaDongFangDaKai-Regular.woff2') format('woff2'); src: url('../assets/font/ali/AlimamaDongFangDaKai-Regular.woff') format('woff'),
font-weight: normal; url('../assets/font/ali/AlimamaDongFangDaKai-Regular.woff2') format('woff2');
font-style: normal; font-weight: normal;
} font-style: normal;
}

247
src/styles/element.css Normal file
View File

@@ -0,0 +1,247 @@
@charset "UTF-8";
/* 修复 Chrome 浏览器输入框内选中字符行高异常的bug-s */
.el-input .el-input__inner {
height: 30px;
line-height: calc(var(--el-input-height, 40px) - 4px);
}
/* 修复 Chrome 浏览器输入框内选中字符行高异常的bug-e */
.datetime-picker {
height: 32px;
padding-top: 0;
padding-bottom: 0;
}
.el-divider__text.is-center {
transform: translateX(-50%) translateY(-62%);
}
.el-menu {
user-select: none;
}
.el-menu .el-menu-item:hover,
.el-menu .el-sub-menu__title:hover {
background-color: var(--el-menu-hover-color) !important;
color: var(--el-menu-active-color) !important;
}
.el-menu .el-menu-item:hover .icon,
.el-menu .el-sub-menu__title:hover .icon {
color: var(--el-menu-active-color) !important;
}
.el-select {
min-width: 200px;
}
.el-dialog {
padding: 0px !important;
}
.el-dialog .el-dialog__footer {
padding: 15px;
box-shadow: var(--el-box-shadow);
width: 100%;
bottom: 0;
}
.el-dialog__body {
max-height: 60vh;
overflow-y: auto;
padding: 10px;
}
.el-dialog__header {
background: var(--el-color-primary);
padding: 15px;
margin-right: 0px;
}
.el-dialog__header .el-dialog__headerbtn {
top: 5px;
}
.el-dialog__header .el-dialog__headerbtn .el-icon {
color: var(--el-color-white);
}
.el-dialog__header .el-dialog__headerbtn:hover .el-icon {
color: #409eff;
}
.el-dialog__header .el-dialog__title {
color: var(--el-color-white);
}
.el-table {
--el-table-border-color: var(--ba-border-color);
}
.el-card {
border: none;
}
.el-card__header {
border-bottom: 1px solid var(--el-border-color-extra-light);
}
.el-textarea__inner {
padding: 5px 11px;
}
/* dialog滚动条-s */
.el-overlay-dialog,
.el-tabs__content,
.ba-scroll-style {
scrollbar-width: none;
}
.el-overlay-dialog::-webkit-scrollbar,
.el-tabs__content::-webkit-scrollbar,
.ba-scroll-style::-webkit-scrollbar {
width: 5px;
height: 5px;
}
.el-overlay-dialog::-webkit-scrollbar-thumb,
.el-tabs__content::-webkit-scrollbar-thumb,
.ba-scroll-style::-webkit-scrollbar-thumb {
background: #eaeaea;
border-radius: var(--el-border-radius-base);
box-shadow: none;
-webkit-box-shadow: none;
cursor: pointer !important;
}
.el-overlay-dialog:hover::-webkit-scrollbar-thumb:hover,
.el-tabs__content:hover::-webkit-scrollbar-thumb:hover,
.ba-scroll-style:hover::-webkit-scrollbar-thumb:hover {
background: #c8c9cc;
}
/* dialog滚动条-e */
/* 小屏设备 el-radio-group 样式优化-s */
.ba-input-item-radio {
margin-bottom: 10px;
}
.ba-input-item-radio .el-radio-group .el-radio {
margin-bottom: 8px;
}
/* 小屏设备 el-radio-group 样式调整-e */
.el-tabs--card > .el-tabs__header .el-tabs__item.is-active {
background: var(--el-color-primary);
color: var(--el-color-white);
}
.el-tabs__header {
margin-bottom: 0;
}
.el-form--inline .el-form-item {
margin-bottom: 10px;
}
.el-tabs--border-card > .el-tabs__content {
padding: 10px;
}
.el-page-header__header {
line-height: 32px;
}
.el-page-header__header .el-page-header__content {
font-size: 14px;
font-weight: 700;
}
.el-tabs__content {
height: calc(100% - 40px);
}
.el-tabs__content .el-tab-pane {
height: 100%;
}
.el-button--primary:focus {
color: var(--el-color-white);
outline: 0;
}
.el-button--primary:hover {
color: var(--el-color-white);
border-color: var(--el-color-primary-light-3);
background-color: var(--el-color-primary-light-3);
outline: 0;
}
.el-button.is-plain:focus {
color: var(--el-button-text-color);
border-color: var(--el-button-border-color);
}
.el-button.is-plain:hover {
color: var(--el-color-primary);
border-color: var(--el-color-primary);
}
.el-button.is-link:focus {
color: var(--el-button-text-color);
}
.el-button.is-link:hover {
color: var(--el-button-hover-link-text-color);
}
.el-button--primary.is-link:hover,
.el-button--primary.is-plain:hover,
.el-button--primary.is-text:hover {
color: var(--el-color-primary-light-5);
background-color: var(--el-color-primary-light-9) !important;
}
.el-divider--horizontal {
margin: 15px 0;
}
.el-step__title {
cursor: pointer;
}
.el-checkbox__input.is-disabled.is-checked .el-checkbox__inner,
.el-radio__input.is-disabled.is-checked .el-radio__inner {
background-color: var(--el-color-primary);
opacity: 0.6;
}
.sgmap-ctrl-bottom-left {
display: none !important;
}
.el-drawer__header {
margin-bottom: 0 !important;
}
.custom-tabs-label {
display: flex;
align-items: center;
justify-content: space-between;
}
.custom-tabs-label .el-icon {
margin-right: 5px;
}
.el-pagination__sizes .el-select {
min-width: 128px;
}
.el-collapse-item__content {
padding-bottom: 0;
}
.el-popup-parent--hidden {
width: 100% !important;
}

1
src/styles/element.min.css vendored Normal file
View File

@@ -0,0 +1 @@
.el-input .el-input__inner{height:30px;line-height:calc(var(--el-input-height, 40px) - 4px)}.datetime-picker{height:32px;padding-top:0;padding-bottom:0}.el-divider__text.is-center{transform:translateX(-50%) translateY(-62%)}.el-menu{user-select:none}.el-menu .el-menu-item:hover,.el-menu .el-sub-menu__title:hover{background-color:var(--el-menu-hover-color) !important;color:var(--el-menu-active-color) !important}.el-menu .el-menu-item:hover .icon,.el-menu .el-sub-menu__title:hover .icon{color:var(--el-menu-active-color) !important}.el-select{min-width:200px}.el-dialog{padding:0px !important}.el-dialog .el-dialog__footer{padding:15px;box-shadow:var(--el-box-shadow);width:100%;bottom:0}.el-dialog__body{max-height:60vh;overflow-y:auto;padding:10px}.el-dialog__header{background:var(--el-color-primary);padding:15px;margin-right:0px}.el-dialog__header .el-dialog__headerbtn{top:5px}.el-dialog__header .el-dialog__headerbtn .el-icon{color:var(--el-color-white)}.el-dialog__header .el-dialog__headerbtn:hover .el-icon{color:#409eff}.el-dialog__header .el-dialog__title{color:var(--el-color-white)}.el-table{--el-table-border-color:var(--ba-border-color)}.el-card{border:none}.el-card__header{border-bottom:1px solid var(--el-border-color-extra-light)}.el-textarea__inner{padding:5px 11px}.el-overlay-dialog,.el-tabs__content,.ba-scroll-style{scrollbar-width:none}.el-overlay-dialog::-webkit-scrollbar,.el-tabs__content::-webkit-scrollbar,.ba-scroll-style::-webkit-scrollbar{width:5px;height:5px}.el-overlay-dialog::-webkit-scrollbar-thumb,.el-tabs__content::-webkit-scrollbar-thumb,.ba-scroll-style::-webkit-scrollbar-thumb{background:#eaeaea;border-radius:var(--el-border-radius-base);box-shadow:none;-webkit-box-shadow:none;cursor:pointer !important}.el-overlay-dialog:hover::-webkit-scrollbar-thumb:hover,.el-tabs__content:hover::-webkit-scrollbar-thumb:hover,.ba-scroll-style:hover::-webkit-scrollbar-thumb:hover{background:#c8c9cc}.ba-input-item-radio{margin-bottom:10px}.ba-input-item-radio .el-radio-group .el-radio{margin-bottom:8px}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active{background:var(--el-color-primary);color:var(--el-color-white)}.el-tabs__header{margin-bottom:0}.el-form--inline .el-form-item{margin-bottom:10px}.el-tabs--border-card>.el-tabs__content{padding:10px}.el-page-header__header{line-height:32px}.el-page-header__header .el-page-header__content{font-size:14px;font-weight:700}.el-tabs__content{height:calc(100% - 40px)}.el-tabs__content .el-tab-pane{height:100%}.el-button--primary:focus{color:var(--el-color-white);outline:0}.el-button--primary:hover{color:var(--el-color-white);border-color:var(--el-color-primary-light-3);background-color:var(--el-color-primary-light-3);outline:0}.el-button.is-plain:focus{color:var(--el-button-text-color);border-color:var(--el-button-border-color)}.el-button.is-plain:hover{color:var(--el-color-primary);border-color:var(--el-color-primary)}.el-button.is-link:focus{color:var(--el-button-text-color)}.el-button.is-link:hover{color:var(--el-button-hover-link-text-color)}.el-button--primary.is-link:hover,.el-button--primary.is-plain:hover,.el-button--primary.is-text:hover{color:var(--el-color-primary-light-5);background-color:var(--el-color-primary-light-9) !important}.el-divider--horizontal{margin:15px 0}.el-step__title{cursor:pointer}.el-checkbox__input.is-disabled.is-checked .el-checkbox__inner,.el-radio__input.is-disabled.is-checked .el-radio__inner{background-color:var(--el-color-primary);opacity:0.6}.sgmap-ctrl-bottom-left{display:none !important}.el-drawer__header{margin-bottom:0 !important}.custom-tabs-label{display:flex;align-items:center;justify-content:space-between}.custom-tabs-label .el-icon{margin-right:5px}.el-pagination__sizes .el-select{min-width:128px}.el-collapse-item__content{padding-bottom:0}.el-popup-parent--hidden{width:100% !important}

View File

@@ -1,237 +1,238 @@
/* 修复 Chrome 浏览器输入框内选中字符行高异常的bug-s */ /* 修复 Chrome 浏览器输入框内选中字符行高异常的bug-s */
.el-input .el-input__inner { .el-input .el-input__inner {
height: 30px; height: 30px;
line-height: calc(var(--el-input-height, 40px) - 4px); line-height: calc(var(--el-input-height, 40px) - 4px);
} }
/* 修复 Chrome 浏览器输入框内选中字符行高异常的bug-e */ /* 修复 Chrome 浏览器输入框内选中字符行高异常的bug-e */
.datetime-picker { .datetime-picker {
height: 32px; height: 32px;
padding-top: 0; padding-top: 0;
padding-bottom: 0; padding-bottom: 0;
} }
.el-divider__text.is-center { .el-divider__text.is-center {
transform: translateX(-50%) translateY(-62%); transform: translateX(-50%) translateY(-62%);
} }
.el-menu { .el-menu {
user-select: none; user-select: none;
.el-menu-item:hover, .el-menu-item:hover,
.el-sub-menu__title:hover { .el-sub-menu__title:hover {
background-color: var(--el-menu-hover-color) !important; background-color: var(--el-menu-hover-color) !important;
color: var(--el-menu-active-color) !important; color: var(--el-menu-active-color) !important;
.icon { .icon {
color: var(--el-menu-active-color) !important; color: var(--el-menu-active-color) !important;
} }
} }
} }
.el-select { .el-select {
min-width: 200px; min-width: 200px;
} }
.el-dialog { .el-dialog {
padding: 0px !important; padding: 0px !important;
.el-dialog__footer { .el-dialog__footer {
padding: 15px; padding: 15px;
box-shadow: var(--el-box-shadow); box-shadow: var(--el-box-shadow);
width: 100%; width: 100%;
bottom: 0; bottom: 0;
} }
} }
.el-dialog__body { .el-dialog__body {
max-height: 60vh; max-height: 60vh;
overflow-y: auto; overflow-y: auto;
padding: 10px; padding: 10px;
} }
.el-dialog__header { .el-dialog__header {
background: var(--el-color-primary); background: var(--el-color-primary);
padding: 15px; padding: 15px;
margin-right: 0px; margin-right: 0px;
.el-dialog__headerbtn { .el-dialog__headerbtn {
top: 5px; top: 5px;
.el-icon { .el-icon {
color: var(--el-color-white); color: var(--el-color-white);
} }
} }
.el-dialog__headerbtn:hover { .el-dialog__headerbtn:hover {
.el-icon { .el-icon {
color: #409eff; color: #409eff;
} }
} }
.el-dialog__title { .el-dialog__title {
color: var(--el-color-white); color: var(--el-color-white);
} }
} }
.el-table { .el-table {
--el-table-border-color: var(--ba-border-color); --el-table-border-color: var(--ba-border-color);
} }
.el-card { .el-card {
border: none; border: none;
} }
.el-card__header { .el-card__header {
border-bottom: 1px solid var(--el-border-color-extra-light); border-bottom: 1px solid var(--el-border-color-extra-light);
} }
.el-textarea__inner { .el-textarea__inner {
padding: 5px 11px; padding: 5px 11px;
} }
/* dialog滚动条-s */ /* dialog滚动条-s */
.el-overlay-dialog, .el-overlay-dialog,
.el-tabs__content, .el-tabs__content,
.ba-scroll-style { .ba-scroll-style {
scrollbar-width: none; scrollbar-width: none;
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 5px; width: 5px;
height: 5px; height: 5px;
} }
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
background: #eaeaea; background: #eaeaea;
border-radius: var(--el-border-radius-base); border-radius: var(--el-border-radius-base);
box-shadow: none; box-shadow: none;
-webkit-box-shadow: none; -webkit-box-shadow: none;
} cursor: pointer !important;
}
&:hover {
&::-webkit-scrollbar-thumb:hover { &:hover {
background: #c8c9cc; &::-webkit-scrollbar-thumb:hover {
} background: #c8c9cc;
} }
} }
}
/* dialog滚动条-e */
/* dialog滚动条-e */
/* 小屏设备 el-radio-group 样式优化-s */
.ba-input-item-radio { /* 小屏设备 el-radio-group 样式优化-s */
margin-bottom: 10px; .ba-input-item-radio {
margin-bottom: 10px;
.el-radio-group {
.el-radio { .el-radio-group {
margin-bottom: 8px; .el-radio {
} margin-bottom: 8px;
} }
} }
}
/* 小屏设备 el-radio-group 样式调整-e */
/* 小屏设备 el-radio-group 样式调整-e */
.el-tabs--card > .el-tabs__header .el-tabs__item.is-active {
background: var(--el-color-primary); .el-tabs--card > .el-tabs__header .el-tabs__item.is-active {
color: var(--el-color-white); background: var(--el-color-primary);
} color: var(--el-color-white);
}
.el-tabs__header {
margin-bottom: 0; .el-tabs__header {
} margin-bottom: 0;
}
.el-form--inline .el-form-item {
margin-bottom: 10px; .el-form--inline .el-form-item {
} margin-bottom: 10px;
}
.el-tabs--border-card > .el-tabs__content {
padding: 10px; .el-tabs--border-card > .el-tabs__content {
} padding: 10px;
}
.el-page-header__header {
line-height: 32px; .el-page-header__header {
.el-page-header__content { line-height: 32px;
font-size: 14px; .el-page-header__content {
font-weight: 700; font-size: 14px;
} font-weight: 700;
} }
}
.el-tabs__content {
height: calc(100% - 40px); .el-tabs__content {
.el-tab-pane { height: calc(100% - 40px);
height: 100%; .el-tab-pane {
} height: 100%;
} }
}
.el-button--primary:focus {
color: var(--el-color-white); .el-button--primary:focus {
// border-color: var(--el-color-primary); color: var(--el-color-white);
// background-color: var(--el-color-primary); // border-color: var(--el-color-primary);
outline: 0; // background-color: var(--el-color-primary);
} outline: 0;
.el-button--primary:hover { }
color: var(--el-color-white); .el-button--primary:hover {
border-color: var(--el-color-primary-light-3); color: var(--el-color-white);
background-color: var(--el-color-primary-light-3); border-color: var(--el-color-primary-light-3);
outline: 0; background-color: var(--el-color-primary-light-3);
} outline: 0;
.el-button.is-plain:focus { }
color: var(--el-button-text-color); .el-button.is-plain:focus {
border-color: var(--el-button-border-color); color: var(--el-button-text-color);
} border-color: var(--el-button-border-color);
.el-button.is-plain:hover { }
color: var(--el-color-primary); .el-button.is-plain:hover {
border-color: var(--el-color-primary); color: var(--el-color-primary);
} border-color: var(--el-color-primary);
.el-button.is-link:focus { }
color: var(--el-button-text-color); .el-button.is-link:focus {
} color: var(--el-button-text-color);
.el-button.is-link:hover { }
color: var(--el-button-hover-link-text-color); .el-button.is-link:hover {
} color: var(--el-button-hover-link-text-color);
.el-button--primary.is-link:hover, }
.el-button--primary.is-plain:hover, .el-button--primary.is-link:hover,
.el-button--primary.is-text:hover { .el-button--primary.is-plain:hover,
color: var(--el-color-primary-light-5); .el-button--primary.is-text:hover {
background-color: var(--el-color-primary-light-9) !important; color: var(--el-color-primary-light-5);
} background-color: var(--el-color-primary-light-9) !important;
}
.el-divider--horizontal {
margin: 15px 0; .el-divider--horizontal {
} margin: 15px 0;
.el-step__title { }
cursor: pointer; .el-step__title {
} cursor: pointer;
}
.el-checkbox__input.is-disabled.is-checked .el-checkbox__inner,
.el-radio__input.is-disabled.is-checked .el-radio__inner { .el-checkbox__input.is-disabled.is-checked .el-checkbox__inner,
background-color: var(--el-color-primary); .el-radio__input.is-disabled.is-checked .el-radio__inner {
opacity: 0.6; background-color: var(--el-color-primary);
} opacity: 0.6;
}
.sgmap-ctrl-bottom-left {
display: none !important; .sgmap-ctrl-bottom-left {
} display: none !important;
}
.el-drawer__header {
margin-bottom: 0 !important; .el-drawer__header {
} margin-bottom: 0 !important;
//tabs 添加icon样式 }
.custom-tabs-label { //tabs 添加icon样式
display: flex; .custom-tabs-label {
align-items: center; display: flex;
justify-content: space-between; align-items: center;
.el-icon { justify-content: space-between;
margin-right: 5px; .el-icon {
} margin-right: 5px;
} }
.el-pagination__sizes { }
.el-select { .el-pagination__sizes {
min-width: 128px; .el-select {
} min-width: 128px;
} }
}
.el-collapse-item__content{
padding-bottom: 0; .el-collapse-item__content{
} padding-bottom: 0;
}
//解决打开dialog body容器宽度变为 calc(100% - 8px)问题
.el-popup-parent--hidden{ //解决打开dialog body容器宽度变为 calc(100% - 8px)问题
width: 100% !important; .el-popup-parent--hidden{
width: 100% !important;
} }

View File

@@ -0,0 +1,6 @@
@import './variables.scss';
// 导出变量
:export {
namespace: $namespace;
elNamespace: $elNamespace;
}

4043
src/styles/mixins.css Normal file

File diff suppressed because it is too large Load Diff

1
src/styles/mixins.min.css vendored Normal file

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More