版本迭代

This commit is contained in:
liuye
2023-08-06 16:50:17 +08:00
parent 590fb406cc
commit 5c0b1afe69
63 changed files with 14131 additions and 40 deletions

View File

@@ -0,0 +1,333 @@
<template>
<div class="ai-article">
<div v-html="value"></div>
</div>
</template>
<script>
export default {
name: 'AiArticle',
props: {
value: {
type: String
}
}
}
</script>
<style lang="scss" scoped>
.ai-article {
width: 100%;
line-height: 1.75;
font-weight: 400;
color: #333;
font-size: 14px;
text-align: justify;
overflow-x: auto;
word-break: break-word;
:deep( h1 ){
margin: 1.3rem 0;
line-height: 1.2
}
:deep( p ){
line-height: 2.27rem
}
:deep( hr ){
border: none;
border-top: 1px solid #ddd;
margin-top: 2.7rem;
margin-bottom: 2.7rem
}
:deep( img:not(.equation)), :deep( iframe), :deep( embed), :deep( video ){
display: block;
margin: 18px auto;
max-width: 100% !important;
}
:deep( img.equation ){
margin: 0 .1em;
max-width: 100% !important;
vertical-align: middle
}
:deep( figure ){
margin: 2.7rem auto;
text-align: center
}
:deep( img:not(.equation) ){
cursor: zoom-in
}
:deep( figure figcaption ){
text-align: center;
font-size: 1rem;
line-height: 2.7rem;
color: #909090
}
:deep( pre ){
line-height: 1.93rem;
overflow: auto
}
:deep( code),
:deep( pre ){
font-family: Menlo, Monaco, Consolas, Courier New, monospace
}
:deep( code ){
font-size: 1rem;
padding: .26rem .53em;
word-break: break-word;
color: #4e5980;
background-color: #f8f8f8;
border-radius: 2px;
overflow-x: auto
}
:deep( pre>code ){
font-size: 1rem;
padding: .67rem 1.3rem;
margin: 0;
word-break: normal;
display: block
}
:deep( a ){
color: #259
}
:deep( a:active),
:deep( a:hover ){
color: #275b8c
}
:deep( table ){
width: 100%;
margin-top: 18px;
margin-bottom: 18px;
overflow: auto;
font-size: 1rem;
text-align: center;
border-top: 1px solid #ccc;
border-left: 1px solid #ccc;
}
:deep( thead ){
background: #f6f6f6;
color: #000;
text-align: left
}
:deep( td),
:deep( th ){
padding: 3px 5px;
border-bottom: 1px solid #ccc;
border-right: 1px solid #ccc;
}
:deep( td ){
min-width: 10rem
}
:deep( blockquote ){
margin: 1em 0;
border-left: 4px solid #ddd;
padding: 0 1.3rem
}
:deep( blockquote>p ){
margin: .6rem 0
}
:deep( ol),
:deep( ul ){
padding-left: 2.7rem
}
:deep( ol li),
:deep( ul li ){
margin-bottom: .6rem
}
:deep( ol ol),
:deep( ol ul),
:deep( ul ol),
:deep( ul ul ){
margin-top: .27rem
}
:deep( pre>code ){
overflow-x: auto;
-webkit-overflow-scrolling: touch;
color: #333;
background: #f8f8f8
}
:deep( p ){
line-height: inherit;
margin-top: 18px;
margin-bottom: 18px
}
:deep( p:first-child){
margin-top: 0!important;
}
:deep( img ){
max-height: none
}
:deep( a ){
color: #0269c8;
border-bottom: 1px solid #d1e9ff
}
:deep( code ){
background-color: #fff5f5;
color: #ff502c;
font-size: .87em;
padding: .065em .4em
}
:deep( blockquote ){
color: #666;
padding: 1px 23px;
margin: 18px 0;
border-left: 4px solid #cbcbcb;
background-color: #f8f8f8
}
:deep( blockquote:after ){
display: block;
content: ""
}
:deep( blockquote>p ){
margin: 10px 0
}
:deep( blockquote.warning ){
position: relative;
border-left-color: #f75151;
margin-left: 8px
}
:deep( blockquote.warning:before ){
position: absolute;
top: 14px;
left: -12px;
background: #f75151;
border-radius: 50%;
content: "!";
width: 20px;
height: 20px;
color: #fff;
display: flex;
align-items: center;
justify-content: center
}
:deep( ol),
:deep( ul ){
padding-left: 28px
}
:deep( ol li),
:deep( ul li ){
margin-bottom: 0;
list-style: inherit
}
:deep( ol li.task-list-item),
:deep( ul li.task-list-item ){
list-style: none
}
:deep( ol li.task-list-item ol),
:deep( ol li.task-list-item ul),
:deep( ul li.task-list-item ol),
:deep( ul li.task-list-item ul ){
margin-top: 0
}
:deep( ol li ){
padding-left: 6px
}
:deep( pre ){
position: relative;
line-height: 1.75
}
:deep( pre>code ){
padding: 15px 12px
}
:deep( pre>code.hljs[lang] ){
padding: 18px 15px 12px
}
:deep( h1),
:deep( h2),
:deep( h3),
:deep( h4),
:deep( h5),
:deep( h6 ){
color: #333;
line-height: 1.5;
margin-top: 35px;
margin-bottom: 10px;
padding-bottom: 5px;
font-weight: 500;
}
:deep( h1 ){
font-size: 30px;
margin-bottom: 5px
}
:deep( h2 ){
padding-bottom: 12px;
font-size: 24px;
border-bottom: 1px solid #ececec
}
:deep( h3 ){
font-size: 18px;
padding-bottom: 0
}
:deep( h4 ){
font-size: 16px
}
:deep( h5 ){
font-size: 15px
}
:deep( h6 ){
margin-top: 5px
}
:deep( h1.heading+h2.heading ){
margin-top: 20px
}
:deep( h1.heading+h3.heading ){
margin-top: 15px
}
:deep( .heading+.heading ){
margin-top: 0
}
:deep( h1+:not(.heading) ){
margin-top: 25px
}
}
</style>

