This commit is contained in:
“xHuPo” 2025-06-09 13:35:15 +08:00
commit 2b8870a40e
51 changed files with 5845 additions and 0 deletions

196
pages/form/form.js Normal file
View file

@ -0,0 +1,196 @@
// pages/form/form.js
const {
addToken,
parseURL,
validateToken,
showToast
} = require('../../utils/util');
Page({
/**
* 页面的初始数据
*/
data: {
type: 'totp', // 默认选择TOTP
isSubmitting: false // 防止重复提交
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
// 准备初始数据
const initialData = {
type: options.type || 'totp',
formData: {
issuer: '',
remark: '',
secret: '',
algo: 'SHA1',
digits: '6',
period: '30',
counter: '0'
},
pageReady: true // 直接设置为ready状态
};
// 如果有扫描结果,预处理数据
if (options.scan) {
try {
const scanData = decodeURIComponent(options.scan);
const parsedToken = parseURL(scanData);
if (parsedToken) {
initialData.type = parsedToken.type;
initialData.formData = {
issuer: parsedToken.issuer || '',
remark: parsedToken.remark || '',
secret: parsedToken.secret || '',
algo: parsedToken.algo || 'SHA1',
digits: parsedToken.digits || '6',
period: parsedToken.period || '30',
counter: parsedToken.counter || '0'
};
// 立即显示成功提示
wx.showToast({
title: '二维码解析成功',
icon: 'success',
duration: 1500
});
}
} catch (error) {
console.error('解析扫描数据失败:', error);
wx.showToast({
title: '无效的二维码数据',
icon: 'none',
duration: 1500
});
}
}
// 一次性设置所有数据
this.setData(initialData);
},
/**
* 处理类型切换
*/
onTypeChange: function(e) {
this.setData({
type: e.detail.value
});
},
/**
* 验证并格式化表单数据
*/
validateAndFormatForm: function(values) {
try {
// 基本字段验证
if (!values.issuer || !values.issuer.trim()) {
throw new Error('请输入服务名称');
}
if (!values.remark || !values.remark.trim()) {
throw new Error('请输入账号备注');
}
if (!values.secret || !values.secret.trim()) {
throw new Error('请输入密钥');
}
// 格式化数据
const tokenData = {
type: values.type,
issuer: values.issuer.trim(),
remark: values.remark.trim(),
secret: values.secret.trim().toUpperCase(),
algo: values.algo,
digits: parseInt(values.digits, 10)
};
// 类型特定字段
if (values.type === 'totp') {
const period = parseInt(values.period, 10);
if (isNaN(period) || period < 15 || period > 300) {
throw new Error('更新周期必须在15-300秒之间');
}
tokenData.period = period;
} else {
const counter = parseInt(values.counter, 10);
if (isNaN(counter) || counter < 0) {
throw new Error('计数器初始值必须大于等于0');
}
tokenData.counter = counter;
}
// 使用工具函数验证完整的令牌数据
const errors = validateToken(tokenData);
if (errors) {
throw new Error(errors.join('\n'));
}
return tokenData;
} catch (error) {
showToast(error.message, 'none');
return null;
}
},
/**
* 提交数据
*/
keySubmit: async function (e) {
// 防止重复提交
if (this.data.isSubmitting) {
return;
}
this.setData({ isSubmitting: true });
try {
const values = e.detail.value;
const tokenData = this.validateAndFormatForm(values);
if (!tokenData) {
this.setData({ isSubmitting: false });
return;
}
// 添加令牌
await addToken(tokenData);
// 显示成功提示
wx.showToast({
title: '添加成功',
icon: 'success',
mask: true,
duration: 800
});
// 获取页面事件通道
const eventChannel = this.getOpenerEventChannel();
// 通知上一页面刷新数据
if (eventChannel && typeof eventChannel.emit === 'function') {
eventChannel.emit('tokenAdded');
}
// 立即跳转到首页
wx.switchTab({
url: '/pages/index/index'
});
} catch (error) {
console.error('添加令牌失败:', error);
wx.showToast({
title: error.message || '添加失败',
icon: 'none',
duration: 2000
});
} finally {
this.setData({ isSubmitting: false });
}
},
// 扫描功能已移至主页面
})

1
pages/form/form.json Normal file
View file

@ -0,0 +1 @@
{}

82
pages/form/form.wxml Normal file
View file

