补全所有弹窗内部内容
This commit is contained in:
@@ -1,14 +1,20 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { defineAsyncComponent } from 'vue'
|
import { defineAsyncComponent } from 'vue'
|
||||||
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
||||||
|
const Create = defineAsyncComponent(() => import('@/components/index/modal/category/Create.vue'))
|
||||||
|
|
||||||
const emit = defineEmits(['show-modal'])
|
const emit = defineEmits(['show-modal', 'show-tip'])
|
||||||
|
|
||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
emit('show-modal', {
|
emit('show-modal', {
|
||||||
title: '创建分类',
|
title: '创建分类',
|
||||||
content: '开发中...',
|
content: Create,
|
||||||
theme: 'light'
|
theme: 'light',
|
||||||
|
width: '300px',
|
||||||
|
height: '30%',
|
||||||
|
events: {
|
||||||
|
'show-tip': (payload) => emit('show-tip', payload)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { defineAsyncComponent, ref } from 'vue'
|
import { defineAsyncComponent, ref } from 'vue'
|
||||||
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
||||||
|
const Create = defineAsyncComponent(() => import('@/components/index/modal/components/Create.vue'))
|
||||||
|
|
||||||
const activeTab = ref('all')
|
const activeTab = ref('all')
|
||||||
|
|
||||||
@@ -17,12 +18,17 @@ const handleTabClick = (tabName) => {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
const emit = defineEmits(['show-modal'])
|
const emit = defineEmits(['show-modal', 'show-tip'])
|
||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
emit('show-modal', {
|
emit('show-modal', {
|
||||||
title: '创建组件',
|
title: '创建组件',
|
||||||
content: '开发中...',
|
content: Create,
|
||||||
theme: 'light'
|
theme: 'light',
|
||||||
|
height: '80%',
|
||||||
|
width: '60%',
|
||||||
|
events: {
|
||||||
|
'show-tip': (payload) => emit('show-tip', payload)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -57,9 +63,13 @@ const handleCreate = () => {
|
|||||||
background-color: #0088c3;
|
background-color: #0088c3;
|
||||||
}
|
}
|
||||||
.header-components-list {
|
.header-components-list {
|
||||||
position: absolute;
|
display: flex;
|
||||||
top: 58px;
|
flex-wrap: wrap;
|
||||||
left: 15px;
|
gap: 8px 0;
|
||||||
|
margin-top: 58px;
|
||||||
|
padding-left: 15px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
.header-components-item {
|
.header-components-item {
|
||||||
background-color: rgba(255,255,255,0.05);
|
background-color: rgba(255,255,255,0.05);
|
||||||
|
|||||||
@@ -1,19 +1,26 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { defineAsyncComponent, ref } from 'vue'
|
import { defineAsyncComponent } from 'vue'
|
||||||
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
||||||
|
const Create = defineAsyncComponent(() => import('@/components/index/modal/datascreen/Create.vue'))
|
||||||
|
const Import = defineAsyncComponent(() => import('@/components/index/modal/datascreen/Import.vue'))
|
||||||
|
|
||||||
const emit = defineEmits(['show-modal'])
|
const emit = defineEmits(['show-modal', 'show-tip'])
|
||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
emit('show-modal', {
|
emit('show-modal', {
|
||||||
title: '创建大屏',
|
title: '创建大屏',
|
||||||
content: '开发中...',
|
content: Create,
|
||||||
theme: 'light'
|
theme: 'light',
|
||||||
|
width: '80%',
|
||||||
|
height: '80%',
|
||||||
|
events: {
|
||||||
|
'show-tip': (payload) => emit('show-tip', payload)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const handleImport = () => {
|
const handleImport = () => {
|
||||||
emit('show-modal', {
|
emit('show-modal', {
|
||||||
title: '导入大屏',
|
title: '导入大屏',
|
||||||
content: '开发中...',
|
content: Import,
|
||||||
theme: 'light'
|
theme: 'light'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { defineAsyncComponent } from 'vue'
|
import { defineAsyncComponent } from 'vue'
|
||||||
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
||||||
|
const Create = defineAsyncComponent(() => import('@/components/index/modal/datasource/Create.vue'))
|
||||||
|
|
||||||
const emit = defineEmits(['show-modal'])
|
const emit = defineEmits(['show-modal', 'show-tip'])
|
||||||
|
|
||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
emit('show-modal', {
|
emit('show-modal', {
|
||||||
title: '创建数据源',
|
title: '创建数据源',
|
||||||
content: '开发中...',
|
content: Create,
|
||||||
theme: 'light'
|
theme: 'light',
|
||||||
|
width: '80%',
|
||||||
|
height: '80%',
|
||||||
|
events: {
|
||||||
|
'show-tip': (payload) => emit('show-tip', payload)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { defineAsyncComponent } from 'vue'
|
import { defineAsyncComponent } from 'vue'
|
||||||
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
||||||
|
const Upload = defineAsyncComponent(() => import('@/components/index/modal/files/Upload.vue'))
|
||||||
|
|
||||||
const emit = defineEmits(['show-modal'])
|
const emit = defineEmits(['show-modal', 'show-tip'])
|
||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
emit('show-modal', {
|
emit('show-modal', {
|
||||||
title: '上传文件',
|
title: '上传文件',
|
||||||
content: '开发中...',
|
content: Upload,
|
||||||
theme: 'light'
|
theme: 'light'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,14 +27,18 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['show-modal'])
|
const emit = defineEmits(['show-modal', 'show-tip'])
|
||||||
const handleShowModal = (payload) => {
|
const handleShowModal = (payload) => {
|
||||||
emit('show-modal', payload)
|
emit('show-modal', payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleShowTip = (payload) => {
|
||||||
|
emit('show-tip', payload)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<component :is="DynamicContent" @show-modal="handleShowModal" />
|
<component :is="DynamicContent" @show-modal="handleShowModal" @show-tip="handleShowTip" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,20 +1,25 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { defineAsyncComponent } from 'vue'
|
import { defineAsyncComponent } from 'vue'
|
||||||
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
||||||
|
const Create = defineAsyncComponent(() => import('@/components/index/modal/maps/Create.vue'))
|
||||||
|
|
||||||
const emit = defineEmits(['show-modal'])
|
const emit = defineEmits(['show-modal', 'show-tip'])
|
||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
emit('show-modal', {
|
emit('show-modal', {
|
||||||
title: '创建地图',
|
title: '创建地图',
|
||||||
content: '开发中...',
|
content: Create,
|
||||||
theme: 'light'
|
theme: 'light',
|
||||||
|
width: '40%',
|
||||||
|
height: '60%',
|
||||||
|
events: {
|
||||||
|
'show-tip': (payload) => emit('show-tip', payload)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const handleDownload = () => {
|
const devTips = () => {
|
||||||
emit('show-modal', {
|
emit('show-tip', {
|
||||||
title: '下载地图',
|
content: '功能开发中...',
|
||||||
content: '开发中...',
|
theme: 'info'
|
||||||
theme: 'light'
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -22,7 +27,7 @@ const handleDownload = () => {
|
|||||||
<header class="header">
|
<header class="header">
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<button class="header-button create-maps" @click="handleCreate"><i class="fa-solid fa-location-dot"></i> 创建地图</button>
|
<button class="header-button create-maps" @click="handleCreate"><i class="fa-solid fa-location-dot"></i> 创建地图</button>
|
||||||
<button class="header-button download-maps" @click="handleDownload"><i class="fa-solid fa-download"></i> 地图下载</button>
|
<button class="header-button download-maps" @click="devTips"><i class="fa-solid fa-download"></i> 地图下载</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<input type="text" placeholder="请输入名称" class="header-search"></input>
|
<input type="text" placeholder="请输入名称" class="header-search"></input>
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { defineAsyncComponent } from 'vue'
|
import { defineAsyncComponent } from 'vue'
|
||||||
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
||||||
|
const Create = defineAsyncComponent(() => import('@/components/index/modal/record/Create.vue'))
|
||||||
|
|
||||||
const emit = defineEmits(['show-modal'])
|
const emit = defineEmits(['show-modal', 'show-tip'])
|
||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
emit('show-modal', {
|
emit('show-modal', {
|
||||||
title: '创建数据集',
|
title: '创建数据集',
|
||||||
content: '开发中...',
|
content: Create,
|
||||||
theme: 'light'
|
theme: 'light',
|
||||||
|
width: '60%',
|
||||||
|
height: '60%',
|
||||||
|
events: {
|
||||||
|
'show-tip': (payload) => emit('show-tip', payload)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
const emit = defineEmits(['show-modal'])
|
const emit = defineEmits(['show-modal', 'show-tip'])
|
||||||
|
|
||||||
|
const devTips = () => {
|
||||||
|
emit('show-tip', {
|
||||||
|
content: '功能开发中...',
|
||||||
|
theme: 'info'
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="tools-item">
|
<div class="tools-item">
|
||||||
<h2>大屏轮播</h2>
|
<h2>大屏轮播</h2>
|
||||||
<input type="text" placeholder="请输入大屏ID多个用','间隔" class="tools-item-input"></input>
|
<input type="text" placeholder="请输入大屏ID多个用','间隔" class="tools-item-input"></input>
|
||||||
<button class="tools-item-btn">预览大屏</button>
|
<button class="tools-item-btn" @click="devTips">预览大屏</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="tools-item">
|
<div class="tools-item">
|
||||||
<h2>HTML 页面</h2>
|
<h2>HTML 页面</h2>
|
||||||
<input type="text" placeholder="请输入大屏ID" class="tools-item-input"></input>
|
<input type="text" placeholder="请输入大屏ID" class="tools-item-input"></input>
|
||||||
<button class="tools-item-btn">预览大屏</button>
|
<button class="tools-item-btn" @click="devTips">预览大屏</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { defineAsyncComponent } from 'vue'
|
import { defineAsyncComponent } from 'vue'
|
||||||
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
||||||
|
const Create = defineAsyncComponent(() => import('@/components/index/modal/variables/Create.vue'))
|
||||||
|
|
||||||
const emit = defineEmits(['show-modal'])
|
const emit = defineEmits(['show-modal', 'show-tip'])
|
||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
emit('show-modal', {
|
emit('show-modal', {
|
||||||
title: '创建变量',
|
title: '创建变量',
|
||||||
content: '开发中...',
|
content: Create,
|
||||||
theme: 'light'
|
theme: 'light',
|
||||||
|
width: '60%',
|
||||||
|
height: '50%',
|
||||||
|
events: {
|
||||||
|
'show-tip': (payload) => emit('show-tip', payload)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
107
src/components/index/modal/category/Create.vue
Normal file
107
src/components/index/modal/category/Create.vue
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<script setup>
|
||||||
|
import { defineEmits } from 'vue'
|
||||||
|
const emit = defineEmits(['close-modal', 'confirm-create', 'show-tip'])
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('close-modal')
|
||||||
|
}
|
||||||
|
|
||||||
|
const devTips = () => {
|
||||||
|
emit('show-tip', {
|
||||||
|
content: '功能开发中...',
|
||||||
|
theme: 'info'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="create-category">
|
||||||
|
<div class="create-category-items">
|
||||||
|
<span><span style="color:red;">*</span>模块名: </span>
|
||||||
|
<input type="text" placeholder="请输入 模块名"></input>
|
||||||
|
</div>
|
||||||
|
<div class="create-category-items">
|
||||||
|
<span><span style="color:red;">*</span>模块值: </span>
|
||||||
|
<input type="number" placeholder="请输入 模块值"></input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-category-btns">
|
||||||
|
<button class="confirm-btn" @click="devTips"><i class="fa-solid fa-check"></i> 保存</button>
|
||||||
|
<button class="cancel-btn" @click="handleClose"><i class="fa-solid fa-xmark"></i> 取消</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.create-category {
|
||||||
|
position: absolute;
|
||||||
|
top: 45px;
|
||||||
|
left: 15px;
|
||||||
|
max-height: calc(100vh - 150px);
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.create-category-items {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
.create-category-items span:first-child {
|
||||||
|
width: 80px;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.create-category-items input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.create-category-items input:hover {
|
||||||
|
border-color: #f472b6;
|
||||||
|
}
|
||||||
|
.create-category-items input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #e267a7;
|
||||||
|
}
|
||||||
|
.create-category-btns {
|
||||||
|
position: absolute;
|
||||||
|
align-items: center;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.confirm-btn,
|
||||||
|
.cancel-btn {
|
||||||
|
height: 32px;
|
||||||
|
width: 85px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.confirm-btn {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.confirm-btn:hover {
|
||||||
|
background-color: #46a049;
|
||||||
|
}
|
||||||
|
.confirm-btn:active {
|
||||||
|
background-color: #38803a;
|
||||||
|
}
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.cancel-btn:hover {
|
||||||
|
background-color: #da190b;
|
||||||
|
}
|
||||||
|
.cancel-btn:active {
|
||||||
|
background-color: #b2100a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
316
src/components/index/modal/components/Create.vue
Normal file
316
src/components/index/modal/components/Create.vue
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
<script setup>
|
||||||
|
import { defineEmits, ref, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
|
|
||||||
|
const emit = defineEmits(['close-modal', 'confirm-create', 'show-tip'])
|
||||||
|
|
||||||
|
const lineNumbersRef = ref(null)
|
||||||
|
const dataTextareaRef = ref(null)
|
||||||
|
const resizeObserver = ref(null)
|
||||||
|
const dataValue = ref('')
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('close-modal')
|
||||||
|
}
|
||||||
|
|
||||||
|
const devTips = () => {
|
||||||
|
emit('show-tip', {
|
||||||
|
content: '功能开发中...',
|
||||||
|
theme: 'info'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateLineNumbers = () => {
|
||||||
|
const textarea = dataTextareaRef.value
|
||||||
|
|
||||||
|
if (!textarea || !lineNumbersRef.value) return
|
||||||
|
|
||||||
|
const lines = textarea.value.split('\n').length
|
||||||
|
lineNumbersRef.value.innerHTML = Array.from({ length: lines }, (_, i) =>
|
||||||
|
`<span style="line-height: 1.5em;">${i + 1}</span>`
|
||||||
|
).join('')
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
if (!textarea || !lineNumbersRef.value) return
|
||||||
|
|
||||||
|
const newHeight = Math.max(textarea.scrollHeight, textarea.clientHeight)
|
||||||
|
lineNumbersRef.value.style.height = `${newHeight}px`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
if (dataTextareaRef.value) {
|
||||||
|
updateLineNumbers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
const textarea = dataTextareaRef.value
|
||||||
|
if (lineNumbersRef.value && textarea) {
|
||||||
|
lineNumbersRef.value.style.transform = `translateY(${-textarea.scrollTop}px)`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setupResizeObserver = () => {
|
||||||
|
if (!dataTextareaRef.value) return
|
||||||
|
|
||||||
|
resizeObserver.value = new ResizeObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (lineNumbersRef.value) {
|
||||||
|
const textarea = dataTextareaRef.value
|
||||||
|
if (textarea) {
|
||||||
|
const newHeight = Math.max(textarea.scrollHeight, textarea.clientHeight)
|
||||||
|
lineNumbersRef.value.style.height = `${newHeight}px`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
resizeObserver.value.observe(dataTextareaRef.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
updateLineNumbers()
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
setupResizeObserver()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', handleResize)
|
||||||
|
if (resizeObserver.value) {
|
||||||
|
resizeObserver.value.disconnect()
|
||||||
|
resizeObserver.value = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="create-components">
|
||||||
|
<div class="components-row">
|
||||||
|
<div class="create-components-items name-field">
|
||||||
|
<span><span style="color:red;">*</span>组件名称: </span>
|
||||||
|
<input type="text" placeholder="请输入 组件名称"></input>
|
||||||
|
</div>
|
||||||
|
<div class="create-components-items type-field">
|
||||||
|
<span><span style="color:red;">*</span>组件类型: </span>
|
||||||
|
<select><option>请选择组件类型</option></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-components-items full-width">
|
||||||
|
<span>组件数据: </span>
|
||||||
|
<div class="textarea-container">
|
||||||
|
<div class="line-numbers" ref="lineNumbersRef"></div>
|
||||||
|
<textarea ref="dataTextareaRef" class="data-textarea" @input="updateLineNumbers" @scroll="handleScroll" v-model="dataValue"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-components-items">
|
||||||
|
<span></span>
|
||||||
|
<button class="run-btn" @click="devTips"><i class="fa-solid fa-check"></i> 运行</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="create-components-items full-width">
|
||||||
|
<span>组件预览: </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-components-btns">
|
||||||
|
<button class="confirm-btn" @click="devTips"><i class="fa-solid fa-check"></i> 保存</button>
|
||||||
|
<button class="cancel-btn" @click="handleClose"><i class="fa-solid fa-xmark"></i> 取消</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.create-components {
|
||||||
|
position: absolute;
|
||||||
|
top: 45px;
|
||||||
|
left: 10px;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 70px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.components-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.create-components-items {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
min-height: 40px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.create-components-items.full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.create-components-items span:first-child {
|
||||||
|
width: 100px;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-self: flex-start;
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
.create-components-items input,
|
||||||
|
.create-components-items select {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
.create-components-items input:hover,
|
||||||
|
.create-components-items select:hover {
|
||||||
|
border-color: #f472b6;
|
||||||
|
}
|
||||||
|
.create-components-items input:focus,
|
||||||
|
.create-components-items select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #e267a7;
|
||||||
|
box-shadow: 0 0 0 2px rgba(226, 103, 167, 0.2);
|
||||||
|
}
|
||||||
|
.create-components-items select {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.textarea-container {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 100px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.line-numbers {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 35px;
|
||||||
|
padding: 8px 0;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
text-align: center;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
user-select: none;
|
||||||
|
color: #999;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
will-change: transform;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.data-textarea {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 256px;
|
||||||
|
padding: 8px 12px 8px 45px;
|
||||||
|
border: none;
|
||||||
|
resize: vertical;
|
||||||
|
background: transparent;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5em;
|
||||||
|
outline: none;
|
||||||
|
color: inherit;
|
||||||
|
overflow: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.data-textarea:hover {
|
||||||
|
border-color: #f472b6;
|
||||||
|
}
|
||||||
|
.run-btn {
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 80px;
|
||||||
|
height: 32px;
|
||||||
|
background-color: #f472b6;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.run-btn:hover {
|
||||||
|
background-color: #da609b;
|
||||||
|
}
|
||||||
|
.run-btn:active {
|
||||||
|
background-color: #b24a80;
|
||||||
|
}
|
||||||
|
.create-components::-webkit-scrollbar,
|
||||||
|
.data-textarea::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
.create-components::-webkit-scrollbar-track,
|
||||||
|
.data-textarea::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.create-components::-webkit-scrollbar-thumb,
|
||||||
|
.data-textarea::-webkit-scrollbar-thumb {
|
||||||
|
background: #c1c1c1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.create-components::-webkit-scrollbar-thumb:hover,
|
||||||
|
.data-textarea::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a8a8a8;
|
||||||
|
}
|
||||||
|
.data-textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.create-components-btns {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.confirm-btn,
|
||||||
|
.cancel-btn {
|
||||||
|
height: 32px;
|
||||||
|
width: 85px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.confirm-btn {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.confirm-btn:hover {
|
||||||
|
background-color: #46a049;
|
||||||
|
}
|
||||||
|
.confirm-btn:active {
|
||||||
|
background-color: #38803a;
|
||||||
|
}
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.cancel-btn:hover {
|
||||||
|
background-color: #da190b;
|
||||||
|
}
|
||||||
|
.cancel-btn:active {
|
||||||
|
background-color: #b2100a;
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.components-row {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
.name-field, .type-field {
|
||||||
|
width: calc(50% - 7.5px);
|
||||||
|
}
|
||||||
|
.name-field {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
156
src/components/index/modal/datascreen/Create.vue
Normal file
156
src/components/index/modal/datascreen/Create.vue
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
<script setup>
|
||||||
|
import { defineEmits } from 'vue'
|
||||||
|
const emit = defineEmits(['close-modal', 'confirm-create', 'show-tip'])
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('close-modal')
|
||||||
|
}
|
||||||
|
|
||||||
|
const devTips = () => {
|
||||||
|
emit('show-tip', {
|
||||||
|
content: '功能开发中...',
|
||||||
|
theme: 'info'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="create-datascreen">
|
||||||
|
<div class="create-datascreen-items">
|
||||||
|
<span><span style="color:red;">*</span>分组: </span>
|
||||||
|
<select><option>请选择分组</option></select>
|
||||||
|
</div>
|
||||||
|
<div class="create-datascreen-items">
|
||||||
|
<span><span style="color:red;">*</span>大屏名称: </span>
|
||||||
|
<input type="text" placeholder="请输入 大屏名称"></input>
|
||||||
|
</div>
|
||||||
|
<div class="create-datascreen-items">
|
||||||
|
<span>密码: </span>
|
||||||
|
<input type="password" placeholder="请输入 密码"></input>
|
||||||
|
</div>
|
||||||
|
<div class="create-datascreen-items">
|
||||||
|
<span><span style="color:red;">*</span>大屏尺寸: </span>
|
||||||
|
<div class="size-inputs">
|
||||||
|
<div class="size-inputs">
|
||||||
|
<input type="text" placeholder="请输入 宽度" value="1920">
|
||||||
|
<span class="size-separator"> <i class="fa-solid fa-xmark"></i> </span>
|
||||||
|
<input type="text" placeholder="请输入 高度" value="1080">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-datascreen-items">
|
||||||
|
<span>缩略图: </span>
|
||||||
|
<button @click="devTips"><i class="fa-solid fa-upload"></i> 点击上传</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-datascreen-btns">
|
||||||
|
<button class="confirm-btn" @click="devTips"><i class="fa-solid fa-check"></i> 确认创建</button>
|
||||||
|
<button class="cancel-btn" @click="handleClose"><i class="fa-solid fa-xmark"></i> 关闭</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.create-datascreen {
|
||||||
|
position: absolute;
|
||||||
|
top: 40px;
|
||||||
|
left: 20px;
|
||||||
|
max-height: calc(100vh - 150px);
|
||||||
|
width: calc(100% - 64px);
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.create-datascreen-items {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
.create-datascreen-items span:first-child {
|
||||||
|
width: 100px;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.create-datascreen-items input,
|
||||||
|
.create-datascreen-items select,
|
||||||
|
.create-datascreen-items button {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.create-datascreen-items input:hover {
|
||||||
|
border-color: #f472b6;
|
||||||
|
}
|
||||||
|
.create-datascreen-items input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #e267a7;
|
||||||
|
}
|
||||||
|
.create-datascreen-items select {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.create-datascreen-items select:hover {
|
||||||
|
border-color: #f472b6;
|
||||||
|
}
|
||||||
|
.create-datascreen-items select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #e267a7;
|
||||||
|
}
|
||||||
|
.create-datascreen-items button:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
.create-datascreen-items button:active {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
.size-inputs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.size-inputs input {
|
||||||
|
width: 40px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.separator {
|
||||||
|
margin: 0 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.create-datascreen-btns {
|
||||||
|
position: absolute;
|
||||||
|
align-items: center;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.confirm-btn,
|
||||||
|
.cancel-btn {
|
||||||
|
height: 35px;
|
||||||
|
width: 128px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.confirm-btn {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.confirm-btn:hover {
|
||||||
|
background-color: #46a049;
|
||||||
|
}
|
||||||
|
.confirm-btn:active {
|
||||||
|
background-color: #38803a;
|
||||||
|
}
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.cancel-btn:hover {
|
||||||
|
background-color: #da190b;
|
||||||
|
}
|
||||||
|
.cancel-btn:active {
|
||||||
|
background-color: #b2100a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
8
src/components/index/modal/datascreen/Import.vue
Normal file
8
src/components/index/modal/datascreen/Import.vue
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<script setup>
|
||||||
|
const emit = defineEmits(['close-modal', 'confirm-create', 'show-tip'])
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
导入功能只需要点击这里或者将文件拖拽到这里即可导入<br>
|
||||||
|
但是导入功能目前暂不可用<br>
|
||||||
|
因为喵喵们正在开发这个功能!
|
||||||
|
</template>
|
||||||
222
src/components/index/modal/datasource/Create.vue
Normal file
222
src/components/index/modal/datasource/Create.vue
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
<script setup>
|
||||||
|
import { defineEmits } from 'vue'
|
||||||
|
const emit = defineEmits(['close-modal', 'confirm-create', 'show-tip'])
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('close-modal')
|
||||||
|
}
|
||||||
|
|
||||||
|
const devTips = () => {
|
||||||
|
emit('show-tip', {
|
||||||
|
content: '功能开发中...',
|
||||||
|
theme: 'info'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="create-datasource">
|
||||||
|
<div class="datasource-grid">
|
||||||
|
<div class="grid-item">
|
||||||
|
<span><span style="color:red;">*</span>名称: </span>
|
||||||
|
<input type="text" placeholder="请输入 名称">
|
||||||
|
</div>
|
||||||
|
<div class="grid-item">
|
||||||
|
<span><span style="color:red;">*</span>类型: </span>
|
||||||
|
<select><option>请选择类型</option></select>
|
||||||
|
</div>
|
||||||
|
<div class="grid-item">
|
||||||
|
<span><span style="color:red;">*</span>用户名: </span>
|
||||||
|
<input type="text" placeholder="请输入 用户名">
|
||||||
|
</div>
|
||||||
|
<div class="grid-item">
|
||||||
|
<span><span style="color:red;">*</span>密码: </span>
|
||||||
|
<input type="password" placeholder="请输入 密码">
|
||||||
|
</div>
|
||||||
|
<div class="grid-item">
|
||||||
|
<span><span style="color:red;">*</span>连接地址: </span>
|
||||||
|
<input type="text" placeholder="请输入 连接地址">
|
||||||
|
</div>
|
||||||
|
<div class="grid-item">
|
||||||
|
<span><span style="color:red;">*</span>数据库名: </span>
|
||||||
|
<input type="text" placeholder="请输入 数据库名">
|
||||||
|
</div>
|
||||||
|
<div class="grid-item full-width grid-column remark-item">
|
||||||
|
<span>备注: </span>
|
||||||
|
<textarea placeholder="请输入 备注"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-datasource-btns">
|
||||||
|
<button class="confirm-btn" @click="devTips"><i class="fa-solid fa-check"></i> 保存</button>
|
||||||
|
<button class="cancel-btn" @click="handleClose"><i class="fa-solid fa-xmark"></i> 取消</button>
|
||||||
|
<button class="test-btn" @click="devTips"><i class="fa-solid fa-link"></i> 测试连接</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.create-datasource {
|
||||||
|
position: absolute;
|
||||||
|
top: 45px;
|
||||||
|
left: 15px;
|
||||||
|
right: 15px;
|
||||||
|
bottom: 70px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.create-datasource::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
.create-datasource::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.create-datasource::-webkit-scrollbar-thumb {
|
||||||
|
background: #c1c1c1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.create-datasource::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a8a8a8;
|
||||||
|
}
|
||||||
|
.datasource-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 15px 20px;
|
||||||
|
padding: 0 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.grid-column {
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
|
.grid-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
min-height: 40px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.grid-item span:first-child {
|
||||||
|
width: 100px;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
.grid-item input,
|
||||||
|
.grid-item select,
|
||||||
|
.grid-item textarea {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.grid-item input:hover,
|
||||||
|
.grid-item select:hover,
|
||||||
|
.grid-item textarea:hover {
|
||||||
|
border-color: #f472b6;
|
||||||
|
}
|
||||||
|
.grid-item input:focus,
|
||||||
|
.grid-item select:focus,
|
||||||
|
.grid-item textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #e267a7;
|
||||||
|
}
|
||||||
|
.remark-item span:first-child {
|
||||||
|
width: 100px;
|
||||||
|
text-align: right;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
margin-right: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.remark-item textarea {
|
||||||
|
height: 250px;
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 100px;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding: 12px;
|
||||||
|
width: 100% !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.datasource-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
.grid-column {
|
||||||
|
grid-column: span 1 !important;
|
||||||
|
}
|
||||||
|
.grid-item {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.grid-item span:first-child {
|
||||||
|
width: 100px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.grid-item input,
|
||||||
|
.grid-item select,
|
||||||
|
.grid-item textarea {
|
||||||
|
width: calc(100% - 115px);
|
||||||
|
}
|
||||||
|
.remark-item textarea {
|
||||||
|
width: calc(100% - 115px) !important;
|
||||||
|
min-height: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.create-datasource-btns {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.confirm-btn,
|
||||||
|
.cancel-btn,
|
||||||
|
.test-btn {
|
||||||
|
height: 32px;
|
||||||
|
width: 85px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.confirm-btn {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.confirm-btn:hover {
|
||||||
|
background-color: #46a049;
|
||||||
|
}
|
||||||
|
.confirm-btn:active {
|
||||||
|
background-color: #38803a;
|
||||||
|
}
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.cancel-btn:hover {
|
||||||
|
background-color: #da190b;
|
||||||
|
}
|
||||||
|
.cancel-btn:active {
|
||||||
|
background-color: #b2100a;
|
||||||
|
}
|
||||||
|
.test-btn {
|
||||||
|
width: 108px;
|
||||||
|
background-color: #f472b6;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.test-btn:hover {
|
||||||
|
background-color: #da5a9e;
|
||||||
|
}
|
||||||
|
.test-btn:active {
|
||||||
|
background-color: #b23a80;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
8
src/components/index/modal/files/Upload.vue
Normal file
8
src/components/index/modal/files/Upload.vue
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<script setup>
|
||||||
|
const emit = defineEmits(['close-modal', 'confirm-create', 'show-tip'])
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
上传文件只需要点击这里或者将文件拖拽到这里即可导入<br>
|
||||||
|
但是上次文件功能目前暂不可用<br>
|
||||||
|
因为喵喵们正在开发这个功能!
|
||||||
|
</template>
|
||||||
190
src/components/index/modal/maps/Create.vue
Normal file
190
src/components/index/modal/maps/Create.vue
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
<script setup>
|
||||||
|
import { defineEmits } from 'vue'
|
||||||
|
const emit = defineEmits(['close-modal', 'confirm-create', 'show-tip'])
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('close-modal')
|
||||||
|
}
|
||||||
|
|
||||||
|
const devTips = () => {
|
||||||
|
emit('show-tip', {
|
||||||
|
content: '功能开发中...',
|
||||||
|
theme: 'info'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="create-maps">
|
||||||
|
<div class="create-maps-items">
|
||||||
|
<span>上一级别: </span>
|
||||||
|
<input type="text" placeholder="请输入 上一级别"></input>
|
||||||
|
</div>
|
||||||
|
<div class="create-maps-items">
|
||||||
|
<span><span style="color:red;">*</span>地图名称: </span>
|
||||||
|
<input type="text" placeholder="请输入 地图名称"></input>
|
||||||
|
</div>
|
||||||
|
<div class="create-maps-items">
|
||||||
|
<span><span style="color:red;">*</span>地图级别: </span>
|
||||||
|
<select><option>请选择 地图级别</option></select>
|
||||||
|
</div>
|
||||||
|
<div class="create-maps-items">
|
||||||
|
<span><span style="color:red;">*</span>地图编号: </span>
|
||||||
|
<input type="text" placeholder="请输入 内容"></input>
|
||||||
|
</div>
|
||||||
|
<div class="create-maps-items full-width">
|
||||||
|
<span>地图数据: </span>
|
||||||
|
<div class="textarea-container">
|
||||||
|
<textarea class="data-textarea" placeholder="地图数据" readonly></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-maps-items">
|
||||||
|
<span></span>
|
||||||
|
<button class="upload-btn" @click="devTips"><i class="fa-solid fa-upload"></i> 点击上传</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-maps-btns">
|
||||||
|
<button class="confirm-btn" @click="devTips"><i class="fa-solid fa-check"></i> 保存</button>
|
||||||
|
<button class="cancel-btn" @click="handleClose"><i class="fa-solid fa-xmark"></i> 取消</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.create-maps {
|
||||||
|
position: absolute;
|
||||||
|
top: 45px;
|
||||||
|
left: 15px;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 70px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.create-maps-items {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
.create-maps-items span:first-child {
|
||||||
|
width: 100px;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.create-maps-items input,
|
||||||
|
.create-maps-items select {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.create-maps-items input:hover,
|
||||||
|
.create-maps-items select:hover {
|
||||||
|
border-color: #f472b6;
|
||||||
|
}
|
||||||
|
.create-maps-items input:focus,
|
||||||
|
.create-maps-items select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #e267a7;
|
||||||
|
}
|
||||||
|
.textarea-container {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 60px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.data-textarea {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: calc(100% - 24px);
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: none;
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 100px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
color: #666;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.create-maps::-webkit-scrollbar,
|
||||||
|
.data-textarea::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
.create-maps::-webkit-scrollbar-track,
|
||||||
|
.data-textarea::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.create-maps::-webkit-scrollbar-thumb,
|
||||||
|
.data-textarea::-webkit-scrollbar-thumb {
|
||||||
|
background: #c1c1c1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.create-maps::-webkit-scrollbar-thumb:hover,
|
||||||
|
.data-textarea::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a8a8a8;
|
||||||
|
}
|
||||||
|
.data-textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.upload-btn {
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 110px;
|
||||||
|
height: 32px;
|
||||||
|
background-color: #f472b6;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.upload-btn:hover {
|
||||||
|
background-color: #da609b;
|
||||||
|
}
|
||||||
|
.upload-btn:active {
|
||||||
|
background-color: #b24a80;
|
||||||
|
}
|
||||||
|
.create-maps-btns {
|
||||||
|
position: absolute;
|
||||||
|
align-items: center;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.confirm-btn,
|
||||||
|
.cancel-btn {
|
||||||
|
height: 32px;
|
||||||
|
width: 85px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.confirm-btn {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.confirm-btn:hover {
|
||||||
|
background-color: #46a049;
|
||||||
|
}
|
||||||
|
.confirm-btn:active {
|
||||||
|
background-color: #38803a;
|
||||||
|
}
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.cancel-btn:hover {
|
||||||
|
background-color: #da190b;
|
||||||
|
}
|
||||||
|
.cancel-btn:active {
|
||||||
|
background-color: #b2100a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
331
src/components/index/modal/record/Create.vue
Normal file
331
src/components/index/modal/record/Create.vue
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
<script setup>
|
||||||
|
import { defineEmits, ref, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
|
|
||||||
|
const emit = defineEmits(['close-modal', 'confirm-create', 'show-tip'])
|
||||||
|
|
||||||
|
const lineNumbersRef = ref(null)
|
||||||
|
const filterTextareaRef = ref(null)
|
||||||
|
const resizeObserver = ref(null)
|
||||||
|
const filterValue = ref('(data)=>{\n return {\n data\n }\n}')
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('close-modal')
|
||||||
|
}
|
||||||
|
|
||||||
|
const devTips = () => {
|
||||||
|
emit('show-tip', {
|
||||||
|
content: '功能开发中...',
|
||||||
|
theme: 'info'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateLineNumbers = () => {
|
||||||
|
const textarea = filterTextareaRef.value
|
||||||
|
|
||||||
|
if (!textarea || !lineNumbersRef.value) return
|
||||||
|
|
||||||
|
const lines = textarea.value.split('\n').length
|
||||||
|
lineNumbersRef.value.innerHTML = Array.from({ length: lines }, (_, i) =>
|
||||||
|
`<span style="line-height: 1.5em;">${i + 1}</span>`
|
||||||
|
).join('')
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
if (!textarea || !lineNumbersRef.value) return
|
||||||
|
|
||||||
|
const newHeight = Math.max(textarea.scrollHeight, textarea.clientHeight)
|
||||||
|
lineNumbersRef.value.style.height = `${newHeight}px`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
if (filterTextareaRef.value) {
|
||||||
|
updateLineNumbers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
const textarea = filterTextareaRef.value
|
||||||
|
if (lineNumbersRef.value && textarea) {
|
||||||
|
lineNumbersRef.value.style.transform = `translateY(${-textarea.scrollTop}px)`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setupResizeObserver = () => {
|
||||||
|
if (!filterTextareaRef.value) return
|
||||||
|
|
||||||
|
resizeObserver.value = new ResizeObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (lineNumbersRef.value) {
|
||||||
|
const textarea = filterTextareaRef.value
|
||||||
|
if (textarea) {
|
||||||
|
const newHeight = Math.max(textarea.scrollHeight, textarea.clientHeight)
|
||||||
|
lineNumbersRef.value.style.height = `${newHeight}px`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
resizeObserver.value.observe(filterTextareaRef.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
updateLineNumbers()
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
setupResizeObserver()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', handleResize)
|
||||||
|
if (resizeObserver.value) {
|
||||||
|
resizeObserver.value.disconnect()
|
||||||
|
resizeObserver.value = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="create-record">
|
||||||
|
<div class="record-row">
|
||||||
|
<div class="create-record-items name-field">
|
||||||
|
<span><span style="color:red;">*</span>名称: </span>
|
||||||
|
<input type="text" placeholder="请输入 名称"></input>
|
||||||
|
</div>
|
||||||
|
<div class="create-record-items type-field">
|
||||||
|
<span><span style="color:red;">*</span>类型: </span>
|
||||||
|
<select><option>请选择类型</option></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-record-items full-width">
|
||||||
|
<span>过滤器: </span>
|
||||||
|
<div class="textarea-container">
|
||||||
|
<div class="line-numbers" ref="lineNumbersRef"></div>
|
||||||
|
<textarea ref="filterTextareaRef" class="filter-textarea" @input="updateLineNumbers" @scroll="handleScroll" v-model="filterValue"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="create-record-items full-width">
|
||||||
|
<span>响应返回: </span>
|
||||||
|
<div class="textarea-container">
|
||||||
|
<textarea class="response-textarea" placeholder="响应返回内容" readonly></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-record-btns">
|
||||||
|
<button class="confirm-btn" @click="devTips"><i class="fa-solid fa-check"></i> 保存</button>
|
||||||
|
<button class="cancel-btn" @click="handleClose"><i class="fa-solid fa-xmark"></i> 取消</button>
|
||||||
|
<button class="reload-btn" @click="devTips"><i class="fa-solid fa-arrow-rotate-right"></i> 刷新数据</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.create-record {
|
||||||
|
position: absolute;
|
||||||
|
top: 45px;
|
||||||
|
left: 10px;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 70px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.record-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.create-record-items {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
min-height: 40px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.create-record-items.full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.create-record-items span:first-child {
|
||||||
|
width: 80px;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-self: flex-start;
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
.create-record-items input,
|
||||||
|
.create-record-items select {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
.create-record-items input:hover,
|
||||||
|
.create-record-items select:hover {
|
||||||
|
border-color: #f472b6;
|
||||||
|
}
|
||||||
|
.create-record-items input:focus,
|
||||||
|
.create-record-items select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #e267a7;
|
||||||
|
box-shadow: 0 0 0 2px rgba(226, 103, 167, 0.2);
|
||||||
|
}
|
||||||
|
.create-record-items select {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.textarea-container {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 100px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.line-numbers {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 35px;
|
||||||
|
padding: 8px 0;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
text-align: center;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
user-select: none;
|
||||||
|
color: #999;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
will-change: transform;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.filter-textarea {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100px;
|
||||||
|
padding: 8px 12px 8px 45px;
|
||||||
|
border: none;
|
||||||
|
resize: vertical;
|
||||||
|
background: transparent;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5em;
|
||||||
|
outline: none;
|
||||||
|
color: inherit;
|
||||||
|
overflow: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.filter-textarea:hover {
|
||||||
|
border-color: #f472b6;
|
||||||
|
}
|
||||||
|
.response-textarea {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: calc(100% - 24px);
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: none;
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 100px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
color: #666;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.create-record::-webkit-scrollbar,
|
||||||
|
.filter-textarea::-webkit-scrollbar,
|
||||||
|
.response-textarea::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
.create-record::-webkit-scrollbar-track,
|
||||||
|
.filter-textarea::-webkit-scrollbar-track,
|
||||||
|
.response-textarea::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.create-record::-webkit-scrollbar-thumb,
|
||||||
|
.filter-textarea::-webkit-scrollbar-thumb,
|
||||||
|
.response-textarea::-webkit-scrollbar-thumb {
|
||||||
|
background: #c1c1c1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.create-record::-webkit-scrollbar-thumb:hover,
|
||||||
|
.filter-textarea::-webkit-scrollbar-thumb:hover,
|
||||||
|
.response-textarea::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a8a8a8;
|
||||||
|
}
|
||||||
|
.filter-textarea:focus,
|
||||||
|
.response-textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.create-record-btns {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.confirm-btn,
|
||||||
|
.cancel-btn,
|
||||||
|
.reload-btn {
|
||||||
|
height: 32px;
|
||||||
|
width: 85px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.confirm-btn {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.confirm-btn:hover {
|
||||||
|
background-color: #46a049;
|
||||||
|
}
|
||||||
|
.confirm-btn:active {
|
||||||
|
background-color: #38803a;
|
||||||
|
}
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.cancel-btn:hover {
|
||||||
|
background-color: #da190b;
|
||||||
|
}
|
||||||
|
.cancel-btn:active {
|
||||||
|
background-color: #b2100a;
|
||||||
|
}
|
||||||
|
.reload-btn {
|
||||||
|
width: 108px;
|
||||||
|
background-color: #f472b6;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.reload-btn:hover {
|
||||||
|
background-color: #da5a9e;
|
||||||
|
}
|
||||||
|
.reload-btn:active {
|
||||||
|
background-color: #b23a80;
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.record-row {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
.name-field, .type-field {
|
||||||
|
width: calc(50% - 7.5px);
|
||||||
|
}
|
||||||
|
.name-field {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
147
src/components/index/modal/variables/Create.vue
Normal file
147
src/components/index/modal/variables/Create.vue
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
<script setup>
|
||||||
|
import { defineEmits } from 'vue'
|
||||||
|
const emit = defineEmits(['close-modal', 'confirm-create', 'show-tip'])
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('close-modal')
|
||||||
|
}
|
||||||
|
|
||||||
|
const devTips = () => {
|
||||||
|
emit('show-tip', {
|
||||||
|
content: '功能开发中...',
|
||||||
|
theme: 'info'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="create-variables">
|
||||||
|
<div class="create-variables-items">
|
||||||
|
<span><span style="color:red;">*</span>名称: </span>
|
||||||
|
<input type="text" placeholder="请输入 名称"></input>
|
||||||
|
</div>
|
||||||
|
<div class="create-variables-items">
|
||||||
|
<span><span style="color:red;">*</span>变量名: </span>
|
||||||
|
<input type="text" placeholder="请输入 变量名"></input>
|
||||||
|
</div>
|
||||||
|
<div class="create-variables-items">
|
||||||
|
<span><span style="color:red;">*</span>变量值: </span>
|
||||||
|
<div class="textarea-container">
|
||||||
|
<textarea type="text" class="value-textarea" placeholder="请输入 变量值"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-variables-btns">
|
||||||
|
<button class="confirm-btn" @click="devTips"><i class="fa-solid fa-check"></i> 保存</button>
|
||||||
|
<button class="cancel-btn" @click="handleClose"><i class="fa-solid fa-xmark"></i> 取消</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.create-variables {
|
||||||
|
position: absolute;
|
||||||
|
top: 45px;
|
||||||
|
left: 15px;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 70px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.create-variables::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
.create-variables::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.create-variables::-webkit-scrollbar-thumb {
|
||||||
|
background: #c1c1c1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.create-variables::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a8a8a8;
|
||||||
|
}
|
||||||
|
.create-variables-items {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
.create-variables-items span:first-child {
|
||||||
|
width: 80px;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.create-variables-items input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.create-variables-items input:hover {
|
||||||
|
border-color: #f472b6;
|
||||||
|
}
|
||||||
|
.create-variables-items input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #e267a7;
|
||||||
|
}
|
||||||
|
.textarea-container {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 90px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.value-textarea {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: calc(100% - 24px);
|
||||||
|
min-height: 90px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
resize: vertical;
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.create-variables-btns {
|
||||||
|
position: absolute;
|
||||||
|
align-items: center;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.confirm-btn,
|
||||||
|
.cancel-btn {
|
||||||
|
height: 32px;
|
||||||
|
width: 85px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.confirm-btn {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.confirm-btn:hover {
|
||||||
|
background-color: #46a049;
|
||||||
|
}
|
||||||
|
.confirm-btn:active {
|
||||||
|
background-color: #38803a;
|
||||||
|
}
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.cancel-btn:hover {
|
||||||
|
background-color: #da190b;
|
||||||
|
}
|
||||||
|
.cancel-btn:active {
|
||||||
|
background-color: #b2100a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -70,10 +70,8 @@ const getDimensionValue = (value) => {
|
|||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
min-height: 80px;
|
min-height: 60px;
|
||||||
max-width: 85%;
|
overflow: hidden;
|
||||||
max-height: 85%;
|
|
||||||
overflow: auto;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 99999;
|
z-index: 99999;
|
||||||
}
|
}
|
||||||
@@ -139,6 +137,6 @@ const getDimensionValue = (value) => {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.modal-main {
|
.modal-main {
|
||||||
padding: 10px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
220
src/components/layout/Tip.vue
Normal file
220
src/components/layout/Tip.vue
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted, onUnmounted, nextTick, watch } from 'vue'
|
||||||
|
|
||||||
|
const notifications = ref([])
|
||||||
|
const timeouts = ref([])
|
||||||
|
|
||||||
|
const visibleNotifications = computed(() => {
|
||||||
|
return notifications.value
|
||||||
|
.filter(n => n && n.show)
|
||||||
|
.sort((a, b) => a.createdAt - b.createdAt)
|
||||||
|
})
|
||||||
|
|
||||||
|
const clearAllTimeouts = () => {
|
||||||
|
timeouts.value.forEach(timer => clearTimeout(timer))
|
||||||
|
timeouts.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const addNotification = (content, theme = 'success', duration = 3000) => {
|
||||||
|
if (!content) return
|
||||||
|
|
||||||
|
const id = Date.now() + Math.random().toString(36).slice(2, 9)
|
||||||
|
const createdAt = Date.now()
|
||||||
|
|
||||||
|
const notification = {
|
||||||
|
id,
|
||||||
|
content,
|
||||||
|
theme,
|
||||||
|
show: true,
|
||||||
|
height: 0,
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
|
||||||
|
notifications.value.push(notification)
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
const stillExists = notifications.value.some(n => n.id === id)
|
||||||
|
if (!stillExists) return
|
||||||
|
|
||||||
|
const el = document.getElementById(`notification-${id}`)
|
||||||
|
if (el) {
|
||||||
|
const styles = window.getComputedStyle(el)
|
||||||
|
const margin = parseFloat(styles.marginBottom) || 15
|
||||||
|
notification.height = el.offsetHeight + margin
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
removeNotification(id)
|
||||||
|
}, duration)
|
||||||
|
timeouts.value.push(timer)
|
||||||
|
|
||||||
|
if (notifications.value.length > 10) {
|
||||||
|
removeNotification(notifications.value[0].id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeNotification = (id) => {
|
||||||
|
const index = notifications.value.findIndex(n => n.id === id)
|
||||||
|
if (index === -1) return
|
||||||
|
|
||||||
|
const notification = notifications.value[index]
|
||||||
|
notification.show = false
|
||||||
|
|
||||||
|
const visibleNotifs = notifications.value.filter(n => n.show).sort((a, b) => a.position - b.position);
|
||||||
|
visibleNotifs.forEach((notif, i) => {
|
||||||
|
notif.position = i;
|
||||||
|
});
|
||||||
|
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
const currentIndex = notifications.value.findIndex(n => n.id === id)
|
||||||
|
if (currentIndex === -1) return
|
||||||
|
|
||||||
|
notifications.value.splice(currentIndex, 1)
|
||||||
|
|
||||||
|
for (let i = currentIndex; i < notifications.value.length; i++) {
|
||||||
|
notifications.value[i].position -= 1
|
||||||
|
}
|
||||||
|
}, 350)
|
||||||
|
timeouts.value.push(timer)
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
addNotification,
|
||||||
|
success: (content, duration) => addNotification(content, 'success', duration),
|
||||||
|
error: (content, duration) => addNotification(content, 'error', duration),
|
||||||
|
warning: (content, duration) => addNotification(content, 'warning', duration),
|
||||||
|
info: (content, duration) => addNotification(content, 'info', duration)
|
||||||
|
})
|
||||||
|
|
||||||
|
const getTopPosition = (index) => {
|
||||||
|
let top = 35;
|
||||||
|
for (let i = 0; i < index; i++) {
|
||||||
|
const notification = visibleNotifications.value[i];
|
||||||
|
if (notification && notification.height) {
|
||||||
|
top += notification.height;
|
||||||
|
} else {
|
||||||
|
top += 70;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `${top}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
content: String,
|
||||||
|
theme: {
|
||||||
|
type: String,
|
||||||
|
default: 'success',
|
||||||
|
validator: (value) => ['success', 'error', 'warning', 'info'].includes(value)
|
||||||
|
},
|
||||||
|
duration: {
|
||||||
|
type: Number,
|
||||||
|
default: 3000
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.content, (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
addNotification(newVal, props.theme, props.duration)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.content) {
|
||||||
|
addNotification(props.content, props.theme, props.duration)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearAllTimeouts()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="notifications-container">
|
||||||
|
<transition-group name="slide-fade" tag="div">
|
||||||
|
<div v-for="(notif, index) in visibleNotifications" :key="notif.id" :id="`notification-${notif.id}`" class="tip-container" :class="`theme-${notif.theme}`" :style="{ top: getTopPosition(index) }">
|
||||||
|
{{ notif.content }}
|
||||||
|
</div>
|
||||||
|
</transition-group>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.notifications-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-container {
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
z-index: 9999;
|
||||||
|
padding: 20px 12px;
|
||||||
|
border-radius: 15px;
|
||||||
|
min-width: 280px;
|
||||||
|
max-width: 80%;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
pointer-events: auto;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-success {
|
||||||
|
background: linear-gradient(135deg, #4caf50, #43a047);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-error {
|
||||||
|
background: linear-gradient(135deg, #f44336, #e53935);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-warning {
|
||||||
|
background: linear-gradient(135deg, #ff9800, #fb8c00);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-info {
|
||||||
|
background: linear-gradient(135deg, #2196f3, #1e88e5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-fade-enter-active,
|
||||||
|
.slide-fade-leave-active {
|
||||||
|
transition: all 0.35s cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-fade-enter-from,
|
||||||
|
.slide-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-fade-enter-to,
|
||||||
|
.slide-fade-leave-from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.tip-container {
|
||||||
|
min-width: 70%;
|
||||||
|
max-width: 80%;
|
||||||
|
padding: 10px 15px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
.slide-fade-enter-from,
|
||||||
|
.slide-fade-leave-to {
|
||||||
|
transform: translate(-50%, -20px);
|
||||||
|
}
|
||||||
|
.slide-fade-enter-to,
|
||||||
|
.slide-fade-leave-from {
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, defineAsyncComponent, ref } from 'vue'
|
import { onMounted, defineAsyncComponent, shallowRef } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
|
||||||
import getConfig from '@/utils/config'
|
import getConfig from '@/utils/config'
|
||||||
|
import Tip from '@/components/layout/Tip.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
const router = useRouter()
|
|
||||||
const config = getConfig()
|
const config = getConfig()
|
||||||
|
|
||||||
const Topbar = defineAsyncComponent(() => import('@/components/index/Topbar.vue'))
|
const Topbar = defineAsyncComponent(() => import('@/components/index/Topbar.vue'))
|
||||||
@@ -30,15 +28,15 @@ onMounted(() => {
|
|||||||
|
|
||||||
const Modal = defineAsyncComponent(() => import('@/components/layout/Modal.vue'))
|
const Modal = defineAsyncComponent(() => import('@/components/layout/Modal.vue'))
|
||||||
|
|
||||||
const showModal = ref(false)
|
const showModal = shallowRef(false)
|
||||||
const modalContent = ref('')
|
const modalContent = shallowRef('')
|
||||||
const modalTheme = ref('light')
|
const modalTheme = shallowRef('light')
|
||||||
const modalTitle = ref('')
|
const modalTitle = shallowRef('')
|
||||||
const modalWidth = ref('auto')
|
const modalWidth = shallowRef('auto')
|
||||||
const modalHeight = ref('auto')
|
const modalHeight = shallowRef('auto')
|
||||||
|
|
||||||
const handleShowModal = (payload) => {
|
const handleShowModal = (payload) => {
|
||||||
modalContent.value = payload.content || ''
|
modalContent.value = payload.content || null
|
||||||
modalTheme.value = ['light', 'dark'].includes(payload.theme) ? payload.theme : 'light'
|
modalTheme.value = ['light', 'dark'].includes(payload.theme) ? payload.theme : 'light'
|
||||||
modalTitle.value = payload.title || ''
|
modalTitle.value = payload.title || ''
|
||||||
showModal.value = true
|
showModal.value = true
|
||||||
@@ -49,13 +47,41 @@ const handleShowModal = (payload) => {
|
|||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
showModal.value = false
|
showModal.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tip = shallowRef(null)
|
||||||
|
|
||||||
|
const handleShowTip = (payload) => {
|
||||||
|
if (!tip.value) return;
|
||||||
|
|
||||||
|
const { content, theme = 'success', duration = 3000 } = payload;
|
||||||
|
|
||||||
|
switch(theme) {
|
||||||
|
case 'success':
|
||||||
|
tip.value.success(content, duration);
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
tip.value.error(content, duration);
|
||||||
|
break;
|
||||||
|
case 'warning':
|
||||||
|
tip.value.warning(content, duration);
|
||||||
|
break;
|
||||||
|
case 'info':
|
||||||
|
tip.value.info(content, duration);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
tip.value.success(content, duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<Topbar />
|
<Topbar />
|
||||||
<Leftbar />
|
<Leftbar />
|
||||||
<Main @show-modal="handleShowModal" />
|
<Main @show-modal="handleShowModal" @show-tip="handleShowTip" />
|
||||||
|
|
||||||
<Modal v-if="showModal" @close="closeModal" :theme="modalTheme" :title="modalTitle" :width="modalWidth" :height="modalHeight">
|
<Modal v-if="showModal" @close="closeModal" :theme="modalTheme" :title="modalTitle" :width="modalWidth" :height="modalHeight">
|
||||||
{{ modalContent }}
|
<component :is="modalContent" v-if="modalContent && typeof modalContent === 'object'" @close-modal="closeModal" @show-tip="handleShowTip" />
|
||||||
|
<template v-else>{{ modalContent || '无内容' }}</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<Tip ref="tip" />
|
||||||
</template>
|
</template>
|
||||||
Reference in New Issue
Block a user