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