|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"tabWidth": 4,
|
||||
"singleQuote": true,
|
||||
"semi": false,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "all",
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
75
README.md
|
|
@ -0,0 +1,75 @@
|
|||
# 博诺思管理系统
|
||||
|
||||
基于若依框架和 Element Plus 组件库构建的企业级组件管理系统。
|
||||
|
||||
## 环境要求
|
||||
|
||||
- Node.js 18.0 或更高版本
|
||||
- npm 或 yarn
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 安装依赖
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### 2. 启动开发服务器
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
项目将在 `http://localhost:80` 启动
|
||||
|
||||
## 项目说明
|
||||
|
||||
### Mock 数据
|
||||
|
||||
项目已集成 Mock 数据模拟,无需后端即可进行开发和测试:
|
||||
|
||||
- **登录接口**:任意用户名密码即可登录(验证码输入任意 4 位数字)
|
||||
- **菜单数据**:自动加载模拟菜单数据
|
||||
- Mock 数据位于 `mock/` 目录
|
||||
|
||||
### 组件库
|
||||
|
||||
基于 Element Plus 进行二次封装,当前已封装:
|
||||
|
||||
- **ComButton**:按钮组件(示例)
|
||||
- 更多组件持续封装中...
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
# 开发环境
|
||||
npm run dev
|
||||
|
||||
# 生产构建
|
||||
npm run build:prod
|
||||
|
||||
# 预览构建结果
|
||||
npm run preview
|
||||
```
|
||||
|
||||
## 技术栈
|
||||
|
||||
- Vue 3 + Vite
|
||||
- Element Plus
|
||||
- Pinia
|
||||
- Vue Router
|
||||
- Axios
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
├── mock/ # Mock 数据
|
||||
├── src/
|
||||
│ ├── api/ # API 接口
|
||||
│ ├── components/ # 公共组件
|
||||
│ ├── views/ # 页面视图
|
||||
│ ├── router/ # 路由配置
|
||||
│ └── store/ # 状态管理
|
||||
└── vite.config.js # Vite 配置
|
||||
```
|
||||
|
|
@ -23,7 +23,37 @@
|
|||
"name": "showText",
|
||||
"meta": {
|
||||
"title": "文本组件",
|
||||
"icon": "text"
|
||||
"icon": "table"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ShowTable",
|
||||
"path": "showTable",
|
||||
"hidden": false,
|
||||
"component": "showComponents/showTable/index",
|
||||
"meta": {
|
||||
"title": "表格组件",
|
||||
"icon": "table",
|
||||
"noCache": false,
|
||||
"link": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "showMap",
|
||||
"component": "showComponents/showMap/index",
|
||||
"name": "showMap",
|
||||
"meta": {
|
||||
"title": "地图组件",
|
||||
"icon": "table"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "showDialog",
|
||||
"component": "showComponents/showDialog/index",
|
||||
"name": "showDialog",
|
||||
"meta": {
|
||||
"title": "弹窗组件",
|
||||
"icon": "table"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "2.3.1",
|
||||
"@turf/turf": "^7.3.1",
|
||||
"@vueup/vue-quill": "1.2.0",
|
||||
"@vueuse/core": "13.3.0",
|
||||
"axios": "1.9.0",
|
||||
|
|
@ -29,22 +30,27 @@
|
|||
"js-cookie": "3.0.5",
|
||||
"jsencrypt": "3.3.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mars3d": "^3.10.11",
|
||||
"mars3d-cesium": "^1.136.0",
|
||||
"nprogress": "0.2.0",
|
||||
"pinia": "3.0.2",
|
||||
"splitpanes": "4.0.4",
|
||||
"vue": "3.5.16",
|
||||
"vue-cropper": "1.1.1",
|
||||
"vue-draggable-plus": "^0.6.0",
|
||||
"vue-router": "4.5.1",
|
||||
"vuedraggable": "4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "5.2.4",
|
||||
"less": "^4.5.1",
|
||||
"mockjs": "^1.1.0",
|
||||
"sass-embedded": "1.89.1",
|
||||
"unplugin-auto-import": "0.18.6",
|
||||
"unplugin-vue-setup-extend-plus": "1.0.1",
|
||||
"vite": "6.3.5",
|
||||
"vite-plugin-compression": "0.5.1",
|
||||
"vite-plugin-mars3d": "^4.2.2",
|
||||
"vite-plugin-mock": "^3.0.2",
|
||||
"vite-plugin-svg-icons": "2.0.1"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="800.000000" height="800.000000" viewBox="0 0 800.000000 800.000000" preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<rdf:Description dc:format="image/svg+xml" dc:Label="1" dc:ContentProducer="001191330110MACRLGPT8B00000" dc:ProduceID="315971907" dc:ReservedCode1="jA7ILZwlq0Mdcs79qlecSqexLoT20IvdYh3YS5kXY7g=" dc:ContentPropagator="001191330110MACRLGPT8B00000" dc:PropagateID="315971907" dc:ReservedCode2="jA7ILZwlq0Mdcs79qlecSqexLoT20IvdYh3YS5kXY7g="/>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g transform="translate(-160.689798,962.759061) scale(0.139993,-0.139993)" fill="#000000" stroke="none">
|
||||
<path d="M2669 5489 c-31 -33 -39 -82 -19 -120 28 -51 -30 -49 1355 -49 1385 0 1327 -2 1355 49 20 39 12 91 -19 122 l-29 29 -1307 0 -1307 0 -29 -31z"/>
|
||||
<path d="M3772 4868 c-107 -106 -200 -207 -207 -223 -18 -41 -8 -80 27 -110 56 -47 78 -38 205 87 l113 112 0 -719 0 -720 -115 115 c-104 104 -117 114 -150 113 -41 -1 -60 -14 -82 -56 -28 -56 -15 -77 184 -284 103 -107 201 -202 216 -210 60 -31 83 -16 266 170 93 95 183 189 200 211 39 49 41 93 5 135 -21 25 -33 31 -67 31 -40 0 -46 -4 -149 -112 l-108 -113 0 715 0 715 108 -108 c123 -122 145 -131 204 -78 26 23 33 36 33 69 0 38 -9 48 -152 198 -228 240 -244 254 -294 254 -42 0 -46 -3 -237 -192z"/>
|
||||
<path d="M2667 2692 c-52 -54 -29 -142 42 -162 47 -13 2559 -14 2594 -1 71 27 91 114 39 164 l-28 27 -1310 0 -1310 0 -27 -28z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
|
@ -13,88 +13,90 @@
|
|||
:disabled="disabled"
|
||||
:autofocus="autofocus"
|
||||
@click="onButtonClick"
|
||||
v-bind="omit(attrs, ['onClick'])">
|
||||
v-bind="omit(attrs, ['onClick'])"
|
||||
>
|
||||
<slot></slot>
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<script setup name="ComButton">
|
||||
import { ref, useAttrs } from "vue";
|
||||
import { omit } from "lodash-es"; // 忽略属性
|
||||
const loading = ref(false); // 按钮加载状态
|
||||
const attrs = useAttrs(); // 获取按钮属性
|
||||
import { ref, useAttrs } from 'vue'
|
||||
import { omit } from 'lodash-es' // 忽略属性
|
||||
const loading = ref(false) // 按钮加载状态
|
||||
const attrs = useAttrs() // 获取按钮属性
|
||||
|
||||
// 不继承属性
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
// 不继承属性
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
// 定义按钮属性
|
||||
defineProps({
|
||||
// 按钮类型
|
||||
type: {
|
||||
type: String,
|
||||
default: "primary",
|
||||
},
|
||||
// 按钮图标
|
||||
icon: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
// 按钮大小
|
||||
size: {
|
||||
type: String,
|
||||
default: "default",
|
||||
},
|
||||
// 按钮是否链接
|
||||
link: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 样式
|
||||
style: {
|
||||
type: Object,
|
||||
default: {},
|
||||
},
|
||||
// 按钮是否圆角
|
||||
round: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 按钮是否朴素
|
||||
plain: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 按钮是否圆形
|
||||
circle: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 按钮是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 按钮是否自动聚焦
|
||||
autofocus: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
// 定义按钮属性
|
||||
defineProps({
|
||||
// 按钮类型
|
||||
type: {
|
||||
type: String,
|
||||
default: 'primary',
|
||||
},
|
||||
// 按钮图标
|
||||
|
||||
// 按钮点击事件
|
||||
const onButtonClick = async () => {
|
||||
// 设置按钮loading
|
||||
loading.value = true;
|
||||
try {
|
||||
// 执行父组件按钮点击事件
|
||||
await attrs.onClick?.();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
// 重置按钮loading
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
icon: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
// 按钮大小
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
// 按钮是否链接
|
||||
link: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 样式
|
||||
style: {
|
||||
type: Object,
|
||||
default: {},
|
||||
},
|
||||
// 按钮是否圆角
|
||||
round: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 按钮是否朴素
|
||||
plain: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 按钮是否圆形
|
||||
circle: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 按钮是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 按钮是否自动聚焦
|
||||
autofocus: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 按钮点击事件
|
||||
const onButtonClick = async () => {
|
||||
// 设置按钮loading
|
||||
loading.value = true
|
||||
try {
|
||||
// 执行父组件按钮点击事件
|
||||
await attrs.onClick?.()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
// 重置按钮loading
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,239 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="列设置"
|
||||
width="600px"
|
||||
:append-to-body="true"
|
||||
@close="handleClose"
|
||||
>
|
||||
<div class="column-setting">
|
||||
<div class="column-setting-header">
|
||||
<el-checkbox
|
||||
:indeterminate="isIndeterminate"
|
||||
:model-value="isAllChecked"
|
||||
@change="handleToggleAll"
|
||||
>
|
||||
列展示
|
||||
</el-checkbox>
|
||||
<el-button type="primary" link @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
<div class="column-list">
|
||||
<draggable
|
||||
v-model="localColumns"
|
||||
:animation="200"
|
||||
handle=".drag-handle"
|
||||
item-key="prop"
|
||||
@end="handleDragEnd"
|
||||
>
|
||||
<template #item="{ element: column }">
|
||||
<div
|
||||
class="column-item"
|
||||
:class="{
|
||||
'is-fixed-left': column.fixed === 'left',
|
||||
'is-fixed-right': column.fixed === 'right',
|
||||
}"
|
||||
>
|
||||
<div class="column-item-left">
|
||||
<el-icon class="drag-handle"><Sort /></el-icon>
|
||||
<el-checkbox v-model="column.visible" @change="handleColumnChange">
|
||||
{{ column.label }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
<div class="column-item-right">
|
||||
<el-button
|
||||
:type="column.fixed === 'left' ? 'primary' : 'info'"
|
||||
:plain="column.fixed !== 'left'"
|
||||
link
|
||||
size="small"
|
||||
@click="handleFixColumn(column, 'left')"
|
||||
>
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
</el-button>
|
||||
<el-button
|
||||
:type="column.fixed === 'right' ? 'primary' : 'info'"
|
||||
:plain="column.fixed !== 'right'"
|
||||
link
|
||||
size="small"
|
||||
@click="handleFixColumn(column, 'right')"
|
||||
>
|
||||
<el-icon><ArrowRight /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleConfirm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import draggable from 'vuedraggable'
|
||||
import { ArrowLeft, ArrowRight, Sort } from '@element-plus/icons-vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'confirm'])
|
||||
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
})
|
||||
|
||||
const localColumns = ref([])
|
||||
const originalColumns = ref([])
|
||||
|
||||
// 初始化列数据
|
||||
const initColumns = () => {
|
||||
localColumns.value = props.columns.map((col) => ({
|
||||
...col,
|
||||
visible: col.visible !== false,
|
||||
fixed: col.fixed || false,
|
||||
}))
|
||||
originalColumns.value = JSON.parse(JSON.stringify(localColumns.value))
|
||||
}
|
||||
|
||||
// 全选状态
|
||||
const isAllChecked = computed(() => {
|
||||
return localColumns.value.length > 0 && localColumns.value.every((col) => col.visible)
|
||||
})
|
||||
|
||||
// 半选状态
|
||||
const isIndeterminate = computed(() => {
|
||||
const checkedCount = localColumns.value.filter((col) => col.visible).length
|
||||
return checkedCount > 0 && checkedCount < localColumns.value.length
|
||||
})
|
||||
|
||||
// 切换全选
|
||||
const handleToggleAll = (val) => {
|
||||
localColumns.value.forEach((col) => {
|
||||
col.visible = val
|
||||
})
|
||||
}
|
||||
|
||||
// 列变化
|
||||
const handleColumnChange = () => {
|
||||
// 可以在这里添加逻辑
|
||||
}
|
||||
|
||||
// 固定列
|
||||
const handleFixColumn = (column, position) => {
|
||||
if (column.fixed === position) {
|
||||
column.fixed = false
|
||||
} else {
|
||||
column.fixed = position
|
||||
}
|
||||
}
|
||||
|
||||
// 拖拽结束
|
||||
const handleDragEnd = () => {
|
||||
// 拖拽后的顺序已经在 localColumns 中更新
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
localColumns.value = JSON.parse(JSON.stringify(originalColumns.value))
|
||||
}
|
||||
|
||||
// 确认
|
||||
const handleConfirm = () => {
|
||||
emit('confirm', localColumns.value)
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
// 关闭
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
// 监听 visible 变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
if (val) {
|
||||
initColumns()
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.column-setting {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.column-setting-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.column-list {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.column-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
margin-bottom: 8px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.column-item:hover {
|
||||
background: #ecf5ff;
|
||||
}
|
||||
|
||||
.column-item.is-fixed-left {
|
||||
border-left: 3px solid #409eff;
|
||||
}
|
||||
|
||||
.column-item.is-fixed-right {
|
||||
border-right: 3px solid #409eff;
|
||||
}
|
||||
|
||||
.column-item-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
cursor: move;
|
||||
color: #909399;
|
||||
font-size: 16px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.drag-handle:hover {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.column-item-right {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
<template>
|
||||
<!-- 通用弹框组件(支持外层 + 内层) -->
|
||||
<div class="com-dialog">
|
||||
<!-- 外层弹框 -->
|
||||
<el-dialog
|
||||
v-if="dialogConfig.outerVisible"
|
||||
v-model="dialogConfig.outerVisible"
|
||||
:title="dialogConfig.outerTitle"
|
||||
:width="dialogConfig.outerWidth || '720px'"
|
||||
:before-close="handleCloseOuter"
|
||||
:append-to-body="true"
|
||||
:close-on-click-modal="false"
|
||||
:style="{
|
||||
'--com-dialog-min-height': dialogConfig.minHeight || '320px',
|
||||
'--com-dialog-max-height': dialogConfig.maxHeight || '80vh',
|
||||
}"
|
||||
class="com-dialog__outer"
|
||||
>
|
||||
<!-- 外层弹框内容插槽 -->
|
||||
<slot name="outerContent" />
|
||||
|
||||
<!-- 内层弹框 -->
|
||||
<el-dialog
|
||||
v-if="dialogConfig.innerVisible"
|
||||
v-model="dialogConfig.innerVisible"
|
||||
:title="dialogConfig.innerTitle"
|
||||
:width="dialogConfig.innerWidth || '640px'"
|
||||
:before-close="handleCloseInner"
|
||||
:append-to-body="true"
|
||||
:close-on-click-modal="false"
|
||||
:style="{
|
||||
'--com-dialog-min-height': dialogConfig.innerMinHeight || '260px',
|
||||
'--com-dialog-max-height': dialogConfig.innerMaxHeight || '70vh',
|
||||
}"
|
||||
class="com-dialog__inner"
|
||||
>
|
||||
<!-- 内层弹框内容插槽 -->
|
||||
<slot name="innerContent" />
|
||||
</el-dialog>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
// 弹框配置对象
|
||||
dialogConfig: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['closeDialogOuter', 'closeDialogInner'])
|
||||
|
||||
// 右上角关闭外层
|
||||
const handleCloseOuter = () => {
|
||||
emit('closeDialogOuter', false)
|
||||
}
|
||||
|
||||
// 右上角关闭内层
|
||||
const handleCloseInner = () => {
|
||||
emit('closeDialogInner', false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* 全局样式:仅作用于带 com-dialog__outer / com-dialog__inner 类名的弹框 */
|
||||
|
||||
/* 外层 + 内层弹框通用外观 */
|
||||
.el-dialog:not(.is-fullscreen) {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
.com-dialog__outer,
|
||||
.com-dialog__inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 !important;
|
||||
position: absolute;
|
||||
top: 50% !important;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) !important;
|
||||
min-height: var(--com-dialog-min-height);
|
||||
max-height: var(--com-dialog-max-height);
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 22px 60px rgba(15, 23, 42, 0.35);
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
/* 头部区域:浅色背景 + 左侧色条 */
|
||||
.com-dialog__outer .el-dialog__header,
|
||||
.com-dialog__inner .el-dialog__header {
|
||||
position: relative;
|
||||
padding: 12px 18px;
|
||||
margin: 0;
|
||||
border-bottom: 1px solid #edf0f5;
|
||||
background: #f8f9fd;
|
||||
color: #1f2937;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.com-dialog__outer .el-dialog__header::before,
|
||||
.com-dialog__inner .el-dialog__header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 3px;
|
||||
border-radius: 0 4px 4px 0;
|
||||
background: linear-gradient(180deg, #1677ff 0%, #69b1ff 100%);
|
||||
}
|
||||
|
||||
.com-dialog__outer .el-dialog__title,
|
||||
.com-dialog__inner .el-dialog__title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.com-dialog__outer .el-dialog__headerbtn,
|
||||
.com-dialog__inner .el-dialog__headerbtn {
|
||||
top: 4px;
|
||||
right: 16px;
|
||||
|
||||
.el-dialog__close {
|
||||
color: #4b5563; /* 默认深灰,保证在浅色标题背景下清晰可见 */
|
||||
transition: transform 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
&:hover .el-dialog__close {
|
||||
color: #111827;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.com-dialog__outer .el-dialog__body,
|
||||
.com-dialog__inner .el-dialog__body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 18px 20px 20px;
|
||||
box-sizing: border-box;
|
||||
background-color: #fafbff;
|
||||
}
|
||||
|
||||
/* 底部区域 */
|
||||
.com-dialog__outer .el-dialog__footer,
|
||||
.com-dialog__inner .el-dialog__footer {
|
||||
padding: 12px 20px 16px;
|
||||
border-top: 1px solid #eef1f6;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import * as mars3d from "mars3d"
|
||||
|
||||
// 通过Canvas绘制复杂或动态对象的图标点Graphic
|
||||
export class CanvasBillboard extends mars3d.graphic.BillboardPrimitive {
|
||||
/**
|
||||
* 文字
|
||||
* @type {string}
|
||||
*/
|
||||
get text() {
|
||||
return this.style.text
|
||||
}
|
||||
|
||||
set text(val) {
|
||||
this.style.text = val
|
||||
|
||||
this.label.text = val
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象添加到图层前创建一些对象的钩子方法,
|
||||
* 只会调用一次
|
||||
* @return {Promise<object>} 无
|
||||
* @private
|
||||
*/
|
||||
_addedHook(style) {
|
||||
style.image = "//data.mars3d.cn/img/marker/bg/textPnl.png"
|
||||
style.label = {
|
||||
...style,
|
||||
text: this.style.text,
|
||||
font_size: 55,
|
||||
color: style.textColor ?? "#ffffff",
|
||||
hasPixelOffset: true,
|
||||
pixelOffsetX: 0,
|
||||
pixelOffsetY: -36 * (style.scale ?? 1)
|
||||
}
|
||||
if (style.scaleByDistance) {
|
||||
style.label.pixelOffsetScaleByDistance = style.scaleByDistance
|
||||
}
|
||||
|
||||
super._addedHook(style)
|
||||
}
|
||||
}
|
||||
|
||||
// 注册下
|
||||
mars3d.GraphicUtil.register("canvasBillboard", CanvasBillboard)
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// 注册mars3d插件 (插件清单访问:http://mars3d.cn/dev/guide/start/architecture.html)
|
||||
// import "mars3d-space"
|
||||
// import "mars3d-heatmap"
|
||||
// import "mars3d-echarts"
|
||||
// import "mars3d-mapv"
|
||||
// import "mars3d-tdt"
|
||||
|
||||
// 注册mars3d继承的相关类
|
||||
import './task/CameraList.js'
|
||||
import './task/CameraView.js'
|
||||
import './task/MapRotate.js'
|
||||
import './task/PointRotate.js'
|
||||
import './task/RouteLine.js'
|
||||
import './task/ZoomIn.js'
|
||||
import './task/ZoomOut.js'
|
||||
import './task/FlickerEntity.js'
|
||||
|
||||
import './graphic/CanvasBillboard.js'
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import * as mars3d from "mars3d"
|
||||
|
||||
/**
|
||||
* 视角列表播放(分步执行)
|
||||
*
|
||||
* @param {object} [options] 参数对象,包括以下:
|
||||
* @param {string} [options.name] 标题名称
|
||||
* @param {string} [options.type] 类型标识,自动赋值的,无需手动传入
|
||||
* @param {number} [options.start] 开始时间,相当于map.clock.startTime的秒数
|
||||
* @param {number} [options.duration] 时长
|
||||
* @param {object[]} [options.list] 视角数组
|
||||
*/
|
||||
export class CameraList extends mars3d.TaskItem {
|
||||
// 进入,激活开始处理事务
|
||||
_activateWork() {
|
||||
this._map.setCameraViewList(this.options.list)
|
||||
}
|
||||
|
||||
// 暂停(非必须)
|
||||
_pauseWork() {
|
||||
this._disableWork()
|
||||
}
|
||||
}
|
||||
mars3d.thing.Task.register("cameraList", CameraList)
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import * as mars3d from "mars3d"
|
||||
|
||||
/**
|
||||
* 单个视角定位
|
||||
*
|
||||
* @param {object} [options] 参数对象,包括以下:
|
||||
* @param {string} [options.name] 标题名称
|
||||
* @param {string} [options.type] 类型标识,自动赋值的,无需手动传入
|
||||
* @param {number} [options.start] 开始时间,相当于map.clock.startTime的秒数
|
||||
* @param {number} [options.duration] 时长
|
||||
*
|
||||
* @param {object} [options.center] 视角参数
|
||||
*/
|
||||
export class CameraView extends mars3d.TaskItem {
|
||||
// 进入,激活开始处理事务
|
||||
_activateWork() {
|
||||
this._map.setCameraView(this.options.center, { duration: this._duration })
|
||||
}
|
||||
|
||||
// 离开,释放相关对象
|
||||
_disableWork() {
|
||||
this._map.cancelFlyTo()
|
||||
}
|
||||
}
|
||||
mars3d.thing.Task.register("camera", CameraView)
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import * as mars3d from "mars3d"
|
||||
|
||||
/**
|
||||
* 矢量对象高亮闪烁(仅Entity)
|
||||
*
|
||||
* @param {object} [options] 参数对象,包括以下:
|
||||
* @param {string} [options.name] 标题名称
|
||||
* @param {string} [options.type] 类型标识,自动赋值的,无需手动传入
|
||||
* @param {number} [options.start] 开始时间,相当于map.clock.startTime的秒数
|
||||
* @param {number} [options.duration] 时长
|
||||
*
|
||||
* @param {string|number} [options.layerId] 图层ID
|
||||
* @param {string|number} [options.graphicId] 矢量对象ID
|
||||
*
|
||||
* @param {number} [options.step=10] 闪烁增量, 控制速度
|
||||
* @param {string} [options.color] 高亮的颜色
|
||||
* @param {number} [options.maxAlpha=0.3] 闪烁的最大透明度,从 0 到 maxAlpha 渐变
|
||||
*/
|
||||
export class FlickerEntity extends mars3d.TaskItem {
|
||||
// 进入,激活开始处理事务
|
||||
_activateWork() {
|
||||
const layer = this._map.getLayerById(this.options.layerId)
|
||||
if (layer) {
|
||||
layer.show = true
|
||||
layer.readyPromise.then(() => {
|
||||
this._graphic = layer.getGraphicById(this.options.graphicId)
|
||||
if (this._graphic) {
|
||||
this._graphic.show = true
|
||||
this._graphic.startFlicker({
|
||||
time: this._duration,
|
||||
step: this.options.step,
|
||||
maxAlpha: this.options.maxAlpha,
|
||||
color: this.options.color
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 离开,释放相关对象
|
||||
_disableWork() {
|
||||
if (this._graphic) {
|
||||
this._graphic.stopFlicker()
|
||||
}
|
||||
}
|
||||
}
|
||||
mars3d.thing.Task.register("flickerEntity", FlickerEntity)
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import * as mars3d from "mars3d"
|
||||
|
||||
/**
|
||||
* 地球自旋转
|
||||
*
|
||||
* @param {object} [options] 参数对象,包括以下:
|
||||
* @param {string} [options.name] 标题名称
|
||||
* @param {string} [options.type] 类型标识,自动赋值的,无需手动传入
|
||||
* @param {number} [options.start] 开始时间,相当于map.clock.startTime的秒数
|
||||
* @param {number} [options.duration] 时长
|
||||
*
|
||||
* @param {object} [options.center] 初始视角
|
||||
* @param {number} [options.speed] 旋转速度
|
||||
*/
|
||||
export class MapRotate extends mars3d.TaskItem {
|
||||
constructor(options = {}) {
|
||||
super(options)
|
||||
|
||||
this._speed = this.options.speed || 0.01
|
||||
this._center = this.options.center || { lat: 29.093038, lng: 108.804459, alt: 23321232.7, heading: 0, pitch: -90 }
|
||||
}
|
||||
|
||||
// 进入,激活开始处理事务
|
||||
_activateWork() {
|
||||
this._map.setCameraView(this._center, {
|
||||
duration: 1,
|
||||
complete: () => {
|
||||
this._map.on(mars3d.EventType.clockTick, this._map_onClockTick, this)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 离开,释放相关对象
|
||||
_disableWork() {
|
||||
this._map.off(mars3d.EventType.clockTick, this._map_onClockTick, this)
|
||||
}
|
||||
|
||||
_map_onClockTick() {
|
||||
if (this.isPause) {
|
||||
return // 暂停时不执行
|
||||
}
|
||||
|
||||
this._map.scene.camera.rotate(mars3d.Cesium.Cartesian3.UNIT_Z, this._speed)
|
||||
}
|
||||
}
|
||||
mars3d.thing.Task.register("mapRotate", MapRotate)
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import * as mars3d from "mars3d"
|
||||
|
||||
/**
|
||||
* 内或外旋转
|
||||
* @param {object} [options] 参数对象,包括以下:
|
||||
* @param {string} [options.name] 标题名称
|
||||
* @param {string} [options.type] 类型标识,自动赋值的,无需手动传入
|
||||
* @param {number} [options.start] 开始时间,相当于map.clock.startTime的秒数
|
||||
* @param {number} [options.duration] 时长
|
||||
*
|
||||
* @param {boolean} [options.isRotateOut] true:绕外旋转 ,false:绕内旋转
|
||||
* @param {boolean} [options.direction=false] 旋转方向, true逆时针,false顺时针
|
||||
* @param {number} [options.time=60] 飞行一周所需时间(单位 秒),控制速度
|
||||
* @param {boolean} [options.autoStop] 是否自动停止
|
||||
* @param {number} [options.autoStopAngle] 自动停止的角度值(0-360度),未设置时不自动停止
|
||||
* @param {object} [options.point] 绕点旋转对应的中心点位置
|
||||
*/
|
||||
export class PointRotate extends mars3d.TaskItem {
|
||||
// 进入,激活开始处理事务
|
||||
_activateWork() {
|
||||
if (this.options.center) {
|
||||
this._map.setCameraView(this.options.center, { duration: 0 })
|
||||
}
|
||||
|
||||
if (this.options.autoStop) {
|
||||
delete this.options.autoStopAngle // 是否自动停止
|
||||
}
|
||||
|
||||
if (this.options.isRotateOut) {
|
||||
this._rotateOut = new mars3d.thing.RotateOut(this.options)
|
||||
this._map.addThing(this._rotateOut)
|
||||
} else {
|
||||
this._rotatePoint = new mars3d.thing.RotatePoint(this.options)
|
||||
this._map.addThing(this._rotatePoint)
|
||||
}
|
||||
|
||||
if (this.options.isRotateOut) {
|
||||
this._rotateOut.start()
|
||||
} else {
|
||||
this._rotatePoint.start(this.options.point)
|
||||
}
|
||||
}
|
||||
|
||||
// 离开,释放相关对象
|
||||
_disableWork() {
|
||||
if (this._rotatePoint) {
|
||||
this._rotatePoint.stop()
|
||||
this._rotatePoint.destroy()
|
||||
delete this._rotatePoint
|
||||
}
|
||||
if (this._rotateOut) {
|
||||
this._rotateOut.stop()
|
||||
this._rotateOut.destroy()
|
||||
delete this._rotateOut
|
||||
}
|
||||
}
|
||||
}
|
||||
mars3d.thing.Task.register("pointRotate", PointRotate)
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import * as mars3d from "mars3d"
|
||||
|
||||
/**
|
||||
* 按路线漫游
|
||||
*
|
||||
* @param {object} [options] 参数对象,包括以下:
|
||||
* @param {string} [options.name] 标题名称
|
||||
* @param {string} [options.type] 类型标识,自动赋值的,无需手动传入
|
||||
* @param {number} [options.start] 开始时间,相当于map.clock.startTime的秒数
|
||||
* @param {number} [options.duration] 时长
|
||||
*
|
||||
* @param {object} [options.route] FixedRoute对应的构造参数
|
||||
*/
|
||||
export class RouteLine extends mars3d.TaskItem {
|
||||
// constructor(options) {
|
||||
// super(options)
|
||||
// }
|
||||
|
||||
// 进入,激活开始处理事务
|
||||
_activateWork() {
|
||||
this._graphicLayer = new mars3d.layer.GraphicLayer()
|
||||
this._map.addLayer(this._graphicLayer)
|
||||
|
||||
const fixedRoute = new mars3d.graphic.FixedRoute(this.options.route)
|
||||
this._graphicLayer.addGraphic(fixedRoute)
|
||||
this._fixedRoute = fixedRoute
|
||||
|
||||
fixedRoute.start()
|
||||
}
|
||||
|
||||
// 暂停(非必须)
|
||||
_pauseWork(options) {
|
||||
if (this._fixedRoute) {
|
||||
this._fixedRoute.pause()
|
||||
}
|
||||
}
|
||||
|
||||
// 继续(非必须)
|
||||
_proceedWork() {
|
||||
if (this._fixedRoute) {
|
||||
this._fixedRoute.proceed()
|
||||
}
|
||||
}
|
||||
|
||||
// 离开,释放相关对象
|
||||
_disableWork() {
|
||||
if (this._fixedRoute) {
|
||||
this._fixedRoute.stop()
|
||||
delete this._fixedRoute
|
||||
}
|
||||
|
||||
if (this._graphicLayer) {
|
||||
this._graphicLayer.destroy()
|
||||
delete this._graphicLayer
|
||||
}
|
||||
}
|
||||
}
|
||||
mars3d.thing.Task.register("routeLine", RouteLine)
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import * as mars3d from "mars3d"
|
||||
|
||||
/**
|
||||
* 放大地图
|
||||
*
|
||||
* @param {object} [options] 参数对象,包括以下:
|
||||
* @param {string} [options.name] 标题名称
|
||||
* @param {string} [options.type] 类型标识,自动赋值的,无需手动传入
|
||||
* @param {number} [options.start] 开始时间,相当于map.clock.startTime的秒数
|
||||
* @param {number} [options.duration] 时长
|
||||
*
|
||||
* @param {number} [options.relativeAmount=2] 相对量
|
||||
*/
|
||||
export class ZoomIn extends mars3d.TaskItem {
|
||||
// 进入,激活开始处理事务
|
||||
_activateWork() {
|
||||
this._map.zoomIn(this.options.relativeAmount)
|
||||
}
|
||||
}
|
||||
mars3d.thing.Task.register("zoomIn", ZoomIn)
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import * as mars3d from "mars3d"
|
||||
|
||||
/**
|
||||
* 缩小地图
|
||||
*
|
||||
* @param {object} [options] 参数对象,包括以下:
|
||||
* @param {string} [options.name] 标题名称
|
||||
* @param {string} [options.type] 类型标识,自动赋值的,无需手动传入
|
||||
* @param {number} [options.start] 开始时间,相当于map.clock.startTime的秒数
|
||||
* @param {number} [options.duration] 时长
|
||||
*
|
||||
* @param {number} [options.relativeAmount=2] 相对量
|
||||
*/
|
||||
export class ZoomOut extends mars3d.TaskItem {
|
||||
// 进入,激活开始处理事务
|
||||
_activateWork() {
|
||||
this._map.zoomOut(this.options.relativeAmount)
|
||||
}
|
||||
}
|
||||
mars3d.thing.Task.register("zoomOut", ZoomOut)
|
||||
|
|
@ -0,0 +1,434 @@
|
|||
<template>
|
||||
<div :id="withKeyId" class="mars3d-container"></div>
|
||||
</template>
|
||||
<script setup>
|
||||
import * as mars3d from 'mars3d'
|
||||
import './expand/index' // 引入插件或注册扩展js
|
||||
|
||||
import { computed, onUnmounted, onMounted, toRaw } from 'vue'
|
||||
// import { $alert, $message } from "@mars/components/mars-ui/index"
|
||||
|
||||
const props = defineProps({
|
||||
mapKey: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
}, // 多个地图时,可传入key区分地图
|
||||
url: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
}, // 传入的地图构造参数url,可为空,只传options
|
||||
options: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
}, // 传入的地图构造参数options,可覆盖url内的参数
|
||||
})
|
||||
|
||||
// 用于存放地球组件实例
|
||||
let map // 地图对象
|
||||
|
||||
// 使用用户传入的 mapKey 拼接生成 withKeyId 作为当前显示容器的id
|
||||
const withKeyId = computed(() => `mars3d-container-${props.mapKey}`)
|
||||
|
||||
// onload事件将在地图渲染后触发
|
||||
const emit = defineEmits(['onload'])
|
||||
|
||||
const initMars3d = async () => {
|
||||
// 获取配置
|
||||
let mapOptions
|
||||
if (props.url) {
|
||||
// 存在url时才读取
|
||||
mapOptions = await mars3d.Util.fetchJson({ url: props.url })
|
||||
if (props.options) {
|
||||
mapOptions = mars3d.Util.merge(mapOptions, toRaw(props.options)) // 合并配置
|
||||
}
|
||||
} else if (props.options) {
|
||||
mapOptions = toRaw(props.options)
|
||||
}
|
||||
console.log('Map地图构造参数', mapOptions)
|
||||
|
||||
map = new mars3d.Map(withKeyId.value, mapOptions)
|
||||
|
||||
map.flyToPoint([106.898417, 33.685833], {
|
||||
radius: 5000, // 可视范围半径(米)
|
||||
duration: 3, // 飞行时间(秒)
|
||||
heading: 0, // 视角方向(0-360度)
|
||||
pitch: -45, // 俯仰角度(-90俯视,0平视,90仰视)
|
||||
})
|
||||
|
||||
// 针对不同终端的优化配置
|
||||
if (mars3d.Util.isPCBroswer()) {
|
||||
map.zoomFactor = 2.0 // 鼠标滚轮放大的步长参数
|
||||
|
||||
// IE浏览器优化
|
||||
if (window.navigator.userAgent.toLowerCase().indexOf('msie') >= 0) {
|
||||
map.viewer.targetFrameRate = 20 // 限制帧率
|
||||
map.scene.requestRenderMode = false // 取消实时渲染
|
||||
}
|
||||
} else {
|
||||
map.zoomFactor = 5.0 // 鼠标滚轮放大的步长参数
|
||||
|
||||
// 移动设备上禁掉以下几个选项,可以相对更加流畅
|
||||
map.scene.requestRenderMode = false // 取消实时渲染
|
||||
map.scene.fog.enabled = false
|
||||
map.scene.skyAtmosphere.show = false
|
||||
map.scene.globe.showGroundAtmosphere = false
|
||||
}
|
||||
|
||||
// 二三维切换不用动画
|
||||
if (map.viewer.sceneModePicker) {
|
||||
map.viewer.sceneModePicker.viewModel.duration = 0.0
|
||||
}
|
||||
|
||||
// 绑定当前项目的默认右键菜单
|
||||
// map.bindContextMenu(getContextMenu())
|
||||
|
||||
// webgl渲染失败后,刷新页面
|
||||
// map.on(mars3d.EventType.renderError, async () => {
|
||||
// await $alert("程序内存消耗过大,请重启浏览器")
|
||||
// window.location.reload()
|
||||
// })
|
||||
|
||||
onMapLoad() // map构造完成后的一些处理
|
||||
|
||||
emit('onload', map)
|
||||
}
|
||||
|
||||
// map构造完成后的一些处理,可以按需注释和选用
|
||||
function onMapLoad() {
|
||||
// Mars3D地图内部使用,如右键菜单弹窗
|
||||
// window.globalAlert = $alert;
|
||||
// window.globalMsg = $message;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initMars3d()
|
||||
})
|
||||
// 组件卸载之前销毁mars3d实例
|
||||
onUnmounted(() => {
|
||||
if (map) {
|
||||
map.destroy()
|
||||
map = null
|
||||
}
|
||||
console.log('map销毁完成', map)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
/**cesium 工具按钮栏*/
|
||||
.cesium-viewer-toolbar {
|
||||
top: auto !important;
|
||||
bottom: 35px !important;
|
||||
left: 12px !important;
|
||||
right: auto !important;
|
||||
}
|
||||
|
||||
.cesium-toolbar-button img {
|
||||
width: 22px;
|
||||
height: 100%;
|
||||
}
|
||||
.cesium-toolbar-button:hover img {
|
||||
width: 28px;
|
||||
}
|
||||
.cesium-svgPath-svg {
|
||||
scale: 0.8;
|
||||
}
|
||||
.cesium-svgPath-svg:hover {
|
||||
scale: 1;
|
||||
}
|
||||
.cesium-button .cesium-baseLayerPicker-selected {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cesium-button:hover .cesium-baseLayerPicker-selected {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cesium-viewer-toolbar > .cesium-toolbar-button,
|
||||
.cesium-navigationHelpButton-wrapper,
|
||||
.cesium-viewer-geocoderContainer {
|
||||
margin-bottom: 5px;
|
||||
float: left;
|
||||
clear: both;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cesium-viewer-geocoderContainer form .cesium-geocoder-input {
|
||||
border-width: 1px;
|
||||
border-image: url('//data.mars3d.cn/img/control/border.svg') 1 round stretch;
|
||||
}
|
||||
|
||||
.cesium-button {
|
||||
background-color: rgba(39, 44, 54, 0.8);
|
||||
|
||||
border-radius: 2px;
|
||||
border-width: 1px;
|
||||
border-image: url('//data.mars3d.cn/img/control/border.svg') 1 round stretch;
|
||||
|
||||
color: #ffffff;
|
||||
fill: #e6e6e6;
|
||||
line-height: 38px;
|
||||
}
|
||||
|
||||
.cesium-button:hover {
|
||||
background-color: rgba(51, 133, 255, 1);
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/**cesium 底图切换面板*/
|
||||
.cesium-baseLayerPicker-dropDown {
|
||||
bottom: 0;
|
||||
left: 40px;
|
||||
max-height: 700px;
|
||||
margin-bottom: 5px;
|
||||
background-color: rgba(23, 49, 71, 0.7);
|
||||
}
|
||||
|
||||
/**cesium 帮助面板*/
|
||||
.cesium-navigation-help {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
left: 40px;
|
||||
transform-origin: left bottom;
|
||||
background: none;
|
||||
background-color: rgba(23, 49, 71, 0.8);
|
||||
|
||||
.cesium-navigation-help-instructions,
|
||||
.cesium-navigation-button {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.cesium-navigation-button-selected,
|
||||
.cesium-navigation-button-unselected:hover {
|
||||
background-color: rgba(1, 35, 22, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**cesium 二维三维切换*/
|
||||
.cesium-sceneModePicker-wrapper {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.cesium-sceneModePicker-wrapper .cesium-sceneModePicker-dropDown-icon {
|
||||
float: right;
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
/**cesium POI查询输入框*/
|
||||
.cesium-viewer-geocoderContainer .search-results {
|
||||
left: 0;
|
||||
right: 40px;
|
||||
width: auto;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.cesium-geocoder-searchButton {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
background-color: rgba(39, 44, 54, 0.8);
|
||||
border-radius: 2px;
|
||||
border-width: 1px;
|
||||
border-image: url('//data.mars3d.cn/img/control/border.svg') 1 round stretch;
|
||||
fill: #e6e6e6;
|
||||
}
|
||||
|
||||
.cesium-viewer-geocoderContainer .cesium-geocoder-input {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
background-color: rgba(63, 72, 84, 0.7);
|
||||
}
|
||||
|
||||
.cesium-viewer-geocoderContainer .cesium-geocoder-input:focus {
|
||||
background-color: var(--mars-base-bg, rgba(63, 72, 84, 0.9));
|
||||
}
|
||||
|
||||
.cesium-viewer-geocoderContainer .search-results {
|
||||
background-color: rgba(23, 49, 71, 0.8);
|
||||
}
|
||||
|
||||
/**cesium info信息框*/
|
||||
.cesium-infoBox {
|
||||
top: 50px;
|
||||
background: var(--mars-base-bg, rgba(63, 72, 84, 0.9));
|
||||
}
|
||||
|
||||
.cesium-infoBox-title {
|
||||
background-color: rgba(23, 49, 71, 0.8);
|
||||
}
|
||||
|
||||
/**cesium 任务栏的FPS信息*/
|
||||
.cesium-performanceDisplay-defaultContainer {
|
||||
top: auto;
|
||||
bottom: 35px;
|
||||
right: 50px;
|
||||
}
|
||||
|
||||
.cesium-performanceDisplay-ms,
|
||||
.cesium-performanceDisplay-fps {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/**cesium tileset调试信息面板*/
|
||||
.cesium-viewer-cesiumInspectorContainer {
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
.cesium-cesiumInspector {
|
||||
background-color: var(--mars-base-bg, rgba(63, 72, 84, 0.9));
|
||||
}
|
||||
|
||||
/**覆盖mars3d内部控件的颜色等样式*/
|
||||
.mars3d-compass .mars3d-compass-outer {
|
||||
fill: rgba(39, 44, 54, 0.8);
|
||||
}
|
||||
.mars3d-compass .mars3d-compass-inner {
|
||||
background: rgba(39, 44, 54, 0.8);
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.mars3d-contextmenu-ul,
|
||||
.mars3d-sub-menu {
|
||||
background-color: var(--mars-base-bg, rgba(63, 72, 84, 0.9));
|
||||
}
|
||||
.mars3d-contextmenu-ul {
|
||||
border-radius: 2px;
|
||||
border-width: 1px;
|
||||
border-image: url('//data.mars3d.cn/img/control/border.svg') 1 round stretch;
|
||||
}
|
||||
|
||||
.mars3d-contextmenu-ul > li > a:hover,
|
||||
.mars3d-sub-menu > li > a:hover,
|
||||
.mars3d-contextmenu-ul > li > a:focus,
|
||||
.mars3d-sub-menu > li > a:focus,
|
||||
.mars3d-contextmenu-ul > li > .active,
|
||||
.mars3d-sub-menu > li > .active {
|
||||
background-color: var(--mars-hover-color, #3ea6ff);
|
||||
}
|
||||
|
||||
.mars3d-contextmenu-ul > .active > a,
|
||||
.mars3d-sub-menu > .active > a,
|
||||
.mars3d-contextmenu-ul > .active > a:hover,
|
||||
.mars3d-sub-menu > .active > a:hover,
|
||||
.mars3d-contextmenu-ul > .active > a:focus,
|
||||
.mars3d-sub-menu > .active > a:focus {
|
||||
background-color: var(--mars-hover-color, #3ea6ff);
|
||||
}
|
||||
|
||||
/* Popup样式*/
|
||||
.mars3d-popup-color {
|
||||
color: var(--mars-text-color, #ffffff);
|
||||
}
|
||||
|
||||
.mars3d-popup-background {
|
||||
// background: none会导致剖面的popup没有颜色
|
||||
background: var(--mars-base-bg, rgba(63, 72, 84, 0.9));
|
||||
}
|
||||
|
||||
// .mars3d-popup-content-wrapper {
|
||||
// box-shadow: none !important;
|
||||
// padding: 0 !important;
|
||||
// background: var(--mars-base-border) !important;
|
||||
// border-radius: 4px;
|
||||
// }
|
||||
|
||||
.mars3d-popup-content {
|
||||
margin: 15px;
|
||||
}
|
||||
.mars3d-popup-btn-custom {
|
||||
padding: 3px 10px;
|
||||
border: 1px solid #209ffd;
|
||||
background: #209ffd1c;
|
||||
color: var(--mars-text-color);
|
||||
}
|
||||
|
||||
.mars3d-tooltip {
|
||||
color: var(--mars-text-color, #ffffff);
|
||||
background: var(--mars-base-bg, rgba(63, 72, 84, 0.9));
|
||||
border: 1px solid var(--mars-base-bg, rgba(63, 72, 84, 0.9));
|
||||
}
|
||||
|
||||
.mars3d-tooltip-top:before {
|
||||
border-top-color: var(--mars-bg-base, rgba(23, 49, 71, 0.8));
|
||||
}
|
||||
|
||||
.mars3d-tooltip-bottom:before {
|
||||
border-bottom-color: var(--mars-bg-base, rgba(23, 49, 71, 0.8));
|
||||
}
|
||||
|
||||
.mars3d-tooltip-left:before {
|
||||
border-left-color: var(--mars-bg-base, rgba(23, 49, 71, 0.8));
|
||||
}
|
||||
|
||||
.mars3d-tooltip-right:before {
|
||||
border-right-color: var(--mars-bg-base, rgba(23, 49, 71, 0.8));
|
||||
}
|
||||
.mars3d-template-content label {
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
/* all 中的html样式 */
|
||||
.mars3d-template-titile {
|
||||
height: 33px;
|
||||
line-height: 33px;
|
||||
padding-left: 10px;
|
||||
border-radius: 4px 4px 0px 0px;
|
||||
box-shadow: 0px 6px 12px -2px rgba(50, 50, 93, 0.15), 0px 3px 7px -3px rgba(0, 0, 0, 0.2);
|
||||
color: var(--mars-control-text) !important;
|
||||
background: var(--mars-msg-title-bg);
|
||||
font-family: var(--mars-font-family);
|
||||
|
||||
a {
|
||||
font-size: 16px;
|
||||
color: var(--mars-msg-title-color, #479be0);
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mars3d-template-content {
|
||||
margin-top: 0 !important;
|
||||
background-color: var(--mars-dropdown-bg);
|
||||
padding: 10px;
|
||||
color: #eaf2ff;
|
||||
|
||||
label {
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
input {
|
||||
color: var(--mars-text-color);
|
||||
background-color: transparent !important;
|
||||
padding: 4px 5px;
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
color: #cdcdcd !important;
|
||||
}
|
||||
|
||||
textarea {
|
||||
color: var(--mars-base-color);
|
||||
background-color: transparent !important;
|
||||
padding: 4px 5px;
|
||||
}
|
||||
|
||||
textarea::placeholder {
|
||||
color: #cdcdcd !important;
|
||||
}
|
||||
}
|
||||
|
||||
.mars3d-popup-btn-custom {
|
||||
padding: 3px 10px;
|
||||
border: 1px solid #209ffd;
|
||||
background: #209ffd1c;
|
||||
color: var(--mars-text-color, #ffffff);
|
||||
}
|
||||
|
||||
.mars3d-popup-content {
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
.mars3d-divGraphic:hover {
|
||||
z-index: 999 !important;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<el-cascader
|
||||
v-model="cascaderValue"
|
||||
:options="options"
|
||||
:props="cascaderProps"
|
||||
:placeholder="placeholder"
|
||||
:clearable="clearable"
|
||||
:disabled="disabled"
|
||||
:size="size"
|
||||
:style="style"
|
||||
filterable
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
// 支持返回整条路径数组或单个值(配合 emitPath 使用)
|
||||
type: [String, Number, Array],
|
||||
default: '',
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
props: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
value: 'value',
|
||||
label: 'label',
|
||||
children: 'children',
|
||||
// 默认单选 + 任意节点可选,且只返回当前节点值
|
||||
multiple: false,
|
||||
checkStrictly: true,
|
||||
emitPath: false,
|
||||
}),
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
style: {
|
||||
type: Object,
|
||||
default: () => ({ width: '200px' }),
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const cascaderValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
})
|
||||
|
||||
const cascaderProps = computed(() => props.props)
|
||||
</script>
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
<el-date-picker
|
||||
v-model="dateValue"
|
||||
:type="dateType"
|
||||
:placeholder="placeholder"
|
||||
:range-separator="rangeSeparator"
|
||||
:start-placeholder="startPlaceholder"
|
||||
:end-placeholder="endPlaceholder"
|
||||
:value-format="valueFormat"
|
||||
:clearable="clearable"
|
||||
:disabled="disabled"
|
||||
:size="size"
|
||||
:style="style"
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [String, Array, Date],
|
||||
default: null,
|
||||
},
|
||||
dateType: {
|
||||
type: String,
|
||||
default: 'date', // date | daterange | datetime | datetimerange
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '选择日期',
|
||||
},
|
||||
rangeSeparator: {
|
||||
type: String,
|
||||
default: '-',
|
||||
},
|
||||
startPlaceholder: {
|
||||
type: String,
|
||||
default: '开始日期',
|
||||
},
|
||||
endPlaceholder: {
|
||||
type: String,
|
||||
default: '结束日期',
|
||||
},
|
||||
valueFormat: {
|
||||
type: String,
|
||||
default: 'YYYY-MM-DD',
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
style: {
|
||||
type: Object,
|
||||
default: () => ({ width: '200px' }),
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const dateValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<template>
|
||||
<el-input
|
||||
v-model="inputValue"
|
||||
:placeholder="placeholder"
|
||||
:clearable="clearable"
|
||||
:disabled="disabled"
|
||||
:size="size"
|
||||
:style="style"
|
||||
v-bind="$attrs"
|
||||
@keyup.enter="handleEnter"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请输入',
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
style: {
|
||||
type: Object,
|
||||
default: () => ({ width: '200px' }),
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'enter'])
|
||||
|
||||
const inputValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
})
|
||||
|
||||
const handleEnter = () => {
|
||||
emit('enter')
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1 @@
|
|||
<!-- 已废弃,请使用 ItemInput.vue -->
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<el-select
|
||||
:style="style"
|
||||
:size="size"
|
||||
v-bind="$attrs"
|
||||
:disabled="disabled"
|
||||
v-model="selectValue"
|
||||
:clearable="clearable"
|
||||
:placeholder="placeholder"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:disabled="item.disabled"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [String, Number, Array],
|
||||
default: '',
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
style: {
|
||||
type: Object,
|
||||
default: () => ({ width: '200px' }),
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const selectValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,327 @@
|
|||
<template>
|
||||
<!-- 添加过渡动画,让搜索表单显示/隐藏更平滑 -->
|
||||
<transition name="search-form-fade">
|
||||
<el-card class="search-form-card" v-show="showSearch">
|
||||
<el-form ref="formRef" :model="formData" :label-width="labelWidth" :size="size">
|
||||
<el-row :gutter="10" style="display: flex; align-items: center">
|
||||
<!-- 左侧表单区域:使用Grid布局,每行固定5个表单项 -->
|
||||
<el-col :span="22" class="form-col">
|
||||
<div class="form-grid">
|
||||
<el-form-item
|
||||
v-for="item in formColumns"
|
||||
:key="item.prop"
|
||||
:label="item.label"
|
||||
:prop="item.prop"
|
||||
>
|
||||
<component
|
||||
@enter="handleSearch"
|
||||
:is="getFormComponent(item)"
|
||||
v-model="formData[item.prop]"
|
||||
v-bind="getComponentProps(item)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-col>
|
||||
<!-- 右侧按钮区域:垂直居中显示 -->
|
||||
<el-col :span="2" class="button-col">
|
||||
<div class="button-wrapper">
|
||||
<ComButton type="primary" icon="Search" @click="handleSearch">
|
||||
搜索
|
||||
</ComButton>
|
||||
<ComButton
|
||||
style="margin-left: 0"
|
||||
type="warning"
|
||||
plain
|
||||
icon="Refresh"
|
||||
@click="handleReset"
|
||||
>
|
||||
重置
|
||||
</ComButton>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import ComButton from '@/components/ComButton/index.vue'
|
||||
import ItemInput from './FormItem/ItemInput.vue'
|
||||
import ItemSelect from './FormItem/ItemSelect.vue'
|
||||
import ItemDate from './FormItem/ItemDate.vue'
|
||||
import ItemCascader from './FormItem/ItemCascader.vue'
|
||||
|
||||
const props = defineProps({
|
||||
// 表单列配置
|
||||
formColumns: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
// 是否显示搜索表单
|
||||
showSearch: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// 表单大小
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
// 表单标签宽度
|
||||
labelWidth: {
|
||||
type: String,
|
||||
default: 'auto',
|
||||
},
|
||||
// 默认表单数据
|
||||
defaultFormData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['search', 'reset'])
|
||||
|
||||
const formRef = ref(null)
|
||||
const formData = reactive({})
|
||||
|
||||
// 组件映射表
|
||||
const componentMap = {
|
||||
input: ItemInput,
|
||||
select: ItemSelect,
|
||||
date: ItemDate,
|
||||
month: ItemDate,
|
||||
cascader: ItemCascader,
|
||||
}
|
||||
|
||||
// 初始化表单数据
|
||||
const initFormData = () => {
|
||||
props.formColumns.forEach((item) => {
|
||||
if (props.defaultFormData[item.prop] !== undefined) {
|
||||
formData[item.prop] = props.defaultFormData[item.prop]
|
||||
} else {
|
||||
formData[item.prop] =
|
||||
item.defaultValue ??
|
||||
(item.type === 'date' && item.dateType?.includes('range') ? [] : '')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取表单组件
|
||||
const getFormComponent = (item) => {
|
||||
return componentMap[item.type] || ItemInput
|
||||
}
|
||||
|
||||
// 获取组件属性
|
||||
const getComponentProps = (item) => {
|
||||
// 如果用户自定义了 style,使用用户的 style,否则不设置 style(让组件继承父容器宽度)
|
||||
const baseProps = {
|
||||
placeholder: item.placeholder,
|
||||
clearable: item.clearable !== false,
|
||||
disabled: item.disabled || false,
|
||||
...(item.style && { style: item.style }),
|
||||
}
|
||||
|
||||
switch (item.type) {
|
||||
case 'select':
|
||||
return {
|
||||
...baseProps,
|
||||
options: item.options || [],
|
||||
}
|
||||
case 'date':
|
||||
return {
|
||||
...baseProps,
|
||||
dateType: item.dateType || 'date',
|
||||
valueFormat: item.valueFormat || 'YYYY-MM-DD',
|
||||
rangeSeparator: item.rangeSeparator || '-',
|
||||
startPlaceholder: item.startPlaceholder,
|
||||
endPlaceholder: item.endPlaceholder,
|
||||
...(item.style && { style: item.style }),
|
||||
}
|
||||
case 'month':
|
||||
return {
|
||||
...baseProps,
|
||||
dateType: 'month',
|
||||
valueFormat: item.valueFormat || 'YYYY-MM',
|
||||
}
|
||||
case 'cascader':
|
||||
return {
|
||||
...baseProps,
|
||||
options: item.options || [],
|
||||
}
|
||||
default:
|
||||
return baseProps
|
||||
}
|
||||
}
|
||||
|
||||
// 处理表单数据,将 paramsList 配置的数组值转换为指定参数名
|
||||
const processFormData = (data) => {
|
||||
const processedData = { ...data }
|
||||
|
||||
props.formColumns.forEach((item) => {
|
||||
// 检查是否有 paramsList 配置
|
||||
if (item.paramsList && Array.isArray(item.paramsList) && item.paramsList.length > 0) {
|
||||
const value = processedData[item.prop]
|
||||
|
||||
// 如果值是数组且不为空,将其拆分为指定的参数名
|
||||
if (Array.isArray(value) && value.length > 0) {
|
||||
item.paramsList.forEach((paramName, index) => {
|
||||
if (
|
||||
value[index] !== undefined &&
|
||||
value[index] !== null &&
|
||||
value[index] !== ''
|
||||
) {
|
||||
processedData[paramName] = value[index]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 删除原来的数组字段,避免出现 month[0] 和 month[1] 的形式
|
||||
delete processedData[item.prop]
|
||||
}
|
||||
})
|
||||
|
||||
return processedData
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
const processedData = processFormData(formData)
|
||||
emit('search', processedData)
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
formRef.value?.resetFields()
|
||||
initFormData()
|
||||
const processedData = processFormData(formData)
|
||||
emit('reset', processedData)
|
||||
}
|
||||
|
||||
// 暴露方法供父组件调用
|
||||
defineExpose({
|
||||
getFormData: () => processFormData(formData),
|
||||
resetForm: handleReset,
|
||||
formRef,
|
||||
})
|
||||
|
||||
// 初始化
|
||||
initFormData()
|
||||
|
||||
// 监听表单列变化
|
||||
watch(
|
||||
() => props.formColumns,
|
||||
() => {
|
||||
initFormData()
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search-form-card {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.search-form-card :deep(.el-card__body) {
|
||||
padding: 15px 10px 5px 20px !important;
|
||||
}
|
||||
|
||||
/* 搜索表单显示/隐藏动画 */
|
||||
.search-form-fade-enter-active,
|
||||
.search-form-fade-leave-active {
|
||||
transition: opacity 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.search-form-fade-enter-from,
|
||||
.search-form-fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-6px);
|
||||
}
|
||||
|
||||
/* 左侧表单区域 */
|
||||
.form-col {
|
||||
border-right: 1px solid #e6e6e6;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
/* Grid布局:每行固定5个表单项 */
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* 表单项样式 */
|
||||
.form-grid :deep(.el-form-item) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.form-grid :deep(.el-form-item__label) {
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
/* 确保所有表单组件宽度为100%,继承父容器宽度 */
|
||||
.form-grid :deep(.el-form-item__content) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 覆盖所有表单组件的宽度,使其继承父容器宽度 */
|
||||
.form-grid :deep(.el-input),
|
||||
.form-grid :deep(.el-input__wrapper),
|
||||
.form-grid :deep(.el-select),
|
||||
.form-grid :deep(.el-date-editor),
|
||||
.form-grid :deep(.el-cascader) {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* 确保输入框内部元素也自适应 */
|
||||
.form-grid :deep(.el-input__inner) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 右侧按钮区域 */
|
||||
.button-col {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding-left: 10px;
|
||||
padding-top: 0;
|
||||
min-height: fit-content;
|
||||
/* 与表单第一行输入框对齐 */
|
||||
/* 由于表单项 margin-bottom: 8px,第一行从顶部开始 */
|
||||
/* 如果表单项有label,需要加上label的高度来对齐输入框 */
|
||||
}
|
||||
|
||||
.button-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 确保按钮宽度一致,高度与表单输入框一致(32px) */
|
||||
.button-wrapper :deep(.el-button) {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 32px;
|
||||
padding: 0 15px;
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 确保按钮内部图标和文字对齐 */
|
||||
.button-wrapper :deep(.el-button) > i,
|
||||
.button-wrapper :deep(.el-button) > span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
<template>
|
||||
<div class="com-table-container">
|
||||
<!-- 查询表单 -->
|
||||
<ComSearchForm
|
||||
v-if="showSearch"
|
||||
ref="searchFormRef"
|
||||
:form-columns="formColumns"
|
||||
:show-search="isShowSearch"
|
||||
:default-form-data="defaultFormData"
|
||||
:size="size"
|
||||
@search="handleSearch"
|
||||
@reset="handleReset"
|
||||
/>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<ComDataTable
|
||||
ref="dataTableRef"
|
||||
:table-data="tableData"
|
||||
:table-columns="tableColumns"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:show-selection="showSelection"
|
||||
:show-index="showIndex"
|
||||
:show-toolbar="showToolbar"
|
||||
:show-action="showAction"
|
||||
:action-columns="actionColumns"
|
||||
:show-more-action="showMoreAction"
|
||||
:more-action-count="moreActionCount"
|
||||
@selection-change="handleSelectionChange"
|
||||
@refresh="handleRefresh"
|
||||
@pagination-change="handlePaginationChange"
|
||||
@action="handleAction"
|
||||
@hide-search="handleHideSearch"
|
||||
>
|
||||
<!-- 工具栏插槽 -->
|
||||
<template #toolbar>
|
||||
<slot
|
||||
name="toolbar"
|
||||
:formData="
|
||||
Object.assign({}, searchFormRef?.getFormData() || {}, {
|
||||
pageNum: pagination.page,
|
||||
pageSize: pagination.limit,
|
||||
}) || {}
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 自定义列插槽 -->
|
||||
<template
|
||||
v-for="column in tableColumns"
|
||||
:key="column.prop"
|
||||
#[column.slot]="{ row, index, prop }"
|
||||
>
|
||||
<slot :name="column.slot" :row="row" :index="index" :prop="prop" />
|
||||
</template>
|
||||
|
||||
<!-- 操作列插槽 -->
|
||||
<template #action="{ row, index }">
|
||||
<slot name="action" :row="row" :index="index" />
|
||||
</template>
|
||||
</ComDataTable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import ComSearchForm from '@/components/ComSearchForm/index.vue'
|
||||
import ComDataTable from '@/components/ComDataTable/index.vue'
|
||||
|
||||
const props = defineProps({
|
||||
// 表单配置
|
||||
formColumns: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
// 表格配置
|
||||
tableColumns: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
// 显示搜索表单
|
||||
showSearch: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// 表单大小
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
// 默认表单数据
|
||||
defaultFormData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
// 表格功能开关
|
||||
showSelection: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showIndex: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showToolbar: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showAction: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 操作列配置
|
||||
actionColumns: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
// 数据加载函数
|
||||
loadData: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
// 初始化默认查询参数
|
||||
defaultQueryParams: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
// 是否开启更多按钮
|
||||
showMoreAction: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 超过几个按钮时开启更多按钮
|
||||
moreActionCount: {
|
||||
type: Number,
|
||||
default: 2,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['search', 'reset', 'selection-change', 'action'])
|
||||
|
||||
const searchFormRef = ref(null)
|
||||
const dataTableRef = ref(null)
|
||||
const isShowSearch = ref(true)
|
||||
|
||||
const loading = ref(false)
|
||||
const tableData = ref([])
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
})
|
||||
|
||||
// 搜索
|
||||
const handleSearch = (formData) => {
|
||||
pagination.page = 1
|
||||
fetchData(formData)
|
||||
emit('search', formData)
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = (formData) => {
|
||||
pagination.page = 1
|
||||
fetchData(formData)
|
||||
emit('reset', formData)
|
||||
}
|
||||
|
||||
// 选择变化
|
||||
const handleSelectionChange = (selection) => {
|
||||
emit('selection-change', selection)
|
||||
}
|
||||
|
||||
// 刷新
|
||||
const handleRefresh = () => {
|
||||
const formData = searchFormRef.value?.getFormData() || {}
|
||||
fetchData(formData)
|
||||
}
|
||||
|
||||
// 隐藏查询表单
|
||||
const handleHideSearch = () => {
|
||||
isShowSearch.value = !isShowSearch.value
|
||||
}
|
||||
|
||||
// 分页变化
|
||||
const handlePaginationChange = (newPagination) => {
|
||||
Object.assign(pagination, newPagination)
|
||||
const formData = searchFormRef.value?.getFormData() || {}
|
||||
fetchData(formData)
|
||||
}
|
||||
|
||||
// 操作
|
||||
const handleAction = (action, row, index) => {
|
||||
emit('action', action, row, index)
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
const fetchData = async (formData = {}) => {
|
||||
if (!props.loadData) {
|
||||
console.warn('ComTable: loadData function is not provided')
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
...formData,
|
||||
...props.defaultQueryParams,
|
||||
pageNum: pagination.page, // 后端可能需要pageNum
|
||||
pageSize: pagination.limit, // 后端可能需要pageSize
|
||||
}
|
||||
|
||||
const response = await props.loadData(params)
|
||||
|
||||
// 假设返回格式:{ rows: [], total: 0 } 或 { data: { rows: [], total: 0 } }
|
||||
if (response) {
|
||||
const data = response.rows || response.data?.rows || response.data || []
|
||||
const total = response.total || response.data?.total || 0
|
||||
|
||||
tableData.value = data
|
||||
pagination.total = total
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('ComTable: fetchData error', error)
|
||||
tableData.value = []
|
||||
pagination.total = 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
})
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
searchFormRef,
|
||||
dataTableRef,
|
||||
refresh: handleRefresh,
|
||||
getFormData: () => searchFormRef.value?.getFormData() || {},
|
||||
resetForm: () => searchFormRef.value?.resetForm(),
|
||||
clearSelection: () => dataTableRef.value?.clearSelection(),
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.com-table-container {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
27
src/main.js
|
|
@ -5,6 +5,8 @@ import Cookies from 'js-cookie'
|
|||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
import 'mars3d-cesium/Build/Cesium/Widgets/widgets.css'
|
||||
import 'mars3d/mars3d.css'
|
||||
import locale from 'element-plus/es/locale/lang/zh-cn'
|
||||
|
||||
import '@/assets/styles/index.scss' // global css
|
||||
|
|
@ -26,21 +28,28 @@ import elementIcons from '@/components/SvgIcon/svgicon'
|
|||
import './permission' // permission control
|
||||
|
||||
import { useDict } from '@/utils/dict'
|
||||
import { getConfigKey } from "@/api/system/config"
|
||||
import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel, selectDictLabels } from '@/utils/ruoyi'
|
||||
import { getConfigKey } from '@/api/system/config'
|
||||
import {
|
||||
parseTime,
|
||||
resetForm,
|
||||
addDateRange,
|
||||
handleTree,
|
||||
selectDictLabel,
|
||||
selectDictLabels,
|
||||
} from '@/utils/ruoyi'
|
||||
|
||||
// 分页组件
|
||||
import Pagination from '@/components/Pagination'
|
||||
// 自定义表格工具组件
|
||||
import RightToolbar from '@/components/RightToolbar'
|
||||
// 富文本组件
|
||||
import Editor from "@/components/Editor"
|
||||
import Editor from '@/components/Editor'
|
||||
// 文件上传组件
|
||||
import FileUpload from "@/components/FileUpload"
|
||||
import FileUpload from '@/components/FileUpload'
|
||||
// 图片上传组件
|
||||
import ImageUpload from "@/components/ImageUpload"
|
||||
import ImageUpload from '@/components/ImageUpload'
|
||||
// 图片预览组件
|
||||
import ImagePreview from "@/components/ImagePreview"
|
||||
import ImagePreview from '@/components/ImagePreview'
|
||||
// 字典标签组件
|
||||
import DictTag from '@/components/DictTag'
|
||||
|
||||
|
|
@ -76,9 +85,9 @@ directive(app)
|
|||
|
||||
// 使用element-plus 并且设置全局的大小
|
||||
app.use(ElementPlus, {
|
||||
locale: locale,
|
||||
// 支持 large、default、small
|
||||
size: Cookies.get('size') || 'default'
|
||||
locale: locale,
|
||||
// 支持 large、default、small
|
||||
size: Cookies.get('size') || 'default',
|
||||
})
|
||||
|
||||
app.mount('#app')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,440 @@
|
|||
<template>
|
||||
<div class="dialog-showcase">
|
||||
<!-- 介绍区 -->
|
||||
<section class="intro">
|
||||
<p class="eyebrow">组件展示 · Dialog</p>
|
||||
<h1>弹框组件设计指南</h1>
|
||||
<p class="subtitle">
|
||||
基于 Element Plus 弹框能力的二次封装,支持外层 +
|
||||
内层嵌套弹框、统一的视觉样式和交互行为,
|
||||
适合在复杂业务中复用,避免每个页面重复堆样式与逻辑。
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 基础示例:外层弹框 -->
|
||||
<section class="card-full">
|
||||
<el-card shadow="hover" class="demo-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<h3>基础弹框</h3>
|
||||
<span>最常见的确认 / 信息展示场景</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<p class="card-description">
|
||||
点击下方按钮打开基础弹框,外层弹框默认居中吸附,带有统一的头部样式、阴影和内容区域滚动处理。
|
||||
</p>
|
||||
|
||||
<div class="demo-actions">
|
||||
<ComButton type="primary" size="large" @click="openBaseDialog">
|
||||
打开基础弹框
|
||||
</ComButton>
|
||||
</div>
|
||||
|
||||
<ComDialog
|
||||
:dialog-config="baseDialog"
|
||||
@closeDialogOuter="handleCloseBaseOuter"
|
||||
@closeDialogInner="handleCloseBaseInner"
|
||||
>
|
||||
<template #outerContent>
|
||||
<div class="dialog-content">
|
||||
<h4 class="dialog-title">创建任务</h4>
|
||||
<p class="dialog-subtitle">
|
||||
在这里展示业务表单或提示文案,内容区域已处理最大高度和滚动,保证在小屏幕下也能完整展示。
|
||||
</p>
|
||||
<el-form label-width="80px" class="dialog-form">
|
||||
<el-form-item label="任务名称">
|
||||
<el-input
|
||||
v-model="baseForm.name"
|
||||
placeholder="请输入任务名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="优先级">
|
||||
<el-select v-model="baseForm.level" placeholder="请选择">
|
||||
<el-option label="普通" value="normal" />
|
||||
<el-option label="重要" value="important" />
|
||||
<el-option label="紧急" value="urgent" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注说明">
|
||||
<el-input
|
||||
v-model="baseForm.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="可选填写,补充说明信息"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="dialog-footer">
|
||||
<ComButton plain @click="closeBaseDialog">取消</ComButton>
|
||||
<ComButton type="primary" @click="submitBaseDialog">
|
||||
确认提交
|
||||
</ComButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ComDialog>
|
||||
</el-card>
|
||||
</section>
|
||||
|
||||
<!-- 内外层嵌套弹框示例 -->
|
||||
<section class="card-full">
|
||||
<el-card shadow="hover" class="demo-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<h3>内外层嵌套弹框</h3>
|
||||
<span>适用于「详情 + 深层操作」的复杂流程</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<p class="card-description">
|
||||
外层弹框承载主流程(如详情、编辑表单),当需要处理更深入的步骤(如高级设置、二次确认)时,
|
||||
可以在外层内部再打开内层弹框,保持上下文不丢失。
|
||||
</p>
|
||||
|
||||
<div class="demo-actions">
|
||||
<ComButton type="primary" @click="openNestedOuter"> 打开嵌套弹框 </ComButton>
|
||||
</div>
|
||||
|
||||
<ComDialog
|
||||
:dialog-config="nestedDialog"
|
||||
@closeDialogOuter="handleCloseNestedOuter"
|
||||
@closeDialogInner="handleCloseNestedInner"
|
||||
>
|
||||
<!-- 外层弹框内容 -->
|
||||
<template #outerContent>
|
||||
<div class="dialog-content">
|
||||
<h4 class="dialog-title">用户详情</h4>
|
||||
<p class="dialog-subtitle">
|
||||
外层用于展示主要信息和基础操作,点击「高级设置」将打开内层弹框,避免页面跳转打断当前上下文。
|
||||
</p>
|
||||
|
||||
<el-descriptions :column="2" border class="dialog-descriptions">
|
||||
<el-descriptions-item label="用户名">
|
||||
bns_admin
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="角色">
|
||||
超级管理员
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="部门"> 技术中心 </el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag type="success">启用</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="dialog-footer">
|
||||
<ComButton plain @click="closeNestedOuter">关闭</ComButton>
|
||||
<ComButton type="primary" @click="openNestedInner">
|
||||
打开高级设置
|
||||
</ComButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 内层弹框内容 -->
|
||||
<template #innerContent>
|
||||
<div class="dialog-content">
|
||||
<h4 class="dialog-title">高级设置</h4>
|
||||
<p class="dialog-subtitle">
|
||||
内层弹框通常体量较小,用于配置少量但影响较大的选项,例如权限范围、敏感操作确认等。
|
||||
</p>
|
||||
|
||||
<el-form label-width="90px" class="dialog-form">
|
||||
<el-form-item label="数据权限">
|
||||
<el-select
|
||||
v-model="nestedForm.scope"
|
||||
placeholder="请选择数据范围"
|
||||
>
|
||||
<el-option label="仅本人" value="self" />
|
||||
<el-option label="本部门" value="dept" />
|
||||
<el-option label="本部门及下级" value="dept_child" />
|
||||
<el-option label="全部数据" value="all" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="登录保护">
|
||||
<el-switch v-model="nestedForm.mfa" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="dialog-footer">
|
||||
<ComButton plain @click="closeNestedInner">取消</ComButton>
|
||||
<ComButton type="primary" @click="submitNestedInner">
|
||||
保存设置
|
||||
</ComButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ComDialog>
|
||||
</el-card>
|
||||
</section>
|
||||
|
||||
<!-- 使用建议 -->
|
||||
<section class="card-full guide-card">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<h3>使用建议</h3>
|
||||
<span>保持一致的交互体验与视觉规范</span>
|
||||
</div>
|
||||
</template>
|
||||
<ul class="guide-list">
|
||||
<li>尽量保证系统中所有弹框都通过 `ComDialog` 使用,统一头部样式与位置。</li>
|
||||
<li>外层弹框承担主要流程,内层弹框仅在确有必要时使用,避免过深的弹框嵌套。</li>
|
||||
<li>
|
||||
推荐使用 `dialogConfig`
|
||||
对象集中管理弹框状态与标题,避免在多个变量间来回切换。
|
||||
</li>
|
||||
<li>
|
||||
关闭弹框时,结合 `closeDialogOuter / closeDialogInner`
|
||||
事件重置表单、清理临时状态。
|
||||
</li>
|
||||
</ul>
|
||||
</el-card>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="ShowDialog">
|
||||
import { reactive } from 'vue'
|
||||
import ComDialog from '@/components/ComDialog/index.vue'
|
||||
import ComButton from '@/components/ComButton/index.vue'
|
||||
|
||||
// 基础弹框配置
|
||||
const baseDialog = reactive({
|
||||
outerVisible: false,
|
||||
outerTitle: '基础弹框示例',
|
||||
outerWidth: '720px',
|
||||
minHeight: '320px',
|
||||
maxHeight: '70vh',
|
||||
// 以下字段用于兼容组件的内层结构(本示例未使用)
|
||||
innerVisible: false,
|
||||
})
|
||||
|
||||
const baseForm = reactive({
|
||||
name: '',
|
||||
level: 'normal',
|
||||
remark: '',
|
||||
})
|
||||
|
||||
const openBaseDialog = () => {
|
||||
baseDialog.outerVisible = true
|
||||
}
|
||||
|
||||
const closeBaseDialog = () => {
|
||||
baseDialog.outerVisible = false
|
||||
}
|
||||
|
||||
const submitBaseDialog = () => {
|
||||
// 此处可替换为实际提交逻辑
|
||||
console.log('提交基础弹框表单:', { ...baseForm })
|
||||
baseDialog.outerVisible = false
|
||||
}
|
||||
|
||||
const handleCloseBaseOuter = () => {
|
||||
baseDialog.outerVisible = false
|
||||
}
|
||||
|
||||
const handleCloseBaseInner = () => {
|
||||
baseDialog.innerVisible = false
|
||||
}
|
||||
|
||||
// 内外层嵌套弹框配置
|
||||
const nestedDialog = reactive({
|
||||
outerVisible: false,
|
||||
outerTitle: '外层弹框 · 用户详情',
|
||||
outerWidth: '760px',
|
||||
minHeight: '360px',
|
||||
maxHeight: '75vh',
|
||||
innerVisible: false,
|
||||
innerTitle: '内层弹框 · 高级设置',
|
||||
innerWidth: '520px',
|
||||
innerMinHeight: '260px',
|
||||
innerMaxHeight: '60vh',
|
||||
})
|
||||
|
||||
const nestedForm = reactive({
|
||||
scope: 'dept_child',
|
||||
mfa: true,
|
||||
})
|
||||
|
||||
const openNestedOuter = () => {
|
||||
nestedDialog.outerVisible = true
|
||||
}
|
||||
|
||||
const closeNestedOuter = () => {
|
||||
nestedDialog.outerVisible = false
|
||||
nestedDialog.innerVisible = false
|
||||
}
|
||||
|
||||
const openNestedInner = () => {
|
||||
nestedDialog.innerVisible = true
|
||||
}
|
||||
|
||||
const closeNestedInner = () => {
|
||||
nestedDialog.innerVisible = false
|
||||
}
|
||||
|
||||
const submitNestedInner = () => {
|
||||
console.log('保存高级设置:', { ...nestedForm })
|
||||
nestedDialog.innerVisible = false
|
||||
}
|
||||
|
||||
const handleCloseNestedOuter = () => {
|
||||
closeNestedOuter()
|
||||
}
|
||||
|
||||
const handleCloseNestedInner = () => {
|
||||
closeNestedInner()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-showcase {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
padding: 32px 40px 48px;
|
||||
background: linear-gradient(160deg, #f6f8ff 0%, #ffffff 55%, #f9fbff 100%);
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.intro {
|
||||
max-width: 720px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
font-size: 14px;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: #5c6aff;
|
||||
margin-bottom: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.intro h1 {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 12px;
|
||||
color: #1f2a56;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 16px;
|
||||
line-height: 1.7;
|
||||
color: #4c5a7a;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.card-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.demo-card {
|
||||
border-radius: 18px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.card-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1f2a56;
|
||||
}
|
||||
|
||||
.card-header span {
|
||||
font-size: 14px;
|
||||
color: #8792b0;
|
||||
}
|
||||
|
||||
.card-description {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #4c5a7a;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.demo-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.dialog-subtitle {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.dialog-form {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.dialog-descriptions {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.guide-card :deep(.el-card__body) {
|
||||
padding: 20px 28px 28px;
|
||||
}
|
||||
|
||||
.guide-list {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
color: #4c5a7a;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.guide-list li {
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.dialog-showcase {
|
||||
padding: 24px 24px 36px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.intro h1 {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
// 表单配置
|
||||
export const formColumns = [
|
||||
{
|
||||
type: 'input',
|
||||
prop: 'queryTableName',
|
||||
label: '', // 表格名称
|
||||
placeholder: '请输入表格名称',
|
||||
defaultValue: '', // 默认值
|
||||
isShowLabel: false,
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
prop: 'queryStatus',
|
||||
label: '', // 状态
|
||||
placeholder: '请选择状态',
|
||||
options: [
|
||||
{ label: '启用', value: '1' },
|
||||
{ label: '禁用', value: '0' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'cascader',
|
||||
prop: 'queryClass',
|
||||
label: '', // 班级
|
||||
placeholder: '请选择班级',
|
||||
options: [
|
||||
{
|
||||
label: '班级1',
|
||||
value: '1',
|
||||
children: [
|
||||
{ label: '班级1-1', value: '1-1' },
|
||||
{ label: '班级1-2', value: '1-2' },
|
||||
{ label: '班级1-3', value: '1-3' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '班级2',
|
||||
value: '2',
|
||||
children: [
|
||||
{ label: '班级2-1', value: '2-1' },
|
||||
{ label: '班级2-2', value: '2-2' },
|
||||
{ label: '班级2-3', value: '2-3' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '班级3',
|
||||
value: '3',
|
||||
children: [
|
||||
{ label: '班级3-1', value: '3-1' },
|
||||
{ label: '班级3-2', value: '3-2' },
|
||||
{ label: '班级3-3', value: '3-3' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
prop: 'queryDateRange',
|
||||
label: '', // 日期区间
|
||||
dateType: 'daterange', // 日期区间
|
||||
paramsList: ['queryStartDate', 'queryEndDate'],
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
startPlaceholder: '开始日期',
|
||||
endPlaceholder: '结束日期',
|
||||
},
|
||||
// 月份区间筛选(Element Plus 支持 monthrange)
|
||||
{
|
||||
type: 'date',
|
||||
prop: 'queryMonthRange',
|
||||
label: '', // 月份区间
|
||||
dateType: 'monthrange', // 月份区间
|
||||
paramsList: ['queryStartMonth', 'queryEndMonth'],
|
||||
valueFormat: 'YYYY-MM',
|
||||
startPlaceholder: '开始月份',
|
||||
endPlaceholder: '结束月份',
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
prop: 'queryYear',
|
||||
label: '', // 年份
|
||||
dateType: 'year', // 单选年份
|
||||
valueFormat: 'YYYY',
|
||||
placeholder: '请选择年份',
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
prop: 'queryMonth',
|
||||
label: '', // 月份
|
||||
dateType: 'month', // 单选月份
|
||||
valueFormat: 'YYYY-MM',
|
||||
placeholder: '请选择月份',
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
prop: 'queryDate',
|
||||
label: '', // 日期
|
||||
dateType: 'date', // 单选日期
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
placeholder: '请选择日期',
|
||||
},
|
||||
]
|
||||
|
||||
// 表格列配置
|
||||
export const tableColumns = [
|
||||
{
|
||||
prop: 'tableName', // 字段名
|
||||
label: '表格名称', // 标签
|
||||
width: 180, // 列宽
|
||||
minWidth: 120, // 最小列宽
|
||||
align: 'left', // 对齐方式
|
||||
showOverflowTooltip: true, // 超出显示提示
|
||||
id: 'tableName',
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
width: 100,
|
||||
// 自定义渲染(使用插槽)
|
||||
slot: 'status',
|
||||
id: 'status',
|
||||
// 或者使用格式化函数
|
||||
// formatter: (row, column, cellValue) => {
|
||||
// return cellValue === '1' ? '启用' : '禁用'
|
||||
// }
|
||||
},
|
||||
{
|
||||
prop: 'createTime',
|
||||
label: '创建时间',
|
||||
width: 180,
|
||||
id: 'createTime',
|
||||
},
|
||||
{
|
||||
prop: 'remark',
|
||||
label: '备注',
|
||||
minWidth: 200,
|
||||
showOverflowTooltip: true,
|
||||
id: 'remark',
|
||||
},
|
||||
]
|
||||
|
||||
// 操作列配置(可选)
|
||||
export const actionColumns = [
|
||||
{
|
||||
label: '编辑',
|
||||
type: 'primary',
|
||||
link: true,
|
||||
handler: (row) => {
|
||||
console.log('编辑', row)
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
type: 'danger',
|
||||
link: true,
|
||||
handler: (row) => {
|
||||
console.log('删除', row)
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '详情',
|
||||
type: 'danger',
|
||||
link: true,
|
||||
handler: (row) => {
|
||||
console.log('删除', row)
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '执行',
|
||||
type: 'danger',
|
||||
link: true,
|
||||
handler: (row) => {
|
||||
console.log('删除', row)
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '测试',
|
||||
type: 'warning',
|
||||
link: true,
|
||||
handler: (row) => {
|
||||
console.log('删除', row)
|
||||
},
|
||||
},
|
||||
]
|
||||
|
|
@ -0,0 +1,444 @@
|
|||
<template>
|
||||
<div class="table-showcase">
|
||||
<section class="intro">
|
||||
<p class="eyebrow">组件展示 · Table</p>
|
||||
<h1>表格组件设计指南</h1>
|
||||
<p class="subtitle">
|
||||
基于配置驱动的列表查询组件,通过简单的配置即可快速构建包含查询表单和数据表格的完整页面,支持丰富的表单类型和表格功能。
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="card-full">
|
||||
<el-card shadow="hover" class="demo-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<h3>列表查询示例</h3>
|
||||
<span>基于配置驱动的表格组件</span>
|
||||
</div>
|
||||
</template>
|
||||
<p class="card-description">
|
||||
以下示例展示了如何使用配置化的方式快速构建一个包含查询表单和数据表格的完整列表页面。
|
||||
通过简单的配置,即可实现表单渲染、数据查询、表格展示等完整功能。
|
||||
</p>
|
||||
<div class="demo-content">
|
||||
<ComTable
|
||||
:size="`large`"
|
||||
:show-action="true"
|
||||
:show-toolbar="true"
|
||||
:load-data="loadData"
|
||||
@action="handleAction"
|
||||
:form-columns="formColumns"
|
||||
:table-columns="tableColumns"
|
||||
:action-columns="actionColumns"
|
||||
:show-more-action="true"
|
||||
:more-action-count="2"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<!-- 工具栏插槽 -->
|
||||
<template #toolbar>
|
||||
<ComButton type="primary" icon="Plus" @click="handleAdd"
|
||||
>新增</ComButton
|
||||
>
|
||||
<ComButton
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
:disabled="!multipleSelection.length"
|
||||
@click="handleBatchDelete"
|
||||
>
|
||||
批量删除
|
||||
</ComButton>
|
||||
</template>
|
||||
|
||||
<!-- 状态列自定义渲染 -->
|
||||
<template #status="{ row }">
|
||||
<el-tag :type="row.status === '1' ? 'success' : 'danger'">
|
||||
{{ row.status === '1' ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</ComTable>
|
||||
</div>
|
||||
</el-card>
|
||||
</section>
|
||||
|
||||
<section class="card-full guide-card">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<h3>配置说明</h3>
|
||||
<span>了解组件的配置项、查询字段与使用方法</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="config-demo">
|
||||
<div class="config-section">
|
||||
<h4>表单配置(formColumns)</h4>
|
||||
<pre class="code-block">{{ formConfigExample }}</pre>
|
||||
</div>
|
||||
<div class="config-section">
|
||||
<h4>表格配置(tableColumns)</h4>
|
||||
<pre class="code-block">{{ tableConfigExample }}</pre>
|
||||
</div>
|
||||
<div class="config-section">
|
||||
<h4>搜索字段示例(本次查询参数)</h4>
|
||||
<p class="config-tip">
|
||||
在上方查询表单中输入条件并点击「搜索」后,这里会实时展示本次请求携带的查询字段名及其对应的值。
|
||||
</p>
|
||||
<pre class="code-block">{{ searchParamsDisplay }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</section>
|
||||
|
||||
<section class="card-full guide-card">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<h3>使用建议</h3>
|
||||
<span>保持一致性,提升开发效率</span>
|
||||
</div>
|
||||
</template>
|
||||
<ul class="guide-list">
|
||||
<li>表单和表格组件采用职责分离设计,可独立使用,也可组合使用。</li>
|
||||
<li>通过配置驱动的方式定义表单字段和表格列,减少重复代码。</li>
|
||||
<li>表单组件已二次封装,提供统一的接口和样式,便于维护和扩展。</li>
|
||||
<li>支持插槽自定义渲染,满足复杂场景的定制化需求。</li>
|
||||
<li>表格支持分页、选择、操作列等常用功能,开箱即用。</li>
|
||||
<li>数据加载通过 loadData 函数统一处理,便于对接后端接口。</li>
|
||||
</ul>
|
||||
</el-card>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="ShowTable">
|
||||
import { ref, computed } from 'vue'
|
||||
import ComTable from '@/components/ComTable/index.vue'
|
||||
import ComButton from '@/components/ComButton/index.vue'
|
||||
import { formColumns, tableColumns, actionColumns } from './config'
|
||||
|
||||
const multipleSelection = ref([])
|
||||
|
||||
// 配置示例代码(与当前 config 保持一致,只展示核心字段)
|
||||
const formConfigExample = `[
|
||||
{
|
||||
type: 'input', // 文本输入
|
||||
prop: 'queryTableName', // 查询字段名
|
||||
placeholder: '请输入表格名称',
|
||||
defaultValue: '',
|
||||
},
|
||||
{
|
||||
type: 'select', // 下拉选择
|
||||
prop: 'queryStatus',
|
||||
placeholder: '请选择状态',
|
||||
options: [
|
||||
{ label: '启用', value: '1' },
|
||||
{ label: '禁用', value: '0' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'cascader', // 级联选择
|
||||
prop: 'queryClass',
|
||||
placeholder: '请选择班级',
|
||||
options: [/* 班级层级数据 */],
|
||||
},
|
||||
{
|
||||
type: 'date', // 日期区间
|
||||
prop: 'queryDateRange',
|
||||
dateType: 'daterange',
|
||||
paramsList: ['queryStartDate', 'queryEndDate'],
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
startPlaceholder: '开始日期',
|
||||
endPlaceholder: '结束日期',
|
||||
},
|
||||
{
|
||||
type: 'date', // 月份区间
|
||||
prop: 'queryMonthRange',
|
||||
dateType: 'monthrange',
|
||||
paramsList: ['queryStartMonth', 'queryEndMonth'],
|
||||
valueFormat: 'YYYY-MM',
|
||||
},
|
||||
{
|
||||
type: 'date', // 单选年份
|
||||
prop: 'queryYear',
|
||||
dateType: 'year',
|
||||
valueFormat: 'YYYY',
|
||||
},
|
||||
]`
|
||||
|
||||
const tableConfigExample = `[
|
||||
{
|
||||
prop: 'tableName', // 字段名
|
||||
label: '表格名称', // 列标题
|
||||
width: 180,
|
||||
align: 'left',
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
width: 100,
|
||||
slot: 'status', // 使用插槽自定义单元格
|
||||
},
|
||||
]`
|
||||
|
||||
// 当前查询参数展示(演示用)
|
||||
const lastQueryParams = ref({})
|
||||
const searchParamsDisplay = computed(() => {
|
||||
const data = lastQueryParams.value || {}
|
||||
const keys = Object.keys(data || {})
|
||||
if (!keys.length) {
|
||||
return '// 暂未触发查询,先在上方表单中输入条件并点击“搜索”\n'
|
||||
}
|
||||
return JSON.stringify(data, null, 2)
|
||||
})
|
||||
|
||||
// 模拟数据加载函数
|
||||
const loadData = async (params) => {
|
||||
console.log('加载数据,参数:', params)
|
||||
// 记录本次查询参数,供下方“搜索字段”展示
|
||||
lastQueryParams.value = params || {}
|
||||
|
||||
// 模拟 API 请求
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
rows: [
|
||||
{
|
||||
id: 1,
|
||||
tableName: '用户表',
|
||||
status: '1',
|
||||
createTime: '2024-01-01 10:00:00',
|
||||
remark: '用户信息表,存储系统用户的基本信息',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
tableName: '角色表',
|
||||
status: '1',
|
||||
createTime: '2024-01-02 10:00:00',
|
||||
remark: '角色信息表,存储系统角色和权限信息',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
tableName: '菜单表',
|
||||
status: '0',
|
||||
createTime: '2024-01-03 10:00:00',
|
||||
remark: '菜单信息表,存储系统菜单和路由信息',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
tableName: '部门表',
|
||||
status: '1',
|
||||
createTime: '2024-01-04 10:00:00',
|
||||
remark: '部门信息表,存储组织架构信息',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
tableName: '字典表',
|
||||
status: '1',
|
||||
createTime: '2024-01-05 10:00:00',
|
||||
remark: '字典信息表,存储系统字典数据',
|
||||
},
|
||||
],
|
||||
total: 5,
|
||||
}
|
||||
resolve(mockData)
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
|
||||
// 选择变化
|
||||
const handleSelectionChange = (selection) => {
|
||||
multipleSelection.value = selection
|
||||
console.log('选择变化:', selection)
|
||||
}
|
||||
|
||||
// 操作按钮处理
|
||||
const handleAction = (action, row, index) => {
|
||||
console.log('操作:', action, row, index)
|
||||
}
|
||||
|
||||
// 新增
|
||||
const handleAdd = () => {
|
||||
console.log('新增')
|
||||
}
|
||||
|
||||
// 编辑
|
||||
const handleEdit = (row) => {
|
||||
console.log('编辑', row)
|
||||
}
|
||||
|
||||
// 删除
|
||||
const handleDelete = (row) => {
|
||||
console.log('删除', row)
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
const handleBatchDelete = () => {
|
||||
console.log('批量删除', multipleSelection.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.table-showcase {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
padding: 18px;
|
||||
background: linear-gradient(160deg, #f6f8ff 0%, #ffffff 55%, #f9fbff 100%);
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.intro {
|
||||
max-width: 720px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
font-size: 14px;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: #5c6aff;
|
||||
margin-bottom: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.intro h1 {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 12px;
|
||||
color: #1f2a56;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 16px;
|
||||
line-height: 1.7;
|
||||
color: #4c5a7a;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.card-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.demo-card {
|
||||
border-radius: 18px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.card-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1f2a56;
|
||||
}
|
||||
|
||||
.card-header span {
|
||||
font-size: 14px;
|
||||
color: #8792b0;
|
||||
}
|
||||
|
||||
.card-description {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #4c5a7a;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.demo-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.guide-card :deep(.el-card__body) {
|
||||
padding: 20px 28px 28px;
|
||||
}
|
||||
|
||||
.config-demo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.config-section h4 {
|
||||
margin: 0 0 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1f2a56;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: #2c3e50;
|
||||
overflow-x: auto;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.config-tip {
|
||||
margin: 0 0 8px;
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.guide-list {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
color: #4c5a7a;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.guide-list li {
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
/* 覆盖组件内部样式 */
|
||||
.demo-content :deep(.search-form-card) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.demo-content :deep(.search-form-card .el-card__body) {
|
||||
padding: 18px 20px 0;
|
||||
}
|
||||
|
||||
.demo-content :deep(.data-table-card) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.table-showcase {
|
||||
padding: 24px 24px 36px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.intro h1 {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.config-demo {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
font-size: 12px;
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -26,9 +26,9 @@
|
|||
v-for="item in group.texts"
|
||||
:key="item.label"
|
||||
class="text-item">
|
||||
<ComText v-bind="item.props">{{
|
||||
item.content
|
||||
}}</ComText>
|
||||
<ComText v-bind="item.props">
|
||||
{{ item.content }}
|
||||
</ComText>
|
||||
<span class="text-note">{{ item.note }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,44 +1,47 @@
|
|||
import { defineConfig, loadEnv } from "vite";
|
||||
import path from "path";
|
||||
import createVitePlugins from "./vite/plugins";
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import path from 'path'
|
||||
import createVitePlugins from './vite/plugins'
|
||||
import { mars3dPlugin } from 'vite-plugin-mars3d'
|
||||
|
||||
const baseUrl = "http://localhost:8080"; // 后端接口
|
||||
const baseUrl = 'http://localhost:8080' // 后端接口
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(({ mode, command }) => {
|
||||
const env = loadEnv(mode, process.cwd());
|
||||
env.VITE_APP_BASE_API = env.VITE_APP_BASE_API || "/dev-api";
|
||||
env.VITE_APP_TITLE = env.VITE_APP_TITLE || "博诺思管理系统";
|
||||
const { VITE_APP_ENV } = env;
|
||||
const env = loadEnv(mode, process.cwd())
|
||||
env.VITE_APP_BASE_API = env.VITE_APP_BASE_API || '/dev-api'
|
||||
env.VITE_APP_TITLE = env.VITE_APP_TITLE || '博诺思管理系统'
|
||||
const { VITE_APP_ENV } = env
|
||||
return {
|
||||
// 部署生产环境和开发环境下的URL。
|
||||
// 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上
|
||||
// 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
|
||||
base: VITE_APP_ENV === "production" ? "/" : "/",
|
||||
plugins: createVitePlugins(env, command === "build"),
|
||||
base: VITE_APP_ENV === 'production' ? '/' : '/',
|
||||
plugins: [...createVitePlugins(env, command === 'build'), mars3dPlugin()],
|
||||
resolve: {
|
||||
// https://cn.vitejs.dev/config/#resolve-alias
|
||||
alias: {
|
||||
// 设置路径
|
||||
"~": path.resolve(__dirname, "./"),
|
||||
'~': path.resolve(__dirname, './'),
|
||||
// 设置别名
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
// https://cn.vitejs.dev/config/#resolve-extensions
|
||||
extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue"],
|
||||
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
|
||||
},
|
||||
// 让 Vite 将 .glb 视为静态资源,直接复制到产物中
|
||||
assetsInclude: ['**/*.glb'],
|
||||
// 打包配置
|
||||
build: {
|
||||
// https://vite.dev/config/build-options.html
|
||||
sourcemap: command === "build" ? false : "inline",
|
||||
outDir: "dist",
|
||||
assetsDir: "assets",
|
||||
sourcemap: command === 'build' ? false : 'inline',
|
||||
outDir: 'dist',
|
||||
assetsDir: 'assets',
|
||||
chunkSizeWarningLimit: 2000,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
chunkFileNames: "static/js/[name]-[hash].js",
|
||||
entryFileNames: "static/js/[name]-[hash].js",
|
||||
assetFileNames: "static/[ext]/[name]-[hash].[ext]",
|
||||
chunkFileNames: 'static/js/[name]-[hash].js',
|
||||
entryFileNames: 'static/js/[name]-[hash].js',
|
||||
assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -49,13 +52,13 @@ export default defineConfig(({ mode, command }) => {
|
|||
open: true,
|
||||
proxy: {
|
||||
// https://cn.vitejs.dev/config/#server-proxy
|
||||
"/dev-api": {
|
||||
'/dev-api': {
|
||||
target: baseUrl,
|
||||
changeOrigin: true,
|
||||
rewrite: (p) => p.replace(/^\/dev-api/, ""),
|
||||
rewrite: (p) => p.replace(/^\/dev-api/, ''),
|
||||
},
|
||||
// springdoc proxy
|
||||
"^/v3/api-docs/(.*)": {
|
||||
'^/v3/api-docs/(.*)': {
|
||||
target: baseUrl,
|
||||
changeOrigin: true,
|
||||
},
|
||||
|
|
@ -65,11 +68,11 @@ export default defineConfig(({ mode, command }) => {
|
|||
postcss: {
|
||||
plugins: [
|
||||
{
|
||||
postcssPlugin: "internal:charset-removal",
|
||||
postcssPlugin: 'internal:charset-removal',
|
||||
AtRule: {
|
||||
charset: (atRule) => {
|
||||
if (atRule.name === "charset") {
|
||||
atRule.remove();
|
||||
if (atRule.name === 'charset') {
|
||||
atRule.remove()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -77,5 +80,5 @@ export default defineConfig(({ mode, command }) => {
|
|||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
|
|
|||