## 阻断性缺陷 - 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:模板与图标资源生成脚本
405 lines
12 KiB
Vue
405 lines
12 KiB
Vue
<template>
|
||
<view>
|
||
<view class="v-preview">
|
||
<image :src="templateSrc" alt="" class="img" mode="widthFix" />
|
||
<view class="btn-area">
|
||
<view class="red-btn-area">
|
||
<button class="red-btn">老兵直播</button>
|
||
<button class="red-btn">老兵故事</button>
|
||
<button class="red-btn">在线注册</button>
|
||
</view>
|
||
<view class="red-btn-plain-area">
|
||
<view class="red-btn-plain" @click="toIndex()">新四军</view>
|
||
<view class="red-btn-plain">抗美援朝</view>
|
||
<view class="red-btn-plain">抗美援越</view>
|
||
<view class="red-btn-plain">对越自卫反击战</view>
|
||
<view class="red-btn-plain">和平时期退役兵</view>
|
||
</view>
|
||
</view>
|
||
|
||
</view>
|
||
<view style="margin-left: 50rpx;margin-right: 50rpx;">
|
||
<view class="example-body">
|
||
<view class="" style="text-align: center;width:70%;font-size: 32rpx;color: #e33;font-weight: bold">
|
||
鱼水情
|
||
</view>
|
||
<view class=""
|
||
style="text-align: left;font-weight: bold;color: #e33;font-size: 20rpx;line-height: 36rpx;">
|
||
张建群
|
||
</view>
|
||
</view>
|
||
|
||
<view class="tab">
|
||
住在大城市里,"军民鱼水情"只是句日常的用语,并没有真正切实的体会。
|
||
</view>
|
||
<image
|
||
src="/static/article/wz1.png"
|
||
mode=""></image>
|
||
<view class="tab">
|
||
1952年的冬天,我团接到上级命令,前往朝鲜战场。这兴奋劲像个麻雀,跳跃不停。经过战前培训,我们坐着向北驰去的列车。一路上歌声不断,当列车到达山东济南时,站台上摆着热气腾腾的饭菜,我们狼吞虎咽地吃着可口的饭菜,这比在列车上吃饼干要好百倍了。经过一天多的劳累,在大年除夕的那天,我们到达了安东。快速转移到一个街坊后,多数人都安排好了住所,可我们班还剩六名战士没法安排,连长还在为难。
|
||
</view>
|
||
<view class="tab">
|
||
这时,一一间小屋走出了一对年轻夫妇,他们说:"志愿军同志,不要在外面过夜,零下三十度是冻不起的。我俩今晚到她娘家去,腾出这间屋,给志愿军战士住。"这是一对当天结婚的新婚夫妇,是第一夜,人生能有几个第一夜呢?一辈子,不就这一个吗?
|
||
|
||
</view>
|
||
<image
|
||
src="/static/article/wz2.png"
|
||
mode=""></image>
|
||
<view class="tab">
|
||
我们住进小屋,六个人挤在一张床上睡觉,可怎么都睡不着。平时说的"军民鱼水情",今天真的出现了吗?老乡说:"志愿军出国作战,到了安东,哪能汽车16团汽车修理工叫他们在野外露营?我俩到我娘那里住一张建群宿就行。"这几句朴实无华的话,真是"鱼水情"的真实体验。
|
||
|
||
</view>
|
||
<view class="tab">
|
||
通过这一事件,我真心体会到,人民军队爱人民,人民爱护子弟兵的"鱼水情"。
|
||
</view>
|
||
</view>
|
||
<view class="" style="font-size: 20rpx;color: #999;margin-left: 50rpx;margin-top: 20rpx;">
|
||
更新于 2024-12-26
|
||
</view>
|
||
|
||
<view class="example-body">
|
||
<button>
|
||
<svg t="1739442625419" class="icon" viewBox="0 0 1024 1024" version="1.1"
|
||
xmlns="http://www.w3.org/2000/svg" p-id="1476" width="16" height="16">
|
||
<path
|
||
d="M109.226667 798.72c-20.48 0-35.84 17.066667-35.84 37.546667 0 20.48 17.066667 35.84 37.546666 35.84 3.413333 0 157.013333 1.706667 252.586667 107.52 6.826667 8.533333 17.066667 11.946667 27.306667 11.946666 8.533333 0 17.066667-3.413333 23.893333-10.24 15.36-13.653333 17.066667-35.84 3.413333-51.2-119.466667-131.413333-302.08-131.413333-308.906666-131.413333zM914.773333 798.72c-6.826667 0-186.026667 0-307.2 131.413333-13.653333 15.36-13.653333 37.546667 1.706667 51.2 6.826667 6.826667 15.36 10.24 25.6 10.24s20.48-3.413333 27.306667-11.946666c98.986667-107.52 250.88-107.52 252.586666-107.52 20.48 0 35.84-15.36 37.546667-35.84 0-20.48-17.066667-37.546667-37.546667-37.546667z"
|
||
fill="#d81e06" p-id="1477"></path>
|
||
<path
|
||
d="M877.226667 600.746667c78.506667-133.12 97.28-307.2 44.373333-431.786667-5.12-13.653333-18.773333-22.186667-32.426667-22.186667h-11.946666c-71.68 0-138.24 13.653333-197.973334 40.96-42.666667-80.213333-90.453333-138.24-145.066666-177.493333-1.706667-1.706667-3.413333-3.413333-5.12-3.413333-10.24-6.826667-23.893333-6.826667-34.133334-3.413334-3.413333 1.706667-6.826667 3.413333-10.24 6.826667-54.613333 39.253333-102.4 98.986667-145.066666 177.493333-59.733333-25.6-124.586667-39.253333-194.56-39.253333h-10.24c-15.36-1.706667-27.306667 8.533333-34.133334 20.48-51.2 124.586667-34.133333 296.96 44.373334 431.786667 68.266667 116.053333 172.373333 187.733333 296.96 203.093333 3.413333 1.706667 6.826667 1.706667 10.24 1.706667h20.48v180.906666c0 20.48 17.066667 37.546667 37.546666 37.546667 20.48 0 37.546667-17.066667 37.546667-37.546667V805.546667h22.186667c3.413333 0 6.826667 0 10.24-1.706667 124.586667-15.36 228.693333-87.04 296.96-203.093333z"
|
||
fill="#d81e06" p-id="1478"></path>
|
||
</svg>
|
||
送花
|
||
</button>
|
||
<button>
|
||
<uni-icons type="gift" size="16">
|
||
|
||
</uni-icons>
|
||
打赏
|
||
</button>
|
||
|
||
</view>
|
||
<view class="" style="text-align: center;
|
||
color:3ee;
|
||
margin-top: 20rpx;
|
||
padding-bottom: 50rpx;
|
||
color: #555;
|
||
">
|
||
———<text style="color: #3ee;">{{ " " }}4 朵花{{ " " }}</text>———
|
||
</view>
|
||
<view class="">
|
||
投诉 | 阅读 3112
|
||
|
||
</view>
|
||
<view class="example-body" style="justify-content:center">
|
||
<image
|
||
src="/static/center/avatar.png"
|
||
style="width: 64rpx;height: 64rpx;border-radius: 50%;"></image>
|
||
<image
|
||
src="/static/center/avatar.png"
|
||
style="width: 64rpx;height: 64rpx;border-radius: 50%;"></image>
|
||
<image
|
||
src="/static/center/avatar.png"
|
||
style="width: 64rpx;height: 64rpx;border-radius: 50%;"></image>
|
||
<image
|
||
src="/static/center/avatar.png"
|
||
style="width: 64rpx;height: 64rpx;border-radius: 50%;"></image>
|
||
<image
|
||
src="/static/center/avatar.png"
|
||
style="width: 64rpx;height: 64rpx;border-radius: 50%;"></image>
|
||
<image
|
||
src="/static/center/avatar.png"
|
||
style="width: 64rpx;height: 64rpx;border-radius: 50%;"></image>
|
||
<image
|
||
src="/static/center/avatar.png"
|
||
style="width: 64rpx;height: 64rpx;border-radius: 50%;"></image>
|
||
</view>
|
||
<view class="example-body" style="justify-content:center">
|
||
<image
|
||
src="/static/center/avatar.png"
|
||
style="width: 64rpx;height: 64rpx;border-radius: 50%;"></image>
|
||
<image
|
||
src="/static/center/avatar.png"
|
||
style="width: 64rpx;height: 64rpx;border-radius: 50%;"></image>
|
||
<image
|
||
src="/static/center/avatar.png"
|
||
style="width: 64rpx;height: 64rpx;border-radius: 50%;"></image>
|
||
<image
|
||
src="/static/center/avatar.png"
|
||
style="width: 64rpx;height: 64rpx;border-radius: 50%;"></image>
|
||
<image
|
||
src="/static/center/avatar.png"
|
||
style="width: 64rpx;height: 64rpx;border-radius: 50%;"></image>
|
||
<image
|
||
src="/static/center/avatar.png"
|
||
style="width: 64rpx;height: 64rpx;border-radius: 50%;"></image>
|
||
<image
|
||
src="/static/center/avatar.png"
|
||
style="width: 64rpx;height: 64rpx;border-radius: 50%;"></image>
|
||
</view>
|
||
<view class="example-body" style="justify-content:center">
|
||
<image
|
||
src="/static/center/avatar.png"
|
||
style="width: 64rpx;height: 64rpx;border-radius: 50%;"></image>
|
||
<image
|
||
src="/static/center/avatar.png"
|
||
style="width: 64rpx;height: 64rpx;border-radius: 50%;"></image>
|
||
<image
|
||
src="/static/center/avatar.png"
|
||
style="width: 64rpx;height: 64rpx;border-radius: 50%;"></image>
|
||
<image
|
||
src="/static/center/avatar.png"
|
||
style="width: 64rpx;height: 64rpx;border-radius: 50%;"></image>
|
||
<image
|
||
src="/static/center/avatar.png"
|
||
style="width: 64rpx;height: 64rpx;border-radius: 50%;"></image>
|
||
<image
|
||
src="/static/center/avatar.png"
|
||
style="width: 64rpx;height: 64rpx;border-radius: 50%;"></image>
|
||
<image
|
||
src="/static/center/avatar.png"
|
||
style="width: 64rpx;height: 64rpx;border-radius: 50%;"></image>
|
||
</view>
|
||
|
||
<view class="comment-eg">
|
||
<hb-comment ref="hbComment" @add="add" @del="del" @like="like" @focusOn="focusOn" :deleteTip="'确认删除?'"
|
||
:cmData="commentData" v-if="commentData"></hb-comment>
|
||
</view>
|
||
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { ref } from 'vue'
|
||
import { onShow } from '@dcloudio/uni-app'
|
||
|
||
export default {
|
||
setup() {
|
||
const hbComment = ref(null)
|
||
const avatar = ref("")
|
||
const templateSrc = ref("/static/template/default.png")
|
||
const commentData = ref(null)
|
||
const reqFlag = ref(false)
|
||
|
||
onShow(() => {
|
||
getComment()
|
||
})
|
||
|
||
function checkLogin() {
|
||
if (true) {
|
||
return true
|
||
} else {
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '请先登录',
|
||
confirmText: '前往登录',
|
||
success: function(res) {
|
||
if (res.confirm) {
|
||
uni.redirectTo({
|
||
url: '/pages3/uni_modules/uni-id-pages/pages/login/login-withoutpwd'
|
||
})
|
||
}
|
||
}
|
||
})
|
||
return false
|
||
}
|
||
}
|
||
|
||
function focusOn() {
|
||
checkLogin()
|
||
}
|
||
|
||
function getComment() {
|
||
let res = {
|
||
"readNumer": 193,
|
||
"commentList": []
|
||
}
|
||
commentData.value = {
|
||
"readNumer": res.readNumer,
|
||
"commentSize": res.commentList.length,
|
||
"comment": getTree(res.commentList)
|
||
}
|
||
}
|
||
|
||
function add(req) {
|
||
if (!checkLogin()) return
|
||
if (reqFlag.value) {
|
||
uni.showToast({
|
||
title: '操作频繁',
|
||
duration: 1000
|
||
})
|
||
return
|
||
}
|
||
reqFlag.value = true
|
||
reqFlag.value = false
|
||
hbComment.value.addComplete()
|
||
getComment()
|
||
}
|
||
|
||
function like(commentId) {
|
||
if (!checkLogin()) return
|
||
if (reqFlag.value) {
|
||
uni.showToast({
|
||
title: '操作频繁',
|
||
duration: 1000
|
||
})
|
||
return
|
||
}
|
||
reqFlag.value = true
|
||
reqFlag.value = false
|
||
hbComment.value.likeComplete(commentId)
|
||
}
|
||
|
||
function del(commentId) {
|
||
if (!checkLogin()) return
|
||
if (reqFlag.value) {
|
||
uni.showToast({
|
||
title: '操作频繁',
|
||
duration: 1000
|
||
})
|
||
return
|
||
}
|
||
reqFlag.value = true
|
||
reqFlag.value = false
|
||
hbComment.value.deleteComplete(commentId)
|
||
}
|
||
|
||
function getTree(data) {
|
||
let result = []
|
||
let map = {}
|
||
data.forEach(item => {
|
||
map[item.id] = item
|
||
})
|
||
data.forEach(item => {
|
||
let parent = map[item.parentId]
|
||
if (parent) {
|
||
(parent.children || (parent.children = [])).push(item)
|
||
} else {
|
||
result.push(item)
|
||
}
|
||
})
|
||
return result
|
||
}
|
||
|
||
function toIndex() {
|
||
uni.switchTab({
|
||
url: '/uni_modules/uni-cms-article/pages/list/list'
|
||
})
|
||
}
|
||
|
||
return {
|
||
hbComment,
|
||
avatar,
|
||
templateSrc,
|
||
commentData,
|
||
reqFlag,
|
||
checkLogin,
|
||
focusOn,
|
||
getComment,
|
||
add,
|
||
like,
|
||
del,
|
||
getTree,
|
||
toIndex
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.example-body {
|
||
/* #ifndef APP-PLUS-NVUE */
|
||
display: flex;
|
||
/* #endif */
|
||
flex-direction: row;
|
||
justify-content: flex-start;
|
||
/* align-items: flex-end; */
|
||
/* flex-wrap: wrap; */
|
||
padding: 10rpx;
|
||
}
|
||
|
||
.btn {
|
||
text-align: center;
|
||
color: #fff;
|
||
padding: 20rpx;
|
||
margin: 20rpx;
|
||
border-radius: 20rpx;
|
||
background-color: #2979ff;
|
||
}
|
||
|
||
.tab {
|
||
text-indent: 2rem;
|
||
}
|
||
|
||
.v-preview {
|
||
.img {
|
||
width: 100%;
|
||
}
|
||
|
||
.btn-area {
|
||
position: relative;
|
||
top: -50rpx;
|
||
}
|
||
|
||
.red-btn-area {
|
||
display: flex;
|
||
justify-content: center;
|
||
padding: 10rpx 0;
|
||
margin: 0 40rpx 20rpx 40rpx;
|
||
font-size: 20rpx;
|
||
|
||
.red-btn {
|
||
position: relative;
|
||
color: #fff;
|
||
font-weight: 700;
|
||
font-size: 24rpx;
|
||
height: 40rpx;
|
||
line-height: 40rpx;
|
||
margin: 0;
|
||
background-image: linear-gradient(to bottom, #c11c1f, #5c0201);
|
||
|
||
+.red-btn {
|
||
margin-left: 20rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.red-btn-plain-area {
|
||
display: flex;
|
||
justify-content: center;
|
||
padding: 10rpx 0;
|
||
margin: 0 40rpx;
|
||
font-size: 21rpx;
|
||
border-top: 2px solid;
|
||
border-bottom: 2px solid;
|
||
border-image: linear-gradient(to right,
|
||
#fff,
|
||
#f44336bf,
|
||
#b13636,
|
||
#f44336bf,
|
||
#fff) 1;
|
||
|
||
.red-btn-plain {
|
||
position: relative;
|
||
color: #b13636;
|
||
font-weight: 700;
|
||
padding-left: 15rpx;
|
||
|
||
+.red-btn-plain {
|
||
margin-left: 10rpx;
|
||
|
||
&::before {
|
||
content: "";
|
||
position: absolute;
|
||
left: 0;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 2px;
|
||
height: 20rpx;
|
||
background-color: #b13636;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|