大屏页面初始化
This commit is contained in:
12
screen-vue/src/api/bizRole.js
Normal file
12
screen-vue/src/api/bizRole.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 获取指标信息列表
|
||||
*/
|
||||
export function getHomeRoleList(params) {
|
||||
return request({
|
||||
url: '/biz/homeRole/list',
|
||||
method: 'get',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
310
screen-vue/src/components/Table/proFilterSelect.vue
Normal file
310
screen-vue/src/components/Table/proFilterSelect.vue
Normal file
@@ -0,0 +1,310 @@
|
||||
<template>
|
||||
<div class="el-card el-card--border" ref="wrapperRef" style="height: 100%; border: none; box-shadow: none;">
|
||||
<el-input
|
||||
v-if="showSearch"
|
||||
v-model="filterText"
|
||||
:placeholder="searchPlaceholder"
|
||||
clearable
|
||||
class="filter-search-input"
|
||||
@clear="handleClear"
|
||||
size="default"
|
||||
/>
|
||||
<el-divider v-if="showSearch" direction="horizontal" class="filter-tree-divider" />
|
||||
<div
|
||||
class="list-container"
|
||||
:style="{ height: listHeight, width: '100%', overflow: 'hidden' }"
|
||||
>
|
||||
<el-scrollbar height="100%" :native="true">
|
||||
<div
|
||||
class="list-item"
|
||||
v-for="item in filteredList"
|
||||
:key="item[nodeKey]"
|
||||
@mouseenter="() => hoveredItemId = item[nodeKey]"
|
||||
@mouseleave="() => handleItemLeave(item[nodeKey])"
|
||||
@click="() => handleItemClick(item)"
|
||||
:class="{ 'list-item--selected': selectedItemId === item[nodeKey] }"
|
||||
>
|
||||
<span class="item-text">{{ item[labelKey] }}</span>
|
||||
<div class="action-wrapper" v-show="hoveredItemId === item[nodeKey]">
|
||||
<el-dropdown
|
||||
trigger="click"
|
||||
placement="bottom"
|
||||
:teleported="false"
|
||||
@click.stop
|
||||
>
|
||||
<el-button size="mini" icon="MoreFilled" circle class="action-btn" />
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="() => handleEdit(item)">
|
||||
<el-icon class="menu-icon"><Edit /></el-icon>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="() => handleDelete(item)" divided>
|
||||
<el-icon class="menu-icon"><Delete /></el-icon>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="() => handleView(item)">
|
||||
<el-icon class="menu-icon"><View /></el-icon>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, defineProps, defineEmits, withDefaults, onMounted, onUnmounted, nextTick, computed } from 'vue'
|
||||
import { ElInput, ElDivider, ElScrollbar, ElDropdown, ElButton, ElDropdownMenu, ElDropdownItem, ElIcon } from 'element-plus'
|
||||
import { Edit, Delete, View, MoreFilled, Search } from '@element-plus/icons-vue'
|
||||
|
||||
export interface ListItem {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
const emit = defineEmits([
|
||||
'edit',
|
||||
'delete',
|
||||
'view',
|
||||
'item-click',
|
||||
'search-change',
|
||||
'search-clear'
|
||||
])
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
listData: ListItem[]
|
||||
showSearch?: boolean
|
||||
searchPlaceholder?: string
|
||||
nodeKey?: string
|
||||
labelKey?: string
|
||||
customFilter?: (value: string, data: ListItem) => boolean
|
||||
autoHeight?: boolean
|
||||
minHeight?: string | number
|
||||
defaultValue?: string | number
|
||||
}>(),
|
||||
{
|
||||
showSearch: true,
|
||||
searchPlaceholder: '请输入关键词过滤',
|
||||
nodeKey: 'id',
|
||||
labelKey: 'label',
|
||||
autoHeight: true,
|
||||
minHeight: '100px',
|
||||
defaultValue: ''
|
||||
}
|
||||
)
|
||||
|
||||
const filterText = ref('')
|
||||
const wrapperRef = ref<HTMLDivElement>(null)
|
||||
const hoveredItemId = ref<string | number | null>(null)
|
||||
const selectedItemId = ref<string | number | null>(props.defaultValue)
|
||||
|
||||
const listHeight = computed(() => {
|
||||
if (!props.autoHeight || !wrapperRef.value) return props.minHeight as string
|
||||
const wrapperHeight = wrapperRef.value.clientHeight
|
||||
const searchHeight = props.showSearch ? 40 : 0
|
||||
const dividerHeight = props.showSearch ? (16 + 2) : 0
|
||||
const baseMargin = 8
|
||||
return `${wrapperHeight - searchHeight - dividerHeight - baseMargin}px`
|
||||
})
|
||||
|
||||
const filteredList = computed(() => {
|
||||
if (!filterText.value) return props.listData
|
||||
if (props.customFilter) {
|
||||
return props.listData.filter(item => props.customFilter!(filterText.value, item))
|
||||
}
|
||||
return props.listData.filter(item => {
|
||||
const label = item[props.labelKey] || ''
|
||||
return label.toString().includes(filterText.value)
|
||||
})
|
||||
})
|
||||
|
||||
watch(
|
||||
() => filterText.value,
|
||||
(val) => {
|
||||
emit('search-change', val)
|
||||
},
|
||||
{ immediate: false }
|
||||
)
|
||||
|
||||
watch(() => props.defaultValue, (val) => {
|
||||
selectedItemId.value = val
|
||||
}, { immediate: true })
|
||||
|
||||
const handleItemLeave = (id: string | number) => {
|
||||
hoveredItemId.value = null
|
||||
}
|
||||
|
||||
const handleItemClick = (item: ListItem) => {
|
||||
selectedItemId.value = item[props.nodeKey]
|
||||
emit('item-click', item)
|
||||
}
|
||||
|
||||
const handleEdit = (item: ListItem) => {
|
||||
emit('edit', item)
|
||||
hoveredItemId.value = null
|
||||
}
|
||||
|
||||
const handleDelete = (item: ListItem) => {
|
||||
emit('delete', item)
|
||||
hoveredItemId.value = null
|
||||
}
|
||||
|
||||
const handleView = (item: ListItem) => {
|
||||
emit('view', item)
|
||||
hoveredItemId.value = null
|
||||
}
|
||||
|
||||
const handleClear = () => {
|
||||
filterText.value = ''
|
||||
emit('search-clear')
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
const resizeHandler = () => void listHeight.value
|
||||
|
||||
window.addEventListener('resize', resizeHandler)
|
||||
|
||||
if (window.ResizeObserver && wrapperRef.value) {
|
||||
const observer = new ResizeObserver(resizeHandler)
|
||||
observer.observe(wrapperRef.value)
|
||||
|
||||
onUnmounted(() => {
|
||||
observer.disconnect()
|
||||
window.removeEventListener('resize', resizeHandler)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
doFilter: (value: string) => {
|
||||
filterText.value = value
|
||||
},
|
||||
clearSearch: handleClear,
|
||||
getFilteredList: () => filteredList.value,
|
||||
setSelected: (id: string | number) => {
|
||||
selectedItemId.value = id
|
||||
},
|
||||
getSelected: () => {
|
||||
return filteredList.value.find(item => item[props.nodeKey] === selectedItemId.value)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-card) {
|
||||
height: 100%;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-search-input {
|
||||
margin: 0 2px !important;
|
||||
width: calc(100% - 4px) !important;
|
||||
}
|
||||
|
||||
.filter-tree-divider {
|
||||
margin: 2px 2px 0 2px !important;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.list-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 2px;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
user-select: none;
|
||||
--el-tree-node-content-hover-bg-color: #f0f9ff;
|
||||
--el-tree-node-selected-bg-color: #e6f7ff;
|
||||
--el-tree-node-selected-color: #1890ff;
|
||||
}
|
||||
|
||||
.list-item:hover {
|
||||
background-color: var(--el-tree-node-content-hover-bg-color);
|
||||
}
|
||||
|
||||
.list-item--selected {
|
||||
background-color: var(--el-tree-node-selected-bg-color) !important;
|
||||
color: var(--el-tree-node-selected-color) !important;
|
||||
}
|
||||
|
||||
.list-item--selected .item-text {
|
||||
color: var(--el-tree-node-selected-color) !important;
|
||||
}
|
||||
|
||||
.item-text {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.action-wrapper {
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
background-color: transparent !important;
|
||||
border: none !important;
|
||||
color: #909399 !important;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
color: #1890ff !important;
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: #606266;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
:deep(.el-dropdown-menu) {
|
||||
min-width: 40px !important;
|
||||
padding: 4px 0;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
:deep(.el-dropdown-item) {
|
||||
text-align: center;
|
||||
padding: 6px 8px !important;
|
||||
}
|
||||
|
||||
:deep(.el-dropdown__popper) {
|
||||
position: absolute !important;
|
||||
top: 100% !important;
|
||||
left: 50% !important;
|
||||
margin-top: 4px !important;
|
||||
transform: translateX(-50%) !important;
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
|
||||
:deep(.el-scrollbar__wrap) {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
</style>
|
||||
222
screen-vue/src/views/desktop/components/Alert.vue
Normal file
222
screen-vue/src/views/desktop/components/Alert.vue
Normal file
@@ -0,0 +1,222 @@
|
||||
<template>
|
||||
<div class="table-container">
|
||||
<div class="main-card">
|
||||
<div class="title-section">
|
||||
<h1 class="title">预警信息</h1>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="content-section">
|
||||
<div class="table-wrapper">
|
||||
<el-table
|
||||
:data="tableData"
|
||||
border
|
||||
:header-cell-style="{ background: '#f5f7fa' }"
|
||||
style="width: 100%;"
|
||||
height="100%"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-table-column
|
||||
prop="name"
|
||||
label="姓名"
|
||||
width="120"
|
||||
fixed="left"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="age"
|
||||
label="年龄"
|
||||
width="80"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="gender"
|
||||
label="性别"
|
||||
width="80"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="phone"
|
||||
label="手机号"
|
||||
width="150"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="email"
|
||||
label="邮箱"
|
||||
width="200"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="address"
|
||||
label="地址"
|
||||
width="250"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="company"
|
||||
label="公司"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="position"
|
||||
label="职位"
|
||||
width="150"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="salary"
|
||||
label="薪资"
|
||||
width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="entryDate"
|
||||
label="入职日期"
|
||||
width="150"
|
||||
fixed="right"
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElTable, ElTableColumn, ElLoading } from 'element-plus'
|
||||
|
||||
// 生成模拟数据
|
||||
const generateTableData = () => {
|
||||
const data = []
|
||||
const names = ['张三', '李四', '王五', '赵六', '钱七', '孙八', '周九', '吴十']
|
||||
const genders = ['男', '女']
|
||||
const positions = ['前端开发', '后端开发', '产品经理', 'UI设计', '测试工程师', '运维工程师']
|
||||
const companies = ['科技有限公司', '网络科技公司', '信息技术公司', '数据服务公司']
|
||||
|
||||
for (let i = 1; i <= 50; i++) {
|
||||
data.push({
|
||||
id: i,
|
||||
name: names[Math.floor(Math.random() * names.length)] + i,
|
||||
age: Math.floor(Math.random() * 30) + 20,
|
||||
gender: genders[Math.floor(Math.random() * genders.length)],
|
||||
phone: `13${Math.floor(Math.random() * 900000000) + 100000000}`,
|
||||
email: `user${i}@example.com`,
|
||||
address: `北京市朝阳区某某街道${Math.floor(Math.random() * 100)}号`,
|
||||
company: Math.floor(Math.random() * 10) + '号' + companies[Math.floor(Math.random() * companies.length)],
|
||||
position: positions[Math.floor(Math.random() * positions.length)],
|
||||
salary: `${Math.floor(Math.random() * 30) + 10}k`,
|
||||
entryDate: `202${Math.floor(Math.random() * 5) + 1}-${Math.floor(Math.random() * 12) + 1}-${Math.floor(Math.random() * 28) + 1}`
|
||||
})
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
const tableData = ref([])
|
||||
const loading = ref(true)
|
||||
|
||||
onMounted(() => {
|
||||
// 模拟接口请求延迟
|
||||
setTimeout(() => {
|
||||
tableData.value = generateTableData()
|
||||
loading.value = false
|
||||
}, 500)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.table-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 2px;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main-card {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.title-section {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
padding: 0 12px;
|
||||
box-sizing: border-box;
|
||||
background-color: #f9fafb;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: #e5e7eb;
|
||||
}
|
||||
|
||||
.content-section {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper) {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper::-webkit-scrollbar) {
|
||||
display: block;
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper::-webkit-scrollbar-track) {
|
||||
background: #f1f1f1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper::-webkit-scrollbar-thumb) {
|
||||
background: #dcdfe6;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper::-webkit-scrollbar-thumb:hover) {
|
||||
background: #c0c4cc;
|
||||
}
|
||||
|
||||
:deep(.el-table__fixed-left) {
|
||||
box-shadow: 2px 0 6px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
:deep(.el-table__fixed-right) {
|
||||
box-shadow: -2px 0 6px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
</style>
|
||||
224
screen-vue/src/views/desktop/components/Note.vue
Normal file
224
screen-vue/src/views/desktop/components/Note.vue
Normal file
@@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<div class="table-container">
|
||||
<div class="main-card">
|
||||
<div class="title-section">
|
||||
<h1 class="title">便签信息</h1>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="content-section">
|
||||
<div class="table-wrapper">
|
||||
<el-table
|
||||
:data="tableData"
|
||||
border
|
||||
:header-cell-style="{ background: '#f5f7fa' }"
|
||||
style="width: 100%;"
|
||||
height="100%"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-table-column
|
||||
prop="name"
|
||||
label="姓名"
|
||||
width="120"
|
||||
fixed="left"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="age"
|
||||
label="年龄"
|
||||
width="80"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="gender"
|
||||
label="性别"
|
||||
width="80"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="phone"
|
||||
label="手机号"
|
||||
width="150"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="email"
|
||||
label="邮箱"
|
||||
width="200"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="address"
|
||||
label="地址"
|
||||
width="250"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="company"
|
||||
label="公司"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="position"
|
||||
label="职位"
|
||||
width="150"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="salary"
|
||||
label="薪资"
|
||||
width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="entryDate"
|
||||
label="入职日期"
|
||||
width="150"
|
||||
fixed="right"
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElTable, ElTableColumn, ElLoading } from 'element-plus'
|
||||
|
||||
// 生成模拟数据
|
||||
const generateTableData = () => {
|
||||
const data = []
|
||||
const names = ['张三', '李四', '王五', '赵六', '钱七', '孙八', '周九', '吴十']
|
||||
const genders = ['男', '女']
|
||||
const positions = ['前端开发', '后端开发', '产品经理', 'UI设计', '测试工程师', '运维工程师']
|
||||
const companies = ['科技有限公司', '网络科技公司', '信息技术公司', '数据服务公司']
|
||||
|
||||
for (let i = 1; i <= 50; i++) {
|
||||
data.push({
|
||||
id: i,
|
||||
name: names[Math.floor(Math.random() * names.length)] + i,
|
||||
age: Math.floor(Math.random() * 30) + 20,
|
||||
gender: genders[Math.floor(Math.random() * genders.length)],
|
||||
phone: `13${Math.floor(Math.random() * 900000000) + 100000000}`,
|
||||
email: `user${i}@example.com`,
|
||||
address: `北京市朝阳区某某街道${Math.floor(Math.random() * 100)}号`,
|
||||
company: Math.floor(Math.random() * 10) + '号' + companies[Math.floor(Math.random() * companies.length)],
|
||||
position: positions[Math.floor(Math.random() * positions.length)],
|
||||
salary: `${Math.floor(Math.random() * 30) + 10}k`,
|
||||
entryDate: `202${Math.floor(Math.random() * 5) + 1}-${Math.floor(Math.random() * 12) + 1}-${Math.floor(Math.random() * 28) + 1}`
|
||||
})
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
const tableData = ref([])
|
||||
const loading = ref(true)
|
||||
|
||||
onMounted(() => {
|
||||
// 模拟接口请求延迟
|
||||
setTimeout(() => {
|
||||
tableData.value = generateTableData()
|
||||
loading.value = false
|
||||
}, 500)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.table-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 2px;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main-card {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.title-section {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
padding: 0 12px;
|
||||
box-sizing: border-box;
|
||||
background-color: #f9fafb;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: #e5e7eb;
|
||||
}
|
||||
|
||||
.content-section {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 表格滚动条样式优化 */
|
||||
:deep(.el-table__body-wrapper) {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper::-webkit-scrollbar) {
|
||||
display: block;
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper::-webkit-scrollbar-track) {
|
||||
background: #f1f1f1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper::-webkit-scrollbar-thumb) {
|
||||
background: #dcdfe6;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper::-webkit-scrollbar-thumb:hover) {
|
||||
background: #c0c4cc;
|
||||
}
|
||||
|
||||
/* 固定表头样式优化 */
|
||||
:deep(.el-table__fixed-left) {
|
||||
box-shadow: 2px 0 6px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
:deep(.el-table__fixed-right) {
|
||||
box-shadow: -2px 0 6px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
</style>
|
||||
313
screen-vue/src/views/desktop/components/Quick.vue
Normal file
313
screen-vue/src/views/desktop/components/Quick.vue
Normal file
@@ -0,0 +1,313 @@
|
||||
<template>
|
||||
<div class="quick-login-container">
|
||||
<div class="main-card">
|
||||
<div class="title-section">
|
||||
<h1 class="title">快捷登录</h1>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="content-section">
|
||||
<div class="card-list-wrapper" ref="cardWrapperRef">
|
||||
<div class="card-list-inner" ref="cardListRef">
|
||||
<div
|
||||
v-for="(item, index) in loginList"
|
||||
:key="index"
|
||||
class="login-card"
|
||||
@click="handleCardClick(item)"
|
||||
>
|
||||
<div class="icon-wrapper">
|
||||
<img :src="item.icon" :alt="item.name" class="login-icon" />
|
||||
</div>
|
||||
<div class="card-name">{{ item.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-button
|
||||
class="control-btn left-btn"
|
||||
@click="scrollCards('left')"
|
||||
:disabled="isLeftDisabled"
|
||||
circle
|
||||
size="mini"
|
||||
icon="ArrowLeft"
|
||||
></el-button>
|
||||
<el-button
|
||||
class="control-btn right-btn"
|
||||
@click="scrollCards('right')"
|
||||
:disabled="isRightDisabled"
|
||||
circle
|
||||
size="mini"
|
||||
icon="ArrowRight"
|
||||
></el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { ElButton, ElMessage } from 'element-plus'
|
||||
|
||||
const loginList = ref([
|
||||
{ icon: 'https://picsum.photos/120/80?1', name: '账号密码' },
|
||||
{ icon: 'https://picsum.photos/120/80?2', name: '手机验证码' },
|
||||
{ icon: 'https://picsum.photos/120/80?3', name: '微信登录' },
|
||||
{ icon: 'https://picsum.photos/120/80?4', name: '支付宝登录' },
|
||||
{ icon: 'https://picsum.photos/120/80?5', name: 'QQ登录' },
|
||||
{ icon: 'https://picsum.photos/120/80?6', name: '微博登录' },
|
||||
{ icon: 'https://picsum.photos/120/80?7', name: '邮箱登录' },
|
||||
{ icon: 'https://picsum.photos/120/80?8', name: '抖音登录' },
|
||||
{ icon: 'https://picsum.photos/120/80?9', name: '快手登录' }
|
||||
])
|
||||
|
||||
const cardWrapperRef = ref(null)
|
||||
const cardListRef = ref(null)
|
||||
const scrollLeft = ref(0)
|
||||
|
||||
const isLeftDisabled = computed(() => {
|
||||
return scrollLeft.value <= 0
|
||||
})
|
||||
|
||||
const isRightDisabled = computed(() => {
|
||||
if (!cardWrapperRef.value || !cardListRef.value) return true
|
||||
const maxScroll = cardListRef.value.scrollWidth - cardWrapperRef.value.clientWidth
|
||||
return Math.round(scrollLeft.value) >= Math.round(maxScroll)
|
||||
})
|
||||
|
||||
const scrollCards = (direction) => {
|
||||
if (!cardListRef.value || !cardWrapperRef.value) return
|
||||
const step = 128
|
||||
const maxScroll = cardListRef.value.scrollWidth - cardWrapperRef.value.clientWidth
|
||||
let newScroll = scrollLeft.value
|
||||
|
||||
if (direction === 'left') {
|
||||
newScroll = Math.max(0, newScroll - step)
|
||||
} else {
|
||||
newScroll = Math.min(maxScroll, newScroll + step)
|
||||
}
|
||||
|
||||
if (newScroll !== scrollLeft.value) {
|
||||
cardListRef.value.scrollTo({
|
||||
left: newScroll,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
scrollLeft.value = newScroll
|
||||
}
|
||||
}
|
||||
|
||||
const handleCardClick = (item) => {
|
||||
ElMessage.success(`选择了${item.name}登录`)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const list = cardListRef.value
|
||||
if (list) {
|
||||
scrollLeft.value = list.scrollLeft
|
||||
list.addEventListener('scroll', () => {
|
||||
scrollLeft.value = list.scrollLeft
|
||||
})
|
||||
}
|
||||
window.addEventListener('resize', () => {
|
||||
if (cardListRef.value) {
|
||||
scrollLeft.value = cardListRef.value.scrollLeft
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
const list = cardListRef.value
|
||||
if (list) {
|
||||
list.removeEventListener('scroll', () => {})
|
||||
}
|
||||
window.removeEventListener('resize', () => {})
|
||||
})
|
||||
|
||||
watch([isLeftDisabled, isRightDisabled], () => {}, { immediate: true })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.quick-login-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 2px;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main-card {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.title-section {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
padding: 0 12px;
|
||||
box-sizing: border-box;
|
||||
background-color: #f9fafb;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: #e5e7eb;
|
||||
}
|
||||
|
||||
.content-section {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 100;
|
||||
width: 16px !important;
|
||||
height: 16px !important;
|
||||
padding: 0 !important;
|
||||
background-color: #e6f7ff !important;
|
||||
border-color: #91d5ff !important;
|
||||
color: #1890ff !important;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
|
||||
.control-btn:disabled {
|
||||
background-color: #f0f8fb !important;
|
||||
border-color: #b3d8ea !important;
|
||||
color: #8cbfe8 !important;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.control-btn:not(:disabled):active {
|
||||
background-color: #bae7ff !important;
|
||||
border-color: #69c0ff !important;
|
||||
color: #096dd9 !important;
|
||||
}
|
||||
|
||||
.left-btn {
|
||||
left: 4px;
|
||||
}
|
||||
|
||||
.right-btn {
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
.card-list-wrapper {
|
||||
width: 100%;
|
||||
height: calc(100% - 16px);
|
||||
padding: 8px 20px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-list-inner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.card-list-inner::-webkit-scrollbar {
|
||||
display: block;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.card-list-inner::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.card-list-inner::-webkit-scrollbar-thumb {
|
||||
background: #dcdfe6;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.card-list-inner::-webkit-scrollbar-thumb:hover {
|
||||
background: #c0c4cc;
|
||||
}
|
||||
|
||||
.card-list-inner {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
height: 100%;
|
||||
width: 120px;
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
background-color: #f5f7fa;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.login-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.12);
|
||||
background-color: #eff6ff;
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.login-icon {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
object-fit: cover;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.card-name {
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding-top: 8px;
|
||||
}
|
||||
</style>
|
||||
152
screen-vue/src/views/desktop/components/UserTop.vue
Normal file
152
screen-vue/src/views/desktop/components/UserTop.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<div class="welcome-bar">
|
||||
<div class="welcome-left">
|
||||
<div class="avatar">
|
||||
<img src="https://picsum.photos/48/48" alt="头像" />
|
||||
</div>
|
||||
<div class="welcome-text">
|
||||
<div class="greeting">您好, 超级管理员, 开始您一天的工作吧!</div>
|
||||
<div class="weather">今日小雪;夜间:多云;温度:(-4.0℃ 至 2.0℃);东北风/1-3级</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="welcome-right">
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">待办</div>
|
||||
<div class="stat-value">0/0</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">日程</div>
|
||||
<div class="stat-value">0/0</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">项目</div>
|
||||
<div class="stat-value">1/7</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">团队</div>
|
||||
<div class="stat-value">6</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
userName: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
weatherInfo: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.welcome-bar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.welcome-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.welcome-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.greeting {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.weather {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.welcome-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 32px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 响应式适配 - 适配主页面小屏幕布局 */
|
||||
@media (max-width: 768px) {
|
||||
.welcome-right {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.greeting {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.weather {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
313
screen-vue/src/views/myapp/web/list.vue
Normal file
313
screen-vue/src/views/myapp/web/list.vue
Normal file
@@ -0,0 +1,313 @@
|
||||
<template>
|
||||
<div class="search-list-page">
|
||||
<div class="search-section">
|
||||
<div class="search-wrapper">
|
||||
<el-input
|
||||
v-model="searchForm.websiteName"
|
||||
placeholder="请输入搜索网站名称"
|
||||
class="search-input"
|
||||
clearable
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
<div class="search-btn-group">
|
||||
<el-button type="primary" icon="Search" @click="handleSearch">查询</el-button>
|
||||
<el-button icon="Refresh" @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="list-wrapper">
|
||||
<div class="list-section">
|
||||
<div v-if="listData.length === 0" class="empty-tip">
|
||||
<el-empty description="暂无数据" />
|
||||
</div>
|
||||
<div v-else class="card-list">
|
||||
<el-card
|
||||
v-for="item in listData"
|
||||
:key="item.websiteId"
|
||||
class="list-card"
|
||||
shadow="hover"
|
||||
>
|
||||
<div class="card-top">
|
||||
<div class="site-main">
|
||||
<el-tooltip :content="item.websiteName" placement="top">
|
||||
<div class="site-name">{{ item.websiteName }}</div>
|
||||
</el-tooltip>
|
||||
<el-tooltip :content="item.websiteUrl" placement="top">
|
||||
<div class="site-url">{{ item.websiteUrl }}</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-divider"></div>
|
||||
<div class="card-bottom">
|
||||
<span class="card-date">{{ item.createTime }}</span>
|
||||
<div class="card-actions">
|
||||
<el-button size="small" type="primary" link @click="handleVisit(item)">
|
||||
<el-icon><Link /></el-icon>
|
||||
访问
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pagination-footer">
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.pageNum"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:page-sizes="[20, 50, 99]"
|
||||
:total="pagination.total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Link } from '@element-plus/icons-vue'
|
||||
import { getWebsiteStorageList } from '@/api/bizWebsiteStorage'
|
||||
|
||||
const searchForm = reactive({
|
||||
websiteName: ''
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const listData = ref([])
|
||||
const getDataList = async () => {
|
||||
try {
|
||||
const reqParmas = {
|
||||
...searchForm,
|
||||
pageNum: pagination.pageNum,
|
||||
pageSize: pagination.pageSize,
|
||||
}
|
||||
const res = await getWebsiteStorageList(reqParmas)
|
||||
listData.value = res.list || []
|
||||
pagination.total = res.total
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleVisit = (item) => {
|
||||
window.open(item.websiteUrl, '_blank')
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.pageNum = 1
|
||||
getDataList()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, {
|
||||
websiteName: ''
|
||||
})
|
||||
pagination.pageNum = 1
|
||||
getDataList()
|
||||
}
|
||||
|
||||
const handleSizeChange = (val) => {
|
||||
pagination.pageSize = val
|
||||
getDataList()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
pagination.pageNum = val
|
||||
getDataList()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDataList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search-list-page {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
width: 100%;
|
||||
margin-bottom: 16px;
|
||||
flex-shrink: 0;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.search-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.search-btn-group {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.list-wrapper {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.list-section {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto !important;
|
||||
overflow-x: hidden !important;
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.card-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 16px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.list-card {
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #f0f0f0 !important;
|
||||
}
|
||||
|
||||
.list-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.08) !important;
|
||||
border-color: #409eff !important;
|
||||
}
|
||||
|
||||
.card-top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 6px 12px 4px;
|
||||
}
|
||||
|
||||
.site-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.site-name {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
color: #1f2937;
|
||||
margin-bottom: 2px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.site-url {
|
||||
font-size: 13px;
|
||||
color: #9ca3af;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.site-url:hover {
|
||||
color: #409eff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.card-divider {
|
||||
height: 1px;
|
||||
background: #f0f0f0;
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.card-bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 12px 8px;
|
||||
}
|
||||
|
||||
.card-date {
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.pagination-footer {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 8px 0;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
flex-shrink: 0;
|
||||
background: #fff;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.list-section::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.list-section::-webkit-scrollbar-track {
|
||||
background: #f1f5f9;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.list-section::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.list-section::-webkit-scrollbar-thumb:hover {
|
||||
background: #94a3b8;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.card-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
199
screen-vue/src/views/system/icon/index.vue
Normal file
199
screen-vue/src/views/system/icon/index.vue
Normal file
@@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<div class="icon-demo-container">
|
||||
<div class="header">
|
||||
<div class="search-wrapper">
|
||||
<el-input
|
||||
v-model="searchKey"
|
||||
placeholder="输入图标名称搜索"
|
||||
class="search-input"
|
||||
>
|
||||
<template #suffix>
|
||||
<el-icon class="search-icon"><Search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="icon-list-wrapper">
|
||||
<div class="icon-list">
|
||||
<div v-if="Object.keys(filteredIcons).length === 0" class="empty-tip">
|
||||
未找到匹配的图标,请更换关键词重试
|
||||
</div>
|
||||
<div
|
||||
v-for="(icon, key) in filteredIcons"
|
||||
:key="key"
|
||||
class="icon-item"
|
||||
@click="copyIconName(key)"
|
||||
>
|
||||
<div class="icon-wrapper">
|
||||
<el-icon :size="32">
|
||||
<component :is="key" />
|
||||
</el-icon>
|
||||
</div>
|
||||
<div class="icon-name">{{ key }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
import { Search } from '@element-plus/icons-vue'
|
||||
|
||||
const searchKey = ref('')
|
||||
const filteredIcons = computed(() => {
|
||||
if (!searchKey.value) {
|
||||
return ElementPlusIconsVue
|
||||
}
|
||||
const lowerKey = searchKey.value.toLowerCase()
|
||||
return Object.fromEntries(
|
||||
Object.entries(ElementPlusIconsVue).filter(([key]) =>
|
||||
key.toLowerCase().includes(lowerKey)
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
const copyIconName = (key) => {
|
||||
navigator.clipboard.writeText(key)
|
||||
.then(() => {
|
||||
ElMessage.success(`已复制图标名称:${key}`)
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage.error('复制失败,请手动复制')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.icon-demo-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 16px;
|
||||
font-family: sans-serif;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header h2 {
|
||||
color: #333;
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.search-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.search-icon:hover {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.icon-list-wrapper {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.icon-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
grid-column: 1 / -1;
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.icon-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 12px 8px;
|
||||
border-radius: 6px;
|
||||
background: #f5f7fa;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.icon-item:hover {
|
||||
background: #e8eaed;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #409eff;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.icon-name {
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
word-break: keep-all;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.icon-list-wrapper::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
.icon-list-wrapper::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.icon-list-wrapper::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.icon-list-wrapper::-webkit-scrollbar-thumb:hover {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.icon-list {
|
||||
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
|
||||
}
|
||||
.header h2 {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user