114
src/components/AiBar.vue Normal file
View File

@@ -0,0 +1,114 @@
<template>
<div class="aibar" :style="{ marginBottom }" :class="{'aibar-center':titlePosition === 'center',card}">
<div v-if="titlePosition === 'center'"/>
<div class="aibar-left" :class="[titlePosition === 'center' ? 'aibar-left__center' : '']">
<template v-if="!$slots.title">{{ title }}</template>
<slot name="title" v-else></slot>
</div>
<div class="aibar-right">
<slot v-if="$slots.right" name="right"/>
<slot v-else/>
</div>
</div>
</template>
<script>
export default {
name: 'AiBar',
props: {
title: {
type: String
},
customCliker: {
type: Boolean,
default: false
},
marginBottom: {
type: String,
default: '16px'
},
titlePosition: {
type: String,
default: 'left'
},
card: Boolean
},
}
</script>
<style lang="scss" scoped>
.aibar {
display: flex;
position: relative;
align-items: center;
justify-content: space-between;
height: 56px;
padding: 0 16px;
box-sizing: border-box;
border-bottom: 1px solid #EEEEEE;
&.card {
background: #fff;
box-shadow: 0 4px 6px -2px rgba(15, 15, 21, 0.15);
}
.aibar-left {
color: #222;
font-size: 16px;
font-weight: 700;
}
.aibar-left__center {
position: relative;
width: 556px;
text-align: center;
word-break: break-all;
line-height: 24px;
}
.aibar-right {
display: flex;
align-items: center;
color: #5088FF;
font-size: 12px;
i {
line-height: 1;
color: #5088FF;
}
span {
font-size: 12px;
}
& > div, & > a {
display: flex;
align-items: center;
margin-right: 20px;
&:last-child {
margin-right: 0;
}
}
}
}
.aibar-center {
height: auto;
padding: 10px 0;
h2 {
margin: 0 0 10px 0;
}
p {
color: #888;
font-size: 14px;
}
}
</style>

64
src/components/AiCard.vue Normal file
View File

