init
This commit is contained in:
commit
2b8870a40e
51 changed files with 5845 additions and 0 deletions
196
pages/form/form.js
Normal file
196
pages/form/form.js
Normal 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
1
pages/form/form.json
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
82
pages/form/form.wxml
Normal file
82
pages/form/form.wxml
Normal 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
142
pages/form/form.wxss
Normal 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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue