koishi-plugin-media-luna 0.0.13 → 0.0.14
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/client/components/ConfigRenderer.vue +32 -3
- package/client/components/TableFieldEditor.vue +587 -0
- package/client/components/settings/PluginsPanel.vue +1 -0
- package/client/types.ts +2 -0
- package/dist/index.js +5 -1
- package/dist/style.css +1 -1
- package/lib/core/plugin-loader.d.ts.map +1 -1
- package/lib/core/plugin-loader.js +2 -1
- package/lib/core/plugin-loader.js.map +1 -1
- package/lib/core/types.d.ts +45 -1
- package/lib/core/types.d.ts.map +1 -1
- package/lib/core/types.js.map +1 -1
- package/lib/types/index.d.ts +70 -1
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -53,6 +53,17 @@
|
|
|
53
53
|
/>
|
|
54
54
|
</template>
|
|
55
55
|
|
|
56
|
+
<!-- Table 类型(数组对象编辑) -->
|
|
57
|
+
<template v-else-if="field.type === 'table' && field.columns">
|
|
58
|
+
<TableFieldEditor
|
|
59
|
+
:columns="field.columns"
|
|
60
|
+
:model-value="getTableRows(field.key)"
|
|
61
|
+
@update:model-value="setFieldValue(field.key, $event)"
|
|
62
|
+
:table-config="field.tableConfig"
|
|
63
|
+
:presets="getPresets(field.tableConfig?.presetsSource)"
|
|
64
|
+
/>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
56
67
|
<!-- Password 类型 -->
|
|
57
68
|
<template v-else-if="field.type === 'password'">
|
|
58
69
|
<el-input
|
|
@@ -83,8 +94,9 @@
|
|
|
83
94
|
</template>
|
|
84
95
|
|
|
85
96
|
<script setup lang="ts">
|
|
86
|
-
import { computed } from 'vue'
|
|
87
|
-
import type { ConfigField } from '../types'
|
|
97
|
+
import { computed, inject } from 'vue'
|
|
98
|
+
import type { ConfigField, TableColumnDefinition } from '../types'
|
|
99
|
+
import TableFieldEditor from './TableFieldEditor.vue'
|
|
88
100
|
|
|
89
101
|
interface Props {
|
|
90
102
|
/** 配置字段定义 */
|
|
@@ -93,10 +105,13 @@ interface Props {
|
|
|
93
105
|
modelValue: Record<string, any>
|
|
94
106
|
/** 是否显示清除按钮 */
|
|
95
107
|
clearable?: boolean
|
|
108
|
+
/** 预设数据源(外部注入) */
|
|
109
|
+
presetsMap?: Record<string, Record<string, any>[]>
|
|
96
110
|
}
|
|
97
111
|
|
|
98
112
|
const props = withDefaults(defineProps<Props>(), {
|
|
99
|
-
clearable: false
|
|
113
|
+
clearable: false,
|
|
114
|
+
presetsMap: () => ({})
|
|
100
115
|
})
|
|
101
116
|
|
|
102
117
|
const emit = defineEmits<{
|
|
@@ -120,6 +135,20 @@ const shouldShowField = (field: ConfigField) => {
|
|
|
120
135
|
const { field: dependField, value } = field.showWhen
|
|
121
136
|
return props.modelValue[dependField] === value
|
|
122
137
|
}
|
|
138
|
+
|
|
139
|
+
// ============ Table 类型支持 ============
|
|
140
|
+
|
|
141
|
+
// 获取表格行数据
|
|
142
|
+
const getTableRows = (key: string): Record<string, any>[] => {
|
|
143
|
+
const value = props.modelValue[key]
|
|
144
|
+
return Array.isArray(value) ? value : []
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 获取预设数据
|
|
148
|
+
const getPresets = (source?: string): Record<string, any>[] => {
|
|
149
|
+
if (!source) return []
|
|
150
|
+
return props.presetsMap?.[source] || []
|
|
151
|
+
}
|
|
123
152
|
</script>
|
|
124
153
|
|
|
125
154
|
<style scoped>
|
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="table-field-editor">
|
|
3
|
+
<!-- 工具栏 -->
|
|
4
|
+
<div class="toolbar">
|
|
5
|
+
<div class="toolbar-left">
|
|
6
|
+
<el-button type="primary" size="small" @click="addRow">
|
|
7
|
+
<k-icon name="add" />
|
|
8
|
+
添加
|
|
9
|
+
</el-button>
|
|
10
|
+
<el-button
|
|
11
|
+
v-if="tableConfig?.enableBatchDelete && selectedRows.length > 0"
|
|
12
|
+
type="danger"
|
|
13
|
+
size="small"
|
|
14
|
+
@click="deleteSelected"
|
|
15
|
+
>
|
|
16
|
+
删除选中 ({{ selectedRows.length }})
|
|
17
|
+
</el-button>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="toolbar-right">
|
|
20
|
+
<el-button
|
|
21
|
+
v-if="presets?.length > 0"
|
|
22
|
+
size="small"
|
|
23
|
+
@click="showPresetsDialog = true"
|
|
24
|
+
>
|
|
25
|
+
内置预设
|
|
26
|
+
</el-button>
|
|
27
|
+
<el-button
|
|
28
|
+
v-if="tableConfig?.enableImport !== false"
|
|
29
|
+
size="small"
|
|
30
|
+
@click="showImportDialog = true"
|
|
31
|
+
>
|
|
32
|
+
导入
|
|
33
|
+
</el-button>
|
|
34
|
+
<el-button
|
|
35
|
+
v-if="tableConfig?.enableExport !== false"
|
|
36
|
+
size="small"
|
|
37
|
+
@click="exportData"
|
|
38
|
+
>
|
|
39
|
+
导出
|
|
40
|
+
</el-button>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<!-- 数据表格 -->
|
|
45
|
+
<div class="table-wrapper">
|
|
46
|
+
<table class="data-table">
|
|
47
|
+
<thead>
|
|
48
|
+
<tr>
|
|
49
|
+
<th v-if="tableConfig?.enableSelection !== false" class="col-check">
|
|
50
|
+
<el-checkbox
|
|
51
|
+
:model-value="isAllSelected"
|
|
52
|
+
:indeterminate="isIndeterminate"
|
|
53
|
+
@change="toggleSelectAll"
|
|
54
|
+
size="small"
|
|
55
|
+
/>
|
|
56
|
+
</th>
|
|
57
|
+
<th
|
|
58
|
+
v-for="col in columns"
|
|
59
|
+
:key="col.key"
|
|
60
|
+
:class="['col-' + col.key, { required: col.required }]"
|
|
61
|
+
>
|
|
62
|
+
{{ col.label }}
|
|
63
|
+
</th>
|
|
64
|
+
<th class="col-op">操作</th>
|
|
65
|
+
</tr>
|
|
66
|
+
</thead>
|
|
67
|
+
<tbody>
|
|
68
|
+
<tr
|
|
69
|
+
v-for="(row, index) in rows"
|
|
70
|
+
:key="index"
|
|
71
|
+
:class="{ selected: selectedRows.includes(index) }"
|
|
72
|
+
>
|
|
73
|
+
<td v-if="tableConfig?.enableSelection !== false" class="col-check">
|
|
74
|
+
<el-checkbox
|
|
75
|
+
:model-value="selectedRows.includes(index)"
|
|
76
|
+
@change="toggleRowSelection(index)"
|
|
77
|
+
size="small"
|
|
78
|
+
/>
|
|
79
|
+
</td>
|
|
80
|
+
<td v-for="col in columns" :key="col.key" :class="'col-' + col.key">
|
|
81
|
+
<el-input
|
|
82
|
+
v-if="col.type === 'text'"
|
|
83
|
+
:model-value="row[col.key]"
|
|
84
|
+
@update:model-value="updateCell(index, col.key, $event)"
|
|
85
|
+
:placeholder="col.placeholder"
|
|
86
|
+
size="small"
|
|
87
|
+
/>
|
|
88
|
+
<el-input-number
|
|
89
|
+
v-else-if="col.type === 'number'"
|
|
90
|
+
:model-value="row[col.key]"
|
|
91
|
+
@update:model-value="updateCell(index, col.key, $event)"
|
|
92
|
+
size="small"
|
|
93
|
+
:controls="false"
|
|
94
|
+
class="num-input"
|
|
95
|
+
/>
|
|
96
|
+
<el-switch
|
|
97
|
+
v-else-if="col.type === 'boolean'"
|
|
98
|
+
:model-value="row[col.key]"
|
|
99
|
+
@update:model-value="updateCell(index, col.key, $event)"
|
|
100
|
+
size="small"
|
|
101
|
+
/>
|
|
102
|
+
<el-select
|
|
103
|
+
v-else-if="col.type === 'select'"
|
|
104
|
+
:model-value="row[col.key]"
|
|
105
|
+
@update:model-value="updateCell(index, col.key, $event)"
|
|
106
|
+
:placeholder="col.placeholder || '请选择'"
|
|
107
|
+
size="small"
|
|
108
|
+
>
|
|
109
|
+
<el-option
|
|
110
|
+
v-for="opt in col.options"
|
|
111
|
+
:key="String(opt.value)"
|
|
112
|
+
:label="opt.label"
|
|
113
|
+
:value="opt.value"
|
|
114
|
+
/>
|
|
115
|
+
</el-select>
|
|
116
|
+
</td>
|
|
117
|
+
<td class="col-op">
|
|
118
|
+
<el-button type="danger" size="small" link @click="removeRow(index)">
|
|
119
|
+
删除
|
|
120
|
+
</el-button>
|
|
121
|
+
</td>
|
|
122
|
+
</tr>
|
|
123
|
+
<!-- 添加行 -->
|
|
124
|
+
<tr class="add-row" @click="addRow">
|
|
125
|
+
<td :colspan="totalColumns" class="add-cell">
|
|
126
|
+
<span class="add-hint">
|
|
127
|
+
<k-icon name="add" />
|
|
128
|
+
点击添加一行
|
|
129
|
+
</span>
|
|
130
|
+
</td>
|
|
131
|
+
</tr>
|
|
132
|
+
</tbody>
|
|
133
|
+
</table>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<!-- 底部统计 -->
|
|
137
|
+
<div class="table-footer" v-if="rows.length > 0">
|
|
138
|
+
共 {{ rows.length }} 条
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<!-- 导入对话框 -->
|
|
142
|
+
<el-dialog
|
|
143
|
+
v-model="showImportDialog"
|
|
144
|
+
title="导入数据"
|
|
145
|
+
width="550px"
|
|
146
|
+
:close-on-click-modal="false"
|
|
147
|
+
>
|
|
148
|
+
<p class="dialog-tip">粘贴 JSON 数组格式数据:</p>
|
|
149
|
+
<el-input
|
|
150
|
+
v-model="importText"
|
|
151
|
+
type="textarea"
|
|
152
|
+
:rows="10"
|
|
153
|
+
placeholder='[{"alias": "xxx", "repoId": "xxx", ...}]'
|
|
154
|
+
/>
|
|
155
|
+
<div class="import-mode">
|
|
156
|
+
<el-radio-group v-model="importMode">
|
|
157
|
+
<el-radio value="append">追加</el-radio>
|
|
158
|
+
<el-radio value="replace">替换</el-radio>
|
|
159
|
+
</el-radio-group>
|
|
160
|
+
</div>
|
|
161
|
+
<template #footer>
|
|
162
|
+
<el-button @click="showImportDialog = false">取消</el-button>
|
|
163
|
+
<el-button type="primary" @click="doImport">导入</el-button>
|
|
164
|
+
</template>
|
|
165
|
+
</el-dialog>
|
|
166
|
+
|
|
167
|
+
<!-- 预设对话框 -->
|
|
168
|
+
<el-dialog
|
|
169
|
+
v-model="showPresetsDialog"
|
|
170
|
+
title="内置预设"
|
|
171
|
+
width="600px"
|
|
172
|
+
:close-on-click-modal="false"
|
|
173
|
+
>
|
|
174
|
+
<div class="presets-header">
|
|
175
|
+
<el-input
|
|
176
|
+
v-model="presetSearch"
|
|
177
|
+
placeholder="搜索..."
|
|
178
|
+
size="small"
|
|
179
|
+
clearable
|
|
180
|
+
class="search-input"
|
|
181
|
+
/>
|
|
182
|
+
<el-button size="small" @click="selectAllPresets">全选</el-button>
|
|
183
|
+
<el-button size="small" @click="selectedPresets = []">清空</el-button>
|
|
184
|
+
<span class="sel-count">已选 {{ selectedPresets.length }}</span>
|
|
185
|
+
</div>
|
|
186
|
+
<div class="presets-list">
|
|
187
|
+
<div
|
|
188
|
+
v-for="p in filteredPresets"
|
|
189
|
+
:key="p.alias"
|
|
190
|
+
class="preset-row"
|
|
191
|
+
:class="{ selected: selectedPresets.includes(p.alias), disabled: existingAliases.has(p.alias?.toLowerCase()) }"
|
|
192
|
+
@click="togglePreset(p.alias)"
|
|
193
|
+
>
|
|
194
|
+
<el-checkbox
|
|
195
|
+
:model-value="selectedPresets.includes(p.alias)"
|
|
196
|
+
:disabled="existingAliases.has(p.alias?.toLowerCase())"
|
|
197
|
+
size="small"
|
|
198
|
+
/>
|
|
199
|
+
<span class="p-alias">{{ p.alias }}</span>
|
|
200
|
+
<span class="p-desc">{{ p.description || p.repoId }}</span>
|
|
201
|
+
<el-tag v-if="existingAliases.has(p.alias?.toLowerCase())" size="small" type="info">已有</el-tag>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
<template #footer>
|
|
205
|
+
<el-button @click="showPresetsDialog = false">取消</el-button>
|
|
206
|
+
<el-button type="primary" @click="importPresets" :disabled="selectedPresets.length === 0">
|
|
207
|
+
导入 ({{ selectedPresets.length }})
|
|
208
|
+
</el-button>
|
|
209
|
+
</template>
|
|
210
|
+
</el-dialog>
|
|
211
|
+
</div>
|
|
212
|
+
</template>
|
|
213
|
+
|
|
214
|
+
<script setup lang="ts">
|
|
215
|
+
import { ref, computed, watch } from 'vue'
|
|
216
|
+
import { ElMessage } from 'element-plus'
|
|
217
|
+
import type { TableColumnDefinition, TableConfig } from '../types'
|
|
218
|
+
|
|
219
|
+
interface Props {
|
|
220
|
+
columns: TableColumnDefinition[]
|
|
221
|
+
modelValue: Record<string, any>[]
|
|
222
|
+
tableConfig?: TableConfig
|
|
223
|
+
presets?: Record<string, any>[]
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
227
|
+
modelValue: () => [],
|
|
228
|
+
presets: () => []
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
const emit = defineEmits<{
|
|
232
|
+
'update:modelValue': [value: Record<string, any>[]]
|
|
233
|
+
}>()
|
|
234
|
+
|
|
235
|
+
const rows = computed({
|
|
236
|
+
get: () => props.modelValue || [],
|
|
237
|
+
set: (val) => emit('update:modelValue', val)
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
const selectedRows = ref<number[]>([])
|
|
241
|
+
const isAllSelected = computed(() => rows.value.length > 0 && selectedRows.value.length === rows.value.length)
|
|
242
|
+
const isIndeterminate = computed(() => selectedRows.value.length > 0 && selectedRows.value.length < rows.value.length)
|
|
243
|
+
|
|
244
|
+
const totalColumns = computed(() => {
|
|
245
|
+
let count = props.columns.length + 1 // columns + operation column
|
|
246
|
+
if (props.tableConfig?.enableSelection !== false) count++ // checkbox column
|
|
247
|
+
return count
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
const showImportDialog = ref(false)
|
|
251
|
+
const importText = ref('')
|
|
252
|
+
const importMode = ref<'append' | 'replace'>('append')
|
|
253
|
+
|
|
254
|
+
const showPresetsDialog = ref(false)
|
|
255
|
+
const selectedPresets = ref<string[]>([])
|
|
256
|
+
const presetSearch = ref('')
|
|
257
|
+
|
|
258
|
+
const existingAliases = computed(() => {
|
|
259
|
+
const set = new Set<string>()
|
|
260
|
+
for (const row of rows.value) {
|
|
261
|
+
if (row.alias) set.add(row.alias.toLowerCase())
|
|
262
|
+
}
|
|
263
|
+
return set
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
const filteredPresets = computed(() => {
|
|
267
|
+
if (!presetSearch.value) return props.presets
|
|
268
|
+
const s = presetSearch.value.toLowerCase()
|
|
269
|
+
return props.presets.filter(p =>
|
|
270
|
+
p.alias?.toLowerCase().includes(s) ||
|
|
271
|
+
p.description?.toLowerCase().includes(s) ||
|
|
272
|
+
p.repoId?.toLowerCase().includes(s)
|
|
273
|
+
)
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
function addRow() {
|
|
277
|
+
const newRow: Record<string, any> = {}
|
|
278
|
+
for (const col of props.columns) {
|
|
279
|
+
newRow[col.key] = col.type === 'boolean' ? false : col.type === 'number' ? undefined : ''
|
|
280
|
+
}
|
|
281
|
+
emit('update:modelValue', [...rows.value, newRow])
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function removeRow(index: number) {
|
|
285
|
+
const arr = [...rows.value]
|
|
286
|
+
arr.splice(index, 1)
|
|
287
|
+
emit('update:modelValue', arr)
|
|
288
|
+
selectedRows.value = selectedRows.value.filter(i => i !== index).map(i => i > index ? i - 1 : i)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function updateCell(rowIndex: number, colKey: string, value: any) {
|
|
292
|
+
const arr = [...rows.value]
|
|
293
|
+
arr[rowIndex] = { ...arr[rowIndex], [colKey]: value }
|
|
294
|
+
emit('update:modelValue', arr)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function toggleRowSelection(index: number) {
|
|
298
|
+
const idx = selectedRows.value.indexOf(index)
|
|
299
|
+
if (idx >= 0) selectedRows.value.splice(idx, 1)
|
|
300
|
+
else selectedRows.value.push(index)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function toggleSelectAll(val: boolean) {
|
|
304
|
+
selectedRows.value = val ? rows.value.map((_, i) => i) : []
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function deleteSelected() {
|
|
308
|
+
if (selectedRows.value.length === 0) return
|
|
309
|
+
const toDelete = new Set(selectedRows.value)
|
|
310
|
+
emit('update:modelValue', rows.value.filter((_, i) => !toDelete.has(i)))
|
|
311
|
+
ElMessage.success(`已删除 ${toDelete.size} 条`)
|
|
312
|
+
selectedRows.value = []
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function exportData() {
|
|
316
|
+
const blob = new Blob([JSON.stringify(rows.value, null, 2)], { type: 'application/json' })
|
|
317
|
+
const url = URL.createObjectURL(blob)
|
|
318
|
+
const a = document.createElement('a')
|
|
319
|
+
a.href = url
|
|
320
|
+
a.download = 'data.json'
|
|
321
|
+
a.click()
|
|
322
|
+
URL.revokeObjectURL(url)
|
|
323
|
+
ElMessage.success('导出成功')
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function doImport() {
|
|
327
|
+
try {
|
|
328
|
+
const data = JSON.parse(importText.value)
|
|
329
|
+
if (!Array.isArray(data)) {
|
|
330
|
+
ElMessage.error('需要 JSON 数组')
|
|
331
|
+
return
|
|
332
|
+
}
|
|
333
|
+
const normalized = data.map(item => {
|
|
334
|
+
const row: Record<string, any> = {}
|
|
335
|
+
for (const col of props.columns) {
|
|
336
|
+
row[col.key] = item[col.key] ?? (col.key === 'repoId' && item.name ? item.name : '')
|
|
337
|
+
}
|
|
338
|
+
return row
|
|
339
|
+
})
|
|
340
|
+
emit('update:modelValue', importMode.value === 'replace' ? normalized : [...rows.value, ...normalized])
|
|
341
|
+
ElMessage.success(`已导入 ${normalized.length} 条`)
|
|
342
|
+
showImportDialog.value = false
|
|
343
|
+
importText.value = ''
|
|
344
|
+
} catch {
|
|
345
|
+
ElMessage.error('JSON 解析失败')
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function selectAllPresets() {
|
|
350
|
+
selectedPresets.value = filteredPresets.value
|
|
351
|
+
.filter(p => !existingAliases.value.has(p.alias?.toLowerCase()))
|
|
352
|
+
.map(p => p.alias)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function togglePreset(alias: string) {
|
|
356
|
+
if (existingAliases.value.has(alias?.toLowerCase())) return
|
|
357
|
+
const idx = selectedPresets.value.indexOf(alias)
|
|
358
|
+
if (idx >= 0) selectedPresets.value.splice(idx, 1)
|
|
359
|
+
else selectedPresets.value.push(alias)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function importPresets() {
|
|
363
|
+
const toImport = props.presets.filter(p => selectedPresets.value.includes(p.alias))
|
|
364
|
+
const newRows = toImport.map(preset => {
|
|
365
|
+
const row: Record<string, any> = {}
|
|
366
|
+
for (const col of props.columns) row[col.key] = preset[col.key] ?? ''
|
|
367
|
+
return row
|
|
368
|
+
})
|
|
369
|
+
emit('update:modelValue', [...rows.value, ...newRows])
|
|
370
|
+
ElMessage.success(`已导入 ${newRows.length} 条`)
|
|
371
|
+
showPresetsDialog.value = false
|
|
372
|
+
selectedPresets.value = []
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
watch(showPresetsDialog, (v) => {
|
|
376
|
+
if (v) {
|
|
377
|
+
selectedPresets.value = []
|
|
378
|
+
presetSearch.value = ''
|
|
379
|
+
}
|
|
380
|
+
})
|
|
381
|
+
</script>
|
|
382
|
+
|
|
383
|
+
<style scoped>
|
|
384
|
+
.table-field-editor {
|
|
385
|
+
width: 100%;
|
|
386
|
+
max-width: 100%;
|
|
387
|
+
overflow: hidden;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.toolbar {
|
|
391
|
+
display: flex;
|
|
392
|
+
justify-content: space-between;
|
|
393
|
+
align-items: center;
|
|
394
|
+
margin-bottom: 10px;
|
|
395
|
+
flex-wrap: wrap;
|
|
396
|
+
gap: 8px;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
.toolbar-left, .toolbar-right {
|
|
400
|
+
display: flex;
|
|
401
|
+
gap: 6px;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.table-wrapper {
|
|
405
|
+
border: 1px solid var(--k-color-border, #dcdfe6);
|
|
406
|
+
border-radius: 6px;
|
|
407
|
+
overflow-x: auto;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.data-table {
|
|
411
|
+
width: 100%;
|
|
412
|
+
min-width: 500px;
|
|
413
|
+
border-collapse: collapse;
|
|
414
|
+
font-size: 13px;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.data-table th,
|
|
418
|
+
.data-table td {
|
|
419
|
+
padding: 6px 8px;
|
|
420
|
+
text-align: left;
|
|
421
|
+
border-bottom: 1px solid var(--k-color-border, #ebeef5);
|
|
422
|
+
vertical-align: middle;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.data-table th {
|
|
426
|
+
background: var(--k-color-fill, #f5f7fa);
|
|
427
|
+
font-weight: 500;
|
|
428
|
+
color: var(--k-color-text-secondary, #909399);
|
|
429
|
+
font-size: 12px;
|
|
430
|
+
white-space: nowrap;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.data-table th.required::after {
|
|
434
|
+
content: '*';
|
|
435
|
+
color: #f56c6c;
|
|
436
|
+
margin-left: 2px;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
.data-table tbody tr:hover {
|
|
440
|
+
background: var(--k-color-fill-light, #f5f7fa);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.data-table tbody tr.selected {
|
|
444
|
+
background: #ecf5ff;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.data-table tbody tr:last-child td {
|
|
448
|
+
border-bottom: none;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/* 列宽控制 - 使用百分比 */
|
|
452
|
+
.col-check { width: 32px; text-align: center; }
|
|
453
|
+
.col-op { width: 45px; text-align: center; }
|
|
454
|
+
.col-alias { width: 15%; min-width: 70px; }
|
|
455
|
+
.col-repoId { width: 35%; min-width: 120px; }
|
|
456
|
+
.col-triggerWords { width: 25%; min-width: 100px; }
|
|
457
|
+
.col-description { width: 25%; min-width: 100px; }
|
|
458
|
+
|
|
459
|
+
.data-table :deep(.el-input) {
|
|
460
|
+
width: 100%;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.data-table :deep(.el-input__wrapper) {
|
|
464
|
+
padding: 0 8px;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.data-table :deep(.el-input__inner) {
|
|
468
|
+
height: 28px;
|
|
469
|
+
font-size: 12px;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.num-input {
|
|
473
|
+
width: 100%;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.empty-tip {
|
|
477
|
+
padding: 30px;
|
|
478
|
+
text-align: center;
|
|
479
|
+
color: var(--k-color-text-description, #c0c4cc);
|
|
480
|
+
font-size: 13px;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.add-row {
|
|
484
|
+
cursor: pointer;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.add-row:hover {
|
|
488
|
+
background: var(--k-color-fill-light, #f5f7fa);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.add-cell {
|
|
492
|
+
text-align: center;
|
|
493
|
+
padding: 12px !important;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.add-hint {
|
|
497
|
+
color: var(--k-color-active, #409eff);
|
|
498
|
+
display: inline-flex;
|
|
499
|
+
align-items: center;
|
|
500
|
+
gap: 4px;
|
|
501
|
+
font-size: 13px;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.table-footer {
|
|
505
|
+
margin-top: 8px;
|
|
506
|
+
font-size: 12px;
|
|
507
|
+
color: var(--k-color-text-description, #909399);
|
|
508
|
+
text-align: right;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/* 对话框 */
|
|
512
|
+
.dialog-tip {
|
|
513
|
+
margin: 0 0 10px;
|
|
514
|
+
font-size: 13px;
|
|
515
|
+
color: var(--k-color-text-secondary);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.import-mode {
|
|
519
|
+
margin-top: 10px;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/* 预设对话框 */
|
|
523
|
+
.presets-header {
|
|
524
|
+
display: flex;
|
|
525
|
+
align-items: center;
|
|
526
|
+
gap: 8px;
|
|
527
|
+
margin-bottom: 10px;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.search-input {
|
|
531
|
+
width: 160px;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.sel-count {
|
|
535
|
+
margin-left: auto;
|
|
536
|
+
font-size: 12px;
|
|
537
|
+
color: var(--k-color-text-description);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
.presets-list {
|
|
541
|
+
max-height: 360px;
|
|
542
|
+
overflow-y: auto;
|
|
543
|
+
border: 1px solid var(--k-color-border, #dcdfe6);
|
|
544
|
+
border-radius: 4px;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.preset-row {
|
|
548
|
+
display: flex;
|
|
549
|
+
align-items: center;
|
|
550
|
+
gap: 10px;
|
|
551
|
+
padding: 8px 12px;
|
|
552
|
+
border-bottom: 1px solid var(--k-color-border, #ebeef5);
|
|
553
|
+
cursor: pointer;
|
|
554
|
+
transition: background 0.15s;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.preset-row:last-child {
|
|
558
|
+
border-bottom: none;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.preset-row:hover:not(.disabled) {
|
|
562
|
+
background: var(--k-color-fill-light, #f5f7fa);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.preset-row.selected:not(.disabled) {
|
|
566
|
+
background: #ecf5ff;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.preset-row.disabled {
|
|
570
|
+
opacity: 0.5;
|
|
571
|
+
cursor: not-allowed;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.p-alias {
|
|
575
|
+
font-weight: 500;
|
|
576
|
+
min-width: 80px;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.p-desc {
|
|
580
|
+
flex: 1;
|
|
581
|
+
font-size: 12px;
|
|
582
|
+
color: var(--k-color-text-description);
|
|
583
|
+
overflow: hidden;
|
|
584
|
+
text-overflow: ellipsis;
|
|
585
|
+
white-space: nowrap;
|
|
586
|
+
}
|
|
587
|
+
</style>
|