@@ -0,0 +1,64 @@
<template>
<section class="ai-card" :class="{panel,headerPanel}">
<ai-bar v-if="!hideHeader" :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" :style="{paddingTop: hideHeader ? '20px' : 0}">
<slot v-if="$scopedSlots.content" name="content"/>
<slot v-else/>
</div>
</section>
</template>
<script>
export default {
name: 'AiCard',
props: {
title: {
type: String
},
hideTitle: Boolean,
panel: Boolean,
headerPanel: Boolean
},
computed: {
hideHeader: v => v.hideTitle || (v.panel && !v.title)
}
}
</script>
<style scoped lang="scss">
.ai-card {
margin-bottom: 20px;
padding-bottom: 20px;
background: #FFFFFF;
overflow: hidden;
box-shadow: 0 4px 6px -2px rgba(15, 15, 21, 0.15);
border-radius: 2px;
.ai-card__body {
height: calc(100% - 72px);
padding: 0 20px 0;
overflow-y: auto;
}
&.panel, &.headerPanel {
margin-bottom: 0;
.ai-card__body {
padding: 12px 16px;
}
.aibar {
margin-bottom: 0 !important;
}
}
}
</style>

131
src/components/AiDetail.vue Normal file
View 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>

121
src/components/AiDialog.vue Normal file
View File

@@ -0,0 +1,121 @@
<template>
<section class="ai-dialog__wrapper">
<el-dialog custom-class="ai-dialog" v-on="$listeners" v-bind="$attrs" :visible.sync="dialog">
<div class="ai-dialog__header fill" slot="title" v-text="title"/>
<div class="ai-dialog__content">
<div class="ai-dialog__content--wrapper pad-r8">
<slot/>
</div>
</div>
<template v-if="customFooter" slot="footer">
<slot name="footer"/>
</template>
<div v-else class="dialog-footer" slot="footer">
<el-button @click="onCancel">取消</el-button>
<el-button @click="onConfirm" type="primary">确认</el-button>
</div>
</el-dialog>
</section>
</template>
<script>
export default {
name: 'AiDialog',
model: {
prop: "visible",
event: "input"
},
props: {
visible: Boolean,
title: {type: String, default: ''},
customFooter: Boolean
},
data() {
return {
dialog: false,
}
},
watch: {
dialog(v) {
this.visible != v && this.$emit("input", v)
},
visible(v) {
this.dialog = v
}
},
methods: {
onCancel() {
this.$emit('input', false)
this.$emit('update:visible', false)
this.$emit('cancel')
},
onConfirm() {
this.$emit('confirm')
}
}
}
</script>
<style lang="scss">
.ai-dialog {
margin: unset !important;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
.el-dialog__body {
padding: 20px 40px 20px;
}
.ai-dialog__content {
overflow-y: auto;
padding-bottom: 4px;
max-height: 500px;
.ai-dialog__content--wrapper {
height: 100%;
overflow-x: hidden;
overflow-y: auto;
}
}
.ai-dialog__header {
flex: 1;
height: 48px;
line-height: 48px;
padding: 0 16px;
font-size: 16px;
font-weight: 700;
}
.el-dialog__footer {
padding: 16px 20px;
box-sizing: border-box;
background: #F3F6F9;
text-align: center;
& + .el-button {
margin-left: 8px;
}
.el-button {
width: 92px;
}
}
.el-dialog__header {
padding: 0;
display: flex;
align-items: center;
border-bottom: 1px solid #eee;
}
.el-dialog__headerbtn {
position: relative;
flex-shrink: 0;
top: unset;
right: unset;
margin: 0 16px;
}
}
</style>

View File

@@ -0,0 +1,34 @@
<template>
<div class="ai-empty">
<div class="ai-empty__bg"></div>
<template v-if="!isHasTitleSlot">暂无数据</template>
<slot v-else></slot>
</div>
</template>
<script>
export default {
name: 'AiEmpty',
computed: {
isHasTitleSlot () {
return this.$slots.default
}
}
}
</script>
<style lang="scss">
.ai-empty {
margin-bottom: 10px;
text-align: center;
color: #acaaad;
.ai-empty__bg {
background: url("../assets/images/empty.svg") no-repeat center;
background-size: 120px 120px;
height: 120px;
margin: 48px auto 0;
}
}
</style>

View File

@@ -0,0 +1,91 @@
<template>
<div class="ai-info-item" :style="contentStyle">
<label class="ai-info-item__left" :style="contentLabelStyle">
<template v-if="$slots.label">
<slot name="label"/>
</template>
<template v-else>{{ label }}</template>
</label>
<div class="ai-info-item__right">
<slot v-if="$scopedSlots.default"/>
<template v-else-if="!!openType">
<ai-open-data :type="openType" :openid="value"/>
</template>
<template v-else>{{ value || value === 0 ? value : '-' }}</template>
</div>
</div>
</template>
<script>
export default {
name: 'AiInfoItem',
inject: ['AiWrapper'],
props: {
label: {
type: String
},
value: {
type: [String, Number]
},
'label-width': {
type: String
},
isLine: {
type: Boolean,
default: false
},
openType: {default: ""}
},
computed: {
contentStyle() {
let width = this.AiWrapper.autoWidth
if (this.isLine) {
width = '100%'
}
return {
width
}
},
contentLabelStyle() {
return {
width: this.labelWidth || this.AiWrapper.autoLableWidth
}
},
},
methods: {}
}
</script>
<style lang="scss" scoped>
.ai-info-item {
display: flex;
line-height: 1.4;
margin-bottom: 16px;
label {
flex-shrink: 0;
width: 96px;
margin-right: 40px;
text-align: right;
color: #888;
font-size: 14px;
}
.ai-info-item__right {
flex: 1;
color: #222;
font-size: 14px;
word-break: break-all;
}
}
</style>

263
src/components/AiList.vue Normal file
View File

@@ -0,0 +1,263 @@
<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>
<el-row type="flex" class="ai-list__blank" v-else-if="$scopedSlots.blank" :class="{hasLeft:$scopedSlots.left}">
<div class="ai-list__content--left" v-if="$scopedSlots.left">
<slot name="left"/>
</div>
<div class="fill">
<slot name="blank"/>
</div>
</el-row>
<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
: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;
: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;
}
:deep(.el-tabs__header ) {
margin: 0;
}
:deep(.el-tabs ) {
height: 100%;
}
: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;
height: calc(100% - 50px);
&.hasLeft {
padding: 16px 0;
}
& > .fill {
height: 100%;
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>

View File

@@ -0,0 +1,76 @@
<template>
<section class="AiPullDown">
<div class="line"/>
<div class="down-content" @click="handleExpand">
<i :class="expandIcon"/>
<span>{{ btnText }}</span>
</div>
<div class="line"/>
</section>
</template>
<script>
export default {
name: "AiPullDown",
props: {
target: String,
height: {default: 4},
},
data() {
return {
expand: false
}
},
methods: {
handleExpand() {
this.expand = !this.expand
if (this.target) {
} else this.$emit('change', this.expandStyle)
}
},
computed: {
btnText() {
return this.expand ? '收起高级搜索' : '展开高级搜索'
},
expandStyle() {
let initStyle = {overflow: 'hidden', height: `${this.height}px`}
this.expand && (initStyle.height = "auto")
return initStyle
},
expandIcon() {
return this.expand ? 'iconfont iconDouble_Up' : 'iconfont iconDouble_Down'
}
},
mounted() {
this.$emit("change", this.expandStyle)
}
}
</script>
<style lang="scss" scoped>
.AiPullDown {
display: flex;
.line {
flex: 1;
min-width: 0;
border-top: 1px solid #eee;
}
.down-content {
cursor: pointer;
padding: 0 8px;
height: 24px;
border-radius: 0 0 8px 8px;
border: 1px solid #eee;
border-top: 0;
box-sizing: border-box;
color: #333;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

View 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;
}
:deep( .el-input__inner ){
border-radius: 0;
}
.content {
display: flex;
align-items: center;
* + * {
.el-input__inner {
margin-left: -1px;
}
}
}
}
</style>

View 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 <= 55
},
},
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;
}
:deep(.searchLeftZone ){
flex: 1;
min-width: 0;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
: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;
}
:deep( .searchLeftZone > .el-button), :deep( .searchRightZone > .el-button ){
margin-left: 0;
}
</style>

View 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 #1FBAD6;
color: #1FBAD6;
}
}
}
</style>

400
src/components/AiTable.vue Normal file
View File

@@ -0,0 +1,400 @@
<template>
<div class="ai-table" :class="[isShowBorder ? 'ai-table__border' : 'ai-table__noborder']">
<el-table :data="tableData"
header-cell-class-name="ai-table__header"
cell-class-name="ai-table__cell"
row-class-name="ai-table__row"
:class="{'ai-header__border': isShowBorder}"
:ref="refName"
:size="tableSize"
:stripe="stripe"
:tooltip-effect="tooltipEffect"
@selection-change="handleSelectionChange"
v-on="$listeners"
v-bind="$attrs"
v-loading="loading">
<template v-for="colConfig in colConfigs.filter(e=>!e.hide)">
<slot v-if="colConfig.slot && colConfig.slot !== 'options'" :name="colConfig.slot"/>
<component
:key="colConfig.id"
v-else-if="colConfig.component"
:is="colConfig.component"
:col-config="colConfig">
</component>
<el-table-column
v-else-if="colConfig.dict"
:key="colConfig.id"
v-bind="colConfig">
<span slot-scope="{row}" :style="{color:colConfig.color||dict.getColor(colConfig.dict, row[colConfig.prop])}">
{{ $dict.getLabel(colConfig.dict, row[colConfig.prop]) }}
</span>
</el-table-column>
<el-table-column
v-else-if="colConfig.openType"
:key="colConfig.id"
v-bind="colConfig">
<template v-slot="{row}">
<ai-open-data :type="colConfig.openType" :openid="row[colConfig.prop]"/>
</template>
</el-table-column>
<el-table-column
v-else-if="colConfig.type"
:key="colConfig.id"
v-bind="colConfig"
:width="colConfig.width || 100"/>
<el-table-column v-else v-bind="colConfig" :key="colConfig.id"
:show-overflow-tooltip="colConfig['show-overflow-tooltip'] != false">
<template slot-scope="scope">
<render-slot v-if="colConfig.render" :render="colConfig.render" :row="scope.row" :index="scope.$index"
:column="colConfig"/>
<span v-else>{{ getValue(colConfig, scope.row) }}</span>
</template>
</el-table-column>
</template>
<slot class="table-options" name="options"/>
<template #empty>
<slot v-if="$scopedSlots.empty" name="empty"/>
<AiEmpty v-else class="no-data" style="height:160px;"/>
</template>
</el-table>
<div class="pagination newPagination" v-if="isShowPagination">
<el-pagination
background
:current-page="page.current"
:total="page.total"
:page-size="page.size"
v-bind="$attrs"
:page-sizes="pageSizes"
:layout="layout"
:pager-count="page.pagerCount"
@size-change="handleSizeChange"
@current-change="handleChange">
<div class="paginationPre">
<el-checkbox v-if="isHasPaginationBtnsSlot" :disabled="!tableData.length" :indeterminate="isIndeterminate"
:value="checkAll" @click.native="toggleAllSelection">全选
</el-checkbox>
<slot name="pagination"/>
<div class="pagination-btns">
<slot name="paginationBtns"></slot>
</div>
<div class="paginationPre-total" :style="{marginLeft: isHasPaginationBtnsSlot ? '24px' : 0}"><label class="color-primary">{{ page.total }}</label>条记录
</div>
</div>
</el-pagination>
</div>
</div>
</template>
<script>
import moment from 'dayjs'
export default {
name: 'AiTable',
model: {
prop: "tableData",
event: "change"
},
props: {
colConfigs: {type: Array, required: true},
tableData: {type: Array, required: true, default: () => []},
current: {default: 1},
size: {default: 10},
isShowPagination: {type: Boolean, default: true},
total: Number,
layout: {type: String, default: 'slot,->, prev, pager, next, sizes, jumper'},
stripe: {type: Boolean, default: true},
loading: {type: Boolean, default: false},
tooltipEffect: String,
tableSize: String,
tableRef: String,
pagerCount: {default: 5},
pageSizes: {default: () => [10, 20, 50, 100]},
pageConfig: Object
},
data() {
return {
name: '',
chooseList: [],
defaultPage: {}
}
},
components: {
renderSlot: {
functional: true,
props: {
row: Object,
render: Function,
index: Number,
column: {type: Object, default: null},
},
render: (h, data) => {
const params = {
row: data.props.row,
index: data.props.index
}
if (data.props.column) {
params.column = data.props.column
}
return data.props.render(h, params)
}
}
},
computed: {
refName: v => v.tableRef || `aiTable${new Date().getTime()}`,
isShowBorder: v => !!v.$attrs.border || v.$attrs.border === '',
isHasPaginationBtnsSlot: v => v.$scopedSlots.paginationBtns,
isIndeterminate: v => v.chooseList.length > 0 && v.chooseList.length < v.tableData.length,
checkAll: v => v.chooseList.length == v.tableData.length && v.tableData !== 0,
page() {
return {
current: this.current,
size: this.size,
total: this.total,
pagerCount: this.pagerCount,
...this.pageConfig
}
},
},
methods: {
handleChange(current) {
this.$emit('update:current', current)
this.$emit('update:pageConfig', {...this.pageConfig, current})
this.$nextTick(() => {
this.$emit('getList')
})
},
handleSizeChange(size) {
this.$emit('update:size', size)
this.$emit('update:pageConfig', {...this.pageConfig, size})
this.$nextTick(() => {
this.$emit('getList')
})
},
handleSelectionChange(e) {
this.chooseList = e
this.$emit('handleSelectionChange', e)
},
getValue(colConfig, row) {
if (typeof colConfig.format === 'function') {
return colConfig.format.call(this, row[colConfig.prop])
}
if (colConfig.dateFormat) {
return moment(row[colConfig.prop]).format(colConfig.dateFormat)
}
return row[colConfig.prop] === 0 || row[colConfig.prop] ? row[colConfig.prop] : '-'
},
/**
* 表格方法代理
*/
clearSelection() {
this.$refs[this.refName].clearSelection()
},
toggleRowSelection() {
this.$refs[this.refName].toggleRowSelection(...arguments)
},
toggleAllSelection() {
this.$refs[this.refName].toggleAllSelection()
},
toggleRowExpansion() {
this.$refs[this.refName].toggleRowExpansion(...arguments)
},
setCurrentRow() {
this.$refs[this.refName].setCurrentRow(...arguments)
},
clearSort() {
this.$refs[this.refName].clearSort()
},
clearFilter() {
this.$refs[this.refName].clearFilter(...arguments)
},
doLayout() {
this.$refs[this.refName].doLayout()
},
sort() {
this.$refs[this.refName].sort(...arguments)
},
}
}
</script>
<style lang="scss" scoped>
.ai-table {
.color-primary {
// color: $primaryColor;
}
:deep( .ai-header__border .ai-table__header ) {
border-bottom: 1px solid #d0d4dc !important;
border-right: 1px solid #d0d4dc !important;
}
:deep( .el-table--border ) {
border: 1px solid #d0d4dc;
border-right: none;
border-bottom: none;
}
:deep( .el-table ) {
color: #222;
.caret-wrapper {
height: 24px;
.ascending {
top: 1px;
}
.descending {
bottom: 1px;
}
}
thead {
color: #555
}
}
:deep( .cell ) {
line-height: 24px;
}
:deep( .el-table__header ) {
th {
padding: 8px 0;
}
tr {
.cell {
font-weight: 700;
}
th:first-child {
.cell {
padding-left: 40px !important;
}
}
}
}
:deep( .el-table__body ) {
tr td:first-child .cell {
padding-left: 40px !important;
}
}
:deep( .el-table__fixed-right ) {
.el-table__body {
tr td:first-child .cell {
padding-left: 0 !important;
padding-right: 0;
}
}
}
:deep( .ai-table__header ) {
border-bottom: none;
background: #F3F4F5;
}
:deep(.el-pager ) {
li.active + li {
border-left: 1px solid #d0d4dc;
}
}
.newPagination {
width: 100%;
display: flex;
align-items: center;
height: 64px;
padding: 0 40px !important;
.el-pagination {
width: 100%;
padding: 0;
}
.paginationPre {
display: flex;
height: 28px;
line-height: 1;
font-size: 14px;
font-weight: normal;
align-items: center;
.pagination-btns {
display: flex;
align-items: center;
gap: 8px;
color: #1FBAD6 !important;
:deep( span), :deep( div ) {
font-size: 12px;
cursor: pointer;
color: #1FBAD6 !important;
&:hover {
opacity: 0.8;
}
}
}
.paginationPre-total {
font-size: 12px;
color: #555;
label {
padding: 0 2px;
font-weight: 700;
}
}
& > * + * {
margin-left: 24px;
}
:deep( .el-pagination button), .el-pagination span:not([class*=suffix]) {
line-height: 1 !important;
}
:deep(.el-checkbox ) {
display: flex;
align-items: center;
.el-checkbox__input, .el-checkbox__inner {
width: 14px;
height: 14px;
min-width: 0 !important;
line-height: 1 !important;
}
.el-checkbox__label {
font-size: 12px;
color: #222222;
height: auto !important;
line-height: 1 !important;
padding-left: 3px !important;
}
}
}
}
}
.ai-table__noborder {
:deep( .el-table td), :deep( .el-table th.is-center ) {
border: none;
}
.el-table::before {
display: none;
}
:deep( .el-table--striped .el-table__body tr.el-table__row--striped td ) {
background: #F5F6F9;
}
:deep( .el-table__fixed-right::before), :deep( .el-table__fixed::before ) {
display: none;
}
}
</style>

