Skip to content

Commit c2b3356

Browse files
committed
feat(webui): 为模板卡片添加导出功能
- 在模板卡片上添加导出按钮,允许用户复制单个模板的TOML内容 - 新增导出相关的国际化文本,包括成功和失败提示 - 实现导出逻辑,使用剪贴板API复制模板内容 - 为所有按钮添加无障碍标签和标题属性
1 parent d86eb4c commit c2b3356

5 files changed

Lines changed: 48 additions & 5 deletions

File tree

module/bin/device_faker_cli

-23.7 KB
Binary file not shown.

webui/src/components/templates/TemplateCard.vue

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,31 @@
66
<p class="template-device">{{ template.brand }} {{ template.model }}</p>
77
</div>
88
<div class="template-actions">
9-
<button class="icon-btn" @click="emit('edit', name, template)">
9+
<button
10+
class="icon-btn"
11+
type="button"
12+
:title="t('templates.actions.export')"
13+
:aria-label="t('templates.actions.export')"
14+
@click="emit('export', name, template)"
15+
>
16+
<Copy :size="18" />
17+
</button>
18+
<button
19+
class="icon-btn"
20+
type="button"
21+
:title="t('common.edit')"
22+
:aria-label="t('common.edit')"
23+
@click="emit('edit', name, template)"
24+
>
1025
<Edit2 :size="18" />
1126
</button>
12-
<button class="icon-btn danger" @click="emit('delete', name)">
27+
<button
28+
class="icon-btn danger"
29+
type="button"
30+
:title="t('common.delete')"
31+
:aria-label="t('common.delete')"
32+
@click="emit('delete', name)"
33+
>
1334
<Trash2 :size="18" />
1435
</button>
1536
</div>
@@ -88,14 +109,14 @@
88109
</template>
89110

90111
<script setup lang="ts">
91-
import { Edit2, Trash2 } from 'lucide-vue-next'
112+
import { Copy, Edit2, Trash2 } from 'lucide-vue-next'
92113
import { toRefs } from 'vue'
93114
import { useI18n } from '../../utils/i18n'
94115
import type { Template } from '../../types'
95116
96117
const props = defineProps<{ name: string; template: Template }>()
97118
const { name, template } = toRefs(props)
98-
const emit = defineEmits<{ edit: [string, Template]; delete: [string] }>()
119+
const emit = defineEmits<{ export: [string, Template]; edit: [string, Template]; delete: [string] }>()
99120
100121
const { t } = useI18n()
101122
</script>

webui/src/components/templates/TemplateList.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
:key="name"
66
:name="name"
77
:template="template"
8+
@export="emit('export', name, template)"
89
@edit="emit('edit', name, template)"
910
@delete="emit('delete', name)"
1011
/>
@@ -26,7 +27,7 @@ import { useI18n } from '../../utils/i18n'
2627
import type { Template } from '../../types'
2728
2829
const props = defineProps<{ templates: Record<string, Template>; isSearching?: boolean }>()
29-
const emit = defineEmits<{ edit: [string, Template]; delete: [string] }>()
30+
const emit = defineEmits<{ export: [string, Template]; edit: [string, Template]; delete: [string] }>()
3031
3132
const { t } = useI18n()
3233

webui/src/pages/TemplatePage.vue

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<TemplateList
1818
:templates="filteredTemplates"
1919
:is-searching="searchQuery.length > 0"
20+
@export="handleExport"
2021
@edit="handleEdit"
2122
@delete="deleteTemplateConfirm"
2223
/>
@@ -45,6 +46,7 @@ import { useConfigStore } from '../stores/config'
4546
import { useOnlineTemplatesStore } from '../stores/onlineTemplates'
4647
import { useI18n } from '../utils/i18n'
4748
import { useLazyMessageBox } from '../utils/elementPlus'
49+
import { copyTextToClipboard, stringifyTemplatesToToml } from '../utils/templateTransfer'
4850
import { toast } from 'kernelsu-alt'
4951
import type { Template } from '../types'
5052
@@ -132,6 +134,19 @@ function handleEdit(name: string, template: Template) {
132134
dialogVisible.value = true
133135
}
134136
137+
async function handleExport(name: string, template: Template) {
138+
try {
139+
const content = stringifyTemplatesToToml({ [name]: template })
140+
const copied = await copyTextToClipboard(content)
141+
toast(
142+
copied ? t('templates.messages.export_copy_success') : t('templates.messages.export_copy_failed')
143+
)
144+
} catch (error) {
145+
console.error('Export template failed:', error)
146+
toast(t('templates.messages.export_copy_failed'))
147+
}
148+
}
149+
135150
async function deleteTemplateConfirm(name: string) {
136151
try {
137152
const messageBox = await getMessageBox()

webui/src/utils/i18n.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export const messages = {
6666
online: '在线模板',
6767
new: '新建模板',
6868
transfer: '导入/导出',
69+
export: '导出',
6970
add: '添加',
7071
},
7172
fields: {
@@ -125,6 +126,8 @@ export const messages = {
125126
name_required: '请输入模板名称',
126127
saved: '模板已保存',
127128
deleted: '模板已删除',
129+
export_copy_success: '模板导出内容已复制',
130+
export_copy_failed: '复制失败,请手动复制',
128131
pkg_exists: '该包名已添加',
129132
},
130133
labels: {
@@ -443,6 +446,7 @@ export const messages = {
443446
online: 'Online Templates',
444447
new: 'New Template',
445448
transfer: 'Import/Export',
449+
export: 'Export',
446450
add: 'Add',
447451
},
448452
fields: {
@@ -502,6 +506,8 @@ export const messages = {
502506
name_required: 'Please enter template name',
503507
saved: 'Template saved',
504508
deleted: 'Template deleted',
509+
export_copy_success: 'Template export copied',
510+
export_copy_failed: 'Copy failed, please copy manually',
505511
pkg_exists: 'Package already added',
506512
},
507513
labels: {

0 commit comments

Comments
 (0)