@ -0,0 +1,82 @@
<!--pages/form/form.wxml-->
<view class="container {{pageReady ? 'page-loaded' : ''}}">
<form bindsubmit="keySubmit">
<!-- 基本信息 -->
<view class="section">
<view class="section-title">基本信息</view>
<view class="input-box">
<text>Service</text>
<input name="issuer" placeholder="服务名称" value="{{formData.issuer}}" placeholder-style="color: #666; font-size: 1rem;" />
</view>
<view class="input-box">
<text>Account</text>
<input name="remark" placeholder="帐号备注" value="{{formData.remark}}" placeholder-style="color: #666; font-size: 1rem;" />
</view>
<view class="input-box">
<text>KEY</text>
<input name="secret" placeholder="Secret" value="{{formData.secret}}" placeholder-style="color: #666; font-size: 1rem;" />
</view>
</view>
<!-- 令牌类型 -->
<view class="section">
<view class="section-title">令牌类型</view>
<view class="input-box">
<text>类型</text>
<radio-group name="type" bindchange="onTypeChange">
<radio value="totp" checked="{{type === 'totp'}}">TOTP (基于时间)</radio>
<radio value="hotp" checked="{{type === 'hotp'}}">HOTP (基于计数器)</radio>
</radio-group>
</view>
</view>
<!-- 高级设置 -->
<view class="section">
<view class="section-title">高级设置</view>
<view class="input-box">
<text>算法</text>
<radio-group name="algo">
<radio value="SHA1" checked="{{!formData.algo || formData.algo === 'SHA1'}}">SHA1</radio>
<radio value="SHA256" checked="{{formData.algo === 'SHA256'}}">SHA256</radio>
<radio value="SHA512" checked="{{formData.algo === 'SHA512'}}">SHA512</radio>
</radio-group>
</view>
<view class="input-box">
<text>验证码位数</text>
<radio-group name="digits">
<radio value="6" checked="{{!formData.digits || formData.digits === '6'}}">6位</radio>
<radio value="7" checked="{{formData.digits === '7'}}">7位</radio>
<radio value="8" checked="{{formData.digits === '8'}}">8位</radio>
</radio-group>
</view>
<!-- TOTP特定设置 -->
<block wx:if="{{type === 'totp'}}">
<view class="input-box">
<text>更新周期(秒)</text>
<input type="number" name="period" value="{{formData.period || '30'}}" />
</view>
</block>
<!-- HOTP特定设置 -->
<block wx:if="{{type === 'hotp'}}">
<view class="input-box">
<text>初始计数器值</text>
<input type="number" name="counter" value="{{formData.counter || '0'}}" />
</view>
</block>
</view>
<!-- 操作按钮 -->
<view class="button-group">
<button
form-type="submit"
type="primary"
disabled="{{isSubmitting}}"
class="submit-button">
{{isSubmitting ? '添加中...' : '确认添加'}}
</button>
</view>
</form>
</view>

142
pages/form/form.wxss Normal file
View file

@ -0,0 +1,142 @@
/* pages/form/form.wxss */
page {
background: #f8f9fa;
padding-bottom: 40rpx;
transition: opacity 0.2s ease;
}
.container {
padding: 20rpx 0;
opacity: 1;
}
/* 页面加载动画 - 优化性能 */
@keyframes quickFadeIn {
from { opacity: 0.8; }
to { opacity: 1; }
}
.page-loaded {
animation: quickFadeIn 0.15s ease-out;
}
/* 区块样式 */
.section {
margin-bottom: 30rpx;
background: #ffffff;
border-radius: 12rpx;
padding: 24rpx;
margin: 0 20rpx 30rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.section-title {
color: #007AFF;
font-size: 32rpx;
font-weight: 500;
margin-bottom: 24rpx;
}
.input-box {
margin-top: 20rpx;
background: transparent;
border: none;
padding: 0;
}
.input-box text {
color: #333333;
font-size: 28rpx;
display: block;
margin-bottom: 12rpx;
}
.input-box input {
width: 100%;
height: 80rpx;
padding: 0 20rpx;
background: #ffffff;
border: 1rpx solid #e0e0e0;
border-radius: 8rpx;
font-size: 28rpx;
color: #333333;
box-sizing: border-box;
}
.input-box input:focus {
border-color: #007AFF;
}
/* 单选框组样式 */
.input-box radio-group {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
margin-top: 20rpx;
}
.input-box radio {
margin-right: 20rpx;
}
.input-box radio .wx-radio-input {
background: #ffffff !important;
border-color: #e0e0e0 !important;
width: 36rpx !important;
height: 36rpx !important;
}
.input-box radio .wx-radio-input.wx-radio-input-checked {
background: #007AFF !important;
border-color: #007AFF !important;
}
/* 按钮组样式 */
.button-group {
padding: 30rpx 20rpx;
display: flex;
justify-content: center;
}
.submit-button {
width: 100% !important;
height: 80rpx !important;
border-radius: 8rpx !important;
background: #007AFF !important;
color: #ffffff !important;
font-weight: 500 !important;
font-size: 28rpx !important;
border: none !important;
box-shadow: 0 4rpx 16rpx rgba(0, 122, 255, 0.3) !important;
}
.submit-button[disabled] {
background: #e0e0e0 !important;
color: #999999 !important;
opacity: 0.7 !important;
box-shadow: none !important;
}
/* 广告样式 */
.form-ad {
position: fixed;
bottom: 0;
width: 100%;
background: #f8f9fa;
padding: 20rpx;
box-sizing: border-box;
}
.form-ad ad {
margin: 0 auto;
border-radius: 8rpx;
overflow: hidden;
}
/* 错误提示样式 */
.error-text {
color: #ff4444;
font-size: 24rpx;
margin-top: 16rpx;
padding: 0 20rpx;
}