Files
yunzerwebsiteallinone/uniapp/pages/tools/account/edit.vue
T
2026-11-03 09:00:29 +08:00

400 lines
7.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page">
<!-- 平台信息 -->
<view class="section-card">
<view class="section-title">平台信息</view>
<view class="field">
<text class="field-label">平台名称 <text class="required">*</text></text>
<u-input
v-model="form.platform"
placeholder="如:淘宝、企业微信"
border="surround"
maxlength="50"
/>
</view>
<view class="field">
<text class="field-label">登录网址</text>
<u-input
v-model="form.url"
placeholder="如:https://login.taobao.com"
border="surround"
maxlength="255"
/>
</view>
<view class="field">
<text class="field-label">平台备注</text>
<u-textarea
v-model="form.remark"
placeholder="选填,如:工作用账号"
border="surround"
maxlength="255"
auto-height
/>
</view>
</view>
<!-- 挂载账号列表 -->
<view class="section-card">
<view class="section-head">
<text class="section-title">挂载账号列表</text>
<view class="add-acc-btn" @click="addAccount">
<FaIcon name="plus" color="#2563eb" :size="13" />
<text class="add-acc-text">添加账号</text>
</view>
</view>
<view v-for="(acc, idx) in form.accounts" :key="idx" class="acc-block">
<view class="acc-head">
<text class="acc-index">账号 {{ idx + 1 }}</text>
<view class="acc-del" @click="removeAccount(idx)">
<FaIcon name="trash-can" color="#ef4444" :size="14" />
<text class="acc-del-text">删除</text>
</view>
</view>
<view class="field">
<text class="field-label">账号 / 用户名</text>
<u-input
v-model="acc.username"
placeholder="登录账号或用户名"
border="surround"
maxlength="100"
/>
</view>
<view class="field">
<view class="field-label-row">
<text class="field-label">登录密码</text>
<view class="gen-btn" @click="genPassword(idx)">
<FaIcon name="rotate" color="#2563eb" :size="12" />
<text class="gen-text">随机生成</text>
</view>
</view>
<view class="pwd-row">
<u-input
v-model="acc.password"
:password="!shownPwd[idx]"
placeholder="登录密码"
border="surround"
maxlength="50"
/>
<view class="pwd-eye" @click="shownPwd[idx] = !shownPwd[idx]">
<FaIcon :name="shownPwd[idx] ? 'eye-slash' : 'eye'" color="#94a3b8" :size="16" />
</view>
</view>
</view>
<view class="field">
<text class="field-label">注册信息</text>
<u-textarea
v-model="acc.registration_info"
placeholder="选填,如:注册手机号、邮箱"
border="surround"
maxlength="255"
auto-height
/>
</view>
<view class="field">
<text class="field-label">账号备注</text>
<u-textarea
v-model="acc.remark"
placeholder="选填"
border="surround"
maxlength="255"
auto-height
/>
</view>
</view>
<view v-if="!form.accounts.length" class="acc-empty">
暂无账号,点击右上角「添加账号」
</view>
</view>
<!-- 保存 -->
<view class="footer">
<u-button
type="primary"
text="保存"
:loading="saving"
:disabled="saving"
shape="circle"
@click="onSubmit"
/>
<u-button
v-if="id"
type="error"
plain
text="删除记录"
shape="circle"
@click="onDelete"
/>
</view>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import {
getPasswordStore,
createPasswordStore,
updatePasswordStore,
deletePasswordStore
} from '@/api/passwordStore.js'
const id = ref(0)
const saving = ref(false)
const shownPwd = ref({})
const form = reactive({
platform: '',
url: '',
remark: '',
accounts: []
})
function blankAccount() {
return { username: '', password: '', registration_info: '', remark: '' }
}
function addAccount() {
form.accounts.push(blankAccount())
}
function removeAccount(idx) {
form.accounts.splice(idx, 1)
}
function genPassword(idx) {
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789!@#$%^&*'
let pwd = ''
for (let i = 0; i < 16; i++) {
pwd += chars.charAt(Math.floor(Math.random() * chars.length))
}
form.accounts[idx].password = pwd
shownPwd.value[idx] = true
uni.showToast({ title: '已生成随机密码', icon: 'none' })
}
async function loadDetail() {
uni.showLoading({ title: '加载中' })
try {
const data = await getPasswordStore(id.value)
form.platform = data.platform
form.url = data.url
form.remark = data.remark
form.accounts = data.accounts.length ? data.accounts : [blankAccount()]
} finally {
uni.hideLoading()
}
}
function validate() {
if (!form.platform.trim()) {
uni.showToast({ title: '请输入平台名称', icon: 'none' })
return false
}
return true
}
async function onSubmit() {
if (!validate()) return
saving.value = true
try {
const payload = {
platform: form.platform.trim(),
url: form.url.trim(),
remark: form.remark.trim(),
accounts: form.accounts.filter(
a => a.username || a.password || a.registration_info || a.remark
)
}
if (id.value) {
await updatePasswordStore(id.value, payload)
} else {
await createPasswordStore(payload)
}
uni.showToast({ title: '保存成功', icon: 'success' })
setTimeout(() => uni.navigateBack(), 400)
} catch (e) {
// 错误提示由 request 层统一处理
} finally {
saving.value = false
}
}
function onDelete() {
uni.showModal({
title: '删除记录',
content: '确定删除该平台及其全部账号?',
confirmText: '删除',
confirmColor: '#ef4444',
success: async res => {
if (!res.confirm) return
try {
await deletePasswordStore(id.value)
uni.showToast({ title: '已删除', icon: 'none' })
setTimeout(() => uni.navigateBack(), 400)
} catch (e) {
// 错误提示由 request 层统一处理
}
}
})
}
onLoad(options => {
if (options.id) {
id.value = Number(options.id)
uni.setNavigationBarTitle({ title: '编辑账号' })
loadDetail()
} else {
form.accounts.push(blankAccount())
}
})
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
padding: 24rpx 24rpx calc(60rpx + env(safe-area-inset-bottom));
background: #f8fafc;
}
.section-card {
background: #ffffff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 16rpx rgba(15, 23, 42, 0.05);
}
.section-head {
display: flex;
align-items: center;
justify-content: space-between;
}
.section-title {
font-size: 28rpx;
font-weight: 600;
color: #0f172a;
margin-bottom: 8rpx;
}
.add-acc-btn {
display: flex;
align-items: center;
gap: 6rpx;
padding: 10rpx 16rpx;
}
.add-acc-text {
font-size: 24rpx;
color: #2563eb;
}
.field {
margin-top: 24rpx;
}
.field-label {
display: block;
font-size: 26rpx;
color: #475569;
margin-bottom: 12rpx;
}
.required {
color: #ef4444;
}
.field-label-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12rpx;
}
.field-label-row .field-label {
margin-bottom: 0;
}
.gen-btn {
display: flex;
align-items: center;
gap: 6rpx;
padding: 6rpx 12rpx;
}
.gen-text {
font-size: 22rpx;
color: #2563eb;
}
.pwd-row {
display: flex;
align-items: center;
gap: 12rpx;
}
.pwd-row :deep(.u-input) {
flex: 1;
min-width: 0;
}
.pwd-eye {
padding: 10rpx;
flex-shrink: 0;
}
.acc-block {
margin-top: 24rpx;
padding: 20rpx;
background: #f8fafc;
border-radius: 16rpx;
}
.acc-head {
display: flex;
align-items: center;
justify-content: space-between;
}
.acc-index {
font-size: 26rpx;
font-weight: 600;
color: #0f172a;
}
.acc-del {
display: flex;
align-items: center;
gap: 6rpx;
padding: 6rpx 12rpx;
}
.acc-del-text {
font-size: 22rpx;
color: #ef4444;
}
.acc-empty {
padding: 32rpx 0;
text-align: center;
font-size: 24rpx;
color: #94a3b8;
}
.footer {
margin-top: 40rpx;
display: flex;
flex-direction: column;
gap: 20rpx;
}
</style>