140
src/components/AiTitle.vue Normal file
View File

@@ -0,0 +1,140 @@
<template>
<section class="AiTitle" :class="{ 'bottomBorder': isShowBottomBorder, AiTitleSub: isHasSub}">
<i class="iconfont iconBack_Large" v-if="isShowBack" @click="onBackBtnClick"/>
<div class="fill">
<div class="ailist-title">
<div class="ailist-title__left">
<h2>{{ title }}</h2>
<p>{{ tips }}</p>
</div>
<div class="ailist-title__right">
<div class="aititle-right__btns">
<slot name="rightBtn"/>
</div>
</div>
</div>
<div class="subtitle" v-if="$scopedSlots.sub">
<slot name="sub"/>
</div>
</div>
</section>
</template>
<script>
export default {
name: 'AiTitle',
model: {
prop: 'value',
event: 'change'
},
props: {
title: {
type: String,
required: true
},
tips: {
type: String
},
instance: {
type: Function
},
value: {
type: String
},
isShowBack: {
type: Boolean,
default: false
},
isShowBottomBorder: {
type: Boolean,
default: false
}
},
computed: {
isHasSub() {
return this.$slots.sub
}
},
methods: {
onBackBtnClick() {
this.closePage()
this.$emit('onBackClick')
this.$emit('back')
}
}
}
</script>
<style scoped lang="scss">
.AiTitle {
display: flex;
.ailist-title {
display: flex;
align-items: center;
justify-content: space-between;
height: 48px;
.ailist-title__left {
display: flex;
align-items: center;
p {
margin-left: 20px;
font-size: 14px;
color: #FA5555;
}
& > i {
width: auto;
height: auto;
margin-right: 8px;
font-size: 16px;
}
h2 {
color: #222;
font-size: 16px;
font-weight: 600;
}
}
.ailist-title__right {
display: flex;
align-items: center;
}
:deep(.el-button ) {
margin-left: 8px !important;
}
}
&.AiTitleSub {
height: auto;
padding: 16px 0;
.ailist-title {
height: auto;
margin-bottom: 3px;
}
}
&.bottomBorder {
border-bottom: 1px solid #D8DCE3;
}
.subtitle {
width: 100%;
color: #888888;
font-size: 12px;
margin-top: 4px;
}
.iconBack_Large {
line-height: 48px;
margin-right: 8px;
}
}
</style>

