## 阻断性缺陷 - list.vue 是 0 字节空文件、slist.vue 与 search/search.vue 从未存在, 而前者是 tabBar 首页、后者是 tabBar「搜索」页 —— 开屏即白屏。 按 .nvue 原型与详情页契约重建三页(CSS 渐变主视觉、分类筛选、搜索历史/热搜/联想)。 - parse-image-url.js 对空封面调 undefined.startsWith 直接抛错,列表页整页崩。 - 云函数目录缺 uni-cms-articles / uni-cms-categories / uni-cms-unlock-record schema 与 schema.ext.js,线上内容渲染与解锁逻辑无配置可用。 ## 越权与数据一致性 - uni-cms-articles:del/update/add 全部无鉴权,未登录即可删任意文章、 改他人文章作者与阅读量。补 login + 作者归属校验,作者与计数改为服务端取值。 - comments.likeComment/relikeComment:直接采信客户端传入的 user_id, 可冒名点赞刷计数。改为以令牌为准,并纳入事务。 - comments.updateComment:对数组取 .author_id,权限判断恒失败; 字段名 updateTime 与 ip_location 类型与 schema 不符。 - comments.deleteComment:`!root_id === 0` 优先级错误导致计数恒不减; 且误更新 uni-cms-articles、按不存在的 type 字段删点赞明细产生孤儿数据。 - cms-articles-like/collect:查重条件混入本次请求时间戳,防重永远失效, 可无限重复刷计数;补唯一索引并事务化。 - cms-vote:读-改-写票数导致并发丢票,记录与统计非原子;改为事务 + 原子自增。 - cms-articles-log:忽略传入 user_id 直接返回全表,泄露全站浏览记录。 - article_info:get() 使用未定义变量必崩;读接口全部无鉴权。 - user-info:公开资料接口可查任意用户 last_login_ip。 ## 资源与数据 - 全项目清空失效的签名外链(expire_at 均为 2025-03,必然 403), 改为本地生成资源:6 套文章模板、8 个编辑器图标、2 张文章配图。 - 新增分类 / 模板 / 礼物 / 热搜词种子数据,并在 db_init.json 登记, 同时补上点赞、收藏、投票、浏览日志的唯一索引。 ## 功能 - 草稿箱:预览页拆出「发布」与「存为草稿」,作品列表按状态筛选并显示徽标。 原实现有 4 个 tab 但只有 1 个有内容,且 article_status 在 UI 上无体现。 - 编辑中断恢复:接上原本空实现的「编辑草稿」回调,区分新建与编辑已有文章。 ## 工具 - tools/audit-project.js:编码 / 页面路由 / 云调用 / 云函数鉴权 / 敏感信息检查 - tools/check-vue.js:SFC 脚本语法(词法扫描处理 import·export 与条件编译) - tools/verify.js:一键验证;两个检查器各带自测,防止"永远通过" - tools/gen-*.py:模板与图标资源生成脚本
106 lines
2.5 KiB
Vue
106 lines
2.5 KiB
Vue
<template>
|
|
<view class="box">
|
|
<!-- 登录框 (选择手机号所属国家和地区需要另行实现) -->
|
|
<uni-easyinput clearable focus type="number" class="input-box" :inputBorder="false" v-model="formData.phone"
|
|
maxlength="11" placeholder="请输入手机号"></uni-easyinput>
|
|
<uni-easyinput clearable type="number" class="input-box" :inputBorder="false" v-model="formData.code" maxlength="6"
|
|
placeholder="请输入验证码">
|
|
<template slot="right">
|
|
<qk-send-sms-code ref="shortCode" scene="bind-mobile-by-sms" :phone="formData.phone"></qk-send-sms-code>
|
|
</template>
|
|
</uni-easyinput>
|
|
<button class="send-btn-box" type="primary" @click="submit">绑定</button>
|
|
</view>
|
|
</template>
|
|
<script>
|
|
import {
|
|
mapMutations,
|
|
mapGetters
|
|
} from 'vuex';
|
|
export default {
|
|
data() {
|
|
return {
|
|
params: {},
|
|
currenPhoneArea: '',
|
|
formData: {
|
|
phone:"",
|
|
code:""
|
|
}
|
|
}
|
|
},
|
|
onLoad (event) {
|
|
let params = event;
|
|
if (params.mobile) {
|
|
this.formData.phone = params.mobile
|
|
}
|
|
},
|
|
computed: {
|
|
tipText() {
|
|
return `验证码已通过短信发送至${this.currenPhoneArea} ${this.formData.phone}。密码为6 - 20位`
|
|
},
|
|
canSubmit() {
|
|
return this.isPhone && this.isPwd && this.isCode;
|
|
}
|
|
},
|
|
methods: {
|
|
...mapMutations({
|
|
setUserInfo: 'user/UPDATE_USER_INFO',
|
|
}),
|
|
/**
|
|
* 完成并提交
|
|
*/
|
|
async submit() {
|
|
if (!/^1\d{10}$/.test(this.formData.phone)) {
|
|
return uni.showToast({ title: '手机号格式错误', icon: 'none' });
|
|
}
|
|
if (!this.formData.code) {
|
|
return uni.showToast({ title: '请输入验证码', icon: 'none' });
|
|
}
|
|
try {
|
|
const uniIdCo = uniCloud.importObject('uni-id-co')
|
|
await uniIdCo.bindMobileBySms({
|
|
mobile: this.formData.phone,
|
|
code: this.formData.code
|
|
});
|
|
this.setUserInfo({ mobile: this.formData.phone })
|
|
uni.showToast({ title: '绑定成功', icon: 'none' });
|
|
setTimeout(() => uni.navigateBack(), 800);
|
|
} catch (e) {
|
|
console.error('绑定失败:', e);
|
|
uni.showToast({
|
|
title: e.errMsg || '绑定失败,请稍后重试',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.box {
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 50rpx;
|
|
padding-top: 10px;
|
|
}
|
|
|
|
.box :deep(.uni-easyinput__content) {
|
|
height: 50px;
|
|
}
|
|
|
|
.input-box {
|
|
width: 100%;
|
|
margin-top: 16px;
|
|
background-color: #f9f9f9;
|
|
border-radius: 6rpx;
|
|
flex-direction: row;
|
|
flex-wrap: nowrap;
|
|
}
|
|
|
|
.send-btn-box {
|
|
width: 650rpx;
|
|
margin-top: 15px;
|
|
}
|
|
</style>
|