npmapps 1.0.20 → 1.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/aiCoach/collapseExpand/index.jsx +108 -0
- package/app/aiCoach/collapseExpand/index.module.scss +97 -0
- package/app/aiCoach/dialogueSegment/index.jsx +158 -0
- package/app/aiCoach/dialogueSegment/index.module.scss +28 -0
- package/app/aiCoach/index.jsx +20 -0
- package/app/aiCoach/scriptTable/index.jsx +196 -0
- package/app/aiCoach/scriptTable/index.module.scss +41 -0
- package/app/aiCoach/scriptTable/inputColumn/index.jsx +183 -0
- package/app/aiCoach/scriptTable/inputColumn/index.module.scss +115 -0
- package/package.json +1 -1
- package/app/btnSelect/btnSelect.vue +0 -169
- package/app/btnSelect/index.vue +0 -69
- package/app/btnSelect.zip +0 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
|
|
2
|
+
import { defineComponent, ref, h } from 'vue'
|
|
3
|
+
import styles from './index.module.scss'
|
|
4
|
+
import { Expand, DeleteFilled } from '@element-plus/icons-vue'
|
|
5
|
+
import draggable from 'vuedraggable'
|
|
6
|
+
import _ from 'lodash'
|
|
7
|
+
export default defineComponent({
|
|
8
|
+
// 折叠展开组件 - 列表展示
|
|
9
|
+
name: 'CollapseExpand',
|
|
10
|
+
props: {
|
|
11
|
+
collapseList: {
|
|
12
|
+
type: Array,
|
|
13
|
+
default: () => [],
|
|
14
|
+
},
|
|
15
|
+
draggableDisabled: {
|
|
16
|
+
type: Boolean,
|
|
17
|
+
default: false,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
emits: ['update:collapseList'],
|
|
21
|
+
setup(props, { emit, attrs, slots }) {
|
|
22
|
+
|
|
23
|
+
const newCollapseList = ref(_.cloneDeep(props.collapseList))
|
|
24
|
+
const dragEnd = (e) => {
|
|
25
|
+
console.log(e)
|
|
26
|
+
attrs.dragEnd && attrs.dragEnd(e, newCollapseList.value)
|
|
27
|
+
emit('update:collapseList', newCollapseList.value)
|
|
28
|
+
}
|
|
29
|
+
const handleDelete = (item) => {
|
|
30
|
+
newCollapseList.value = newCollapseList.value.filter((i) => i.id !== item.id)
|
|
31
|
+
emit('update:collapseList', newCollapseList.value)
|
|
32
|
+
}
|
|
33
|
+
return () => (
|
|
34
|
+
<div class={styles.collapseExpand}>
|
|
35
|
+
<el-collapse
|
|
36
|
+
{...attrs}
|
|
37
|
+
>
|
|
38
|
+
<draggable
|
|
39
|
+
v-model={newCollapseList.value}
|
|
40
|
+
item-key="id"
|
|
41
|
+
class="list"
|
|
42
|
+
ghost-class="ghost"
|
|
43
|
+
disabled={props.draggableDisabled}
|
|
44
|
+
onEnd={dragEnd}
|
|
45
|
+
animation={attrs.animation || 100}
|
|
46
|
+
handle=".drag-icon"
|
|
47
|
+
>
|
|
48
|
+
{{
|
|
49
|
+
item: (({ element: item, index }) => (
|
|
50
|
+
<el-collapse-item class={[styles.item,
|
|
51
|
+
index === 0 ?
|
|
52
|
+
styles.firstItem : '',
|
|
53
|
+
index === props.collapseList.length - 1 ?
|
|
54
|
+
styles.lastItem : '']}
|
|
55
|
+
key={item.id}
|
|
56
|
+
name={item.id}>
|
|
57
|
+
{{
|
|
58
|
+
// 自定义标题栏
|
|
59
|
+
title: () => (
|
|
60
|
+
attrs.title ? attrs.title(item) :
|
|
61
|
+
<div class={[styles.header]}>
|
|
62
|
+
<div class={[styles.headerLeft]}>
|
|
63
|
+
<div>
|
|
64
|
+
<el-icon style={{ cursor: 'grab' }} class={'drag-icon'} size={20}><Expand /></el-icon>
|
|
65
|
+
</div>
|
|
66
|
+
<div class={[styles.headerContent]}>
|
|
67
|
+
<div class={[styles.title]}>{item.title}</div>
|
|
68
|
+
{/* 目标描述 */}
|
|
69
|
+
<div class={[styles.goal]}>
|
|
70
|
+
{item.goal}
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
{/* 阻止冒泡避免触发折叠 */}
|
|
75
|
+
<el-popconfirm
|
|
76
|
+
title="确认删除吗?"
|
|
77
|
+
confirm-button-text="确定"
|
|
78
|
+
cancel-button-text="取消"
|
|
79
|
+
onConfirm={(e) => {
|
|
80
|
+
handleDelete(item)
|
|
81
|
+
e.stopPropagation()
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
{{
|
|
85
|
+
reference: () => (
|
|
86
|
+
<el-icon onClick={(e) => e.stopPropagation()} style={{ marginRight: '8px' }}><DeleteFilled /></el-icon>
|
|
87
|
+
)
|
|
88
|
+
}}
|
|
89
|
+
|
|
90
|
+
</el-popconfirm>
|
|
91
|
+
</div>
|
|
92
|
+
),
|
|
93
|
+
// 展开的内容区域
|
|
94
|
+
default: () => (
|
|
95
|
+
<div>
|
|
96
|
+
{item.item ? item.item(item) : null}
|
|
97
|
+
</div>
|
|
98
|
+
)
|
|
99
|
+
}}
|
|
100
|
+
</el-collapse-item>
|
|
101
|
+
))
|
|
102
|
+
}}
|
|
103
|
+
</draggable>
|
|
104
|
+
</el-collapse>
|
|
105
|
+
</div>
|
|
106
|
+
)
|
|
107
|
+
},
|
|
108
|
+
})
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
.collapseExpand {
|
|
2
|
+
:global(.el-collapse .el-collapse-item__header) {
|
|
3
|
+
padding-left: 10px;
|
|
4
|
+
}
|
|
5
|
+
.header {
|
|
6
|
+
display: flex;
|
|
7
|
+
justify-content: space-between;
|
|
8
|
+
width: 100%;
|
|
9
|
+
align-items: center;
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
.headerLeft {
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
|
|
16
|
+
.headerContent {
|
|
17
|
+
margin-left: 10px;
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
align-items: flex-start;
|
|
21
|
+
|
|
22
|
+
.title,
|
|
23
|
+
.goal {
|
|
24
|
+
display: flex;
|
|
25
|
+
height: 16px;
|
|
26
|
+
font-size: 14px;
|
|
27
|
+
font-weight: 500;
|
|
28
|
+
line-height: 16px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.goal {
|
|
32
|
+
margin-top: 5px;
|
|
33
|
+
color: #999;
|
|
34
|
+
font-size: 12px;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.item {
|
|
41
|
+
position: relative;
|
|
42
|
+
padding-left: 12px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
/* 画竖线 */
|
|
47
|
+
.item::before {
|
|
48
|
+
content: '';
|
|
49
|
+
position: absolute;
|
|
50
|
+
left: 0px;
|
|
51
|
+
top: 0;
|
|
52
|
+
bottom: 0;
|
|
53
|
+
width: 2px;
|
|
54
|
+
background-color: #FC6506;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.firstItem::before {
|
|
58
|
+
content: '';
|
|
59
|
+
position: absolute;
|
|
60
|
+
left: 0px;
|
|
61
|
+
top: 24px;
|
|
62
|
+
bottom: 0;
|
|
63
|
+
width: 2px;
|
|
64
|
+
background-color: #FC6506;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.lastItem::before {
|
|
68
|
+
content: '';
|
|
69
|
+
position: absolute;
|
|
70
|
+
left: 0px;
|
|
71
|
+
top: 0;
|
|
72
|
+
bottom: 24px;
|
|
73
|
+
width: 2px;
|
|
74
|
+
height: 24px;
|
|
75
|
+
background-color: #FC6506;
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* 每个 item 的标题前加圆点 */
|
|
80
|
+
:global(.el-collapse-item__header) {
|
|
81
|
+
position: relative;
|
|
82
|
+
padding-left: 24px;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
:global(.el-collapse-item__header)::before {
|
|
86
|
+
content: '';
|
|
87
|
+
position: absolute;
|
|
88
|
+
left: -15px;
|
|
89
|
+
top: 50%;
|
|
90
|
+
transform: translateY(-50%);
|
|
91
|
+
width: 8px;
|
|
92
|
+
height: 8px;
|
|
93
|
+
background-color: #FC6506;
|
|
94
|
+
border-radius: 50%;
|
|
95
|
+
z-index: 1;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
|
|
2
|
+
import { defineComponent, ref, watch } from 'vue'
|
|
3
|
+
import styles from './index.module.scss'
|
|
4
|
+
import CollapseExpand from '../collapseExpand/index.jsx'
|
|
5
|
+
import ScriptTable from '../scriptTable/index.jsx'
|
|
6
|
+
|
|
7
|
+
export default defineComponent({
|
|
8
|
+
// 对话环节
|
|
9
|
+
name: 'DialogueSegment',
|
|
10
|
+
|
|
11
|
+
setup() {
|
|
12
|
+
const collapseList = ref([
|
|
13
|
+
{
|
|
14
|
+
id: '1',
|
|
15
|
+
title: '环节1:需求表达',
|
|
16
|
+
goal: '目标:顾客直接向导购表明自己想购买一台高品质跑步机',
|
|
17
|
+
scripts: {
|
|
18
|
+
qaScripts: [
|
|
19
|
+
{
|
|
20
|
+
order: 1,
|
|
21
|
+
problemDesc: '1',
|
|
22
|
+
standardScript: '',
|
|
23
|
+
keyword: '',
|
|
24
|
+
id: '1',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
order: 2,
|
|
28
|
+
problemDesc: '2',
|
|
29
|
+
standardScript: '',
|
|
30
|
+
keyword: '',
|
|
31
|
+
id: '2',
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
proactiveScript: [
|
|
35
|
+
{
|
|
36
|
+
order: 1,
|
|
37
|
+
problemDesc: '',
|
|
38
|
+
standardScript: '',
|
|
39
|
+
keyword: '',
|
|
40
|
+
id: '1',
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: '2',
|
|
48
|
+
title: '环节2:产品介绍',
|
|
49
|
+
goal: '目标:导购根据顾客需求,详细介绍不同款式跑步机的特点、性能和价格',
|
|
50
|
+
scripts: {
|
|
51
|
+
qaScripts: [
|
|
52
|
+
{
|
|
53
|
+
order: 1,
|
|
54
|
+
problemDesc: '',
|
|
55
|
+
standardScript: '',
|
|
56
|
+
keyword: '',
|
|
57
|
+
id: '1',
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
proactiveScript: [
|
|
61
|
+
{
|
|
62
|
+
order: 1,
|
|
63
|
+
problemDesc: '',
|
|
64
|
+
standardScript: '',
|
|
65
|
+
keyword: '',
|
|
66
|
+
id: '1',
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: '3',
|
|
73
|
+
title: '环节3:价格确认',
|
|
74
|
+
goal: '目标:顾客确认购买价格,确认无误后,引导顾客完成支付',
|
|
75
|
+
scripts: {
|
|
76
|
+
qaScripts: [
|
|
77
|
+
{
|
|
78
|
+
order: 1,
|
|
79
|
+
problemDesc: '',
|
|
80
|
+
standardScript: '',
|
|
81
|
+
keyword: '',
|
|
82
|
+
id: '1',
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
proactiveScript: [
|
|
86
|
+
{
|
|
87
|
+
order: 1,
|
|
88
|
+
problemDesc: '',
|
|
89
|
+
standardScript: '',
|
|
90
|
+
keyword: '',
|
|
91
|
+
id: '1',
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
])
|
|
97
|
+
const activeNames = ref(['1'])
|
|
98
|
+
const addScript = (item,props) => {
|
|
99
|
+
console.log(item,props,'添加话术');
|
|
100
|
+
item.scripts[props.activeName].push({
|
|
101
|
+
order: item.scripts[props.activeName].length + 1,
|
|
102
|
+
problemDesc: '',
|
|
103
|
+
standardScript: '',
|
|
104
|
+
keyword: '',
|
|
105
|
+
id: Date.now().toString(),
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
collapseList.value.forEach((item) => {
|
|
109
|
+
item.item = () => (
|
|
110
|
+
<div class={[styles.scriptTableContainer]}>
|
|
111
|
+
<ScriptTable
|
|
112
|
+
isDraggable={true}
|
|
113
|
+
scripts={item.scripts}
|
|
114
|
+
footer={(props) => (
|
|
115
|
+
<div>
|
|
116
|
+
<el-button type="text" size="small" onClick={() => {
|
|
117
|
+
addScript(item,props)
|
|
118
|
+
}}>
|
|
119
|
+
十添加话术
|
|
120
|
+
</el-button>
|
|
121
|
+
<el-button type="text" size="small">
|
|
122
|
+
Ai生成话术
|
|
123
|
+
</el-button>
|
|
124
|
+
</div>
|
|
125
|
+
)}
|
|
126
|
+
></ScriptTable>
|
|
127
|
+
|
|
128
|
+
</div>
|
|
129
|
+
)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
watch(() => collapseList.value, (newVal, oldVal) => {
|
|
133
|
+
console.log(newVal, oldVal)
|
|
134
|
+
})
|
|
135
|
+
return () => {
|
|
136
|
+
return <div class={[styles.dialogueSegment]}>
|
|
137
|
+
<div class={[styles.card]}>
|
|
138
|
+
<div class={[styles.cardHeader]}>
|
|
139
|
+
<div class={[styles.title]}>
|
|
140
|
+
对话环节
|
|
141
|
+
</div>
|
|
142
|
+
<div class={[styles.operation]}>
|
|
143
|
+
<el-button type="text" size="small">
|
|
144
|
+
添加环节
|
|
145
|
+
</el-button>
|
|
146
|
+
<el-button type="text" size="small">
|
|
147
|
+
重新生成环节
|
|
148
|
+
</el-button>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
<div class={[styles.cardBody]}>
|
|
152
|
+
<CollapseExpand v-model:collapseList={collapseList.value} v-model={activeNames.value} />
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
.dialogueSegment {
|
|
2
|
+
width: 800px;
|
|
3
|
+
.card {
|
|
4
|
+
border-radius: 8px;
|
|
5
|
+
border: 1px solid #E5E5E5;
|
|
6
|
+
.cardHeader {
|
|
7
|
+
display: flex;
|
|
8
|
+
justify-content: space-between;
|
|
9
|
+
align-items: center;
|
|
10
|
+
background-color: #F8F8F8;
|
|
11
|
+
padding: 12px 20px;
|
|
12
|
+
}
|
|
13
|
+
.title {
|
|
14
|
+
font-size: 16px;
|
|
15
|
+
font-weight: 500;
|
|
16
|
+
}
|
|
17
|
+
.operation {
|
|
18
|
+
font-size: 14px;
|
|
19
|
+
font-weight: 400;
|
|
20
|
+
}
|
|
21
|
+
.cardBody {
|
|
22
|
+
padding: 20px;
|
|
23
|
+
}
|
|
24
|
+
.scriptTableContainer {
|
|
25
|
+
padding: 10px 10px 0;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineComponent, ref } from 'vue'
|
|
2
|
+
import DialogueSegment from './dialogueSegment/index.jsx'
|
|
3
|
+
|
|
4
|
+
export default defineComponent({
|
|
5
|
+
// 智能教练
|
|
6
|
+
name: 'AiCoach',
|
|
7
|
+
|
|
8
|
+
setup() {
|
|
9
|
+
const activeTab = ref('dialogue-segment')
|
|
10
|
+
return () => {
|
|
11
|
+
return <div style={{padding: '30px'}}>
|
|
12
|
+
<el-tabs v-model={activeTab.value} type="border-card">
|
|
13
|
+
<el-tab-pane label="对话环节" name="dialogue-segment">
|
|
14
|
+
<DialogueSegment />
|
|
15
|
+
</el-tab-pane>
|
|
16
|
+
</el-tabs>
|
|
17
|
+
</div>
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
})
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { defineComponent, ref, computed, watch, onUnmounted, nextTick } from 'vue'
|
|
2
|
+
import styles from './index.module.scss'
|
|
3
|
+
import { Expand, DeleteFilled } from '@element-plus/icons-vue'
|
|
4
|
+
import InputColumn from './inputColumn/index.jsx'
|
|
5
|
+
import Sortable from 'sortablejs'
|
|
6
|
+
export default defineComponent({
|
|
7
|
+
// 脚本表格组件
|
|
8
|
+
name: 'ScriptTable',
|
|
9
|
+
props: {
|
|
10
|
+
scripts: {
|
|
11
|
+
type: Object,
|
|
12
|
+
default: () => { },
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
setup(props, { emit, attrs, slots }) {
|
|
16
|
+
console.log(props.scripts);
|
|
17
|
+
const labelEmu = {
|
|
18
|
+
qaScripts: '问答脚本',
|
|
19
|
+
proactiveScript: '主动脚本',
|
|
20
|
+
}
|
|
21
|
+
const type = ref('all')
|
|
22
|
+
const activeName = ref('qaScripts')
|
|
23
|
+
|
|
24
|
+
const tabs = computed(() => {
|
|
25
|
+
const lsit = []
|
|
26
|
+
for (const key in props.scripts) {
|
|
27
|
+
lsit.push({
|
|
28
|
+
label: labelEmu[key],
|
|
29
|
+
value: props.scripts[key],
|
|
30
|
+
key: key,
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
return lsit
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
watch(() => props.scripts, (newVal, oldVal) => {
|
|
37
|
+
console.log(newVal, oldVal, 'scripts变化');
|
|
38
|
+
|
|
39
|
+
}, {
|
|
40
|
+
deep: true,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const tableRef = ref(null)
|
|
44
|
+
let sortable = null
|
|
45
|
+
watch(() => tableRef.value, async (newVal, oldVal) => {
|
|
46
|
+
if (newVal) {
|
|
47
|
+
await nextTick()
|
|
48
|
+
const tbody = tableRef.value?.$el.querySelector('.el-table__body-wrapper tbody')
|
|
49
|
+
if (!tbody) return
|
|
50
|
+
sortable?.destroy()
|
|
51
|
+
sortable = Sortable.create(tbody, {
|
|
52
|
+
animation: 150,
|
|
53
|
+
handle: '.drag-cell', // 只有手柄能拖
|
|
54
|
+
onEnd: evt => {
|
|
55
|
+
const { oldIndex, newIndex } = evt
|
|
56
|
+
if (oldIndex === undefined || newIndex === undefined) return
|
|
57
|
+
const [removed] = props.scripts[activeName.value].splice(oldIndex, 1)
|
|
58
|
+
props.scripts[activeName.value].splice(newIndex, 0, removed)
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
onUnmounted(() => sortable?.destroy())
|
|
64
|
+
return () => (
|
|
65
|
+
<div class={styles.scriptTable}>
|
|
66
|
+
{/* <el-button type="primary" size="small" onClick={() => {
|
|
67
|
+
console.log(props.scripts[activeName.value]);
|
|
68
|
+
}}>测试 </el-button> */}
|
|
69
|
+
<div class={[styles.header]}>
|
|
70
|
+
<div class={[styles.tabs]}>
|
|
71
|
+
{
|
|
72
|
+
tabs.value.map((item) => (
|
|
73
|
+
<div
|
|
74
|
+
class={[styles.tab, { [styles.active]: item.key === activeName.value }]}
|
|
75
|
+
key={item.key}
|
|
76
|
+
onClick={() => activeName.value = item.key}
|
|
77
|
+
>
|
|
78
|
+
{item.label} {item.value.length}
|
|
79
|
+
</div>
|
|
80
|
+
))
|
|
81
|
+
}
|
|
82
|
+
</div>
|
|
83
|
+
<div class={[styles.right]}>
|
|
84
|
+
<span>学员需练习</span>
|
|
85
|
+
<el-select size="small" v-model={type.value} style={{ width: '60px', marginLeft: '10px', marginRight: '10px' }}>
|
|
86
|
+
{
|
|
87
|
+
{
|
|
88
|
+
default: () => (
|
|
89
|
+
<>
|
|
90
|
+
<el-option label="全部" value="all" />
|
|
91
|
+
<el-option label="部分" value="partial" />
|
|
92
|
+
</>
|
|
93
|
+
),
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
</el-select>
|
|
97
|
+
<span>话术</span>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
<div>
|
|
101
|
+
<el-table
|
|
102
|
+
ref={tableRef}
|
|
103
|
+
data={props.scripts[activeName.value]}
|
|
104
|
+
style={{ width: '100%' }}
|
|
105
|
+
border
|
|
106
|
+
size="small"
|
|
107
|
+
row-key="id"
|
|
108
|
+
headerCellStyle={{
|
|
109
|
+
background: '#F8F8F8',
|
|
110
|
+
color: '#303133',
|
|
111
|
+
fontWeight: 'bold',
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
{{
|
|
115
|
+
default: (scope) => (
|
|
116
|
+
<>
|
|
117
|
+
{
|
|
118
|
+
attrs.isDraggable &&
|
|
119
|
+
|
|
120
|
+
<el-table-column prop="order" label="排序" width="50px">
|
|
121
|
+
{{
|
|
122
|
+
default: (scope) => (
|
|
123
|
+
<div class="drag-cell" style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
|
124
|
+
<el-icon style={{ cursor: 'grab' }} class={'drag-icon'} size={20}><Expand /></el-icon>
|
|
125
|
+
</div>
|
|
126
|
+
)
|
|
127
|
+
}}
|
|
128
|
+
</el-table-column>
|
|
129
|
+
}
|
|
130
|
+
<el-table-column prop="problemDesc" label="问题描述">
|
|
131
|
+
{{
|
|
132
|
+
default: (scope) => (
|
|
133
|
+
<InputColumn scope={scope} tableColumn={{
|
|
134
|
+
prop: 'problemDesc',
|
|
135
|
+
label: '问题描述',
|
|
136
|
+
}} />
|
|
137
|
+
)
|
|
138
|
+
}}
|
|
139
|
+
</el-table-column>
|
|
140
|
+
<el-table-column prop="standardScript" label="标准话术">
|
|
141
|
+
{{
|
|
142
|
+
default: (scope) => (
|
|
143
|
+
<InputColumn scope={scope} tableColumn={{
|
|
144
|
+
prop: 'standardScript',
|
|
145
|
+
label: '标准话术',
|
|
146
|
+
}} />
|
|
147
|
+
)
|
|
148
|
+
}}
|
|
149
|
+
</el-table-column>
|
|
150
|
+
<el-table-column prop="keyword" label="关键词">
|
|
151
|
+
{{
|
|
152
|
+
default: (scope) => (
|
|
153
|
+
<InputColumn scope={scope} tableColumn={{
|
|
154
|
+
prop: 'keyword',
|
|
155
|
+
label: '关键词',
|
|
156
|
+
placeholder: '请输入请输入请输入关键词,多个关键词用逗号分隔',
|
|
157
|
+
tag: true,
|
|
158
|
+
split: ',',
|
|
159
|
+
noAiGenerate: true,
|
|
160
|
+
}} />
|
|
161
|
+
)
|
|
162
|
+
}}
|
|
163
|
+
</el-table-column>
|
|
164
|
+
<el-table-column prop="aiScript" label="操作" width="50px">
|
|
165
|
+
{{
|
|
166
|
+
default: (scope) => (
|
|
167
|
+
<el-icon style={{ cursor: 'pointer' }} ><DeleteFilled /></el-icon>
|
|
168
|
+
)
|
|
169
|
+
}}
|
|
170
|
+
</el-table-column>
|
|
171
|
+
{/* <InputColumn tableColumn={{
|
|
172
|
+
prop: 'standardScript',
|
|
173
|
+
label: '标准话术',
|
|
174
|
+
}} />
|
|
175
|
+
<InputColumn tableColumn={{
|
|
176
|
+
prop: 'keyword',
|
|
177
|
+
label: '关键词',
|
|
178
|
+
}} /> */}
|
|
179
|
+
</>
|
|
180
|
+
),
|
|
181
|
+
}}
|
|
182
|
+
</el-table>
|
|
183
|
+
</div>
|
|
184
|
+
<div>
|
|
185
|
+
{
|
|
186
|
+
attrs.footer && attrs.footer({
|
|
187
|
+
labelEmu,
|
|
188
|
+
activeName: activeName.value,
|
|
189
|
+
type: type.value,
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
.scriptTable{
|
|
2
|
+
.header{
|
|
3
|
+
display: flex;
|
|
4
|
+
justify-content: space-between;
|
|
5
|
+
align-items: center;
|
|
6
|
+
background-color: #F8F8F8;
|
|
7
|
+
border: 1px solid #EBEEF5;
|
|
8
|
+
border-bottom: none;
|
|
9
|
+
padding: 10px;
|
|
10
|
+
.tabs{
|
|
11
|
+
display: flex;
|
|
12
|
+
justify-content: flex-start;
|
|
13
|
+
align-items: center;
|
|
14
|
+
}
|
|
15
|
+
.tab{
|
|
16
|
+
padding: 5px 10px;
|
|
17
|
+
cursor: pointer;
|
|
18
|
+
}
|
|
19
|
+
.tab.active{
|
|
20
|
+
color: #FC6506;
|
|
21
|
+
font-weight: bold;
|
|
22
|
+
position: relative;
|
|
23
|
+
&::after{
|
|
24
|
+
content: '';
|
|
25
|
+
position: absolute;
|
|
26
|
+
bottom: -10px;
|
|
27
|
+
left: 0;
|
|
28
|
+
display: block;
|
|
29
|
+
transform: translateX(50%);
|
|
30
|
+
width: 50%;
|
|
31
|
+
height: 2px;
|
|
32
|
+
background-color: #FC6506;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
.right{
|
|
36
|
+
display: flex;
|
|
37
|
+
justify-content: flex-end;
|
|
38
|
+
align-items: center;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { defineComponent, ref, nextTick, watch, Teleport } from 'vue'
|
|
2
|
+
import styles from './index.module.scss'
|
|
3
|
+
import { Close } from '@element-plus/icons-vue'
|
|
4
|
+
export default defineComponent({
|
|
5
|
+
// 输入列组件
|
|
6
|
+
name: 'InputColumn',
|
|
7
|
+
props: {
|
|
8
|
+
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
setup(props, { attrs }) {
|
|
13
|
+
const showInput = ref(false)
|
|
14
|
+
|
|
15
|
+
const inputColumnnRef = ref(null)
|
|
16
|
+
const textareaRef = ref(null)
|
|
17
|
+
const aiGenerateRef = ref(null)
|
|
18
|
+
const showAiGenerate = ref(false)
|
|
19
|
+
const aiGenerateValue = ref('你好,我想咨询一下关于高品质跑步机的选购问题。 你好,我想咨询一下关于高品质跑步机的选购问题。')
|
|
20
|
+
const onClickOutside = (element, handler) => {
|
|
21
|
+
const listener = (e) => {
|
|
22
|
+
if (!element || element.contains(e.target)) return;
|
|
23
|
+
handler(e);
|
|
24
|
+
};
|
|
25
|
+
document.addEventListener('click', listener, false);
|
|
26
|
+
// 返回取消函数,想停就调它
|
|
27
|
+
return () => document.removeEventListener('click', listener, false);
|
|
28
|
+
}
|
|
29
|
+
watch(() => showInput.value, (newVal) => {
|
|
30
|
+
console.log(attrs, 'attrs');
|
|
31
|
+
|
|
32
|
+
if (newVal) {
|
|
33
|
+
const cell = inputColumnnRef.value.parentNode
|
|
34
|
+
if (!cell) return
|
|
35
|
+
const column = cell.parentNode
|
|
36
|
+
console.log(column, 'column');
|
|
37
|
+
if (!column) return
|
|
38
|
+
const elTable__cell = column.parentNode
|
|
39
|
+
if (!elTable__cell) return
|
|
40
|
+
const width = elTable__cell.offsetWidth
|
|
41
|
+
const rect = elTable__cell.getBoundingClientRect() // 相对于视口
|
|
42
|
+
const x = rect.left + window.scrollX
|
|
43
|
+
const y = rect.top + window.scrollY
|
|
44
|
+
nextTick(() => {
|
|
45
|
+
const textarea = textareaRef.value
|
|
46
|
+
if (!textarea) return
|
|
47
|
+
textarea.style.width = `${width}px`
|
|
48
|
+
document.body.style.position = 'relative'
|
|
49
|
+
textarea.style.top = `${y}px`
|
|
50
|
+
textarea.style.left = `${x}px`
|
|
51
|
+
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
watch(() => textareaRef.value, (newVal, _, onCleanup) => {
|
|
59
|
+
if (newVal) {
|
|
60
|
+
setTimeout(() => {
|
|
61
|
+
const removeClickOutside = onClickOutside(newVal, () => {
|
|
62
|
+
showInput.value = false
|
|
63
|
+
showAiGenerate.value = false
|
|
64
|
+
})
|
|
65
|
+
onCleanup(() => {
|
|
66
|
+
removeClickOutside()
|
|
67
|
+
})
|
|
68
|
+
const elTextareaInner = textareaRef.value.querySelector('.el-textarea__inner')
|
|
69
|
+
if (elTextareaInner) {
|
|
70
|
+
elTextareaInner.focus()
|
|
71
|
+
}
|
|
72
|
+
}, 0)
|
|
73
|
+
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
watch(() => showAiGenerate.value, async (newVal) => {
|
|
79
|
+
if (newVal) {
|
|
80
|
+
await nextTick()
|
|
81
|
+
const width = textareaRef.value.offsetWidth
|
|
82
|
+
const rect = textareaRef.value.getBoundingClientRect() // 相对于视口
|
|
83
|
+
const left = rect.left
|
|
84
|
+
if (left > width) {
|
|
85
|
+
aiGenerateRef.value.style.left = `${-width}px`
|
|
86
|
+
} else {
|
|
87
|
+
aiGenerateRef.value.style.left = `${width}px`
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
return () => {
|
|
94
|
+
return <div class={[styles.inputColumn]}>
|
|
95
|
+
<div ref={inputColumnnRef} >
|
|
96
|
+
{
|
|
97
|
+
showInput.value && (
|
|
98
|
+
<Teleport to="body">
|
|
99
|
+
<div class={[styles.inputColumnTextarea]} ref={textareaRef}>
|
|
100
|
+
<div>
|
|
101
|
+
<el-input maxlength={500} type="textarea" rows={4} v-model={attrs.scope.row[attrs.tableColumn.prop]} />
|
|
102
|
+
<div class={[styles.aiGenerateBtnContainer]}>
|
|
103
|
+
{
|
|
104
|
+
!attrs.tableColumn.noAiGenerate ?(
|
|
105
|
+
<span class={[styles.aiGenerateBtn]} onClick={() => {
|
|
106
|
+
showAiGenerate.value = true
|
|
107
|
+
}}>
|
|
108
|
+
AI润色
|
|
109
|
+
</span>
|
|
110
|
+
)
|
|
111
|
+
:
|
|
112
|
+
<span> </span>
|
|
113
|
+
}
|
|
114
|
+
<span>
|
|
115
|
+
{attrs.scope.row[attrs.tableColumn.prop].length}/<span style={{ color: '#999' }}>500</span>
|
|
116
|
+
</span>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
{
|
|
120
|
+
showAiGenerate.value &&
|
|
121
|
+
|
|
122
|
+
<div class={[styles.aiGenerate]} ref={aiGenerateRef}>
|
|
123
|
+
<div class={[styles.header]}>
|
|
124
|
+
<div class={[styles.title]}>
|
|
125
|
+
AI生成
|
|
126
|
+
</div>
|
|
127
|
+
<el-icon class={[styles.close]} onClick={(e) => {
|
|
128
|
+
showAiGenerate.value = false
|
|
129
|
+
e.stopPropagation()
|
|
130
|
+
}}>
|
|
131
|
+
<Close />
|
|
132
|
+
</el-icon>
|
|
133
|
+
</div>
|
|
134
|
+
<el-scrollbar>
|
|
135
|
+
<div class={[styles.content]}>
|
|
136
|
+
{aiGenerateValue.value || 'AI润色后的脚本'}
|
|
137
|
+
</div>
|
|
138
|
+
</el-scrollbar>
|
|
139
|
+
<div class={[styles.footer]} >
|
|
140
|
+
<span onClick={(e) => {
|
|
141
|
+
attrs.scope.row[attrs.tableColumn.prop] = aiGenerateValue.value
|
|
142
|
+
showAiGenerate.value = false
|
|
143
|
+
e.stopPropagation()
|
|
144
|
+
}}>
|
|
145
|
+
一键使用
|
|
146
|
+
</span>
|
|
147
|
+
<span onClick={(e) => {
|
|
148
|
+
e.stopPropagation()
|
|
149
|
+
}}>
|
|
150
|
+
重新生成
|
|
151
|
+
</span>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
}
|
|
155
|
+
</div>
|
|
156
|
+
</Teleport >
|
|
157
|
+
)}
|
|
158
|
+
<div onClick={(e) => {
|
|
159
|
+
console.log(e.target, attrs.scope.row[attrs.tableColumn.prop], '点击了输入框');
|
|
160
|
+
showInput.value = true
|
|
161
|
+
// e.stopPropagation()
|
|
162
|
+
}}>
|
|
163
|
+
{
|
|
164
|
+
attrs.scope.row[attrs.tableColumn.prop] ?
|
|
165
|
+
(attrs.tableColumn.tag ?
|
|
166
|
+
<div class={[styles.tagContainer]} title={attrs.scope.row[attrs.tableColumn.prop]}>
|
|
167
|
+
|
|
168
|
+
{attrs.scope.row[attrs.tableColumn.prop].split(attrs.tableColumn.split).map((item, index) => (
|
|
169
|
+
item.trim() ? <div class={[styles.tag]} key={index}>{item}</div> : null
|
|
170
|
+
))}
|
|
171
|
+
</div>
|
|
172
|
+
:
|
|
173
|
+
<div class={[styles.inputColumnTextareaValue]} title={attrs.scope.row[attrs.tableColumn.prop]}>{attrs.scope.row[attrs.tableColumn.prop]}111</div>
|
|
174
|
+
)
|
|
175
|
+
:
|
|
176
|
+
<div class={[styles.inputColumnTextareaValue]}>{attrs.tableColumn.placeholder || '请输入'}</div>
|
|
177
|
+
}
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
})
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
.inputColumn {
|
|
2
|
+
.inputColumnTextareaValue {
|
|
3
|
+
overflow: hidden;
|
|
4
|
+
display: -webkit-box;
|
|
5
|
+
-webkit-box-orient: vertical;
|
|
6
|
+
-webkit-line-clamp: 2;
|
|
7
|
+
/* 想几行就写几 */
|
|
8
|
+
text-overflow: ellipsis;
|
|
9
|
+
word-break: break-all;
|
|
10
|
+
/* 可选,英文长单词截断 */
|
|
11
|
+
line-height: 18px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.tagContainer {
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-wrap: wrap;
|
|
17
|
+
gap: 4px;
|
|
18
|
+
.tag {
|
|
19
|
+
padding: 2px 8px;
|
|
20
|
+
background-color: #F5F5F5;
|
|
21
|
+
border-radius: 4px;
|
|
22
|
+
font-size: 12px;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.inputColumnTextarea {
|
|
29
|
+
border: 1px solid #D9D9D9;
|
|
30
|
+
background-color: #fff;
|
|
31
|
+
border-radius: 8px;
|
|
32
|
+
|
|
33
|
+
.aiGenerateBtnContainer {
|
|
34
|
+
background-color: #fff;
|
|
35
|
+
padding: 0 10px 6px;
|
|
36
|
+
display: flex;
|
|
37
|
+
justify-content: space-between;
|
|
38
|
+
border-radius: 8px;
|
|
39
|
+
|
|
40
|
+
.aiGenerateBtn {
|
|
41
|
+
color: #4F8BFF;
|
|
42
|
+
cursor: pointer;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
:global {
|
|
47
|
+
.el-textarea__inner {
|
|
48
|
+
box-shadow: none;
|
|
49
|
+
border-radius: 8px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.el-textarea {
|
|
53
|
+
box-shadow: none;
|
|
54
|
+
border-radius: 8px;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
position: absolute;
|
|
60
|
+
top: 0;
|
|
61
|
+
left: 0;
|
|
62
|
+
z-index: 999999;
|
|
63
|
+
|
|
64
|
+
.aiGenerate {
|
|
65
|
+
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.1);
|
|
66
|
+
border-radius: 8px;
|
|
67
|
+
position: absolute;
|
|
68
|
+
top: 0;
|
|
69
|
+
left: -100%;
|
|
70
|
+
z-index: 999999;
|
|
71
|
+
background-color: #F2F4FA;
|
|
72
|
+
width: 100%;
|
|
73
|
+
height: 100%;
|
|
74
|
+
padding: 10px;
|
|
75
|
+
box-sizing: border-box;
|
|
76
|
+
display: flex;
|
|
77
|
+
flex-direction: column;
|
|
78
|
+
justify-content: space-between;
|
|
79
|
+
|
|
80
|
+
.header {
|
|
81
|
+
display: flex;
|
|
82
|
+
justify-content: space-between;
|
|
83
|
+
align-items: center;
|
|
84
|
+
font-size: 12px;
|
|
85
|
+
color: #222222;
|
|
86
|
+
font-weight: bold;
|
|
87
|
+
|
|
88
|
+
.close {
|
|
89
|
+
cursor: pointer;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.content {
|
|
94
|
+
font-size: 12px;
|
|
95
|
+
color: #222222;
|
|
96
|
+
flex: 1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.footer {
|
|
100
|
+
display: flex;
|
|
101
|
+
height: 12px;
|
|
102
|
+
gap: 10px;
|
|
103
|
+
|
|
104
|
+
span {
|
|
105
|
+
cursor: pointer;
|
|
106
|
+
font-size: 12px;
|
|
107
|
+
color: #4F8BFF;
|
|
108
|
+
|
|
109
|
+
&:hover {
|
|
110
|
+
color: #3A73E8;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
package/package.json
CHANGED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
<script lang="jsx">
|
|
2
|
-
import { defineComponent, ref, computed, watch } from "vue";
|
|
3
|
-
import { ArrowDown } from "@element-plus/icons-vue";
|
|
4
|
-
export default defineComponent({
|
|
5
|
-
name: "BtnSelect",
|
|
6
|
-
props: {
|
|
7
|
-
options: { type: Array, default: () => [] },
|
|
8
|
-
modelValue: { type: [String, Number, Array, Object, Boolean], default: null },
|
|
9
|
-
multiple: { type: Boolean, default: false },
|
|
10
|
-
searchable: { type: Boolean, default: false },
|
|
11
|
-
label: { type: String, default: "" },
|
|
12
|
-
optionKeys: { type: Object, default: () => ({ label: "label", value: "value" }) },
|
|
13
|
-
maxLabelCount: { type: Number, default: 3 },
|
|
14
|
-
disabled: { type: Boolean, default: false },
|
|
15
|
-
collapseTags: { type: Boolean, default: false },
|
|
16
|
-
itemRender: { type: Function, default: null },
|
|
17
|
-
},
|
|
18
|
-
emits: ["update:modelValue", "change"],
|
|
19
|
-
setup(props, { emit }) {
|
|
20
|
-
const keyword = ref("");
|
|
21
|
-
const internal = ref(props.multiple ? ([]).concat(props.modelValue || []) : props.modelValue);
|
|
22
|
-
watch(
|
|
23
|
-
() => props.modelValue,
|
|
24
|
-
(v) => {
|
|
25
|
-
internal.value = props.multiple ? ([]).concat(v || []) : v;
|
|
26
|
-
}
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
const isChecked = (val) => {
|
|
30
|
-
const cur = internal.value;
|
|
31
|
-
return Array.isArray(cur) ? cur.includes(val) : cur === val;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const toggle = (val) => {
|
|
35
|
-
const list = props.options || [];
|
|
36
|
-
const { label: labelKey, value: valueKey } = props.optionKeys || { label: "label", value: "value" };
|
|
37
|
-
const found = list.find((i) => i[valueKey] === val);
|
|
38
|
-
if (found && found.disabled) return;
|
|
39
|
-
if (props.multiple) {
|
|
40
|
-
const set = new Set(internal.value || []);
|
|
41
|
-
if (set.has(val)) set.delete(val);
|
|
42
|
-
else set.add(val);
|
|
43
|
-
const next = Array.from(set);
|
|
44
|
-
internal.value = next;
|
|
45
|
-
emit("update:modelValue", next);
|
|
46
|
-
emit("change", next);
|
|
47
|
-
} else {
|
|
48
|
-
internal.value = val;
|
|
49
|
-
emit("update:modelValue", val);
|
|
50
|
-
emit("change", val);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const filtered = computed(() => {
|
|
55
|
-
const list = props.options || [];
|
|
56
|
-
const { label: labelKey, value: valueKey } = props.optionKeys || { label: "label", value: "value" };
|
|
57
|
-
if (!props.searchable || !keyword.value) return list;
|
|
58
|
-
const k = String(keyword.value).toLowerCase();
|
|
59
|
-
return list.filter((o) => String(o[labelKey]).toLowerCase().includes(k));
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
const displayMeta = computed(() => {
|
|
63
|
-
const list = props.options || [];
|
|
64
|
-
const { label: labelKey, value: valueKey } = props.optionKeys || { label: "label", value: "value" };
|
|
65
|
-
if (props.multiple) {
|
|
66
|
-
const vals = internal.value || [];
|
|
67
|
-
if (!vals.length) return { text: props.label || "请选择", rest: 0 };
|
|
68
|
-
const labels = vals.map((v) => {
|
|
69
|
-
const o = list.find((i) => i[valueKey] === v);
|
|
70
|
-
return o ? o[labelKey] : v;
|
|
71
|
-
});
|
|
72
|
-
if (props.collapseTags) {
|
|
73
|
-
const first = labels[0];
|
|
74
|
-
const rest = labels.length - 1;
|
|
75
|
-
return { text: `${props.label ? props.label + ":" : ""}${first}`, rest };
|
|
76
|
-
}
|
|
77
|
-
if (labels.length > props.maxLabelCount) return { text: (props.label ? props.label + ":" : "") + labels.slice(0, props.maxLabelCount).join("、") + ` 等${labels.length}项`, rest: 0 };
|
|
78
|
-
return { text: (props.label ? props.label + ":" : "") + labels.join("、"), rest: 0 };
|
|
79
|
-
} else {
|
|
80
|
-
const v = internal.value;
|
|
81
|
-
if (v === undefined || v === null || v === "") return { text: props.label || "请选择", rest: 0 };
|
|
82
|
-
const o = list.find((i) => i[valueKey] === v);
|
|
83
|
-
const text = o ? o[labelKey] : String(v);
|
|
84
|
-
return { text: props.label ? `${props.label}:${text}` : text, rest: 0 };
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const clear = () => {
|
|
89
|
-
if (props.disabled) return;
|
|
90
|
-
internal.value = props.multiple ? [] : null;
|
|
91
|
-
emit("update:modelValue", internal.value);
|
|
92
|
-
emit("change", internal.value);
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const hasValue = computed(() => (Array.isArray(internal.value) ? internal.value.length > 0 : internal.value !== null && internal.value !== undefined && internal.value !== "") );
|
|
96
|
-
|
|
97
|
-
return () => (
|
|
98
|
-
<el-dropdown hideOnClick={props.multiple ? false : true} disabled={props.disabled}>
|
|
99
|
-
{{
|
|
100
|
-
default: () => (
|
|
101
|
-
<el-button>
|
|
102
|
-
<span style="display:inline-flex;align-items:center;gap:6px;">
|
|
103
|
-
<span>{displayMeta.value.text}</span>
|
|
104
|
-
{props.collapseTags && displayMeta.value.rest > 0 ? (
|
|
105
|
-
<span style="display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 4px;border-radius:9px;background:rgba(64,158,255,0.15);color:#409EFF;font-size:12px;line-height:18px;">{`+${displayMeta.value.rest}`}</span>
|
|
106
|
-
) : null}
|
|
107
|
-
{hasValue.value ? (
|
|
108
|
-
<span
|
|
109
|
-
title="清除"
|
|
110
|
-
style="display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:50%;background:rgba(0,0,0,0.06);cursor:pointer;margin-left:4px;"
|
|
111
|
-
onClick={(e) => {
|
|
112
|
-
e.stopPropagation();
|
|
113
|
-
clear();
|
|
114
|
-
}}
|
|
115
|
-
>
|
|
116
|
-
×
|
|
117
|
-
</span>
|
|
118
|
-
) : (
|
|
119
|
-
<span style="margin-left:4px;opacity:0.7;">
|
|
120
|
-
<el-icon><ArrowDown /></el-icon>
|
|
121
|
-
</span>
|
|
122
|
-
)}
|
|
123
|
-
</span>
|
|
124
|
-
</el-button>
|
|
125
|
-
),
|
|
126
|
-
dropdown: () => (
|
|
127
|
-
<el-dropdown-menu>
|
|
128
|
-
{props.searchable ? (
|
|
129
|
-
<div style="padding:8px 12px;">
|
|
130
|
-
<el-input
|
|
131
|
-
modelValue={keyword.value}
|
|
132
|
-
onUpdate:modelValue={(v) => {
|
|
133
|
-
keyword.value = v;
|
|
134
|
-
}}
|
|
135
|
-
clearable={true}
|
|
136
|
-
placeholder="搜索"
|
|
137
|
-
size="small"
|
|
138
|
-
/>
|
|
139
|
-
</div>
|
|
140
|
-
) : null}
|
|
141
|
-
{filtered.value.map((opt) => (
|
|
142
|
-
<el-dropdown-item
|
|
143
|
-
key={opt[(props.optionKeys || {}).value || "value"]}
|
|
144
|
-
command={opt[(props.optionKeys || {}).value || "value"]}
|
|
145
|
-
disabled={Boolean(opt.disabled)}
|
|
146
|
-
onClick={() => toggle(opt[(props.optionKeys || {}).value || "value"])}
|
|
147
|
-
style={isChecked(opt[(props.optionKeys || {}).value || "value"]) ? "background: rgba(64,158,255,0.08);" : ""}
|
|
148
|
-
>
|
|
149
|
-
{props.itemRender ? (
|
|
150
|
-
props.itemRender(opt, {
|
|
151
|
-
checked: isChecked(opt[(props.optionKeys || {}).value || "value"]),
|
|
152
|
-
})
|
|
153
|
-
) : (
|
|
154
|
-
<div style="display:flex;align-items:center;justify-content:space-between;min-width:180px;">
|
|
155
|
-
<span>{opt[(props.optionKeys || {}).label || "label"]}</span>
|
|
156
|
-
{isChecked(opt[(props.optionKeys || {}).value || "value"]) ? <span style="color:#67C23A;">✓ 已选</span> : null}
|
|
157
|
-
</div>
|
|
158
|
-
)}
|
|
159
|
-
</el-dropdown-item>
|
|
160
|
-
))}
|
|
161
|
-
{!filtered.value.length ? <el-dropdown-item disabled={true}>无数据</el-dropdown-item> : null}
|
|
162
|
-
</el-dropdown-menu>
|
|
163
|
-
),
|
|
164
|
-
}}
|
|
165
|
-
</el-dropdown>
|
|
166
|
-
);
|
|
167
|
-
},
|
|
168
|
-
});
|
|
169
|
-
</script>
|
package/app/btnSelect/index.vue
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
<script lang="jsx">
|
|
2
|
-
import { defineComponent, ref } from "vue";
|
|
3
|
-
import BtnSelect from "./btnSelect.vue";
|
|
4
|
-
|
|
5
|
-
export default defineComponent({
|
|
6
|
-
name: "BtnSelectView",
|
|
7
|
-
setup() {
|
|
8
|
-
const options = ref([
|
|
9
|
-
{ label: "选项一", value: "a" },
|
|
10
|
-
{ label: "选项二", value: "b" },
|
|
11
|
-
{ label: "选项三", value: "c" },
|
|
12
|
-
{ label: "禁用项", value: "d", disabled: true },
|
|
13
|
-
]);
|
|
14
|
-
const singleValue = ref(null);
|
|
15
|
-
const multiValue = ref([]);
|
|
16
|
-
const searchValue = ref(null);
|
|
17
|
-
return () => (
|
|
18
|
-
<>
|
|
19
|
-
<div style="display:flex; gap:16px; align-items:center; flex-wrap:wrap;">
|
|
20
|
-
<span>按钮下拉框(普通)</span>
|
|
21
|
-
<BtnSelect
|
|
22
|
-
label="名称"
|
|
23
|
-
options={options.value}
|
|
24
|
-
modelValue={singleValue.value}
|
|
25
|
-
onUpdate:modelValue={(v) => {
|
|
26
|
-
singleValue.value = v;
|
|
27
|
-
}}
|
|
28
|
-
/>
|
|
29
|
-
<span>按钮下拉框(多选)</span>
|
|
30
|
-
<BtnSelect
|
|
31
|
-
label="名称"
|
|
32
|
-
options={options.value}
|
|
33
|
-
multiple={true}
|
|
34
|
-
modelValue={multiValue.value}
|
|
35
|
-
onUpdate:modelValue={(v) => {
|
|
36
|
-
multiValue.value = v;
|
|
37
|
-
}}
|
|
38
|
-
/>
|
|
39
|
-
<span>按钮下拉框(带搜索)</span>
|
|
40
|
-
<BtnSelect
|
|
41
|
-
label="名称"
|
|
42
|
-
options={options.value}
|
|
43
|
-
searchable={true}
|
|
44
|
-
multiple={true}
|
|
45
|
-
collapseTags={true}
|
|
46
|
-
itemRender={(opt, { checked }) => (
|
|
47
|
-
<div style="display:flex;align-items:center;justify-content:space-between;min-width:200px;">
|
|
48
|
-
<span>
|
|
49
|
-
{opt.label}
|
|
50
|
-
{opt.disabled ? <span style="margin-left:8px;color:#F56C6C;">禁用</span> : null}
|
|
51
|
-
</span>
|
|
52
|
-
{checked ? (
|
|
53
|
-
<span style="color:#67C23A;">✓ 已选</span>
|
|
54
|
-
) : (
|
|
55
|
-
<span style="color:#909399;">点击选择</span>
|
|
56
|
-
)}
|
|
57
|
-
</div>
|
|
58
|
-
)}
|
|
59
|
-
modelValue={searchValue.value}
|
|
60
|
-
onUpdate:modelValue={(v) => {
|
|
61
|
-
searchValue.value = v;
|
|
62
|
-
}}
|
|
63
|
-
/>
|
|
64
|
-
</div>
|
|
65
|
-
</>
|
|
66
|
-
);
|
|
67
|
-
},
|
|
68
|
-
});
|
|
69
|
-
</script>
|
package/app/btnSelect.zip
DELETED
|
Binary file
|