Compare commits
No commits in common. 'master' and 'develop' have entirely different histories.
@ -1,24 +1,8 @@ |
|||
# 忽略文件 |
|||
examples/ |
|||
build/ |
|||
node_modules/ |
|||
*.map |
|||
*.html |
|||
|
|||
# 本地开发文件 |
|||
.DS_Store |
|||
*.local |
|||
|
|||
# 日志文件 |
|||
npm-debug.log* |
|||
yarn-debug.log* |
|||
yarn-error.log* |
|||
|
|||
# 编辑器目录和文件 |
|||
.idea |
|||
.vscode |
|||
*.suo |
|||
*.ntvs* |
|||
*.njsproj |
|||
*.sln |
|||
*.sw? |
|||
*.log |
|||
*.md |
|||
*.yml |
|||
webpack.config.js |
|||
.gitignore |
|||
@ -0,0 +1,13 @@ |
|||
module.exports = { |
|||
presets: ['@vue/cli-plugin-babel/preset'], |
|||
plugins: [ |
|||
[ |
|||
'component', |
|||
{ |
|||
libraryName: 'element-ui', |
|||
styleLibraryName: 'theme-chalk' |
|||
} |
|||
] |
|||
] |
|||
} |
|||
|
|||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 771 B |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 662 B |
|
After Width: | Height: | Size: 49 KiB |
@ -0,0 +1,287 @@ |
|||
|
|||
<template> |
|||
<transition :name="customize ? '' : 'menu-collapse'"> |
|||
<el-menu v-if="!customize" class="el-menu-vertical-demo custom-menu" @open="handleOpen" |
|||
:default-active="currentMenuItem?.index" |
|||
@close="handleClose" @select="handleSelect" :collapse="isCollapse" :collapse-transition="true" |
|||
> |
|||
<div style="height: 100%;padding: 0 0 20px;box-sizing: border-box;"> |
|||
<div class="menu-top"> |
|||
<span v-show="!isCollapse"> |
|||
导航 |
|||
</span> |
|||
<GuipToolTip :content="isCollapse ? '展开' : '收起'"> |
|||
<img v-if="!isCollapse" class="point" src="../assets/menu-close.svg" @click="changeMenuStatus(true)" alt=""> |
|||
<img v-else class="point" src="../assets/menu-open.svg" @click="changeMenuStatus(false)" alt=""> |
|||
</GuipToolTip> |
|||
</div> |
|||
<template v-for="item in menuData" > |
|||
<el-submenu v-if="item.children" :key="item.index" :index="item.index"> |
|||
<template slot="title"> |
|||
<SvgIcon1 :iconPath="require(`@/assets/menu/${item.icon}.svg`)" defaultColor="#8A9099" |
|||
activeColor="#006AFF" :isActive="item.index == currentMenuItem?.index?.substring(0,1) && isCollapse" /> |
|||
<span class="title_text" >{{ item.title }}</span> |
|||
</template> |
|||
<el-menu-item style="padding: 0 22px 0 32px;" v-for="subItem in item.children" :key="subItem.index" |
|||
:index="subItem.index" @click="handleSelect(subItem.index, [item.index, subItem.index], subItem)"> |
|||
{{ subItem.title }} |
|||
</el-menu-item> |
|||
</el-submenu> |
|||
<el-menu-item v-else :index="item.index" :key="item.index" @click="handleSelect(item.index, [item.index], item)"> |
|||
<div class="flex"> |
|||
<SvgIcon1 :iconPath="require(`@/assets/menu/${item.icon}.svg`)" defaultColor="#8A9099" |
|||
activeColor="#006AFF" :isActive="item.index == currentMenuItem?.index" /> |
|||
<span class="title_text" slot="title">{{ item.title }}</span> |
|||
</div> |
|||
</el-menu-item> |
|||
</template> |
|||
</div> |
|||
</el-menu> |
|||
|
|||
<SetLeftMenu v-else :menuList="menuData"/> |
|||
</transition> |
|||
|
|||
</template> |
|||
<script> |
|||
import SetLeftMenu from '@/components/SetLeftMenu.vue' |
|||
import SvgIcon1 from '@/components/SvgIcon1.vue'; |
|||
import GuipToolTip from '@/components/GuipToolTip.vue'; |
|||
export default { |
|||
name: 'SliderMenu', |
|||
components: { |
|||
SvgIcon1, |
|||
GuipToolTip, |
|||
SetLeftMenu |
|||
}, |
|||
props: { |
|||
menuData: { |
|||
type: Array, |
|||
default: () => [] |
|||
}, |
|||
customize:{ |
|||
type:Boolean, |
|||
default:false |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
isCollapse: false, |
|||
routerList: [], |
|||
currentMenuItem:'', |
|||
currentMenuParent:'' |
|||
}; |
|||
}, |
|||
watch: { |
|||
"$route.path"() { |
|||
if(this.customize)return |
|||
this.updateCurrentMenu(); |
|||
}, |
|||
}, |
|||
methods: { |
|||
changeMenuStatus(flag) { |
|||
this.isCollapse = flag; |
|||
}, |
|||
handleOpen(key, keyPath) { |
|||
console.log(key, keyPath); |
|||
}, |
|||
handleClose(key, keyPath) { |
|||
console.log(key, keyPath); |
|||
}, |
|||
// handleSelect(index,indexPath) { |
|||
// this.activeMenu = index; |
|||
// console.log(index,indexPath, '---index'); |
|||
// // 找到被点击的 menu 项对应的 path |
|||
// const allItems = this.menuData.flatMap(menu => menu.children); |
|||
// const targetItem = allItems.find(item => item.index === index); |
|||
|
|||
// if (targetItem && this.$route.path !== targetItem.path) { |
|||
// this.$router.push(targetItem.path); |
|||
// } |
|||
// }, |
|||
handleSelect(index, indexPath, menuItem) { |
|||
if (menuItem.path && this.$route.path !== menuItem.path) { |
|||
this.$router.push(menuItem.path); |
|||
} |
|||
}, |
|||
updateCurrentMenu() { |
|||
const result = this.findMenuItemByPath(this.menuData, this.$route.path); |
|||
this.currentMenuItem = result?.item; |
|||
}, |
|||
findMenuItemByPath(menuItems, targetPath, parent = null) { |
|||
for (const item of menuItems) { |
|||
if (item.path === targetPath) return { item, parent }; |
|||
if (item.children?.length) { |
|||
const found = this.findMenuItemByPath(item.children, targetPath, item); |
|||
if (found) return found; |
|||
} |
|||
} |
|||
return null; |
|||
}, |
|||
}, |
|||
mounted() { |
|||
// console.log(this.$route.path,'this.$route.path----'); |
|||
// const allItems = this.menuData.flatMap(menu =>menu.children ? menu.children?.map(child => ({ ...child, parentIndex: menu.index })) : menu.children); |
|||
// console.log(allItems,'====='); |
|||
// const current = allItems.find(item => item.path === this.$route.path); |
|||
// this.activeMenu = current ? current.index : ''; |
|||
// this.defaultOpeneds = current ? [current.parentIndex] : []; |
|||
|
|||
// this.routerList = this.$router.options.router;//配置路由 |
|||
// console.log(this.$route.path, this.$router.options.router, 'this.$route.path==='); |
|||
// // 初始化时设置默认激活的菜单项 |
|||
// this.activeMenu = this.$route.path; |
|||
this.updateCurrentMenu(); |
|||
} |
|||
} |
|||
</script> |
|||
<style lang="scss" scoped> |
|||
.menu-collapse-leave-active, |
|||
.menu-collapse-enter-active { |
|||
transition: all 0.5s; |
|||
} |
|||
|
|||
.menu-collapse-enter, |
|||
.menu-collapse-leave-to { |
|||
opacity: 0; |
|||
transform: translateX(-30px); |
|||
} |
|||
|
|||
.el-menu--vertical { |
|||
.el-menu-item { |
|||
height: 40px; |
|||
line-height: 40px; |
|||
|
|||
} |
|||
|
|||
.el-menu-item.is-active { |
|||
font-weight: bold; |
|||
} |
|||
} |
|||
|
|||
.title_img { |
|||
width: 16px; |
|||
margin-right: 4px; |
|||
} |
|||
|
|||
.title_text { |
|||
margin-left: 4px; |
|||
font-size: 14px; |
|||
font-weight: bold; |
|||
display: block; |
|||
|
|||
} |
|||
|
|||
.el-menu--collapse .title_text { |
|||
display: none; |
|||
} |
|||
|
|||
.el-menu--collapse ::v-deep .el-submenu__icon-arrow { |
|||
display: none; |
|||
} |
|||
|
|||
.el-menu-vertical-demo:not(.el-menu--collapse) { |
|||
width: 158px; |
|||
min-width: 158px; |
|||
min-height: calc(100vh - 62px); |
|||
overflow-y: auto; |
|||
} |
|||
|
|||
.el-menu-item { |
|||
padding: 0 22px; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.menu-top { |
|||
background: #FFFFFF; |
|||
box-sizing: border-box; |
|||
/* middle/middle_line_1 */ |
|||
border-width: 0px 0px 1px 0px; |
|||
border-style: solid; |
|||
border-color: #DFE2E6; |
|||
padding: 15px 0px; |
|||
margin: 0 22px; |
|||
display: flex; |
|||
font-size: 14px; |
|||
color: #1E2226; |
|||
font-weight: bold; |
|||
line-height: normal; |
|||
letter-spacing: 0.08em; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
|
|||
img { |
|||
width: 14px; |
|||
height: 14px; |
|||
|
|||
} |
|||
} |
|||
|
|||
::v-deep .el-submenu { |
|||
// width: 16px; |
|||
font-size: 12px; |
|||
} |
|||
|
|||
::v-deep .el-submenu .el-menu-item { |
|||
min-width: 138px; |
|||
width: 100%; |
|||
} |
|||
|
|||
::v-deep .el-submenu__title { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: flex-start; |
|||
} |
|||
|
|||
::v-deep .el-submenu__title:hover { |
|||
background-color: transparent; |
|||
// cursor: not-allowed; |
|||
} |
|||
|
|||
::v-deep .el-submenu__title .el-submenu__icon-arrow.el-icon-arrow-down { |
|||
// display: none; |
|||
} |
|||
|
|||
::v-deep .el-submenu .el-menu-item { |
|||
padding: 0 22px; |
|||
display: flex; |
|||
justify-content: flex-start; |
|||
} |
|||
|
|||
::v-deep .el-submenu .el-menu-item { |
|||
height: 36px; |
|||
} |
|||
|
|||
::v-deep.el-submenu .el-menu-item { |
|||
height: 36px; |
|||
line-height: 36px; |
|||
color: #8A9099; |
|||
letter-spacing: 0.08em; |
|||
} |
|||
|
|||
::v-deep .el-menu-item:hover { |
|||
background: #F6F7FA; |
|||
} |
|||
|
|||
::v-deep .el-menu-item.is-active { |
|||
color: #006AFF; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
// .el-submenu .el-menu { |
|||
// transition: all 0.3s ease; |
|||
// } |
|||
|
|||
// .el-menu--collapse .el-submenu > .el-menu { |
|||
// display: block !important; |
|||
// overflow: hidden; |
|||
// transition: all 0.3s ease; |
|||
// opacity: 0; |
|||
// height: 0; |
|||
// transform: translateY(-10px); |
|||
// } |
|||
|
|||
// .el-menu--collapse .el-submenu.is-opened > .el-menu { |
|||
// opacity: 0; |
|||
// height: 0; |
|||
// } |
|||
</style> |
|||
@ -1,13 +1,24 @@ |
|||
import Vue from 'vue/dist/vue.esm.js' // 必须用完整版
|
|||
import App from './App.vue' |
|||
import ElementUI from 'element-ui'; |
|||
// import ZhichengUI from '@zhicheng1012/zhicheng-components'
|
|||
// import '@zhicheng1012/zhicheng-components/dist/css/zhicheng-components.css';
|
|||
import 'element-ui/lib/theme-chalk/index.css' // 如果依赖Element
|
|||
|
|||
|
|||
import MyComponents from '../../packages' // 本地引用组件库
|
|||
// import 'element-ui/lib/theme-chalk/index.css' // 如果依赖Element
|
|||
// main.js
|
|||
import 'core-js/stable' |
|||
// import './style/theme/index.css'
|
|||
// import './style/theme/common.scss'
|
|||
|
|||
Vue.use(MyComponents) |
|||
|
|||
Vue.use(ElementUI); |
|||
|
|||
|
|||
// Vue.use(ZhichengUI)
|
|||
|
|||
Vue.config.productionTip = false |
|||
new Vue({ |
|||
render: h => h(App) |
|||
|
|||
@ -0,0 +1,109 @@ |
|||
<template> |
|||
<div> |
|||
<h3>测试表格</h3> |
|||
|
|||
<el-table |
|||
:data="tableData" |
|||
style="width: 100%"> |
|||
<el-table-column |
|||
prop="date" |
|||
label="日期" |
|||
width="180"> |
|||
</el-table-column> |
|||
<el-table-column |
|||
prop="name" |
|||
label="姓名" |
|||
width="180"> |
|||
</el-table-column> |
|||
<el-table-column |
|||
prop="address" |
|||
label="地址"> |
|||
</el-table-column> |
|||
</el-table> |
|||
<el-form ref="form" :model="form" label-width="80px"> |
|||
<el-form-item label="活动名称"> |
|||
<el-input v-model="form.name"></el-input> |
|||
</el-form-item> |
|||
<el-form-item label="活动区域"> |
|||
<el-select v-model="form.region" placeholder="请选择活动区域"> |
|||
<el-option label="区域一" value="shanghai"></el-option> |
|||
<el-option label="区域二" value="beijing"></el-option> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="活动时间"> |
|||
<el-col :span="11"> |
|||
<el-date-picker type="date" placeholder="选择日期" v-model="form.date1" style="width: 100%;"></el-date-picker> |
|||
</el-col> |
|||
<el-col class="line" :span="2">-</el-col> |
|||
<el-col :span="11"> |
|||
<el-time-picker placeholder="选择时间" v-model="form.date2" style="width: 100%;"></el-time-picker> |
|||
</el-col> |
|||
</el-form-item> |
|||
<el-form-item label="即时配送"> |
|||
<el-switch v-model="form.delivery"></el-switch> |
|||
</el-form-item> |
|||
<el-form-item label="活动性质"> |
|||
<el-checkbox-group v-model="form.type"> |
|||
<el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox> |
|||
<el-checkbox label="地推活动" name="type"></el-checkbox> |
|||
<el-checkbox label="线下主题活动" name="type"></el-checkbox> |
|||
<el-checkbox label="单纯品牌曝光" name="type"></el-checkbox> |
|||
</el-checkbox-group> |
|||
</el-form-item> |
|||
<el-form-item label="特殊资源"> |
|||
<el-radio-group v-model="form.resource"> |
|||
<el-radio label="线上品牌商赞助"></el-radio> |
|||
<el-radio label="线下场地免费"></el-radio> |
|||
</el-radio-group> |
|||
</el-form-item> |
|||
<el-form-item label="活动形式"> |
|||
<el-input type="textarea" v-model="form.desc"></el-input> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button type="primary" @click="onSubmit">立即创建</el-button> |
|||
<el-button>取消</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
tableData: [{ |
|||
date: '2016-05-02', |
|||
name: '王小虎', |
|||
address: '上海市普陀区金沙江路 1518 弄' |
|||
}, { |
|||
date: '2016-05-04', |
|||
name: '王小虎', |
|||
address: '上海市普陀区金沙江路 1517 弄' |
|||
}, { |
|||
date: '2016-05-01', |
|||
name: '王小虎', |
|||
address: '上海市普陀区金沙江路 1519 弄' |
|||
}, { |
|||
date: '2016-05-03', |
|||
name: '王小虎', |
|||
address: '上海市普陀区金沙江路 1516 弄' |
|||
}], |
|||
form: { |
|||
name: '', |
|||
region: '', |
|||
date1: '', |
|||
date2: '', |
|||
delivery: false, |
|||
type: [], |
|||
resource: '', |
|||
desc: '' |
|||
} |
|||
} |
|||
}, |
|||
methods:{ |
|||
onSubmit() { |
|||
console.log('submit!'); |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
@ -1,22 +1,17 @@ |
|||
// .eslintrc.js
|
|||
module.exports = { |
|||
root: true, |
|||
env: { |
|||
browser: true, |
|||
node: true, |
|||
es6: true |
|||
}, |
|||
extends: [ |
|||
'eslint:recommended', |
|||
'plugin:vue/essential' |
|||
], |
|||
parserOptions: { |
|||
ecmaVersion: 2018, // 改为 2018(兼容 Vue 2)
|
|||
sourceType: 'module', |
|||
parser: 'babel-eslint' |
|||
}, |
|||
rules: { |
|||
'vue/multi-word-component-names': 'off', |
|||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off' |
|||
} |
|||
} |
|||
env: { |
|||
browser: true, |
|||
es2021: true |
|||
}, |
|||
extends: [ |
|||
'eslint:recommended', |
|||
'plugin:vue/essential' |
|||
], |
|||
parserOptions: { |
|||
ecmaVersion: 12, |
|||
sourceType: 'module' |
|||
}, |
|||
rules: { |
|||
'vue/multi-word-component-names': 'off' |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
import Breadcrumb from './src/index.vue' |
|||
|
|||
Breadcrumb.install = function(Vue) { |
|||
Vue.component(Breadcrumb.name || 'Breadcrumb', Breadcrumb) |
|||
} |
|||
|
|||
export default Breadcrumb |
|||
@ -0,0 +1,116 @@ |
|||
<template> |
|||
<div v-if="breadcrumbs.length > 0" class="guip-breadcrumb-container flex-between"> |
|||
<nav> |
|||
<ol class="breadcrumb"> |
|||
<li |
|||
v-for="(item, index) in breadcrumbs" |
|||
:key="index" |
|||
class="breadcrumb-item" |
|||
:class="{ active: index === breadcrumbs.length - 1 }" |
|||
> |
|||
<template v-if="index !== breadcrumbs.length - 1"> |
|||
<router-link to="/" v-if="item.title == '首页'"> |
|||
<!-- <SvgIcon1 :iconPath="require(`../../assets/home-bread.png`)" defaultColor="#8A9099" :size="16" activeColor="#006AFF"/> --> |
|||
<img class="home-icon" src="../../assets/home-bread.png" alt=""> |
|||
</router-link> |
|||
<router-link v-else :to="item.path">{{ item.title }}</router-link> |
|||
<img class="separator" src="../../assets/separator.png" alt=""> |
|||
</template> |
|||
<template v-else> |
|||
<span>{{ item.title }}</span> |
|||
</template> |
|||
</li> |
|||
</ol> |
|||
</nav> |
|||
<div v-if="breadRightText" class="gap8 breadRight"> |
|||
<img class="ml-8" src="../../assets/bind_sites.svg" alt="" /> |
|||
站点简称:<a :href="breadRightText">{{ breadRightText }}</a> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
<script> |
|||
import SvgIcon1 from '../..//SvgIcon1'; |
|||
export default { |
|||
name: 'Breadcrumb', |
|||
components: { |
|||
SvgIcon1, |
|||
}, |
|||
computed: { |
|||
breadcrumbs() { |
|||
if (this.$route.meta.hideBreadcrumb) return []; |
|||
|
|||
const crumbs = [ |
|||
// { |
|||
// path:{ |
|||
// path:'/', |
|||
// query:{uid: "2300"}, |
|||
// params:{} |
|||
// }, |
|||
// title:'首页' |
|||
// }, |
|||
// { |
|||
// path:{ |
|||
// path: "/agent/siteList", |
|||
// query:{uid: "2300"}, |
|||
// params:{} |
|||
// }, |
|||
// title:"站点列表" |
|||
// }, |
|||
]; |
|||
let currentRoute = this.$route; |
|||
|
|||
//// 递归查找所有父级路由 |
|||
while (currentRoute) { |
|||
// 获取匹配的路由记录 |
|||
// const matchedRoute = this.$router.options.routes.find( |
|||
// r => r.name === currentRoute.name |
|||
// ); |
|||
|
|||
// 构建包含完整参数的对象 |
|||
const routeWithParams = { |
|||
path: currentRoute.path, |
|||
query: currentRoute.query, |
|||
params: currentRoute.params |
|||
}; |
|||
|
|||
crumbs.unshift({ |
|||
path: routeWithParams, |
|||
title: this.getTitle(currentRoute) |
|||
}); |
|||
|
|||
// 通过 meta.breadcrumbParent 查找父级路由 |
|||
if (currentRoute.meta.breadcrumbParent) { |
|||
currentRoute = this.$router.options.routes.find( |
|||
r => r.name === currentRoute.meta.breadcrumbParent |
|||
); |
|||
|
|||
// 如果找到了父路由,创建一个模拟的$route对象 |
|||
if (currentRoute) { |
|||
currentRoute = { |
|||
...currentRoute, |
|||
path: currentRoute.path, |
|||
query: this.$route.query, // 保留当前查询参数 |
|||
params: this.$route.params, // 保留当前路由参数 |
|||
meta: currentRoute.meta || {} |
|||
}; |
|||
} |
|||
} else { |
|||
currentRoute = null; |
|||
} |
|||
} |
|||
|
|||
return crumbs; |
|||
}, |
|||
breadRightText() { |
|||
return this.$store && this.$store.state.breadRightText |
|||
} |
|||
}, |
|||
methods: { |
|||
getTitle(route) { |
|||
return typeof route.meta.title === 'function' |
|||
? route.meta.title(route) |
|||
: route.meta.title || route.name; |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
@ -1,271 +0,0 @@ |
|||
<template> |
|||
<div class="custom-select" v-clickaway="closeDropdown" ref="dropdown" :class="{ 'is-open': isOpen }" |
|||
:style="{ width }"> |
|||
<!-- 触发按钮 --> |
|||
<div class="select-trigger" @click="toggleDropdown"> |
|||
<slot name="trigger"> |
|||
{{ localSelected ? localSelected[displayKey] : placeholder }} |
|||
</slot> |
|||
<img class="arrow-icon" |
|||
:src="isOpen ? require('../../assets/dropDown_open.png') : require('../../assets/dropDown_expand.png')" |
|||
alt=""> |
|||
</div> |
|||
|
|||
<!-- 下拉内容 --> |
|||
<transition name="slide-fade"> |
|||
<div v-if="isOpen" class="select-dropdown"> |
|||
<slot v-if="isOpen" name="normal"></slot> |
|||
<div v-if="options"> |
|||
<div v-for="(item, index) in options" :key="index" class="dropdown-item " |
|||
:class="{ 'is-selected': isSelected(item) }" @click="selectItem(item)"> |
|||
<slot name="item" :item="item"> |
|||
<div class="flex-between"> |
|||
<div class="left"> |
|||
<p class="one">{{ item[displayKey] }}</p> |
|||
</div> |
|||
<div class="right"> |
|||
<img v-if="localSelected[displayKey] == item[displayKey]" |
|||
src="../../assets/drop-selected.svg" alt=""> |
|||
</div> |
|||
</div> |
|||
</slot> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="flex-between dropdown-item" v-if="options_null" @click="selectNullItem"> |
|||
<div class="left"> |
|||
<p class="one">暂无收款账号</p> |
|||
<p>暂时没有收款账号,我想稍后配置</p> |
|||
</div> |
|||
<div class="right"> |
|||
<img src="../../assets/drop-selected.svg" alt=""> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</transition> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { $emit, $on, $off } from '../../utils/eventBus' |
|||
export default { |
|||
name: 'CustomDropdown', |
|||
props: { |
|||
width: { |
|||
type: String, |
|||
default: "200px", |
|||
}, |
|||
options: { |
|||
type: Array, |
|||
default: () => [], |
|||
}, |
|||
options_null: { |
|||
type: Object, |
|||
default: () => { }, |
|||
}, |
|||
placeholder: { |
|||
type: String, |
|||
default: "请选择", |
|||
}, |
|||
value: { |
|||
type: [String, Number, Object], |
|||
default: null, |
|||
}, |
|||
valueKey: { |
|||
type: String, |
|||
default: "value", |
|||
}, |
|||
displayKey: { |
|||
type: String, |
|||
default: "label", |
|||
}, |
|||
}, |
|||
data() { |
|||
return { |
|||
isOpen: false, |
|||
localSelected: null, // 完全独立的内部状态 |
|||
isUpdating: false // 循环终止标志 |
|||
// selectedItem: null, |
|||
}; |
|||
}, |
|||
watch: { |
|||
value: { |
|||
immediate: true, |
|||
handler(val) { |
|||
// 仅当外部值确实变化时更新内部状态 |
|||
if (!this.localSelected || this.localSelected[this.valueKey] !== val) { |
|||
this.localSelected = this.options.find(item => item[this.valueKey] === val); |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
computed: { |
|||
// selectedItem: { |
|||
// get() { |
|||
// // 仅从props获取值,不做任何修改 |
|||
// return this.options.find(item => item[this.valueKey] === this.value) || null; |
|||
// }, |
|||
// set(newVal) { |
|||
// // 仅触发事件,不修改内部状态 |
|||
// this.$emit('input', newVal ? newVal[this.valueKey] : null); |
|||
// this.$emit('change', newVal); |
|||
// } |
|||
// } |
|||
// selectedItem: { |
|||
// get() { |
|||
// if (this.isUpdating) return this._selectedItem; |
|||
// return this.options.find(item => item[this.valueKey] === this.value) || null; |
|||
// }, |
|||
// set(newVal) { |
|||
// this.isUpdating = true; |
|||
// this._selectedItem = newVal; |
|||
// this.$nextTick(() => { |
|||
// this.$emit('input', newVal ? newVal[this.valueKey] : null); |
|||
// this.isUpdating = false; |
|||
// }); |
|||
// } |
|||
// } |
|||
}, |
|||
// 移除 data 中的 selectedItem 和 watch |
|||
created() { |
|||
// 监听关闭所有下拉框的事件 |
|||
$on('close-all-dropdowns', this.closeDropdown) |
|||
}, |
|||
beforeDestroy() { |
|||
// 组件销毁移除监听 |
|||
$off('close-all-dropdowns', this.closeDropdown) |
|||
}, |
|||
methods: { |
|||
closeDropdown() { |
|||
this.isOpen = false; |
|||
}, |
|||
toggleDropdown(e) { |
|||
if (e) { |
|||
e.stopPropagation(); |
|||
} |
|||
// 先通知所有下拉框关闭 |
|||
$emit('close-all-dropdowns') |
|||
this.isOpen = !this.isOpen; |
|||
}, |
|||
// selectItem(item) { |
|||
// this.selectedItem = item; |
|||
// this.$emit("input", item[this.valueKey]); // Use the specified valueKey |
|||
// this.$emit("change", item); |
|||
// this.isOpen = false; |
|||
// }, |
|||
// selectItem(item) { |
|||
// // 先触发外部事件 |
|||
// this.$emit("input", item[this.valueKey]); |
|||
// this.$emit("change", item); |
|||
|
|||
// // 延迟内部状态更新,让父组件先处理 |
|||
// this.$nextTick(() => { |
|||
// this.selectedItem = item; |
|||
// this.isOpen = false; |
|||
// }); |
|||
// }, |
|||
selectItem(item) { |
|||
this.localSelected = item; |
|||
this.$emit('input', item[this.valueKey]); |
|||
this.$emit('change', item); |
|||
this.isOpen = false; |
|||
}, |
|||
// selectItem(item) { |
|||
// this.selectedItem = item; // 这会自动触发 computed setter |
|||
// // this.$emit("change", item); |
|||
// this.isOpen = false; |
|||
// }, |
|||
isSelected(item) { |
|||
return this.localSelected && this.localSelected[this.valueKey] === item[this.valueKey]; |
|||
}, |
|||
selectNullItem() { |
|||
this.$emit("changeNormal", ''); |
|||
this.isOpen = false; |
|||
} |
|||
}, |
|||
}; |
|||
</script> |
|||
|
|||
<style scoped> |
|||
/* Your existing styles remain the same */ |
|||
.custom-select { |
|||
display: inline-block; |
|||
vertical-align: middle; |
|||
height: 38px; |
|||
position: relative; |
|||
font-family: Arial, sans-serif; |
|||
} |
|||
|
|||
.select-trigger { |
|||
border-radius: 2px; |
|||
opacity: 1; |
|||
background: #FFFFFF; |
|||
border: 1px solid #DFE2E6; |
|||
width: 100%; |
|||
height: 40px; |
|||
box-sizing: border-box; |
|||
padding: 10px 12px; |
|||
cursor: pointer; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
} |
|||
|
|||
.is-open .select-trigger { |
|||
border: 1px solid #006AFF; |
|||
transition: all .5s; |
|||
outline: 3px solid #D8E9FA; |
|||
} |
|||
|
|||
.select-trigger:hover { |
|||
border-color: #006AFF; |
|||
transition: all .5s; |
|||
} |
|||
|
|||
.arrow-icon { |
|||
width: 12px; |
|||
} |
|||
|
|||
.select-dropdown { |
|||
position: absolute; |
|||
top: 100%; |
|||
right: 0; |
|||
width: 100%; |
|||
border: 1px solid #ccc; |
|||
box-sizing: border-box; |
|||
border-radius: 4px; |
|||
background-color: #fff; |
|||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); |
|||
z-index: 1000; |
|||
margin-top: 4px; |
|||
max-height: 384px; |
|||
overflow-y: auto; |
|||
padding: 0 12px 12px 12px; |
|||
} |
|||
|
|||
.dropdown-item { |
|||
padding: 12px 10px; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.dropdown-item:hover { |
|||
background: #F6F7FA; |
|||
} |
|||
|
|||
.dropdown-item.is-selected { |
|||
background-color: #F6F7FA; |
|||
color: #006AFF; |
|||
} |
|||
|
|||
/* 展开收起动画 */ |
|||
.slide-fade-enter-active, |
|||
.slide-fade-leave-active { |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.slide-fade-enter-from, |
|||
.slide-fade-leave-to { |
|||
opacity: 0; |
|||
transform: translateY(-10px); |
|||
} |
|||
</style> |
|||
@ -1,328 +1,421 @@ |
|||
<template> |
|||
<div class="custom-select" |
|||
v-clickaway="handleClickAway" |
|||
ref="dropdown" |
|||
:class="{ 'is-open': state.isOpen }" |
|||
:style="{ width: computedWidth }"> |
|||
<!-- 触发按钮 --> |
|||
<div class="select-trigger" @click.stop="toggleDropdown"> |
|||
<slot name="trigger"> |
|||
{{ displayText }} |
|||
</slot> |
|||
<img class="arrow-icon" |
|||
:src="state.isOpen ? openIcon : expandIcon" |
|||
alt="dropdown indicator"> |
|||
</div> |
|||
|
|||
<!-- 下拉内容 --> |
|||
<transition name="slide-fade"> |
|||
<div v-show="state.isOpen" class="select-dropdown"> |
|||
<slot v-if="state.isOpen" name="normal"></slot> |
|||
|
|||
<template v-if="filteredOptions.length"> |
|||
<div v-for="(item, index) in filteredOptions" |
|||
:key="`option-${index}`" |
|||
class="dropdown-item" |
|||
:class="{ 'is-selected': isSelected(item) }" |
|||
@click.stop="selectItem(item)"> |
|||
<slot name="item" :item="item"> |
|||
<div class="flex-between"> |
|||
<div class="left"> |
|||
<p class="one">{{ item[displayKey] }}</p> |
|||
</div> |
|||
<div class="right"> |
|||
<img v-if="isSelected(item)" |
|||
:src="selectedIcon" |
|||
alt="selected"> |
|||
</div> |
|||
<div class="custom-select" v-clickaway="handleClickAway" ref="dropdown" :class="{ 'is-open': state.isOpen }" |
|||
:style="{ width: computedWidth }"> |
|||
<!-- 触发按钮 --> |
|||
<div class="select-trigger" @click.stop="toggleDropdown"> |
|||
<slot name="trigger"> |
|||
{{ displayText }} |
|||
</slot> |
|||
<img class="arrow-icon" :src="state.isOpen ? openIcon : expandIcon" alt="dropdown indicator"> |
|||
</div> |
|||
|
|||
<!-- 下拉内容 --> |
|||
<transition name="slide-fade"> |
|||
<div v-show="state.isOpen" class="select-dropdown"> |
|||
<slot v-if="state.isOpen" name="normal"></slot> |
|||
|
|||
<template v-if="filteredOptions.length"> |
|||
<div v-for="(item, index) in filteredOptions" :key="`option-${index}`" class="dropdown-item" |
|||
:class="{ 'is-selected': isSelected(item) }" @click.stop="selectItem(item)"> |
|||
<slot name="item" :item="item"> |
|||
<div class="flex-between"> |
|||
<div class="left"> |
|||
<p class="one">{{ item[displayKey] }}</p> |
|||
</div> |
|||
<div class="right"> |
|||
<img v-if="isSelected(item)" :src="selectedIcon" alt="selected"> |
|||
</div> |
|||
</div> |
|||
</slot> |
|||
</div> |
|||
</template> |
|||
|
|||
<div v-if="showNullOption" class="flex-between dropdown-item" @click.stop="selectNullItem"> |
|||
<div class="left"> |
|||
<p class="one">暂无收款账号</p> |
|||
<p>暂时没有收款账号,我想稍后配置</p> |
|||
</div> |
|||
<div class="right"> |
|||
<img :src="selectedIcon" alt="selected"> |
|||
</div> |
|||
</div> |
|||
</slot> |
|||
</div> |
|||
</template> |
|||
|
|||
<div v-if="showNullOption" |
|||
class="flex-between dropdown-item" |
|||
@click.stop="selectNullItem"> |
|||
<div class="left"> |
|||
<p class="one">暂无收款账号</p> |
|||
<p>暂时没有收款账号,我想稍后配置</p> |
|||
</div> |
|||
<div class="right"> |
|||
<img :src="selectedIcon" alt="selected"> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</transition> |
|||
</transition> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
// 静态资源引入(Webpack编译时处理) |
|||
const STATIC_ICONS = Object.freeze({ |
|||
</template> |
|||
|
|||
<script> |
|||
// 静态资源引入(Webpack编译时处理) |
|||
const STATIC_ICONS = Object.freeze({ |
|||
expand: require('../../assets/dropDown_expand.png'), |
|||
open: require('../../assets/dropDown_open.png'), |
|||
selected: require('../../assets/drop-selected.svg') |
|||
}) |
|||
|
|||
export default { |
|||
}) |
|||
|
|||
export default { |
|||
name: 'CustomDropdown', |
|||
props: { |
|||
width: { |
|||
type: String, |
|||
default: "200px", |
|||
validator: (val) => /^\d+(px|%|rem|em|vw)$/.test(val) |
|||
}, |
|||
options: { |
|||
type: Array, |
|||
default: () => [], |
|||
validator: (arr) => !arr.some(item => item === null || typeof item !== 'object') |
|||
}, |
|||
nullOption: { |
|||
type: Object, |
|||
default: null |
|||
}, |
|||
placeholder: { |
|||
type: String, |
|||
default: "请选择" |
|||
}, |
|||
value: { |
|||
type: [String, Number, Object], |
|||
default: null |
|||
}, |
|||
valueKey: { |
|||
type: String, |
|||
default: "value", |
|||
validator: (key) => typeof key === 'string' && key.trim().length > 0 |
|||
}, |
|||
displayKey: { |
|||
type: String, |
|||
default: "label", |
|||
validator: (key) => typeof key === 'string' && key.trim().length > 0 |
|||
} |
|||
width: { |
|||
type: String, |
|||
default: "200px", |
|||
validator: (val) => /^\d+(px|%|rem|em|vw)$/.test(val) |
|||
}, |
|||
options: { |
|||
type: Array, |
|||
default: () => [], |
|||
validator: (arr) => !arr.some(item => item === null || typeof item !== 'object') |
|||
}, |
|||
nullOption: { |
|||
type: Object, |
|||
default: null |
|||
}, |
|||
placeholder: { |
|||
type: String, |
|||
default: "请选择" |
|||
}, |
|||
value: { |
|||
type: [String, Number, Object], |
|||
default: null |
|||
}, |
|||
valueKey: { |
|||
type: String, |
|||
default: "value", |
|||
validator: (key) => typeof key === 'string' && key.trim().length > 0 |
|||
}, |
|||
displayKey: { |
|||
type: String, |
|||
default: "label", |
|||
validator: (key) => typeof key === 'string' && key.trim().length > 0 |
|||
}, |
|||
group: { |
|||
type: String, |
|||
default: null |
|||
}, |
|||
}, |
|||
|
|||
|
|||
// 使用非响应式数据存储关键状态 |
|||
data: () => ({ |
|||
state: Object.seal({ |
|||
isOpen: false, |
|||
lastEmittedValue: null, |
|||
updateDepth: 0, |
|||
eventLock: false |
|||
}), |
|||
icons: STATIC_ICONS |
|||
state: Object.seal({ |
|||
isOpen: false, |
|||
lastEmittedValue: null, |
|||
updateDepth: 0, |
|||
eventLock: false |
|||
}), |
|||
icons: STATIC_ICONS |
|||
}), |
|||
|
|||
|
|||
computed: { |
|||
computedWidth() { |
|||
return this.width.endsWith('px') ? this.width : `${parseInt(this.width)}px` |
|||
}, |
|||
filteredOptions() { |
|||
return Array.isArray(this.options) ? [...this.options] : [] |
|||
}, |
|||
showNullOption() { |
|||
return this.nullOption && !this.filteredOptions.length |
|||
}, |
|||
displayText() { |
|||
const current = this.findOptionByValue(this.value) |
|||
return current ? current[this.displayKey] : this.placeholder |
|||
}, |
|||
openIcon() { |
|||
return this.icons.open |
|||
}, |
|||
expandIcon() { |
|||
return this.icons.expand |
|||
}, |
|||
selectedIcon() { |
|||
return this.icons.selected |
|||
} |
|||
computedWidth() { |
|||
// console.log(this.width.endsWith('px'),this.width,'this.width.endsWith'); |
|||
// return this.width.endsWith('px') ? this.width : `${parseInt(this.width)}px` |
|||
return this.width |
|||
}, |
|||
filteredOptions() { |
|||
return Array.isArray(this.options) ? [...this.options] : [] |
|||
}, |
|||
showNullOption() { |
|||
return this.nullOption && !this.filteredOptions.length |
|||
}, |
|||
displayText() { |
|||
const current = this.findOptionByValue(this.value) |
|||
return current ? current[this.displayKey] : this.placeholder |
|||
}, |
|||
openIcon() { |
|||
return this.icons.open |
|||
}, |
|||
expandIcon() { |
|||
return this.icons.expand |
|||
}, |
|||
selectedIcon() { |
|||
return this.icons.selected |
|||
} |
|||
}, |
|||
created() { |
|||
// 监听同组下拉框的打开事件 |
|||
if (this.group) { |
|||
this.$busOn(`dropdown-group-${this.group}`, (openedId) => { |
|||
if (openedId !== this._uid && this.state.isOpen) { |
|||
this.closeDropdown(); |
|||
} |
|||
}); |
|||
} |
|||
// console.log(`Dropdown created with _uid:`, this._uid); |
|||
}, |
|||
|
|||
watch: { |
|||
value: { |
|||
immediate: true, |
|||
handler(newVal) { |
|||
if (this.state.eventLock) return |
|||
this.state.lastEmittedValue = JSON.stringify(newVal) |
|||
value: { |
|||
immediate: true, |
|||
handler(newVal) { |
|||
if (this.state.eventLock) return |
|||
this.state.lastEmittedValue = JSON.stringify(newVal) |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
|
|||
|
|||
methods: { |
|||
// 安全查找方法 |
|||
findOptionByValue(value) { |
|||
if (value === null || value === undefined) return null |
|||
return this.filteredOptions.find(item => { |
|||
return JSON.stringify(item[this.valueKey]) === JSON.stringify(value) |
|||
}) |
|||
}, |
|||
|
|||
isSelected(item) { |
|||
if (!this.value) return false |
|||
return JSON.stringify(item[this.valueKey]) === JSON.stringify(this.value) |
|||
}, |
|||
|
|||
// 防抖关闭方法 |
|||
handleClickAway() { |
|||
if (!this.state.isOpen) return |
|||
// 安全查找方法 |
|||
findOptionByValue(value) { |
|||
if (value === null || value === undefined) return null |
|||
return this.filteredOptions.find(item => { |
|||
return JSON.stringify(item[this.valueKey]) === JSON.stringify(value) |
|||
}) |
|||
}, |
|||
|
|||
isSelected(item) { |
|||
if (!this.value) return false |
|||
return JSON.stringify(item[this.valueKey]) === JSON.stringify(this.value) |
|||
}, |
|||
|
|||
// 防抖关闭方法 |
|||
// handleClickAway() { |
|||
// // console.log(this.state.isOpen,'1111----执行了'); |
|||
// // if (!this.state.isOpen) return |
|||
// // console.log(this.state.isOpen,'2222----执行了'); |
|||
|
|||
// // this.state.isOpen = false |
|||
// // console.log(this.state.isOpen,'333----执行了'); |
|||
// // this.$nextTick(() => { |
|||
// // this.$busEmit('closed') |
|||
// // }) |
|||
// // 确保点击的不是下拉框本身或其子元素 |
|||
// if (this.$refs.dropdown.contains(event.target)) return; |
|||
|
|||
// if (this.state.isOpen) { |
|||
// this.state.isOpen = false; |
|||
// this.$busEmit('closed'); |
|||
// } |
|||
// }, |
|||
toggleDropdown() { |
|||
console.log('Toggling dropdown, current state:', this.state.isOpen) |
|||
if (this.state.eventLock) return |
|||
// 强制更新状态 |
|||
// this.state.isOpen = !this.state.isOpen |
|||
|
|||
// 调试输出 |
|||
console.log('New state:', this.state.isOpen) |
|||
console.log('Dropdown element:', this.$refs.dropdown) |
|||
|
|||
// 触发相应事件 |
|||
// if (this.state.isOpen) { |
|||
// // this.$busEmit('opened') |
|||
// this.openDropdown(); |
|||
|
|||
// } else { |
|||
// this.closeDropdown(); |
|||
|
|||
// this.$busEmit('closed') |
|||
// } |
|||
if (this.state.isOpen) { |
|||
this.closeDropdown(); |
|||
} else { |
|||
this.openDropdown(); |
|||
} |
|||
|
|||
// 强制重新渲染(调试用) |
|||
this.$forceUpdate() |
|||
|
|||
}, |
|||
// 统一关闭方法 |
|||
closeDropdown() { |
|||
this.state.isOpen = false; |
|||
this.$busEmit('closed'); |
|||
this.$emit('closed'); |
|||
}, |
|||
|
|||
this.state.isOpen = false |
|||
this.$nextTick(() => { |
|||
this.$emit('closed') |
|||
}) |
|||
}, |
|||
|
|||
// 安全切换方法 |
|||
toggleDropdown() { |
|||
if (this.state.eventLock) return |
|||
// 统一打开方法 |
|||
openDropdown() { |
|||
// 通知同组其他下拉框关闭 |
|||
if (this.group) { |
|||
this.$busEmit(`dropdown-group-${this.group}`, this._uid); |
|||
} |
|||
|
|||
this.state.isOpen = true; |
|||
this.$busEmit('opened'); |
|||
this.$emit('opened'); |
|||
}, |
|||
|
|||
if (this.state.isOpen) { |
|||
this.handleClickAway() |
|||
} else { |
|||
this.$emit('opened') |
|||
this.state.isOpen = true |
|||
} |
|||
}, |
|||
|
|||
// 核心安全选择方法 |
|||
selectItem(item) { |
|||
if (this.state.updateDepth > 1) { |
|||
console.error('Recursion detected in dropdown selection') |
|||
this.state.isOpen = false |
|||
this.state.updateDepth = 0 |
|||
return |
|||
} |
|||
|
|||
const newValue = item[this.valueKey] |
|||
// 优化后的切换方法 |
|||
// toggleDropdown() { |
|||
// if (this.state.eventLock) return; |
|||
|
|||
// if (this.state.isOpen) { |
|||
// this.closeDropdown(); |
|||
// } else { |
|||
// this.openDropdown(); |
|||
// } |
|||
// }, |
|||
|
|||
// 值无变化时直接返回 |
|||
if (this.state.lastEmittedValue === JSON.stringify(newValue)) { |
|||
this.state.isOpen = false |
|||
return |
|||
// 优化后的点击外部处理 |
|||
handleClickAway(event) { |
|||
// 排除点击的是触发元素的情况 |
|||
// debugger |
|||
// const trigger = this.$el.querySelector('.select-trigger'); |
|||
// if (trigger && trigger.contains(event.target)) return; |
|||
|
|||
// if (this.state.isOpen) { |
|||
// this.closeDropdown(); |
|||
// } |
|||
console.log('Before:', { |
|||
isOpen: this.state.isOpen, |
|||
display: this.$refs.dropdown.querySelector('.select-dropdown').style.display |
|||
}); |
|||
|
|||
// 确保下拉框元素实际可见 |
|||
const dropdownEl = this.$refs.dropdown.querySelector('.select-dropdown'); |
|||
const isActuallyVisible = dropdownEl && |
|||
(dropdownEl.style.display !== 'none'); |
|||
|
|||
if (isActuallyVisible && !this.state.isOpen) { |
|||
// 状态不同步时强制修正 |
|||
this.state.isOpen = true; |
|||
this.$nextTick(() => { |
|||
this.closeDropdown(); |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
// 正常处理 |
|||
if (this.state.isOpen) { |
|||
this.closeDropdown(); |
|||
} |
|||
}, |
|||
|
|||
// 核心安全选择方法 |
|||
selectItem(item) { |
|||
if (this.state.updateDepth > 1) { |
|||
console.error('Recursion detected in dropdown selection') |
|||
this.state.isOpen = false |
|||
this.state.updateDepth = 0 |
|||
return |
|||
} |
|||
|
|||
const newValue = item[this.valueKey] |
|||
|
|||
// 值无变化时直接返回 |
|||
if (this.state.lastEmittedValue === JSON.stringify(newValue)) { |
|||
this.state.isOpen = false |
|||
return |
|||
} |
|||
|
|||
this.state.updateDepth++ |
|||
this.state.eventLock = true |
|||
this.state.lastEmittedValue = JSON.stringify(newValue) |
|||
|
|||
// 使用setTimeout确保调用栈清空 |
|||
setTimeout(() => { |
|||
try { |
|||
this.$emit('input', newValue) |
|||
this.$emit('change', Object.freeze({ ...item })) |
|||
} catch (e) { |
|||
console.error('Dropdown emit error:', e) |
|||
} finally { |
|||
this.state.isOpen = false |
|||
this.state.updateDepth = 0 |
|||
this.state.eventLock = false |
|||
} |
|||
}, 0) |
|||
}, |
|||
|
|||
selectNullItem() { |
|||
this.state.eventLock = true |
|||
this.state.lastEmittedValue = null |
|||
|
|||
setTimeout(() => { |
|||
this.$emit('input', null) |
|||
this.$emit('change', null) |
|||
this.state.isOpen = false |
|||
this.state.eventLock = false |
|||
}, 0) |
|||
} |
|||
|
|||
this.state.updateDepth++ |
|||
this.state.eventLock = true |
|||
this.state.lastEmittedValue = JSON.stringify(newValue) |
|||
|
|||
// 使用setTimeout确保调用栈清空 |
|||
setTimeout(() => { |
|||
try { |
|||
this.$emit('input', newValue) |
|||
this.$emit('change', Object.freeze({ ...item })) |
|||
} catch (e) { |
|||
console.error('Dropdown emit error:', e) |
|||
} finally { |
|||
this.state.isOpen = false |
|||
this.state.updateDepth = 0 |
|||
this.state.eventLock = false |
|||
} |
|||
}, 0) |
|||
}, |
|||
|
|||
selectNullItem() { |
|||
this.state.eventLock = true |
|||
this.state.lastEmittedValue = null |
|||
|
|||
setTimeout(() => { |
|||
this.$emit('input', null) |
|||
this.$emit('change', null) |
|||
this.state.isOpen = false |
|||
this.state.eventLock = false |
|||
}, 0) |
|||
} |
|||
}, |
|||
|
|||
// 添加性能标记 |
|||
created() { |
|||
this.$_performanceMark = `dropdown-${Date.now()}` |
|||
performance.mark(this.$_performanceMark) |
|||
}, |
|||
beforeDestroy() { |
|||
performance.measure('dropdown-lifetime', this.$_performanceMark) |
|||
if (this.group) { |
|||
this.$busOff(`dropdown-group-${this.group}`); |
|||
} |
|||
// performance.measure('dropdown-lifetime', this.$_performanceMark) |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped lang="scss"> |
|||
/* Your existing styles remain the same */ |
|||
.custom-select { |
|||
display: inline-block; |
|||
vertical-align: middle; |
|||
height: 38px; |
|||
position: relative; |
|||
font-family: Arial, sans-serif; |
|||
} |
|||
|
|||
.select-trigger { |
|||
border-radius: 2px; |
|||
opacity: 1; |
|||
background: #FFFFFF; |
|||
border: 1px solid #DFE2E6; |
|||
width: 100%; |
|||
height: 40px; |
|||
box-sizing: border-box; |
|||
padding: 10px 12px; |
|||
cursor: pointer; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
} |
|||
|
|||
.is-open .select-trigger { |
|||
border: 1px solid #006AFF; |
|||
transition: all .5s; |
|||
outline: 3px solid #D8E9FA; |
|||
} |
|||
|
|||
.select-trigger:hover { |
|||
border-color: #006AFF; |
|||
transition: all .5s; |
|||
} |
|||
|
|||
.arrow-icon { |
|||
width: 12px; |
|||
} |
|||
|
|||
.select-dropdown { |
|||
position: absolute; |
|||
top: 100%; |
|||
right: 0; |
|||
width: 100%; |
|||
border: 1px solid #ccc; |
|||
box-sizing: border-box; |
|||
border-radius: 4px; |
|||
background-color: #fff; |
|||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); |
|||
z-index: 1000; |
|||
margin-top: 4px; |
|||
max-height: 384px; |
|||
overflow-y: auto; |
|||
padding: 0 12px 12px 12px; |
|||
} |
|||
|
|||
.dropdown-item { |
|||
padding: 12px 10px; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.dropdown-item:hover { |
|||
background: #F6F7FA; |
|||
} |
|||
|
|||
.dropdown-item.is-selected { |
|||
background-color: #F6F7FA; |
|||
color: #006AFF; |
|||
} |
|||
|
|||
/* 展开收起动画 */ |
|||
.slide-fade-enter-active, |
|||
.slide-fade-leave-active { |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.slide-fade-enter-from, |
|||
.slide-fade-leave-to { |
|||
opacity: 0; |
|||
transform: translateY(-10px); |
|||
} |
|||
</style> |
|||
} |
|||
</script> |
|||
|
|||
<style scoped lang="scss"> |
|||
/* Your existing styles remain the same */ |
|||
.custom-select { |
|||
display: inline-block; |
|||
vertical-align: middle; |
|||
height: 38px; |
|||
position: relative; |
|||
font-family: Arial, sans-serif; |
|||
} |
|||
|
|||
.select-trigger { |
|||
border-radius: 2px; |
|||
opacity: 1; |
|||
background: #FFFFFF; |
|||
border: 1px solid #DFE2E6; |
|||
width: 100%; |
|||
height: 40px; |
|||
box-sizing: border-box; |
|||
padding: 10px 12px; |
|||
cursor: pointer; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
} |
|||
|
|||
.is-open .select-trigger { |
|||
border: 1px solid #006AFF; |
|||
transition: all .5s; |
|||
outline: 3px solid #D8E9FA; |
|||
} |
|||
|
|||
.select-trigger:hover { |
|||
border-color: #006AFF; |
|||
transition: all .5s; |
|||
} |
|||
|
|||
.arrow-icon { |
|||
width: 12px; |
|||
} |
|||
|
|||
.select-dropdown { |
|||
position: absolute; |
|||
top: 100%; |
|||
right: 0; |
|||
width: 100%; |
|||
border: 1px solid #ccc; |
|||
box-sizing: border-box; |
|||
border-radius: 4px; |
|||
background-color: #fff; |
|||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); |
|||
z-index: 1000; |
|||
margin-top: 4px; |
|||
max-height: 384px; |
|||
overflow-y: auto; |
|||
padding: 0 12px 12px 12px; |
|||
} |
|||
|
|||
.dropdown-item { |
|||
padding: 12px 10px; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.dropdown-item:hover { |
|||
background: #F6F7FA; |
|||
} |
|||
|
|||
.dropdown-item.is-selected { |
|||
background-color: #F6F7FA; |
|||
color: #006AFF; |
|||
} |
|||
|
|||
/* 展开收起动画 */ |
|||
.slide-fade-enter-active, |
|||
.slide-fade-leave-active { |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.slide-fade-enter-from, |
|||
.slide-fade-leave-to { |
|||
opacity: 0; |
|||
transform: translateY(-10px); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,64 @@ |
|||
|
|||
.preview-container { |
|||
max-width: 800px; |
|||
min-width: 300px; |
|||
margin: 0 auto; |
|||
padding: 24px; |
|||
border-radius: 4px; |
|||
background: #FAFAFA; |
|||
|
|||
.preview_top { |
|||
margin-bottom: 20px; |
|||
} |
|||
|
|||
.preview-title { |
|||
text-align: center; |
|||
color: #1E2226; |
|||
} |
|||
|
|||
.toggle-container { |
|||
display: flex; |
|||
height: 26px; |
|||
justify-content: center; |
|||
align-items: center; |
|||
padding: 3px 6px; |
|||
border-radius: 4px; |
|||
background: #F2F3F5; |
|||
} |
|||
|
|||
.toggle-button { |
|||
padding: 1px 12px; |
|||
border-radius: 2px; |
|||
box-sizing: border-box; |
|||
cursor: pointer; |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.toggle-button.active { |
|||
color: #006AFF; |
|||
background: #FFFFFF; |
|||
} |
|||
|
|||
.toggle-button.active:after { |
|||
/* content: ''; |
|||
position: absolute; |
|||
bottom: -11px; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 2px; |
|||
background-color: #1890ff; */ |
|||
} |
|||
|
|||
.content-container { |
|||
/* min-height: 300px; |
|||
padding: 20px; |
|||
border: 2px solid #ffd700; |
|||
border-radius: 4px; |
|||
background-color: #fff; */ |
|||
} |
|||
|
|||
.desktop-view, |
|||
.mobile-view { |
|||
width: 100%; |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
import Vue from 'vue' |
|||
import GuipMessage from './src/index.vue' |
|||
|
|||
const defaultIcons = { |
|||
success: 'icon-success', |
|||
info: 'icon-info', |
|||
warning: 'icon-warning', |
|||
error: 'icon-error' |
|||
} |
|||
|
|||
const defaultImages = { |
|||
success: require('../assets/message_Success.png'), |
|||
info: require('../assets/message_Warning.png'), |
|||
warning: require('../assets/message_Warning.png'), |
|||
error: require('../assets/message_error.png') |
|||
} |
|||
|
|||
const MessageConstructor = Vue.extend(GuipMessage) |
|||
let messageQueue = [] |
|||
|
|||
const showMessage = (options) => { |
|||
if (typeof options === 'string') { |
|||
options = { message: options } |
|||
} |
|||
|
|||
const config = { |
|||
type: 'info', |
|||
duration: 3000, |
|||
position: 'top-center', |
|||
showImage: true, |
|||
...options |
|||
} |
|||
|
|||
if (config.showImage) { |
|||
config.imageUrl = options.imageUrl || defaultImages[config.type] |
|||
} else { |
|||
config.iconClass = options.iconClass || defaultIcons[config.type] |
|||
} |
|||
|
|||
const instance = new MessageConstructor({ |
|||
propsData: config |
|||
}).$mount() |
|||
|
|||
document.body.appendChild(instance.$el) |
|||
messageQueue.push(instance) |
|||
|
|||
return { |
|||
close: () => instance.close() |
|||
} |
|||
} |
|||
|
|||
// 添加类型快捷方法
|
|||
['success', 'info', 'warning', 'error'].forEach(type => { |
|||
showMessage[type] = (message, options = {}) => { |
|||
return showMessage({ message, type, ...options }) |
|||
} |
|||
}) |
|||
|
|||
// 添加关闭所有方法
|
|||
showMessage.closeAll = () => { |
|||
messageQueue.forEach(instance => instance.close()) |
|||
messageQueue = [] |
|||
} |
|||
|
|||
export default showMessage |
|||
@ -0,0 +1,72 @@ |
|||
<template> |
|||
<transition name="fade"> |
|||
<div v-if="visible" class="guip-custom-message" :class="[type, position]"> |
|||
<img v-if="showImage" :src="imageUrl" class="message-image" /> |
|||
<i v-else :class="iconClass"></i> |
|||
<span class="message-content">{{ message }}</span> |
|||
</div> |
|||
</transition> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'GuipMessage', |
|||
props: { |
|||
type: { |
|||
type: String, |
|||
default: 'info', |
|||
validator: value => ['success', 'warning', 'error', 'info'].includes(value) |
|||
}, |
|||
message: { |
|||
type: String, |
|||
required: true |
|||
}, |
|||
duration: { |
|||
type: Number, |
|||
default: 3000 |
|||
}, |
|||
position: { |
|||
type: String, |
|||
default: 'top-center', |
|||
validator: value => ['top-center', 'top-right', 'top-left', 'bottom-center', 'bottom-right', 'bottom-left'].includes(value) |
|||
}, |
|||
showImage: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
imageUrl: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
iconClass: { |
|||
type: String, |
|||
default: '' |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
visible: false |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.visible = true |
|||
this.startTimer() |
|||
}, |
|||
methods: { |
|||
startTimer() { |
|||
if (this.duration > 0) { |
|||
setTimeout(() => { |
|||
this.close() |
|||
}, this.duration) |
|||
} |
|||
}, |
|||
close() { |
|||
this.visible = false |
|||
setTimeout(() => { |
|||
this.$destroy() |
|||
this.$el.parentNode.removeChild(this.$el) |
|||
}, 300) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,7 @@ |
|||
import GuipSwitch from './src/index.vue' |
|||
|
|||
GuipSwitch.install = function(Vue) { |
|||
Vue.component(GuipSwitch.name || 'GuipSwitch', GuipSwitch) |
|||
} |
|||
|
|||
export default GuipSwitch |
|||
@ -0,0 +1,109 @@ |
|||
<template> |
|||
<el-form-item :prop="prop" :label="label" :rules="rules"> |
|||
<div class="guip_switchWrap"> |
|||
<span :class="['switchDesc', { 'fl': float == 'left' }, { 'fr': float == 'right' }]" |
|||
v-if="activeText || inactiveText"> |
|||
{{ internalValue === activeValue ? activeText : inactiveText |
|||
}}</span> |
|||
|
|||
<el-switch v-model="internalValue" :active-color="activeColor" :inactive-color="inactiveColor" |
|||
v-bind="$attrs" :disabled="disabled" :active-value="activeValue" :inactive-value="inactiveValue" |
|||
@change="handleChange"> |
|||
<!-- 自定义开启时的图标 --> |
|||
<template #active-icon> |
|||
</template> |
|||
<!-- 自定义关闭时的图标 --> |
|||
<template #inactive-icon> |
|||
</template> |
|||
</el-switch> |
|||
</div> |
|||
</el-form-item> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'GuipSwitch', |
|||
inheritAttrs: false, |
|||
props: { |
|||
// modelValue: { type: [Boolean, String, Number], default: undefined }, |
|||
value: { type: [Boolean, String, Number], default: undefined }, |
|||
prop: String, |
|||
label: String, |
|||
rules: Array, |
|||
labelWidth: String, |
|||
|
|||
activeText: { |
|||
type: String, |
|||
default: '', |
|||
}, |
|||
inactiveText: { |
|||
type: String, |
|||
default: '', |
|||
}, |
|||
activeValue: { |
|||
type: [Boolean, String, Number], |
|||
default: true, |
|||
}, |
|||
inactiveValue: { |
|||
type: [Boolean, String, Number], |
|||
default: false, |
|||
}, |
|||
activeColor: { |
|||
type: String, |
|||
default: '#00C261', |
|||
}, |
|||
inactiveColor: { |
|||
type: String, |
|||
default: '#BABDC2', |
|||
}, |
|||
float: { |
|||
type: String, |
|||
default: 'left', |
|||
}, |
|||
disabled: { |
|||
type: Boolean, |
|||
default: false, |
|||
}, |
|||
}, |
|||
data() { |
|||
return { |
|||
internalValue: this.value, |
|||
errorMsg: '' |
|||
} |
|||
}, |
|||
computed: { |
|||
// internalValue: { |
|||
// get() { |
|||
// return this.modelValue !== undefined ? this.modelValue : this.value |
|||
// }, |
|||
// set(val) { |
|||
// this.$emit('update:modelValue', val) |
|||
// this.$emit('input', val) |
|||
// } |
|||
// } |
|||
}, |
|||
watch: { |
|||
value(newVal) { |
|||
this.internalValue = newVal |
|||
}, |
|||
// modelValue(newVal) { |
|||
// this.internalValue = newVal |
|||
// }, |
|||
}, |
|||
methods: { |
|||
handleChange(val) { |
|||
// this.$emit('update:modelValue', val) |
|||
this.$emit('input', val) |
|||
this.$emit('change', val) |
|||
this.validateField() |
|||
}, |
|||
validateField() { |
|||
if (this.prop && this.$parent.validateField) { |
|||
this.$parent.validateField(this.prop, (error) => { |
|||
this.errorMsg = error || '' |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,7 @@ |
|||
import GuipTable from './src/index.vue' |
|||
|
|||
GuipTable.install = function(Vue) { |
|||
Vue.component(GuipTable.name || 'GuipTable', GuipTable) |
|||
} |
|||
|
|||
export default GuipTable |
|||
@ -0,0 +1,145 @@ |
|||
<template> |
|||
<div class="table-container"> |
|||
|
|||
<el-table ref="guiptable" v-bind="$attrs" :data="tableData" v-on="$listeners" :border="border" @selection-change="handleSelectionChange" |
|||
:style="{ width: width ? width : '100%', height: height ? height : '100%' }" v-loading="loading" empty-text=" "> |
|||
<!-- 多选 --> |
|||
<template v-if="multiple"> |
|||
<el-table-column type="selection" width="55"> |
|||
</el-table-column> |
|||
</template> |
|||
<!-- 自定义header --> |
|||
<slot></slot> |
|||
<!-- 通过json数据遍历渲染 --> |
|||
<template v-if="columns"> |
|||
<el-table-column v-for="column in columns" :key="column.prop" :prop="column.prop" :label="column.label" |
|||
:width="column.width"> |
|||
<template #default="{ row }" v-if="column.popover"> |
|||
<el-popover :visible="row[`popoverVisible_${column.prop}`]" class="tablePopover" placement="top" |
|||
trigger="click"> |
|||
<!-- 自定义内容插槽 --> |
|||
<slot :name="`popover-content-${column.prop}`" :row="row" :column="column"> |
|||
<div> |
|||
<p>默认内容:{{ `popoverVisible_${column.prop}` }}</p> |
|||
<el-input v-model="row[`edit_${column.prop}`]" :placeholder="`请输入${column.label}`" /> |
|||
</div> |
|||
</slot> |
|||
<!-- 确定和取消按钮 --> |
|||
<div class="flex" style="text-align: right; margin-top: 32px;justify-content: flex-end;"> |
|||
<GuipButton size="medium" @click="handleCancel(row, column.prop)">取消</GuipButton> |
|||
<GuipButton type="primary" @click="handleConfirm(row, column.prop)" size="medium">确定</GuipButton> |
|||
</div> |
|||
<!-- 单元格内容 --> |
|||
<template #reference> |
|||
<span style="cursor: pointer">{{ row[column.prop] }}</span> |
|||
</template> |
|||
</el-popover> |
|||
</template> |
|||
|
|||
</el-table-column> |
|||
</template> |
|||
</el-table> |
|||
<div v-if="tableData && tableData.length === 0" class="custom-empty"> |
|||
<img :src="emptyImg" alt="无数据" /> |
|||
<p>暂无数据</p> |
|||
</div> |
|||
</div> |
|||
|
|||
</template> |
|||
|
|||
<script> |
|||
import GuipButton from '../../GuipButton/src/index.vue'; |
|||
|
|||
export default { |
|||
name: 'GuipTextarea', |
|||
props: ['tableData', 'loading', 'width', 'height', 'autoColumn', 'columns', 'border', 'multiple'], |
|||
data() { |
|||
return { |
|||
emptyImg:require('../../assets/table_empty.png') |
|||
// loading:false |
|||
} |
|||
}, |
|||
components: { |
|||
GuipButton, |
|||
}, |
|||
mounted() { |
|||
}, |
|||
methods: { |
|||
handleSelectionChange(row) { |
|||
// 获取的当前行信息 |
|||
console.log(row, '======'); |
|||
this.$emit('selectChange', row) |
|||
}, |
|||
// 点击确定按钮 |
|||
handleConfirm(row, prop) { |
|||
// 将编辑后的值同步到原始数据 |
|||
row[prop] = row[`edit_${prop}`]; |
|||
row[`popoverVisible_${prop}`] = false; // 关闭气泡框 |
|||
this.$emit('confirm', row, prop); // 触发确认事件 |
|||
}, |
|||
// 点击取消按钮 |
|||
handleCancel(row, prop) { |
|||
// 恢复原始数据 |
|||
row[`edit_${prop}`] = row[prop]; |
|||
row[`popoverVisible_${prop}`] = false; // 关闭气泡框 |
|||
this.$emit('cancel', row, prop); // 触发取消事件 |
|||
}, |
|||
} |
|||
} |
|||
</script> |
|||
<style scoped lang="scss"> |
|||
|
|||
/* 外层容器需设为 relative,以便空状态定位 */ |
|||
.table-container { |
|||
position: relative; |
|||
width: 100%; |
|||
} |
|||
|
|||
/* 空状态固定定位(不随滚动移动) */ |
|||
.custom-empty { |
|||
position: absolute; |
|||
left: 50%; |
|||
top: 50%; |
|||
transform: translate(-50%, -35%); |
|||
width: 200px; |
|||
text-align: center; |
|||
background-color: #fff; /* 避免透明背景被表格线干扰 */ |
|||
// padding: 20px; |
|||
z-index: 10; /* 确保显示在表格上方 */ |
|||
img{ |
|||
width: 80px; |
|||
height: 80px; |
|||
} |
|||
p{ |
|||
font-size: 12px; |
|||
font-weight: normal; |
|||
line-height: 13px; |
|||
letter-spacing: 0.08em; |
|||
color: #626573; |
|||
} |
|||
} |
|||
|
|||
/* 确保表格有最小高度(避免高度塌陷) */ |
|||
.el-table ::v-deep .el-table__body-wrapper { |
|||
min-height: 162px !important; |
|||
} |
|||
|
|||
.empty-image { |
|||
width: 160px; |
|||
height: 160px; |
|||
} |
|||
|
|||
.empty-text { |
|||
color: #626573; |
|||
letter-spacing: 0.08em; |
|||
height: 18px; |
|||
line-height: 18px; |
|||
} |
|||
.el-empty{ |
|||
padding: 0; |
|||
} |
|||
.guip-table ::v-deep .el-empty__description{ |
|||
line-height: 20px; |
|||
margin-top: 0; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,7 @@ |
|||
import GuipToolTip from './src/index.vue' |
|||
|
|||
GuipToolTip.install = function(Vue) { |
|||
Vue.component(GuipToolTip.name || 'GuipToolTip', GuipToolTip) |
|||
} |
|||
|
|||
export default GuipToolTip; |
|||
@ -0,0 +1,104 @@ |
|||
<template> |
|||
<el-tooltip |
|||
v-bind="mergedProps" |
|||
:disabled="disabled" |
|||
:visible="controlledVisible" |
|||
@show="handleShow" |
|||
@hide="handleHide" |
|||
> |
|||
<slot></slot> |
|||
|
|||
<!-- 用于自定义提示内容 --> |
|||
<template v-if="$slots.content" #content> |
|||
<slot name="content"></slot> |
|||
</template> |
|||
</el-tooltip> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'GuipToolTip', |
|||
props: { |
|||
content: String, |
|||
placement: {//位置 |
|||
type: String, |
|||
default: 'top' |
|||
}, |
|||
effect: {//主题颜色 |
|||
type: String, |
|||
default: 'dark' |
|||
}, |
|||
disabled: Boolean, |
|||
// 控制模式,如果为true则需要手动控制显示/隐藏 |
|||
manual: Boolean, |
|||
// 延迟显示/隐藏(毫秒) |
|||
openDelay: { |
|||
type: Number, |
|||
default: 0 |
|||
}, |
|||
closeDelay: { |
|||
type: Number, |
|||
default: 0 |
|||
}, |
|||
// 是否在点击后隐藏(适用于点击触发) |
|||
hideAfterClick: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
controlledVisible: false |
|||
} |
|||
}, |
|||
computed: { |
|||
mergedProps() { |
|||
return { |
|||
effect: this.effect, |
|||
placement: this.placement, |
|||
content: this.content, |
|||
openDelay: this.openDelay, |
|||
closeDelay: this.closeDelay, |
|||
hideAfterClick: this.hideAfterClick, |
|||
...this.$attrs |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
handleShow() { |
|||
if (this.manual) { |
|||
this.controlledVisible = true |
|||
} |
|||
this.$emit('show') |
|||
}, |
|||
handleHide() { |
|||
if (this.manual) { |
|||
this.controlledVisible = false |
|||
} |
|||
this.$emit('hide') |
|||
}, |
|||
// 手动显示方法 |
|||
show() { |
|||
if (this.manual && !this.disabled) { |
|||
this.controlledVisible = true |
|||
} |
|||
}, |
|||
// 手动隐藏方法 |
|||
hide() { |
|||
if (this.manual) { |
|||
this.controlledVisible = false |
|||
} |
|||
}, |
|||
// 切换显示/隐藏状态 |
|||
toggle() { |
|||
if (this.manual && !this.disabled) { |
|||
this.controlledVisible = !this.controlledVisible |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
/* 可以添加一些自定义样式 */ |
|||
</style> |
|||
@ -0,0 +1,41 @@ |
|||
.prompt-text { |
|||
padding: 8px 13px; |
|||
border-radius: 4px; |
|||
|
|||
.flex-text { |
|||
display: flex; |
|||
align-items: center; |
|||
align-self: stretch; |
|||
justify-content: space-between; |
|||
z-index: 1; |
|||
} |
|||
|
|||
.prompt-icon { |
|||
width: 16px; |
|||
height: 16px; |
|||
; |
|||
margin-right: 8px; |
|||
} |
|||
|
|||
.prompt-desc { |
|||
color: #1E2226; |
|||
letter-spacing: 0.08em; |
|||
} |
|||
|
|||
.prompt-extra {} |
|||
} |
|||
|
|||
.prompt-text.info { |
|||
background: #F2F7FF; |
|||
border: 1px solid #BFDAFF; |
|||
} |
|||
|
|||
.prompt-text.notice { |
|||
background: #FEFCE8; |
|||
border: 1px solid rgba(255, 140, 0, 0.3); |
|||
} |
|||
|
|||
.prompt-text.warning { |
|||
background: #FFF1F0; |
|||
border: 1px solid #FFA39E; |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
import SvgIcon from './src/index.vue' |
|||
|
|||
SvgIcon.install = function(Vue) { |
|||
Vue.component(SvgIcon.name || 'SvgIcon', SvgIcon) |
|||
} |
|||
|
|||
export default SvgIcon |
|||
@ -0,0 +1,126 @@ |
|||
<template> |
|||
<div |
|||
class="guip-svg-icon-wrapper" |
|||
:style="{ width: size + 'px', height: size + 'px' }" |
|||
@click="handleClick" |
|||
@mouseenter="isHovered = true" |
|||
@mouseleave="isHovered = false" |
|||
> |
|||
<div |
|||
class="svg-icon" |
|||
v-html="svgContent" |
|||
:style="{ |
|||
'--icon-color': isHovered ? hoverColor : color, |
|||
'--icon-hover-color': hoverColor |
|||
}" |
|||
></div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'SvgIcon', |
|||
props: { |
|||
// SVG文件路径(必须) |
|||
path: { |
|||
type: String, |
|||
required: true, |
|||
validator: value => value.endsWith('.svg') |
|||
}, |
|||
// 图标大小(像素) |
|||
size: { |
|||
type: Number, |
|||
default: 24 |
|||
}, |
|||
// 默认颜色 |
|||
color: { |
|||
type: String, |
|||
default: '#333333' |
|||
}, |
|||
// 悬停颜色 |
|||
hoverColor: { |
|||
type: String, |
|||
default: '#409EFF' |
|||
}, |
|||
// 禁用状态 |
|||
disabled: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
svgContent: '', |
|||
isHovered: false |
|||
} |
|||
}, |
|||
watch: { |
|||
path: { |
|||
immediate: true, |
|||
handler: 'loadSvg' |
|||
} |
|||
}, |
|||
methods: { |
|||
async loadSvg() { |
|||
try { |
|||
// 使用动态import加载SVG文件 |
|||
const response = await fetch(this.path) |
|||
if (!response.ok) throw new Error('SVG加载失败') |
|||
this.svgContent = await response.text() |
|||
|
|||
// 如果SVG中有fill属性,替换为currentColor以便CSS控制 |
|||
this.svgContent = this.svgContent.replace(/fill="[^"]*"/g, 'fill="currentColor"') |
|||
} catch (error) { |
|||
console.error('加载SVG图标失败:', error) |
|||
this.svgContent = '' |
|||
} |
|||
}, |
|||
handleClick(event) { |
|||
if (!this.disabled) { |
|||
this.$emit('click', event) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
/* .svg-icon-wrapper1 { |
|||
display: inline-flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
cursor: pointer; |
|||
transition: all 0.3s; |
|||
} |
|||
|
|||
.svg-icon-wrapper1:hover { |
|||
opacity: 0.8; |
|||
} |
|||
|
|||
.svg-icon { |
|||
width: 100%; |
|||
height: 100%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
color: var(--icon-color); |
|||
transition: color 0.3s; |
|||
} |
|||
|
|||
.svg-icon:hover { |
|||
color: var(--icon-hover-color); |
|||
} |
|||
|
|||
.svg-icon-wrapper1[disabled] { |
|||
cursor: not-allowed; |
|||
opacity: 0.5; |
|||
} |
|||
|
|||
.svg-icon >>> path { |
|||
fill: currentColor; |
|||
} |
|||
|
|||
.svg-icon >>> circle { |
|||
fill: currentColor; |
|||
} */ |
|||
</style> |
|||
@ -0,0 +1,7 @@ |
|||
import SvgIcon1 from './src/index.vue' |
|||
|
|||
SvgIcon1.install = function(Vue) { |
|||
Vue.component(SvgIcon1.name || 'SvgIcon1', SvgIcon1) |
|||
} |
|||
|
|||
export default SvgIcon1 |
|||
@ -0,0 +1,167 @@ |
|||
<template> |
|||
<div class="guip-svg-icon-wrapper1" :style="wrapperStyle" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave"> |
|||
<div class="svg-icon" v-html="svgContent" :style="{ |
|||
'--icon-color': (hoverEffect || isActive) ? activeColor : defaultColor, |
|||
'--icon-hover-color': activeColor |
|||
}"></div> |
|||
<!-- :style="iconStyle" --> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'SvgIcon1', |
|||
props: { |
|||
// 图标路径(必须) |
|||
iconPath: { |
|||
type: String, |
|||
required: true |
|||
}, |
|||
// 默认颜色 |
|||
defaultColor: { |
|||
type: String, |
|||
default: '#606266' |
|||
}, |
|||
// 激活颜色(传入后才允许变色) |
|||
activeColor: { |
|||
type: String, |
|||
default: null |
|||
}, |
|||
// 是否开启悬停变色 |
|||
hoverEffect: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
// 图标大小 |
|||
size: { |
|||
type: [String, Number], |
|||
default: '14px' |
|||
}, |
|||
// 是否当前激活状态 |
|||
isActive: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
svgContent: '', |
|||
isHovering: false, |
|||
currentColor: this.defaultColor |
|||
} |
|||
}, |
|||
computed: { |
|||
wrapperStyle() { |
|||
return { |
|||
width: typeof this.size === 'number' ? `${this.size}px` : this.size, |
|||
height: typeof this.size === 'number' ? `${this.size}px` : this.size, |
|||
display: 'inline-flex', |
|||
alignItems: 'center', |
|||
justifyContent: 'center' |
|||
} |
|||
}, |
|||
iconStyle() { |
|||
return { |
|||
width: '100%', |
|||
height: '100%', |
|||
color: this.currentColor, |
|||
transition: 'color 0.3s ease' |
|||
} |
|||
}, |
|||
shouldChangeColor() { |
|||
return this.activeColor && (this.hoverEffect || this.isActive) |
|||
} |
|||
}, |
|||
watch: { |
|||
isActive(newVal) { |
|||
console.log(newVal, 'newVal---'); |
|||
if (this.shouldChangeColor) { |
|||
this.currentColor = newVal ? this.activeColor : this.defaultColor |
|||
console.log(this.currentColor, 'this.currentColor--'); |
|||
} |
|||
}, |
|||
defaultColor(newVal) { |
|||
if (!this.isHovering && !this.isActive) { |
|||
this.currentColor = newVal |
|||
} |
|||
}, |
|||
activeColor() { |
|||
this.updateColorState() |
|||
} |
|||
}, |
|||
methods: { |
|||
async loadSvg() { |
|||
try { |
|||
const response = await fetch(this.iconPath) |
|||
this.svgContent = await response.text() |
|||
this.processSvg() |
|||
} catch (error) { |
|||
console.error('Failed to load SVG:', error) |
|||
} |
|||
}, |
|||
processSvg() { |
|||
// 确保SVG没有自带颜色,以便用CSS控制 |
|||
this.$nextTick(() => { |
|||
const svgElement = this.$el.querySelector('svg') |
|||
if (svgElement) { |
|||
// 更彻底地移除颜色属性 |
|||
svgElement.removeAttribute('fill') |
|||
svgElement.removeAttribute('style') |
|||
const paths = svgElement.querySelectorAll('path, circle, rect, polygon') |
|||
paths.forEach(el => { |
|||
el.removeAttribute('fill') |
|||
}) |
|||
svgElement.style.fill = 'currentColor' |
|||
svgElement.style.width = '100%' |
|||
svgElement.style.height = '100%' |
|||
} |
|||
}) |
|||
}, |
|||
handleMouseEnter() { |
|||
this.isHovering = true |
|||
this.updateColorState() |
|||
}, |
|||
handleMouseLeave() { |
|||
this.isHovering = false |
|||
this.updateColorState() |
|||
}, |
|||
updateColorState() { |
|||
if (this.activeColor) { |
|||
if (this.isActive) { |
|||
this.currentColor = this.activeColor |
|||
} else { |
|||
this.currentColor = this.isHovering && this.hoverEffect ? |
|||
this.activeColor : |
|||
this.defaultColor |
|||
} |
|||
} else { |
|||
this.currentColor = this.defaultColor |
|||
} |
|||
} |
|||
}, |
|||
created() { |
|||
this.loadSvg() |
|||
} |
|||
} |
|||
</script> |
|||
<style scoped> |
|||
|
|||
/* .svg-icon { |
|||
width: 100%; |
|||
height: 100%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
color: var(--icon-color); |
|||
transition: color 0.3s; |
|||
} |
|||
|
|||
.svg-icon:hover { |
|||
color: var(--icon-hover-color); |
|||
} |
|||
|
|||
.svg-icon { |
|||
display: inline-flex; |
|||
} */ |
|||
|
|||
</style> |
|||
|
After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 673 B After Width: | Height: | Size: 297 B |
|
After Width: | Height: | Size: 760 B |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 802 B |
|
After Width: | Height: | Size: 654 B |
|
After Width: | Height: | Size: 746 B |
|
After Width: | Height: | Size: 662 B |
|
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,539 @@ |
|||
// 提示框组件 |
|||
.guip-prompt-text { |
|||
padding: 8px 13px; |
|||
border-radius: 4px; |
|||
|
|||
.flex-text { |
|||
display: flex; |
|||
align-items: center; |
|||
align-self: stretch; |
|||
justify-content: space-between; |
|||
z-index: 1; |
|||
} |
|||
|
|||
.prompt-icon { |
|||
width: 16px; |
|||
height: 16px; |
|||
; |
|||
margin-right: 8px; |
|||
} |
|||
|
|||
.prompt-desc { |
|||
color: #1E2226; |
|||
letter-spacing: 0.08em; |
|||
} |
|||
|
|||
.prompt-extra {} |
|||
} |
|||
|
|||
.guip-prompt-text.info { |
|||
background: #F2F7FF; |
|||
border: 1px solid #BFDAFF; |
|||
} |
|||
|
|||
.guip-prompt-text.notice { |
|||
background: #FEFCE8; |
|||
border: 1px solid rgba(255, 140, 0, 0.3); |
|||
} |
|||
|
|||
.guip-prompt-text.warning { |
|||
background: #FFF1F0; |
|||
border: 1px solid #FFA39E; |
|||
} |
|||
|
|||
// 实时预览外框组件 |
|||
.guip-preview-container { |
|||
max-width: 800px; |
|||
min-width: 300px; |
|||
margin: 0 auto; |
|||
padding: 24px; |
|||
border-radius: 4px; |
|||
background: #FAFAFA; |
|||
|
|||
.preview_top { |
|||
margin-bottom: 20px; |
|||
} |
|||
|
|||
.preview-title { |
|||
text-align: center; |
|||
color: #1E2226; |
|||
} |
|||
|
|||
.toggle-container { |
|||
display: flex; |
|||
height: 26px; |
|||
justify-content: center; |
|||
align-items: center; |
|||
padding: 3px 6px; |
|||
border-radius: 4px; |
|||
background: #F2F3F5; |
|||
} |
|||
|
|||
.toggle-button { |
|||
padding: 1px 12px; |
|||
border-radius: 2px; |
|||
box-sizing: border-box; |
|||
cursor: pointer; |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.toggle-button.active { |
|||
color: #006AFF; |
|||
background: #FFFFFF; |
|||
} |
|||
|
|||
.toggle-button.active:after { |
|||
/* content: ''; |
|||
position: absolute; |
|||
bottom: -11px; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 2px; |
|||
background-color: #1890ff; */ |
|||
} |
|||
|
|||
.content-container { |
|||
/* min-height: 300px; |
|||
padding: 20px; |
|||
border: 2px solid #ffd700; |
|||
border-radius: 4px; |
|||
background-color: #fff; */ |
|||
} |
|||
|
|||
.desktop-view, |
|||
.mobile-view { |
|||
width: 100%; |
|||
} |
|||
} |
|||
|
|||
// 面包屑组件 |
|||
.guip-breadcrumb-container { |
|||
padding: 16px 12px; |
|||
background-color: #f5f5f5; |
|||
border-radius: 4px; |
|||
|
|||
.breadRight { |
|||
a { |
|||
text-decoration: none; |
|||
color: #006AFF; |
|||
} |
|||
} |
|||
|
|||
.home-icon { |
|||
width: 16px; |
|||
height: 16px; |
|||
} |
|||
|
|||
.breadcrumb { |
|||
display: flex; |
|||
flex-wrap: wrap; |
|||
height: 22px; |
|||
align-items: center; |
|||
padding: 0; |
|||
margin: 0; |
|||
list-style: none; |
|||
} |
|||
|
|||
.breadcrumb-item { |
|||
display: flex; |
|||
align-items: center; |
|||
height: 100%; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.router-link-active { |
|||
height: 100%; |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.breadcrumb-item a { |
|||
color: #626573; |
|||
text-decoration: none; |
|||
|
|||
&:hover { |
|||
color: #006AFF; |
|||
} |
|||
} |
|||
|
|||
.breadcrumb-item.active span { |
|||
color: #1E2226; |
|||
; |
|||
|
|||
} |
|||
|
|||
.separator { |
|||
width: 12px; |
|||
height: 12px; |
|||
} |
|||
} |
|||
|
|||
// 图标组件 svgicon1 |
|||
.guip-svg-icon-wrapper1 { |
|||
cursor: pointer; |
|||
|
|||
.svg-icon { |
|||
width: 100%; |
|||
height: 100%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
color: var(--icon-color); |
|||
transition: color 0.3s; |
|||
} |
|||
|
|||
.svg-icon:hover { |
|||
color: var(--icon-hover-color); |
|||
} |
|||
|
|||
.svg-icon { |
|||
display: inline-flex; |
|||
} |
|||
|
|||
} |
|||
|
|||
// 图标组件 svgicon |
|||
.guip-svg-icon-wrapper { |
|||
display: inline-flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
cursor: pointer; |
|||
transition: all 0.3s; |
|||
|
|||
.svg-icon { |
|||
width: 100%; |
|||
height: 100%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
color: var(--icon-color); |
|||
transition: color 0.3s; |
|||
} |
|||
|
|||
.svg-icon:hover { |
|||
color: var(--icon-hover-color); |
|||
} |
|||
|
|||
} |
|||
|
|||
.guip-svg-icon-wrapper .svg-icon ::v-deep path { |
|||
fill: currentColor; |
|||
} |
|||
|
|||
.guip-svg-icon-wrapper .svg-icon ::v-deep circle { |
|||
fill: currentColor; |
|||
} |
|||
|
|||
.guip-svg-icon-wrapper:hover { |
|||
opacity: 0.8; |
|||
} |
|||
|
|||
.guip-svg-icon-wrapper[disabled] { |
|||
cursor: not-allowed; |
|||
opacity: 0.5; |
|||
} |
|||
|
|||
// .guip-svg-icon-wrapper .svg-icon ::v-deep svg { |
|||
// fill: currentColor; |
|||
// } |
|||
|
|||
// 表格组件 |
|||
.guip-table { |
|||
.custom-empty { |
|||
text-align: center; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
} |
|||
|
|||
.empty-image { |
|||
width: 160px; |
|||
height: 160px; |
|||
} |
|||
|
|||
.empty-text { |
|||
color: #626573; |
|||
letter-spacing: 0.08em; |
|||
height: 18px; |
|||
line-height: 18px; |
|||
} |
|||
|
|||
.el-empty { |
|||
padding: 0; |
|||
} |
|||
} |
|||
|
|||
.guip-table ::v-deep .el-empty__description { |
|||
line-height: 20px; |
|||
margin-top: 0; |
|||
} |
|||
|
|||
// switch组件 |
|||
.guip_switchWrap { |
|||
|
|||
.fl { |
|||
float: left; |
|||
margin-right: 12px; |
|||
} |
|||
|
|||
.fr { |
|||
float: right; |
|||
margin-left: 12px; |
|||
} |
|||
|
|||
.switchDesc { |
|||
font-size: 12px; |
|||
font-weight: normal; |
|||
line-height: 20px; |
|||
letter-spacing: 0.08em; |
|||
font-variation-settings: "opsz"auto; |
|||
/* text/text_3 */ |
|||
color: #626573; |
|||
display: inline-block; |
|||
} |
|||
|
|||
.error-msg { |
|||
color: #f56c6c; |
|||
font-size: 12px; |
|||
margin-left: 10px; |
|||
} |
|||
} |
|||
|
|||
// 自定义下拉选择框组件 start |
|||
.guip-custom-select { |
|||
height: 38px; |
|||
position: relative; |
|||
font-family: Arial, sans-serif; |
|||
|
|||
.select-trigger { |
|||
border-radius: 2px; |
|||
opacity: 1; |
|||
background: #FFFFFF; |
|||
border: 1px solid #DFE2E6; |
|||
width: 100%; |
|||
height: 40px; |
|||
box-sizing: border-box; |
|||
padding: 10px 12px; |
|||
cursor: pointer; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
} |
|||
|
|||
.is-open .select-trigger { |
|||
border: 1px solid #006AFF; |
|||
transition: all .5s; |
|||
outline: 3px solid #D8E9FA; |
|||
} |
|||
|
|||
.select-trigger:hover { |
|||
border-color: #006AFF; |
|||
transition: all .5s; |
|||
} |
|||
|
|||
.arrow-icon { |
|||
width: 12px; |
|||
} |
|||
|
|||
.select-dropdown { |
|||
position: absolute; |
|||
top: 100%; |
|||
right: 0; |
|||
width: 100%; |
|||
border: 1px solid #ccc; |
|||
box-sizing: border-box; |
|||
border-radius: 4px; |
|||
background-color: #fff; |
|||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); |
|||
z-index: 1000; |
|||
margin-top: 4px; |
|||
max-height: 384px; |
|||
overflow-y: auto; |
|||
padding: 0 12px 12px 12px; |
|||
} |
|||
|
|||
.dropdown-item { |
|||
padding: 12px 10px; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.dropdown-item:hover { |
|||
background: #F6F7FA; |
|||
} |
|||
|
|||
.dropdown-item.is-selected { |
|||
background-color: #F6F7FA; |
|||
color: #006AFF; |
|||
} |
|||
|
|||
/* 展开收起动画 */ |
|||
.slide-fade-enter-active, |
|||
.slide-fade-leave-active { |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.slide-fade-enter-from, |
|||
.slide-fade-leave-to { |
|||
opacity: 0; |
|||
transform: translateY(-10px); |
|||
} |
|||
} |
|||
|
|||
// 自定义下拉选择框组件 end |
|||
|
|||
// input 组件 start |
|||
.guip-input-unit { |
|||
position: absolute; |
|||
right: 12px; |
|||
/* 根据需要调整位置 */ |
|||
top: 50%; |
|||
transform: translateY(-50%); |
|||
pointer-events: none; |
|||
/* 防止单位文本影响输入框的点击事件 */ |
|||
} |
|||
|
|||
// input 组件 end |
|||
|
|||
// 提示信息组件 start |
|||
.guip-custom-message { |
|||
position: fixed; |
|||
z-index: 9999; |
|||
display: flex; |
|||
align-items: center; |
|||
padding: 14px 21px; |
|||
transition: all 0.3s; |
|||
font-size: 14px; |
|||
font-weight: normal; |
|||
line-height: normal; |
|||
letter-spacing: 0.08em; |
|||
color: #626573; |
|||
border-radius: 4px; |
|||
background: #FFFFFF; |
|||
/* 阴影/低阴影 */ |
|||
box-shadow: 0px 3px 8px 0px rgba(0, 0, 0, 0.16); |
|||
|
|||
.message-image { |
|||
width: 18px; |
|||
height: 18px; |
|||
margin-right: 10px; |
|||
} |
|||
} |
|||
|
|||
/* 位置样式 */ |
|||
.guip-custom-message.top-center { |
|||
top: 20px; |
|||
left: 50%; |
|||
transform: translateX(-50%); |
|||
} |
|||
|
|||
.guip-custom-message.top-right { |
|||
top: 20px; |
|||
right: 20px; |
|||
} |
|||
|
|||
.guip-custom-message.top-left { |
|||
top: 20px; |
|||
left: 20px; |
|||
} |
|||
|
|||
.guip-custom-message.bottom-center { |
|||
bottom: 20px; |
|||
left: 50%; |
|||
transform: translateX(-50%); |
|||
} |
|||
|
|||
.guip-custom-message.bottom-right { |
|||
bottom: 20px; |
|||
right: 20px; |
|||
} |
|||
|
|||
.guip-custom-message.bottom-left { |
|||
bottom: 20px; |
|||
left: 20px; |
|||
} |
|||
|
|||
|
|||
/* 动画效果 */ |
|||
.fade-enter-active, |
|||
.fade-leave-active { |
|||
transition: opacity 0.3s, transform 0.3s; |
|||
} |
|||
|
|||
.fade-enter, |
|||
.fade-leave-to { |
|||
opacity: 0; |
|||
} |
|||
|
|||
.fade-enter.top-center, |
|||
.fade-leave-to.top-center { |
|||
transform: translate(-50%, -20px); |
|||
} |
|||
|
|||
.fade-enter.top-right, |
|||
.fade-leave-to.top-right, |
|||
.fade-enter.top-left, |
|||
.fade-leave-to.top-left { |
|||
transform: translateY(-20px); |
|||
} |
|||
|
|||
.fade-enter.bottom-center, |
|||
.fade-leave-to.bottom-center { |
|||
transform: translate(-50%, 20px); |
|||
} |
|||
|
|||
.fade-enter.bottom-right, |
|||
.fade-leave-to.bottom-right, |
|||
.fade-enter.bottom-left, |
|||
.fade-leave-to.bottom-left { |
|||
transform: translateY(20px); |
|||
} |
|||
|
|||
// 提示信息组件 end |
|||
|
|||
// 自定义下拉框、input+下拉组合框 |
|||
.combo-formItem { |
|||
::v-deep { |
|||
.form-item-bottom { |
|||
position: relative; |
|||
} |
|||
|
|||
.select-trigger { |
|||
background: #F6F7FA; |
|||
border-color: transparent; |
|||
} |
|||
|
|||
.is-open .select-trigger { |
|||
border-color: #006AFF; |
|||
} |
|||
|
|||
.el-input__inner { |
|||
border-radius: 2px 0 0 2px; |
|||
} |
|||
} |
|||
|
|||
.self-drop-wrap { |
|||
position: absolute; |
|||
z-index: 1; |
|||
width: 100%; |
|||
} |
|||
|
|||
.appendDrop { |
|||
height: 38px; |
|||
align-items: center; |
|||
border-radius: 0 2px 2px 0; |
|||
border: 1px solid #DFE2E6; |
|||
border-left-color: transparent; |
|||
justify-content: center; |
|||
box-sizing: border-box; |
|||
padding: 0 30px 0 12px; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
overflow: hidden; |
|||
|
|||
&:hover { |
|||
border: 1px solid #006AFF; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 组合框样式 end |
|||
@ -0,0 +1,5 @@ |
|||
module.exports = { |
|||
plugins: [ |
|||
require('autoprefixer')({ grid: true }) |
|||
] |
|||
} |
|||