View File

@@ -0,0 +1,86 @@
<template>
<div class="ai-wrapper">
<ai-title class="w100" v-if="title" :title="title"/>
<div class="ai-wrapper-content" :class="{border}">
<slot/>
</div>
</div>
</template>
<script>
import AiTitle from "./AiTitle";
export default {
name: 'AiWrapper',
components: {AiTitle},
componentName: 'AiWrapper',
provide() {
return {
AiWrapper: this
}
},
props: {
'label-width': {
type: String
},
columnsNumber: {
type: Number,
default: 2
},
border: Boolean,
title: String
},
computed: {
autoWidth() {
return ((1 / this.columnsNumber) * 100).toFixed(2) + '%'
},
autoLableWidth() {
return this.labelWidth
}
},
}
</script>
<style lang="scss" scoped>
.ai-wrapper {
.w100 {
width: 100%;
}
.ai-wrapper-content {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
&.border {
border-left: 1px solid #d0d4dc;
border-top: 1px solid #d0d4dc;
.ai-info-item {
border-bottom: 1px solid #d0d4dc;
border-right: 1px solid #d0d4dc;
margin-bottom: 0;
line-height: 32px;
.ai-info-item__left {
background: rgba(0, 0, 0, .03);
padding-right: 16px;
border-right: 1px solid #d0d4dc;
margin-right: 16px;
}
.el-textarea__inner {
border: none;
padding-left: 0;
}
}
}
}
}
</style>