适配安卓客户端
This commit is contained in:
parent
a0050dd94c
commit
7bf39b8a6d
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@ -5,7 +5,7 @@
|
||||
<profile name="Gradle Imported" enabled="true">
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<processorPath useClasspath="false">
|
||||
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.32/17d46b3e205515e1e8efd3ee4d57ce8018914163/lombok-1.18.32.jar" />
|
||||
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.34/ec547ef414ab1d2c040118fb9c1c265ada63af14/lombok-1.18.34.jar" />
|
||||
</processorPath>
|
||||
<module name="password-xl-service.main" />
|
||||
</profile>
|
||||
|
@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '3.3.1'
|
||||
id 'io.spring.dependency-management' version '1.1.5'
|
||||
id 'org.springframework.boot' version '3.3.2'
|
||||
id 'io.spring.dependency-management' version '1.1.6'
|
||||
id 'org.graalvm.buildtools.native' version '0.10.2'
|
||||
}
|
||||
|
||||
@ -26,15 +26,18 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'cn.hutool:hutool-all:5.8.27'
|
||||
implementation 'cn.hutool:hutool-all:5.8.29'
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
implementation 'com.alibaba.fastjson2:fastjson2:2.0.51'
|
||||
implementation 'com.alibaba.fastjson2:fastjson2:2.0.52'
|
||||
implementation 'io.hotmoka:toml4j:0.7.3'
|
||||
}
|
||||
|
||||
tasks.bootBuildImage {
|
||||
imageName = "${project.name}"
|
||||
docker {
|
||||
host = "////./pipe/dockerDesktopLinuxEngine"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('printVersion') {
|
||||
|
@ -1,10 +1,5 @@
|
||||
import {contextBridge, ipcRenderer} from 'electron'
|
||||
|
||||
// 设置环境变量
|
||||
contextBridge.exposeInMainWorld('env', {
|
||||
electron: true
|
||||
})
|
||||
|
||||
// 开放给源码的API
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
getFile: async (fileName) => ipcRenderer.invoke('get-file', fileName),
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 4.8 KiB |
1
password-xl-web/src/components.d.ts
vendored
1
password-xl-web/src/components.d.ts
vendored
@ -8,6 +8,7 @@ export {}
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
About: typeof import('./components/common/setting/About.vue')['default']
|
||||
AndroidLoginForm: typeof import('./components/login/AndroidLoginForm.vue')['default']
|
||||
BackupAndRecovery: typeof import('./components/common/setting/BackupAndRecovery.vue')['default']
|
||||
CancelAccount: typeof import('./components/common/setting/CancelAccount.vue')['default']
|
||||
CommonProblem: typeof import('./components/common/setting/CommonProblem.vue')['default']
|
||||
|
@ -14,7 +14,7 @@ const visFastLogin = ref(false)
|
||||
// 触发快速登录提示
|
||||
const fastLoginTip = (form: any) => {
|
||||
console.log('快速登录提示')
|
||||
if (location.href.indexOf('autoLogin') !== -1 || (window.env && window.env.electron)) {
|
||||
if (location.href.indexOf('autoLogin') !== -1 || window.electronAPI || window.androidAPI) {
|
||||
router.push('/')
|
||||
return
|
||||
}
|
||||
|
@ -46,9 +46,9 @@ const config = {
|
||||
// 每行/每列手势点数量
|
||||
pointCellSize: 3,
|
||||
// 手势点在其所在区域的占比
|
||||
cellPointRadio: 0.3,
|
||||
cellPointRadio: 0.33,
|
||||
// 手势点颜色
|
||||
gesturePointColor: '#d5d5d5',
|
||||
gesturePointColor: '#cacaca',
|
||||
// 手势点错误时的颜色
|
||||
gesturePointErrorColor: '#F56C6C',
|
||||
// 圆心占比
|
||||
@ -289,7 +289,7 @@ const mousemove = (e: MouseEvent) => {
|
||||
|
||||
// 鼠标抬起
|
||||
const mouseup = () => {
|
||||
if(!pressed.value) return
|
||||
if (!pressed.value) return
|
||||
pressed.value = false
|
||||
hoverPoint.value = null
|
||||
canvasUp()
|
||||
@ -297,7 +297,7 @@ const mouseup = () => {
|
||||
|
||||
// 鼠标离开
|
||||
const mouseleave = () => {
|
||||
if(!pressed.value) return
|
||||
if (!pressed.value) return
|
||||
pressed.value = false
|
||||
hoverPoint.value = null
|
||||
canvasUp()
|
||||
@ -330,12 +330,23 @@ const touchmove = (e: TouchEvent) => {
|
||||
|
||||
// 手指抬起
|
||||
const touchend = () => {
|
||||
if(!pressed.value) return
|
||||
if (!pressed.value) return
|
||||
pressed.value = false
|
||||
hoverPoint.value = null
|
||||
canvasUp()
|
||||
}
|
||||
|
||||
function createHDCanvas(canvas: any, w: number, h: number) {
|
||||
const ratio = window.devicePixelRatio || 1;
|
||||
canvas.width = w * ratio; // 实际渲染像素
|
||||
canvas.height = h * ratio; // 实际渲染像素
|
||||
canvas.style.width = `${w}px`; // 控制显示大小
|
||||
canvas.style.height = `${h}px`; // 控制显示大小
|
||||
const ctx = canvas.getContext('2d')
|
||||
ctx.scale(ratio, ratio)
|
||||
return ctx;
|
||||
}
|
||||
|
||||
// 初始化画板
|
||||
const initCanvas = () => {
|
||||
if (!gestureDivRef.value || !canvasRef.value) return
|
||||
@ -344,7 +355,8 @@ const initCanvas = () => {
|
||||
console.log('画板大小:', size)
|
||||
canvasRef.value.width = size
|
||||
canvasRef.value.height = size
|
||||
ctx = canvasRef.value.getContext('2d') as CanvasRenderingContext2D
|
||||
|
||||
ctx = createHDCanvas(canvasRef.value, size, size) as CanvasRenderingContext2D
|
||||
|
||||
let incrId = 0
|
||||
allPointArray.length = 0
|
||||
|
@ -194,6 +194,7 @@ defineExpose({
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
top="20vh"
|
||||
:width="['xs', 'sm'].includes(displaySize().value)?'95%':'400px'"
|
||||
v-model="visVerify"
|
||||
:close-on-click-modal="passwordStore.serviceStatus !== ServiceStatus.NO_LOGIN"
|
||||
|
@ -9,17 +9,16 @@ import packageJson from '../../../../package.json'
|
||||
</div>
|
||||
<div style="padding: 10px;">
|
||||
<el-descriptions :column="1">
|
||||
<el-descriptions-item label="项目名称:">{{ packageJson.name}}</el-descriptions-item>
|
||||
<el-descriptions-item label="当前版本:">{{ packageJson.version }}</el-descriptions-item>
|
||||
<el-descriptions-item label="官方网站:">
|
||||
<el-descriptions-item label="项目名称:">{{ packageJson.name}}</el-descriptions-item>
|
||||
<el-descriptions-item label="当前版本:">{{ packageJson.version }}</el-descriptions-item>
|
||||
<el-descriptions-item label="官方网站:">
|
||||
<el-link type="primary" target="_blank" :href="packageJson.homepage">{{ packageJson.homepage }}</el-link>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="开源地址:">
|
||||
<el-descriptions-item label="开源地址:">
|
||||
<el-link type="primary" target="_blank" :href="packageJson.repository.url">{{ packageJson.repository.url }}</el-link>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="作者名称:">{{ packageJson.contributors[0].name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="作者邮箱:">{{ packageJson.contributors[0].email }}</el-descriptions-item>
|
||||
<el-descriptions-item label="作者微信:">{{ packageJson.contributors[0].weChat }}</el-descriptions-item>
|
||||
<el-descriptions-item label="作者邮箱:">{{ packageJson.contributors[0].email }}</el-descriptions-item>
|
||||
<el-descriptions-item label="作者微信:">{{ packageJson.contributors[0].weChat }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -6,19 +6,22 @@ import {useLoginStore} from "@/stores/LoginStore.ts";
|
||||
const loginStore = useLoginStore()
|
||||
|
||||
const isElectron = () => {
|
||||
return window.env && window.env.electron
|
||||
return !!window.electronAPI
|
||||
}
|
||||
const isAndroid = () => {
|
||||
return !!window.androidAPI
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form label-width="110px">
|
||||
<el-form label-width="80px">
|
||||
<template v-if="loginStore.loginType === 'oss'">
|
||||
<div style="text-align: center;margin-bottom: 5px;width: 100%">
|
||||
<img alt="" class="login-type-image" style="height: 28px" src="@/assets/images/login/oss.png">
|
||||
<el-text style="font-size: 24px">阿里云OSS</el-text>
|
||||
</div>
|
||||
<el-divider></el-divider>
|
||||
<div style="padding: 0 50px 0 20px">
|
||||
<div>
|
||||
<el-form-item label="region">
|
||||
<el-input :model-value="loginStore.loginForm.region" :readonly="true">
|
||||
<template #append>
|
||||
@ -55,7 +58,7 @@ const isElectron = () => {
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="快速登录链接" v-if="!isElectron()">
|
||||
<el-form-item label="快速登录链接" v-if="!isElectron() && !isAndroid()">
|
||||
<el-input :model-value="getFastLoginLink(loginStore.loginForm)" :readonly="true">
|
||||
<template #append>
|
||||
<el-button @click="copyText(getFastLoginLink(loginStore.loginForm))" class="copy-btn">
|
||||
@ -72,7 +75,7 @@ const isElectron = () => {
|
||||
<el-text style="font-size: 24px">腾讯云COS</el-text>
|
||||
</div>
|
||||
<el-divider></el-divider>
|
||||
<div style="padding: 0 50px 0 20px">
|
||||
<div>
|
||||
<el-form-item label="region">
|
||||
<el-input :model-value="loginStore.loginForm.region" :readonly="true">
|
||||
<template #append>
|
||||
@ -109,7 +112,7 @@ const isElectron = () => {
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="快速登录链接" v-if="!isElectron()">
|
||||
<el-form-item label="快速登录链接" v-if="!isElectron() && !isAndroid()">
|
||||
<el-input :model-value="getFastLoginLink(loginStore.loginForm)" :readonly="true">
|
||||
<template #append>
|
||||
<el-button @click="copyText(getFastLoginLink(loginStore.loginForm))" class="copy-btn">
|
||||
@ -126,7 +129,7 @@ const isElectron = () => {
|
||||
<el-text style="font-size: 24px">私有服务</el-text>
|
||||
</div>
|
||||
<el-divider></el-divider>
|
||||
<div style="padding: 0 50px 0 20px">
|
||||
<div>
|
||||
<el-form-item label="服务地址">
|
||||
<el-input :model-value="loginStore.loginForm.serverUrl" :readonly="true">
|
||||
<template #append>
|
||||
@ -154,7 +157,7 @@ const isElectron = () => {
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="快速登录链接" v-if="!isElectron()">
|
||||
<el-form-item label="快速登录链接" v-if="!isElectron() && !isAndroid()">
|
||||
<el-input :model-value="getFastLoginLink(loginStore.loginForm)" :readonly="true">
|
||||
<template #append>
|
||||
<el-button @click="copyText(getFastLoginLink(loginStore.loginForm))" class="copy-btn">
|
||||
@ -167,9 +170,9 @@ const isElectron = () => {
|
||||
</template>
|
||||
<template v-else>
|
||||
<div style="text-align: center;margin-top: 50px;width: 100%">
|
||||
<img alt="" class="login-type-image" style="height: 150px" src="@/assets/images/login/local.png">
|
||||
<div style="margin-top: 40px">
|
||||
<el-text style="font-size: 24px">您正在使用本地文件存储</el-text>
|
||||
<img alt="" class="login-type-image" style="height: 130px" src="@/assets/images/login/local.png">
|
||||
<div style="margin-top: 25px">
|
||||
<el-text style="font-size: 22px">您正在使用本地文件存储</el-text>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding: 0 50px 0 20px">
|
||||
|
@ -321,6 +321,11 @@ watch(() => settingStore.setting.generateRule, (newValue: GenerateRule) => {
|
||||
}, {
|
||||
deep: true
|
||||
})
|
||||
|
||||
const isAndroid = () => {
|
||||
return !!window.androidAPI
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -488,9 +493,8 @@ watch(() => settingStore.setting.generateRule, (newValue: GenerateRule) => {
|
||||
<el-scrollbar :height="scrollbarHeight()">
|
||||
<div class="function-div">
|
||||
<div class="function-header" style="margin-bottom: 5px">
|
||||
<el-text tag="b">默认排序规则</el-text>
|
||||
<el-text tag="b">密码排序</el-text>
|
||||
<div>
|
||||
默认根据
|
||||
<el-select v-model="settingStore.setting.sortField" size="small" style="width: 90px;">
|
||||
<el-option value="addTime" label="添加时间"/>
|
||||
<el-option value="updateTime" label="修改时间"/>
|
||||
@ -503,7 +507,6 @@ watch(() => settingStore.setting.generateRule, (newValue: GenerateRule) => {
|
||||
<el-option :value="Sort.ASC" label="正序"/>
|
||||
<el-option :value="Sort.DESC" label="倒序"/>
|
||||
</el-select>
|
||||
排列
|
||||
</div>
|
||||
</div>
|
||||
<el-divider class="function-line"/>
|
||||
@ -524,7 +527,7 @@ watch(() => settingStore.setting.generateRule, (newValue: GenerateRule) => {
|
||||
<div class="function-div">
|
||||
<div class="function-header" style="margin-bottom: 5px">
|
||||
<el-text tag="b">在密码列表中显示时间</el-text>
|
||||
<el-select v-model="settingStore.setting.showTimeForTable" size="small" style="width: 120px;">
|
||||
<el-select v-model="settingStore.setting.showTimeForTable" size="small" style="width: 100px;">
|
||||
<el-option value="no" label="不显示时间"/>
|
||||
<el-option value="addTime" label="显示添加时间"/>
|
||||
<el-option value="updateTime" label="显示修改时间"/>
|
||||
@ -641,7 +644,7 @@ watch(() => settingStore.setting.generateRule, (newValue: GenerateRule) => {
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane>
|
||||
<el-tab-pane v-if="!isAndroid()">
|
||||
<template #label>
|
||||
<el-text>
|
||||
<span class="iconfont icon-recovery action-icon" style="color: #ffc400"></span>
|
||||
@ -649,67 +652,67 @@ watch(() => settingStore.setting.generateRule, (newValue: GenerateRule) => {
|
||||
</el-text>
|
||||
</template>
|
||||
<el-scrollbar :height="scrollbarHeight()">
|
||||
<div class="function-div">
|
||||
<div class="function-header" style="margin-bottom: 5px">
|
||||
<el-text tag="b">备份密码</el-text>
|
||||
<el-button plain type="primary" @click="refStore.backupAndRecoveryRef.backup(false)" size="small">备份
|
||||
</el-button>
|
||||
<div class="function-div">
|
||||
<div class="function-header" style="margin-bottom: 5px">
|
||||
<el-text tag="b">备份密码</el-text>
|
||||
<el-button plain type="primary" @click="refStore.backupAndRecoveryRef.backup(false)" size="small">备份
|
||||
</el-button>
|
||||
</div>
|
||||
<el-divider class="function-line"/>
|
||||
<el-text type="info" tag="p" style="text-indent: 10px">
|
||||
密码备份功能可以安全的将加密后的密码文件导出,在需要时通过
|
||||
<el-text>恢复备份密码</el-text>
|
||||
功能还原。
|
||||
</el-text>
|
||||
</div>
|
||||
<el-divider class="function-line"/>
|
||||
<el-text type="info" tag="p" style="text-indent: 10px">
|
||||
密码备份功能可以安全的将加密后的密码文件导出,在需要时通过
|
||||
<el-text>恢复备份密码</el-text>
|
||||
功能还原。
|
||||
</el-text>
|
||||
</div>
|
||||
<div class="function-div">
|
||||
<div class="function-header" style="margin-bottom: 5px">
|
||||
<el-text tag="b">恢复备份密码</el-text>
|
||||
<el-button plain type="primary" @click="refStore.backupAndRecoveryRef.recovery" size="small">恢复
|
||||
</el-button>
|
||||
<div class="function-div">
|
||||
<div class="function-header" style="margin-bottom: 5px">
|
||||
<el-text tag="b">恢复备份密码</el-text>
|
||||
<el-button plain type="primary" @click="refStore.backupAndRecoveryRef.recovery" size="small">恢复
|
||||
</el-button>
|
||||
</div>
|
||||
<el-divider class="function-line"/>
|
||||
<el-text type="info" tag="p" style="text-indent: 10px">
|
||||
此功能可以将备份文件中的密码恢复到密码列表。在验证备份文件的主密码后,您可以选择合并或覆盖方式恢复密码。
|
||||
</el-text>
|
||||
</div>
|
||||
<el-divider class="function-line"/>
|
||||
<el-text type="info" tag="p" style="text-indent: 10px">
|
||||
此功能可以将备份文件中的密码恢复到密码列表。在验证备份文件的主密码后,您可以选择合并或覆盖方式恢复密码。
|
||||
</el-text>
|
||||
</div>
|
||||
|
||||
<div class="function-div">
|
||||
<div class="function-header" style="margin-bottom: 5px">
|
||||
<el-text tag="b">导出密码</el-text>
|
||||
<el-button plain type="primary" @click="refStore.exportExcelRef.exportExcel(false)" size="small">导出
|
||||
</el-button>
|
||||
<div class="function-div">
|
||||
<div class="function-header" style="margin-bottom: 5px">
|
||||
<el-text tag="b">导出密码</el-text>
|
||||
<el-button plain type="primary" @click="refStore.exportExcelRef.exportExcel(false)" size="small">导出
|
||||
</el-button>
|
||||
</div>
|
||||
<el-divider class="function-line"/>
|
||||
<el-text type="info" tag="p" style="text-indent: 10px">
|
||||
导出密码会将敏感信息明文存储在excel表格中,请注意密码安全。若您仅需要迁移账号或备份密码建议使用密码备份与恢复功能更加高效安全。
|
||||
</el-text>
|
||||
</div>
|
||||
<el-divider class="function-line"/>
|
||||
<el-text type="info" tag="p" style="text-indent: 10px">
|
||||
导出密码会将敏感信息明文存储在excel表格中,请注意密码安全。若您仅需要迁移账号或备份密码建议使用密码备份与恢复功能更加高效安全。
|
||||
</el-text>
|
||||
</div>
|
||||
<div class="function-div">
|
||||
<div class="function-header" style="margin-bottom: 5px">
|
||||
<el-text tag="b">导入密码</el-text>
|
||||
<el-button plain type="primary" @click="refStore.importExcelRef.importExcel" size="small">导入
|
||||
</el-button>
|
||||
<div class="function-div">
|
||||
<div class="function-header" style="margin-bottom: 5px">
|
||||
<el-text tag="b">导入密码</el-text>
|
||||
<el-button plain type="primary" @click="refStore.importExcelRef.importExcel" size="small">导入
|
||||
</el-button>
|
||||
</div>
|
||||
<el-divider class="function-line"/>
|
||||
<el-text type="info" tag="p" style="text-indent: 10px">
|
||||
密码导入功能支持按照密码模板导入excel文件中的密码,您可以通过下载模板功能获取密码模板。
|
||||
</el-text>
|
||||
</div>
|
||||
<el-divider class="function-line"/>
|
||||
<el-text type="info" tag="p" style="text-indent: 10px">
|
||||
密码导入功能支持按照密码模板导入excel文件中的密码,您可以通过下载模板功能获取密码模板。
|
||||
</el-text>
|
||||
</div>
|
||||
<div class="function-div">
|
||||
<div class="function-header" style="margin-bottom: 5px">
|
||||
<el-text tag="b">下载导入模板</el-text>
|
||||
<el-button plain type="primary" @click="refStore.exportExcelRef.exportExcel(true)" size="small">下载
|
||||
</el-button>
|
||||
<div class="function-div">
|
||||
<div class="function-header" style="margin-bottom: 5px">
|
||||
<el-text tag="b">下载导入模板</el-text>
|
||||
<el-button plain type="primary" @click="refStore.exportExcelRef.exportExcel(true)" size="small">下载
|
||||
</el-button>
|
||||
</div>
|
||||
<el-divider class="function-line"/>
|
||||
<el-text type="info" tag="p" style="text-indent: 10px">
|
||||
您可在下载的Excel模板中按照要求填写您的密码列表(
|
||||
<el-text type="danger">密码名称为必填</el-text>
|
||||
),然后使用密码导入功能导入您的密码。
|
||||
</el-text>
|
||||
</div>
|
||||
<el-divider class="function-line"/>
|
||||
<el-text type="info" tag="p" style="text-indent: 10px">
|
||||
您可在下载的Excel模板中按照要求填写您的密码列表(
|
||||
<el-text type="danger">密码名称为必填</el-text>
|
||||
),然后使用密码导入功能导入您的密码。
|
||||
</el-text>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane>
|
||||
<template #label>
|
||||
@ -752,7 +755,7 @@ watch(() => settingStore.setting.generateRule, (newValue: GenerateRule) => {
|
||||
</el-text>
|
||||
</template>
|
||||
<el-scrollbar :height="scrollbarHeight()">
|
||||
<div style="padding: 0 15px">
|
||||
<div style="padding: 0">
|
||||
<About></About>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
@ -765,7 +768,7 @@ watch(() => settingStore.setting.generateRule, (newValue: GenerateRule) => {
|
||||
</el-text>
|
||||
</template>
|
||||
<el-scrollbar :height="scrollbarHeight()">
|
||||
<div style="padding: 0 15px">
|
||||
<div style="padding: 0">
|
||||
<SupportMe></SupportMe>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
|
@ -93,7 +93,7 @@ const cardStyle = (password: Password) => {
|
||||
<template>
|
||||
<EmptyList v-if="!passwordStore.visPasswordArray.length"></EmptyList>
|
||||
<el-scrollbar
|
||||
height="calc(100vh - 90px)"
|
||||
height="calc(100vh - 85px)"
|
||||
v-if="passwordStore.visPasswordArray.length">
|
||||
<div
|
||||
style="display: grid;padding: 6px;"
|
||||
@ -138,7 +138,7 @@ const cardStyle = (password: Password) => {
|
||||
</li>
|
||||
<li v-if="password.address">
|
||||
<el-text class="password-field-name">地址:</el-text>
|
||||
<el-text class="password-field-value" style="max-width: 20vw">
|
||||
<el-text class="password-field-value">
|
||||
<el-link v-if="isUrl(password.address)" type="primary" :href="password.address" target="_blank">
|
||||
{{ password.address }}
|
||||
</el-link>
|
||||
|
@ -97,7 +97,7 @@ const tableRowStyle = (data: { row: any, rowIndex: number }): CSSProperties => {
|
||||
<el-table
|
||||
:data="passwordStore.visPasswordArray"
|
||||
ref="passwordTableRef"
|
||||
height="calc(100vh - 120px)"
|
||||
height="calc(100vh - 120px)"
|
||||
style="background-color: rgba(0,0,0,0);"
|
||||
:header-row-style="{'background-color':'rgba(0,0,0,0)'}"
|
||||
:header-cell-style="{'background-color':'rgba(0,0,0,0)'}"
|
||||
|
47
password-xl-web/src/components/login/AndroidLoginForm.vue
Normal file
47
password-xl-web/src/components/login/AndroidLoginForm.vue
Normal file
@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {usePasswordStore} from "@/stores/PasswordStore.ts";
|
||||
import {RespData} from "@/types";
|
||||
import {useRouter} from "vue-router";
|
||||
import {browserFingerprint, encryptAES} from "@/utils/security.ts";
|
||||
import {DatabaseForAndroid} from "@/database/DatabaseForAndroid.ts";
|
||||
|
||||
const passwordStore = usePasswordStore()
|
||||
const router = useRouter()
|
||||
|
||||
const useLocalLogin = async () => {
|
||||
console.log('android 点击登录')
|
||||
let database = new DatabaseForAndroid();
|
||||
|
||||
// 初始化密码管理器
|
||||
passwordStore.passwordManager.login(database).then((resp: RespData) => {
|
||||
if (!resp.status) {
|
||||
console.log('android 登录失败:', resp)
|
||||
ElNotification.error({title: '登录失败', message: resp.message})
|
||||
return
|
||||
}
|
||||
|
||||
// 使用浏览器指纹加密登录信息
|
||||
// 此刻想法:因为此时刚刚登录还没有主密码,因此将登录信息用浏览器指纹加密后保存在session中,如果存在pinia中页面一刷新就没了,用户体验不好
|
||||
let fingerprint = browserFingerprint()
|
||||
let loginForm = {loginType: 'android'}
|
||||
let ciphertext = encryptAES(fingerprint, JSON.stringify(loginForm));
|
||||
// 保存到sessionStorage
|
||||
sessionStorage.setItem('loginForm', ciphertext)
|
||||
|
||||
console.log('android 登录 跳转首页')
|
||||
router.push('/')
|
||||
}).catch(() => null)
|
||||
}
|
||||
defineExpose({
|
||||
useLocalLogin
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -1,20 +1,31 @@
|
||||
<!--登录类型选择组件-->
|
||||
<script setup lang="ts">
|
||||
import ElectronLoginForm from "@/components/login/ElectronLoginForm.vue";
|
||||
import AndroidLoginForm from "@/components/login/AndroidLoginForm.vue";
|
||||
|
||||
const electronLoginFormRef = ref()
|
||||
const androidLoginFormRef = ref()
|
||||
const emits = defineEmits(['loginTypeChange'])
|
||||
|
||||
const isElectron = () => {
|
||||
return window.env && window.env.electron
|
||||
return !!window.electronAPI
|
||||
}
|
||||
const isAndroid = () => {
|
||||
return !!window.androidAPI
|
||||
}
|
||||
|
||||
const electronStore = () => {
|
||||
console.log('使用本地存储electron')
|
||||
emits('loginTypeChange','electrons')
|
||||
emits('loginTypeChange','electron')
|
||||
electronLoginFormRef.value.useLocalLogin()
|
||||
}
|
||||
|
||||
const androidStore = () => {
|
||||
console.log('使用本地存储android')
|
||||
emits('loginTypeChange','android')
|
||||
androidLoginFormRef.value.useLocalLogin()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -45,7 +56,13 @@ const electronStore = () => {
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12" v-if="isElectron()">
|
||||
<div class="login-type-item electron" @click="electronStore">
|
||||
<div class="login-type-item local" @click="electronStore">
|
||||
<img alt="" src="../../assets/images/login/local.png">
|
||||
<div><el-text>本地存储</el-text></div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12" v-if="isAndroid()">
|
||||
<div class="login-type-item local" @click="androidStore">
|
||||
<img alt="" src="../../assets/images/login/local.png">
|
||||
<div><el-text>本地存储</el-text></div>
|
||||
</div>
|
||||
@ -60,6 +77,7 @@ const electronStore = () => {
|
||||
</el-col>
|
||||
</el-row>
|
||||
<ElectronLoginForm ref="electronLoginFormRef"></ElectronLoginForm>
|
||||
<AndroidLoginForm ref="androidLoginFormRef"></AndroidLoginForm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -137,12 +155,5 @@ const electronStore = () => {
|
||||
background: rgba(40, 193, 39, 0.4);
|
||||
box-shadow: 0 0 10px #bbb;
|
||||
}
|
||||
.login-type-item.electron {
|
||||
background: rgba(40, 193, 39, 0.3);
|
||||
}
|
||||
|
||||
.login-type-item.electron:hover {
|
||||
background: rgba(40, 193, 39, 0.4);
|
||||
box-shadow: 0 0 10px #bbb;
|
||||
}
|
||||
</style>
|
80
password-xl-web/src/database/DatabaseForAndroid.ts
Normal file
80
password-xl-web/src/database/DatabaseForAndroid.ts
Normal file
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* android存储引擎
|
||||
*/
|
||||
import {Database, RespData} from "@/types";
|
||||
|
||||
export class DatabaseForAndroid implements Database {
|
||||
|
||||
// 文件名称定义
|
||||
private fileNames = {
|
||||
store: 'store.json',
|
||||
setting: 'setting.json',
|
||||
}
|
||||
|
||||
// 登录并验证文件权限、初始化基本信息
|
||||
async login(_form: any): Promise<RespData> {
|
||||
console.log('使用android存储')
|
||||
return Promise.resolve({status: true})
|
||||
}
|
||||
|
||||
// 获取密码数据
|
||||
async getStoreData(): Promise<string> {
|
||||
return this.getFile(this.fileNames.store)
|
||||
}
|
||||
|
||||
// 设置密码数据
|
||||
async setStoreData(text: string) {
|
||||
return this.uploadFile(this.fileNames.store, text)
|
||||
}
|
||||
|
||||
// 删除密码数据
|
||||
async deleteStoreData() {
|
||||
return this.deleteFile(this.fileNames.store)
|
||||
}
|
||||
|
||||
// 获取设置
|
||||
async getSettingData(): Promise<string> {
|
||||
return this.getFile(this.fileNames.setting)
|
||||
}
|
||||
|
||||
// 更新设置
|
||||
async setSettingData(text: string) {
|
||||
return this.uploadFile(this.fileNames.setting, text)
|
||||
}
|
||||
|
||||
// 删除设置数据
|
||||
async deleteSettingData() {
|
||||
return this.deleteFile(this.fileNames.setting)
|
||||
}
|
||||
|
||||
// 获取android文件
|
||||
private async getFile(fileName: string): Promise<string> {
|
||||
console.log('获取android文件', fileName)
|
||||
return new Promise(async (resolve) => {
|
||||
let data = await window.androidAPI.getFile(fileName)
|
||||
if (data) {
|
||||
resolve(data);
|
||||
} else {
|
||||
resolve('')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 上传android文件
|
||||
private async uploadFile(fileName: string, content: string): Promise<RespData> {
|
||||
console.log('上传android文件', fileName)
|
||||
return new Promise(async (resolve) => {
|
||||
window.androidAPI.uploadFile(fileName, content)
|
||||
resolve({status: true})
|
||||
})
|
||||
}
|
||||
|
||||
// 删除android文件
|
||||
private async deleteFile(fileName: string): Promise<RespData> {
|
||||
console.log('删除android文件', fileName)
|
||||
return new Promise((resolve) => {
|
||||
window.androidAPI.deleteFile(fileName)
|
||||
resolve({status: true})
|
||||
})
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import {useSettingStore} from "@/stores/SettingStore.ts";
|
||||
import {useRefStore} from "@/stores/RefStore.ts";
|
||||
import {DatabaseForPrivate} from "@/database/DatabaseForPrivate.ts";
|
||||
import {DatabaseForElectron} from "@/database/DatabaseForElectron.ts";
|
||||
import {DatabaseForAndroid} from "@/database/DatabaseForAndroid.ts";
|
||||
|
||||
|
||||
export const useLoginStore = defineStore('loginStore', {
|
||||
@ -30,8 +31,10 @@ export const useLoginStore = defineStore('loginStore', {
|
||||
database = new DatabaseForCOS()
|
||||
} else if (loginForm.loginType === 'private') {
|
||||
database = new DatabaseForPrivate()
|
||||
} else if (loginForm.loginType === 'electron') {
|
||||
} else if (loginForm.loginType === 'electron') {
|
||||
database = new DatabaseForElectron()
|
||||
} else if (loginForm.loginType === 'android') {
|
||||
database = new DatabaseForAndroid()
|
||||
} else {
|
||||
console.error('未知的登录类型,无法自动登录:', loginForm.loginType)
|
||||
resolve(false)
|
||||
|
@ -90,7 +90,7 @@ export const usePasswordStore = defineStore('passwordStore', {
|
||||
return array
|
||||
},
|
||||
// 收藏的密码列表
|
||||
favoritePasswordArray(): Array<Password>{
|
||||
favoritePasswordArray(): Array<Password> {
|
||||
console.log('获取收藏的密码列表')
|
||||
let array = this.passwordArray.filter(p => p.favorite)
|
||||
.sort((a: Password, b: Password) => {
|
||||
@ -195,21 +195,18 @@ export const usePasswordStore = defineStore('passwordStore', {
|
||||
let isDarkTheme = window.matchMedia("(prefers-color-scheme: dark)")
|
||||
useDark().value = isDarkTheme.matches
|
||||
this.topicMode = isDarkTheme.matches ? TopicMode.DARK : TopicMode.LIGHT
|
||||
if (window.env && window.env.electron) {
|
||||
window.electronAPI.setTopic('system');
|
||||
}
|
||||
window.electronAPI?.setTopic('system');
|
||||
window.androidAPI?.setTopic('system');
|
||||
} else if (topic === TopicMode.DARK) {
|
||||
useDark().value = true
|
||||
this.topicMode = TopicMode.DARK
|
||||
if (window.env && window.env.electron) {
|
||||
window.electronAPI.setTopic(this.topicMode);
|
||||
}
|
||||
window.electronAPI?.setTopic(this.topicMode);
|
||||
window.androidAPI?.setTopic(this.topicMode);
|
||||
} else if (topic === TopicMode.LIGHT) {
|
||||
useDark().value = false
|
||||
this.topicMode = TopicMode.LIGHT
|
||||
if (window.env && window.env.electron) {
|
||||
window.electronAPI.setTopic(this.topicMode);
|
||||
}
|
||||
window.electronAPI?.setTopic(this.topicMode);
|
||||
window.androidAPI?.setTopic(this.topicMode);
|
||||
}
|
||||
},
|
||||
// 全局加载
|
||||
|
11
password-xl-web/src/types/types.d.ts
vendored
11
password-xl-web/src/types/types.d.ts
vendored
@ -31,13 +31,8 @@ interface FileSystemWritableFileStream extends WritableStream {
|
||||
close(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface Env {
|
||||
electron: true
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
env: Env;
|
||||
showOpenFilePicker: (options?: FilePickerOptions) => Promise<FileHandle[]>;
|
||||
showSaveFilePicker: (options?: SaveFilePickerOptions) => Promise<FileHandle>;
|
||||
electronAPI: {
|
||||
@ -46,5 +41,11 @@ declare global {
|
||||
deleteFile(fileName: string): Promise<RespData>;
|
||||
setTopic(topic: string): void;
|
||||
}
|
||||
androidAPI: {
|
||||
getFile(fileName: string): Promise<string>;
|
||||
uploadFile(fileName: string, content: string): Promise<RespData>;
|
||||
deleteFile(fileName: string): Promise<RespData>;
|
||||
setTopic(topic: string): void;
|
||||
}
|
||||
}
|
||||
}
|
@ -75,7 +75,7 @@ if (['xs', 'sm'].includes(displaySize().value) && settingStore.setting.passwordD
|
||||
</div>
|
||||
|
||||
<!-- 手机版 -->
|
||||
<div v-else style="backdrop-filter: blur(50px);height: calc(100vh - 70px)"
|
||||
<div v-else style="backdrop-filter: blur(50px);height: 100vh"
|
||||
:style="{'background-color': passwordStore.isDark?'rgba(0,0,0,0.4)':'rgba(255,255,255,0.4)'}">
|
||||
<!-- 密码表头 -->
|
||||
<PasswordHeader></PasswordHeader>
|
||||
|
@ -12,7 +12,7 @@ const loginStep = ref(1)
|
||||
const loginTypeChange = (type: string) => {
|
||||
console.log('登录,选择了登录方式:', type)
|
||||
loginStore.loginType = type;
|
||||
if (type === 'electron') {
|
||||
if (type === 'electron' || type === 'android') {
|
||||
return
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user