ui库和web端产品库合并版本(还需修复细节)
This commit is contained in:
41
ui/packages/layout/AiCard.vue
Normal file
41
ui/packages/layout/AiCard.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<section class="ai-card">
|
||||
<ai-bar v-if="!hideTitle" :title="title" v-bind="$attrs">
|
||||
<template #title>
|
||||
<slot name="title"></slot>
|
||||
</template>
|
||||
<template #right>
|
||||
<slot name="right"></slot>
|
||||
</template>
|
||||
</ai-bar>
|
||||
<div class="ai-card__body">
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AiCard',
|
||||
props: {
|
||||
title: {
|
||||
type: String
|
||||
},
|
||||
hideTitle: Boolean
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.ai-card {
|
||||
margin-bottom: 20px;
|
||||
background: #FFFFFF;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 6px -2px rgba(15, 15, 21, 0.15);
|
||||
border-radius: 2px;
|
||||
|
||||
.ai-card__body {
|
||||
padding: 12px 40px 22px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
131
ui/packages/layout/AiDetail.vue
Normal file
131
ui/packages/layout/AiDetail.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<div class="ai-detail">
|
||||
<div class="ai-detail__title">
|
||||
<slot name="title"></slot>
|
||||
</div>
|
||||
<div class="ai-detail__step" v-if="isHasStepSlot">
|
||||
<slot name="step"></slot>
|
||||
</div>
|
||||
<div class="ai-detail__content" :class="className">
|
||||
<div class="ai-detail__content--wrapper" :class="{'ai-detail__content--side':isHasSidebar,list}">
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ai-detail__footer" v-if="isHasFooterSlot">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AiDetail',
|
||||
|
||||
props: {
|
||||
isHasSidebar: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
list: Boolean
|
||||
},
|
||||
|
||||
computed: {
|
||||
isHasFooterSlot() {
|
||||
return this.$slots.footer
|
||||
},
|
||||
|
||||
isHasStepSlot() {
|
||||
return this.$slots.step
|
||||
},
|
||||
|
||||
className() {
|
||||
if (this.isHasFooterSlot) {
|
||||
if (this.isHasStepSlot) {
|
||||
return 'ai-detail__content--active-step'
|
||||
}
|
||||
|
||||
return 'ai-detail__content--active'
|
||||
} else {
|
||||
if (this.isHasStepSlot) {
|
||||
return 'ai-detail__content--step'
|
||||
}
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ai-detail {
|
||||
height: 100%;
|
||||
background: #F5F6F9;
|
||||
overflow: hidden;
|
||||
|
||||
.ai-detail__title {
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
.ai-detail__step {
|
||||
height: 72px;
|
||||
}
|
||||
|
||||
.ai-detail__content {
|
||||
height: calc(100% - 48px);
|
||||
padding: 0 0 20px 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: overlay;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1740px) {
|
||||
.ai-detail__content {
|
||||
padding: 0 20px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-detail__content--wrapper {
|
||||
position: relative;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding-top: 20px;
|
||||
|
||||
&.list {
|
||||
max-width: unset;
|
||||
margin: 0 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1740px) {
|
||||
.ai-detail__content--side {
|
||||
margin-left: 128px;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-detail__content--active {
|
||||
height: calc(100% - 48px - 64px);
|
||||
}
|
||||
|
||||
.ai-detail__content--active-step {
|
||||
height: calc(100% - 48px - 64px - 72px);
|
||||
}
|
||||
|
||||
.ai-detail__content--step {
|
||||
height: calc(100% - 48px - 72px);
|
||||
}
|
||||
|
||||
.ai-detail__footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
background: #F5F5F5;
|
||||
box-shadow: 0px 1px 0px 0px #E5E5E5;
|
||||
|
||||
.el-button {
|
||||
width: 92px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
37
ui/packages/layout/AiDialogBtn.vue
Normal file
37
ui/packages/layout/AiDialogBtn.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<section class="AiDialogBtn">
|
||||
<div @click="dialog=true">
|
||||
<slot v-if="$scopedSlots.btn" name="btn"/>
|
||||
<el-button v-else type="text">{{ text }}</el-button>
|
||||
</div>
|
||||
<ai-dialog :visible.sync="dialog" :title="dialogTitle" :width="width" :customFooter="customFooter" v-on="$listeners"
|
||||
@onConfirm="dialog=false,$emit('onConfirm')" v-bind="$attrs">
|
||||
<slot/>
|
||||
<template #footer>
|
||||
<el-button @click="dialog=false">关闭</el-button>
|
||||
</template>
|
||||
</ai-dialog>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiDialogBtn",
|
||||
props: {
|
||||
text: {default: "点击弹窗"},
|
||||
dialogTitle: {default: "展示信息"},
|
||||
customFooter: {default: true},
|
||||
width: {default: "1200px"}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialog: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiDialogBtn {
|
||||
}
|
||||
</style>
|
||||
55
ui/packages/layout/AiHighlight.vue
Normal file
55
ui/packages/layout/AiHighlight.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<section class="AiHighlight" :class="{bold,color}" v-html="html"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiHighlight",
|
||||
props: {
|
||||
value: {default: ""},
|
||||
content: {default: ""},
|
||||
keywords: {default: () => []},
|
||||
color: {default: ""},
|
||||
bold: Boolean
|
||||
},
|
||||
computed: {
|
||||
words: v => [v.keywords].flat(),
|
||||
html() {
|
||||
let {content, words, value} = this
|
||||
const reg = new RegExp(`(${words.join("|")})`, 'g')
|
||||
content = content?.replace(/(@v)/g, this.keywordRender(value))
|
||||
return !!words.join("|") ? content?.replace(reg, this.keywordRender('$1')) : content
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
keywordRender(word) {
|
||||
const {color} = this
|
||||
return `<p class="keyword" style="color:${color}">${word}</p>`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep.AiHighlight {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
|
||||
.keyword {
|
||||
display: block;
|
||||
width: auto;
|
||||
color: $primaryColor;
|
||||
}
|
||||
|
||||
&.color {
|
||||
.keyword {
|
||||
}
|
||||
}
|
||||
|
||||
&.bold {
|
||||
.keyword {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
54
ui/packages/layout/AiIntro.vue
Normal file
54
ui/packages/layout/AiIntro.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<section class="AiIntro" v-if="detail.subtitle">
|
||||
<ai-title :title="detail.menuName">
|
||||
<div slot="rightBtn">
|
||||
<el-button type="text" icon="iconfont iconDocumentation">操作示例</el-button>
|
||||
</div>
|
||||
<div slot="sub" v-html="detail.subtitle"/>
|
||||
</ai-title>
|
||||
<el-button type="primary" @click="$emit('start')">开始使用</el-button>
|
||||
<div v-html="detail.guideContent"/>
|
||||
</section>
|
||||
<ai-empty v-else>暂未配置引导页</ai-empty>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiIntro",
|
||||
props: {
|
||||
id: {default: ""},
|
||||
instance: Function,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rid: "",
|
||||
detail: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getConfigs() {
|
||||
const {rid: id} = this
|
||||
this.instance.post("/admin/sysappguideconfig/queryDetailById", null, {
|
||||
params: {id}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.detail = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.rid = this.id || this.$route.name
|
||||
this.getConfigs()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiIntro {
|
||||
::v-deep.ailist-title__right {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
248
ui/packages/layout/AiList.vue
Normal file
248
ui/packages/layout/AiList.vue
Normal file
@@ -0,0 +1,248 @@
|
||||
<template>
|
||||
<div class="ai-list" :class="listClass">
|
||||
<div class="ai-list__title" v-if="$slots.title">
|
||||
<slot name="title"></slot>
|
||||
</div>
|
||||
<div class="ai-list__tabs" v-if="$slots.tabs">
|
||||
<slot name="tabs"></slot>
|
||||
</div>
|
||||
<div class="ai-list__blank" v-else-if="$slots.blank">
|
||||
<slot name="blank"/>
|
||||
</div>
|
||||
<div class="ai-list__content" v-else :class="contentClass">
|
||||
<div class="ai-list__content--wrapper">
|
||||
<slot name="custom" v-if="!!$slots.custom"/>
|
||||
<template v-else>
|
||||
<div class="ai-list__content--left" v-if="$slots.left">
|
||||
<slot name="left"></slot>
|
||||
</div>
|
||||
<div class="ai-list__content--right" :style="{width: !$slots.left ? '100%' : 'auto' }">
|
||||
<div class="ai-list__content--right-wrapper" :style="{minHeight: $slots.left ? '100%' : 'auto' }">
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AiList',
|
||||
props: {
|
||||
isTabs: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
||||
listClass() {
|
||||
if (this.$slots.left || this.$slots.tabs) {
|
||||
return 'ai-left__list'
|
||||
}
|
||||
|
||||
if (this.isTabs) {
|
||||
return 'ai-left__list ai-list__notab'
|
||||
}
|
||||
|
||||
if (!this.isTabs && !this.$slots.tabs && !this.$slots.left) {
|
||||
return 'ai-list__single'
|
||||
}
|
||||
|
||||
return ''
|
||||
},
|
||||
|
||||
contentClass() {
|
||||
if (this.isTabs) {
|
||||
return 'ai-list__tab--content'
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
//全局tab css
|
||||
::v-deep .ai-list__tabs {
|
||||
margin-top: 0 !important;
|
||||
|
||||
.el-tabs__item {
|
||||
position: relative;
|
||||
width: auto !important;;
|
||||
padding: 0 16px !important;
|
||||
}
|
||||
|
||||
.el-tabs__item.is-active {
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.el-tabs__nav-wrap::after {
|
||||
height: 1px;
|
||||
background-color: #D8DCE3;
|
||||
}
|
||||
|
||||
.el-tabs__active-bar {
|
||||
// left: -16px;
|
||||
// width: 50%!important;
|
||||
}
|
||||
|
||||
.el-tabs__nav {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.el-tabs__header {
|
||||
padding: 0;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.el-tab-pane {
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 20px;
|
||||
overflow: hidden;
|
||||
background: #f3f6f9;
|
||||
|
||||
&.ai-list__single {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
|
||||
.ai-list__title {
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
.ai-list__content {
|
||||
flex: 1;
|
||||
padding: 20px 20px 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ai-list__content--wrapper {
|
||||
height: 100%;
|
||||
margin: 0 !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ai-list__content--right-wrapper {
|
||||
// margin: 0 20px;
|
||||
padding: 20px 20px !important;
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ai-list__tabs {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
|
||||
::v-deep .el-tabs__item {
|
||||
min-width: 80px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
font-size: 14px;
|
||||
color: #222;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid transparent !important;
|
||||
}
|
||||
|
||||
::v-deep.el-tabs__header {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
::v-deep.el-tabs {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
::v-deep.el-tabs__content {
|
||||
height: calc(100% - 32px);
|
||||
background: #f3f6f9;
|
||||
|
||||
& > div {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ai-list__content {
|
||||
.ai-list__content--wrapper {
|
||||
display: flex;
|
||||
margin: 20px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-list__content--left {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
margin-right: 20px;
|
||||
padding-left: 1px;
|
||||
height: 100%;
|
||||
box-shadow: 0 4px 6px -2px rgba(15, 15, 21, 0.15);
|
||||
min-width: 264px;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.ai-list__content--right {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
||||
.ai-list__content--right-wrapper {
|
||||
padding: 12px 16px 12px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 4px 6px -2px rgba(15, 15, 21, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.ai-list__tab--content {
|
||||
height: 100% !important;
|
||||
|
||||
& > div {
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.ai-list__notab {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ai-list__blank {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-left__list {
|
||||
height: 100%;
|
||||
background: #f3f6f9;
|
||||
overflow: hidden;
|
||||
|
||||
.ai-list__content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
|
||||
.ai-list__content--wrapper {
|
||||
height: calc(100% - 32px);
|
||||
}
|
||||
|
||||
.ai-list__content--right {
|
||||
// margin-left: 11px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
52
ui/packages/layout/AiSearch.vue
Normal file
52
ui/packages/layout/AiSearch.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<section class="AiSearch">
|
||||
<slot v-if="$slots.label" name="label"/>
|
||||
<span v-else>{{ label }}</span>
|
||||
<div class="content">
|
||||
<slot/>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiSearch",
|
||||
props: {
|
||||
label: {default: ""}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiSearch {
|
||||
display: flex;
|
||||
|
||||
span {
|
||||
background: #F5F5F5;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
border: 1px solid #D0D4DC;
|
||||
border-right: none;
|
||||
padding: 0 8px;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
::v-deep .el-input__inner {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
* + * {
|
||||
.el-input__inner {
|
||||
margin-left: -1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
101
ui/packages/layout/AiSearchBar.vue
Normal file
101
ui/packages/layout/AiSearchBar.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<section>
|
||||
<div ref="AiSearchBarZone" class="AiSearchBar" :class="{bottomBorder}" :style="searchBarStyle">
|
||||
<div class="searchLeftZone">
|
||||
<slot name="left"/>
|
||||
</div>
|
||||
<div class="searchRightZone" ref="searchRightZone">
|
||||
<slot name="right"/>
|
||||
</div>
|
||||
</div>
|
||||
<ai-pull-down v-if="!isSingleRow" @change="handlePullDown" :height="rightHeight"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiSearchBar",
|
||||
props: {
|
||||
bottomBorder: Boolean,
|
||||
size: {default: "small"}
|
||||
},
|
||||
computed: {
|
||||
isSingleRow() {
|
||||
return this.height <= 45
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
height: 0,
|
||||
rightHeight: 0,
|
||||
searchBarStyle: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handlePullDown(style) {
|
||||
this.searchBarStyle = style
|
||||
if (style.height == 'auto') {
|
||||
this.searchBarStyle.marginBottom = '16px'
|
||||
} else {
|
||||
this.searchBarStyle.marginBottom = '0'
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.height = this.$refs?.AiSearchBarZone?.offsetHeight
|
||||
this.rightHeight = this.$refs?.searchRightZone?.offsetHeight
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiSearchBar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding-bottom: 12px;
|
||||
|
||||
&.bottomBorder {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
::v-deep.searchLeftZone {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
::v-deep.searchRightZone {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
align-self: flex-start;
|
||||
|
||||
.el-input {
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
* + button, * + div, * + section {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-input {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.AiPullDown {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
::v-deep .searchLeftZone > .el-button, ::v-deep .searchRightZone > .el-button {
|
||||
margin-left: 0;
|
||||
}
|
||||
</style>
|
||||
98
ui/packages/layout/AiSidebar.vue
Normal file
98
ui/packages/layout/AiSidebar.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="ai-sidebar">
|
||||
<div class="ai-sidebar__tab">
|
||||
<span
|
||||
:class="[currIndex === index ? 'ai-sidebar__tab--active' : '']"
|
||||
v-for="(item, index) in tabTitle"
|
||||
:key="index"
|
||||
@click="changeTab(index)">
|
||||
{{ item }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AiSidebar',
|
||||
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
require: true,
|
||||
default: 0
|
||||
},
|
||||
tabTitle: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value (v) {
|
||||
if (v >= 0 && this.currIndex !== v) {
|
||||
this.currIndex = v
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
currIndex: 0
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
if (this.value > 0) {
|
||||
this.currIndex = this.value
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
changeTab(index) {
|
||||
this.currIndex = index
|
||||
this.$emit('change', index)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ai-sidebar {
|
||||
position: fixed;
|
||||
margin-left: -20px;
|
||||
font-size: 14px;
|
||||
transform: translateX(-100%);
|
||||
|
||||
.ai-sidebar__tab {
|
||||
min-width: 88px;
|
||||
overflow: hidden;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
padding: 0 16px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
font-stretch: normal;
|
||||
letter-spacing: 0;
|
||||
color: #666666;
|
||||
cursor: pointer;
|
||||
border-right: 3px solid #D8DCE3;
|
||||
}
|
||||
|
||||
.ai-sidebar__tab--active {
|
||||
border-right: 3px solid $primaryColor;
|
||||
color: $primaryColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
134
ui/packages/layout/AiTreeMenu.vue
Normal file
134
ui/packages/layout/AiTreeMenu.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<section class="AiTreeMenu">
|
||||
<b>{{ title }}</b>
|
||||
<el-input v-if="!hideInput" size="small" class="searchInput" v-model="searchText" :placeholder="searchPlaceholder"
|
||||
suffix-icon="iconfont iconSearch" @change="handleSearch()" clearable/>
|
||||
<div class="treePanel" v-if="$slots.default">
|
||||
<slot/>
|
||||
</div>
|
||||
<div class="bottomBar" v-if="$slots.bottom">
|
||||
<slot name="bottom"/>
|
||||
</div>
|
||||
<div v-else class="mar-t8"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiTreeMenu",
|
||||
props: {
|
||||
title: String,
|
||||
search: String,
|
||||
searchPlaceholder: {type: String, default: "请输入..."},
|
||||
hideInput: Boolean
|
||||
},
|
||||
watch: {
|
||||
searchText(v) {
|
||||
this.$emit("update:search", v)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchText: "",
|
||||
origin: [],
|
||||
root: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSearch() {
|
||||
if (this.$slots.default) {
|
||||
this.$emit('search', this.searchText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiTreeMenu {
|
||||
background: #FAFAFB;
|
||||
border-radius: 2px 2px 0 0;
|
||||
border: 1px solid #e5e5e5;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 264px;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
|
||||
& > b {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
color: #222;
|
||||
flex-shrink: 0;
|
||||
height: 40px;
|
||||
background: #EEEFF1;
|
||||
border-bottom: 1px solid #E5E5E5;
|
||||
}
|
||||
|
||||
::v-deep .searchInput {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
input {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.el-input__suffix {
|
||||
transform: translateX(-12px);
|
||||
color: #8899BB;
|
||||
}
|
||||
}
|
||||
|
||||
.treePanel {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
|
||||
& > * {
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
::v-deep.el-tree {
|
||||
height: 100%;
|
||||
background: #FAFAFB;
|
||||
|
||||
.el-tree-node__children {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.el-tree-node__label {
|
||||
// overflow: hidden;
|
||||
// text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.el-tree-node__expand-icon {
|
||||
color: #8899BB;
|
||||
|
||||
&.is-leaf {
|
||||
color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.is-current > .el-tree-node__content {
|
||||
min-width: fit-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottomBar {
|
||||
flex-shrink: 0;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user