feat(ui): add QR code sharing for remote profiles

Add "Share QR Code" context menu item to remote profile cards.
This commit is contained in:
Tunglies
2026-04-19 22:20:17 +08:00
parent d203121abe
commit 72394eb9b7
20 changed files with 137 additions and 11 deletions
+1
View File
@@ -14,6 +14,7 @@
### ✨ 新增功能
- 订阅 QR code 分享
- 新增 macOS 托盘速率显示
- 快捷键操作通知操作结果
+1
View File
@@ -67,6 +67,7 @@
"monaco-editor": "^0.55.1",
"monaco-yaml": "^5.4.1",
"nanoid": "^5.1.7",
"qrcode.react": "^4.2.0",
"react": "19.2.5",
"react-dom": "19.2.5",
"react-error-boundary": "6.1.1",
+12
View File
@@ -107,6 +107,9 @@ importers:
nanoid:
specifier: ^5.1.7
version: 5.1.7
qrcode.react:
specifier: ^4.2.0
version: 4.2.0(react@19.2.5)
react:
specifier: 19.2.5
version: 19.2.5
@@ -3101,6 +3104,11 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
qrcode.react@4.2.0:
resolution: {integrity: sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom@19.2.5:
resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==}
peerDependencies:
@@ -6762,6 +6770,10 @@ snapshots:
punycode@2.3.1: {}
qrcode.react@4.2.0(react@19.2.5):
dependencies:
react: 19.2.5
react-dom@19.2.5(react@19.2.5):
dependencies:
react: 19.2.5
+31 -11
View File
@@ -1,20 +1,20 @@
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import {
RefreshRounded,
DragIndicatorRounded,
CheckBoxRounded,
CheckBoxOutlineBlankRounded,
CheckBoxRounded,
DragIndicatorRounded,
RefreshRounded,
} from '@mui/icons-material'
import {
Box,
Typography,
LinearProgress,
CircularProgress,
IconButton,
keyframes,
MenuItem,
LinearProgress,
Menu,
CircularProgress,
MenuItem,
Typography,
} from '@mui/material'
import { open } from '@tauri-apps/plugin-shell'
import { useLockFn } from 'ahooks'
@@ -28,11 +28,11 @@ import { GroupsEditorViewer } from '@/components/profile/groups-editor-viewer'
import { RulesEditorViewer } from '@/components/profile/rules-editor-viewer'
import { useEditorDocument } from '@/hooks/use-editor-document'
import {
viewProfile,
readProfileFile,
updateProfile,
saveProfileFile,
getNextUpdateTime,
readProfileFile,
saveProfileFile,
updateProfile,
viewProfile,
} from '@/services/cmds'
import { showNotice } from '@/services/notice-service'
import { useLoadingCache, useSetLoadingCache } from '@/services/states'
@@ -42,6 +42,7 @@ import parseTraffic from '@/utils/parse-traffic'
import { ProfileBox } from './profile-box'
import { ProxiesEditorViewer } from './proxies-editor-viewer'
import { QrViewer } from './qr-viewer'
const round = keyframes`
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
@@ -285,6 +286,7 @@ export const ProfileItem = (props: Props) => {
const [mergeOpen, setMergeOpen] = useState(false)
const [scriptOpen, setScriptOpen] = useState(false)
const [confirmOpen, setConfirmOpen] = useState(false)
const [qrOpen, setQrOpen] = useState(false)
const loadProfileDocument = useCallback(() => readProfileFile(uid), [uid])
const loadMergeDocument = useCallback(
@@ -319,6 +321,11 @@ export const ProfileItem = (props: Props) => {
onEdit()
}
const onShareQrCode = () => {
setAnchorEl(null)
setQrOpen(true)
}
const onEditFile = () => {
setAnchorEl(null)
setFileOpen(true)
@@ -409,6 +416,7 @@ export const ProfileItem = (props: Props) => {
const menuLabels: Record<string, TranslationKey> = {
home: 'profiles.components.menu.home',
select: 'profiles.components.menu.select',
shareQrCode: 'profiles.components.menu.shareQrCode',
editInfo: 'profiles.components.menu.editInfo',
editFile: 'profiles.components.menu.editFile',
editRules: 'profiles.components.menu.editRules',
@@ -437,6 +445,11 @@ export const ProfileItem = (props: Props) => {
handler: onForceSelect,
disabled: false,
},
{
label: menuLabels.shareQrCode,
handler: onShareQrCode,
disabled: false,
},
{
label: menuLabels.editInfo,
handler: onEditInfo,
@@ -978,6 +991,13 @@ export const ProfileItem = (props: Props) => {
setConfirmOpen(false)
}}
/>
{qrOpen && itemData.url && (
<QrViewer
open={true}
value={`${itemData.url}${itemData.url.includes('?') ? '&' : '?'}name=${encodeURIComponent(name)}`}
onClose={() => setQrOpen(false)}
/>
)}
</Box>
)
}
+34
View File
@@ -0,0 +1,34 @@
import { Box, Dialog, DialogContent, DialogTitle } from '@mui/material'
import { QRCodeSVG } from 'qrcode.react'
import { useTranslation } from 'react-i18next'
interface Props {
open: boolean
value: string
title?: string
onClose: () => void
}
export const QrViewer = (props: Props) => {
const { open, value, title, onClose } = props
const { t } = useTranslation()
return (
<Dialog open={open} onClose={onClose} maxWidth="xs">
<DialogTitle>{title ?? t('profiles.modals.qrViewer.title')}</DialogTitle>
<DialogContent sx={{ pb: 3 }}>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
p: 2,
bgcolor: '#fff',
borderRadius: 1,
}}
>
<QRCodeSVG value={value} size={256} level="M" />
</Box>
</DialogContent>
</Dialog>
)
}
+4
View File
@@ -59,6 +59,7 @@
"menu": {
"home": "Home",
"select": "اختيار",
"shareQrCode": "Share QR Code",
"editInfo": "تعديل المعلومات",
"editFile": "تعديل الملف",
"editRules": "تعديل القواعد",
@@ -177,6 +178,9 @@
},
"logViewer": {
"title": "وحدة التحكم للسكريبت"
},
"qrViewer": {
"title": "Subscription QR Code"
}
}
}
+4
View File
@@ -59,6 +59,7 @@
"menu": {
"home": "Startseite",
"select": "Verwenden",
"shareQrCode": "Share QR Code",
"editInfo": "Informationen bearbeiten",
"editFile": "Datei bearbeiten",
"editRules": "Regeln bearbeiten",
@@ -177,6 +178,9 @@
},
"logViewer": {
"title": "Skript-Konsole-Ausgabe"
},
"qrViewer": {
"title": "Subscription QR Code"
}
}
}
+4
View File
@@ -59,6 +59,7 @@
"menu": {
"home": "Home",
"select": "Select",
"shareQrCode": "Share QR Code",
"editInfo": "Edit Info",
"editFile": "Edit File",
"editRules": "Edit Rules",
@@ -177,6 +178,9 @@
},
"logViewer": {
"title": "Script Console"
},
"qrViewer": {
"title": "Subscription QR Code"
}
}
}
+4
View File
@@ -59,6 +59,7 @@
"menu": {
"home": "Hogar",
"select": "Usar",
"shareQrCode": "Share QR Code",
"editInfo": "Editar información",
"editFile": "Editar archivo",
"editRules": "Editar reglas",
@@ -177,6 +178,9 @@
},
"logViewer": {
"title": "Salida de la consola del script"
},
"qrViewer": {
"title": "Subscription QR Code"
}
}
}
+4
View File
@@ -59,6 +59,7 @@
"menu": {
"home": "Home",
"select": "انتخاب",
"shareQrCode": "Share QR Code",
"editInfo": "ویرایش اطلاعات",
"editFile": "ویرایش فایل",
"editRules": "ویرایش قوانین",
@@ -177,6 +178,9 @@
},
"logViewer": {
"title": "کنسول اسکریپت"
},
"qrViewer": {
"title": "Subscription QR Code"
}
}
}
+4
View File
@@ -59,6 +59,7 @@
"menu": {
"home": "Home",
"select": "Pilih",
"shareQrCode": "Share QR Code",
"editInfo": "Ubah Info",
"editFile": "Ubah Berkas",
"editRules": "Ubah Aturan",
@@ -177,6 +178,9 @@
},
"logViewer": {
"title": "Konsol Skrip"
},
"qrViewer": {
"title": "Subscription QR Code"
}
}
}
+4
View File
@@ -59,6 +59,7 @@
"menu": {
"home": "ホーム",
"select": "使用する",
"shareQrCode": "Share QR Code",
"editInfo": "情報を編集",
"editFile": "ファイルを編集",
"editRules": "ルールを編集",
@@ -177,6 +178,9 @@
},
"logViewer": {
"title": "スクリプトコンソール出力"
},
"qrViewer": {
"title": "Subscription QR Code"
}
}
}
+4
View File
@@ -59,6 +59,7 @@
"menu": {
"home": "홈",
"select": "선택",
"shareQrCode": "Share QR Code",
"editInfo": "정보 편집",
"editFile": "파일 편집",
"editRules": "규칙 편집",
@@ -177,6 +178,9 @@
},
"logViewer": {
"title": "스크립트 콘솔"
},
"qrViewer": {
"title": "Subscription QR Code"
}
}
}
+4
View File
@@ -59,6 +59,7 @@
"menu": {
"home": "Главная",
"select": "Выбрать",
"shareQrCode": "Share QR Code",
"editInfo": "Изменить информацию",
"editFile": "Изменить файл",
"editRules": "Редактировать правила",
@@ -177,6 +178,9 @@
},
"logViewer": {
"title": "Консоль скрипта"
},
"qrViewer": {
"title": "Subscription QR Code"
}
}
}
+4
View File
@@ -59,6 +59,7 @@
"menu": {
"home": "Ana Sayfa",
"select": "Seç",
"shareQrCode": "Share QR Code",
"editInfo": "Bilgileri Düzenle",
"editFile": "Dosyayı Düzenle",
"editRules": "Kuralları Düzenle",
@@ -177,6 +178,9 @@
},
"logViewer": {
"title": "Betik Konsolu"
},
"qrViewer": {
"title": "Subscription QR Code"
}
}
}
+4
View File
@@ -59,6 +59,7 @@
"menu": {
"home": "Home",
"select": "Сайлау",
"shareQrCode": "Share QR Code",
"editInfo": "Мәгълүматны үзгәртү",
"editFile": "Файлны үзгәртү",
"editRules": "Кагыйдәләрне үзгәртү",
@@ -177,6 +178,9 @@
},
"logViewer": {
"title": "Скрипт консоле"
},
"qrViewer": {
"title": "Subscription QR Code"
}
}
}
+4
View File
@@ -59,6 +59,7 @@
"menu": {
"home": "首 页",
"select": "使用",
"shareQrCode": "分享二维码",
"editInfo": "编辑信息",
"editFile": "编辑文件",
"editRules": "编辑规则",
@@ -177,6 +178,9 @@
},
"logViewer": {
"title": "脚本控制台输出"
},
"qrViewer": {
"title": "订阅二维码"
}
}
}
+4
View File
@@ -59,6 +59,7 @@
"menu": {
"home": "首 頁",
"select": "使用",
"shareQrCode": "Share QR Code",
"editInfo": "編輯資訊",
"editFile": "編輯檔案",
"editRules": "編輯規則",
@@ -177,6 +178,9 @@
},
"logViewer": {
"title": "指令碼控制台輸出"
},
"qrViewer": {
"title": "Subscription QR Code"
}
}
}
+2
View File
@@ -142,6 +142,7 @@ export const translationKeys = [
'profiles.components.fileInput.chooseFile',
'profiles.components.menu.home',
'profiles.components.menu.select',
'profiles.components.menu.shareQrCode',
'profiles.components.menu.editInfo',
'profiles.components.menu.editFile',
'profiles.components.menu.editRules',
@@ -210,6 +211,7 @@ export const translationKeys = [
'profiles.modals.confirmDelete.title',
'profiles.modals.confirmDelete.message',
'profiles.modals.logViewer.title',
'profiles.modals.qrViewer.title',
'proxies.page.modes.rule',
'proxies.page.modes.global',
'proxies.page.modes.direct',
+4
View File
@@ -226,6 +226,7 @@ export interface TranslationResources {
home: string
openFile: string
select: string
shareQrCode: string
update: string
updateViaProxy: string
}
@@ -337,6 +338,9 @@ export interface TranslationResources {
}
title: string
}
qrViewer: {
title: string
}
}
page: {
actions: {