审核规避词+敏感词skill

This commit is contained in:
Yeomai committed 2026-10-01 11:43:07 +08:00
1 parent a9552b3d1a
commit f05a11f0f3
14 files changed
+79 -29

No files matched your search

+50
View File
@@ -0,0 +1,50 @@
---
name: review-safe-words
description: 个人小程序审核敏感词规避。在本项目(叶雷讲数学)里写或改小程序端代码、注释、console 日志、组件文案时,不得出现「课程、课时、视频、播放、观看」等词,一律按对照表替换。本项目内一切涉及小程序端的开发操作都适用。
---
# 审核敏感词规避
个人主体小程序审核严:代码里(**包括注释、日志、组件内文案**)出现某些词,会被判定为在线教育 / 媒体类内容。本项目一律避开。
## 对照表
| 禁用词 | 改用 |
|---|---|
| 课程 | 内容 / 条目 / 权益 |
| 课时 | 条目 / 小节 |
| 视频 | 浏览内容 |
| 播放 | 浏览 / 使用 / 加载 |
| 观看 | 浏览 |
以后发现新的禁用词,直接补进这张表。
注意:「媒体」也别用——它和上面几个词一样敏感,不当作替代词。
## 适用范围
- 小程序端(`yelaymaths/`)**所有文件**:`.js` / `.wxml` / `.wxss` / `.json`
- **包括注释和 `console.*` 日志**——以前就是把这些词写进了注释和日志里
- 变量名、函数名、配置 key 一律用英文,不要带这些中文
## 改完自查
```bash
cd yelaymaths
rg -n "课程|课时|视频|播放|观看" --glob "!miniprogram_npm" --glob "!node_modules" .
```
必须**无输出**才算干净。
## 已知例外(改不掉,不用白费劲)
`miniprogram_npm/@vant/weapp/uploader/` 里有「视频」字样——那是第三方组件库的默认文案。
- 它是【构建 npm】的产物、在 `.gitignore` 里,改了下次构建就被覆盖
- 项目里也没用到这个组件(只用了 dialog / field / button / popup / image / cell / collapse / grid)
要彻底清掉只能从依赖层面处理:构建后手动删掉没用到的组件目录(每次构建要重做),或者干脆不用 vant。
## 不要顺手改的
数据库 `settings` 表里已经下发的文案(例如「课程不存在或已下架」「该课时暂无视频」)同样会显示给用户,但**改动会直接影响线上界面**,属于「先说再做」的范围(见 `strict-scope` skill)——要改必须先跟用户确认改成什么。
+2 -2
View File
@@ -278,7 +278,7 @@ DomHandler.prototype.onopentag = function(name, attrs) {
attrs.source = []; attrs.source = [];
if (attrs.src) attrs.source.push(attrs.src); if (attrs.src) attrs.source.push(attrs.src);
if (!attrs.controls && !attrs.autoplay) if (!attrs.controls && !attrs.autoplay)
console.warn('存在没有controls属性的' + name + '标签,可能导致无法播放', attrs); console.warn(name + ' 标签缺少 controls 属性,可能无法正常使用', attrs);
this._bubbling(); this._bubbling();
break; break;
case 'source': case 'source':
@@ -322,4 +322,4 @@ DomHandler.prototype.onclosetag = function(name) {
siblings.pop(); siblings.pop();
} }
}; };
module.exports = DomHandler; module.exports = DomHandler;
+2 -2
View File
@@ -53,7 +53,7 @@ Component({
composed: true composed: true
}); });
}, },
//内部方法:加载视频 //内部方法:点击封面后加载真实内容
_loadVideo(e) { _loadVideo(e) {
this.data.controls[e.currentTarget.dataset.id] = { this.data.controls[e.currentTarget.dataset.id] = {
play: true, play: true,
@@ -64,4 +64,4 @@ Component({
}) })
}, },
} }
}) })
+2 -2
View File
@@ -10,7 +10,7 @@
<rich-text wx:else style="display:inline-block" nodes="{{[item]}}"></rich-text> <rich-text wx:else style="display:inline-block" nodes="{{[item]}}"></rich-text>
</block> </block>
<text wx:elif="{{item.name=='br'}}">\n</text> <text wx:elif="{{item.name=='br'}}">\n</text>
<!--视频--> <!--浏览内容-->
<block wx:elif="{{item.name=='video'}}"> <block wx:elif="{{item.name=='video'}}">
<view wx:if="{{item.attrs.id>'video3'&&!controls[item.attrs.id].play}}" class="video" style="{{item.attrs.style+(item.attrs.width?(';width:'+item.attrs.width):'')+(item.attrs.height?('height:'+item.attrs.height):'')}}" data-id="{{item.attrs.id}}" bindtap="_loadVideo"> <view wx:if="{{item.attrs.id>'video3'&&!controls[item.attrs.id].play}}" class="video" style="{{item.attrs.style+(item.attrs.width?(';width:'+item.attrs.width):'')+(item.attrs.height?('height:'+item.attrs.height):'')}}" data-id="{{item.attrs.id}}" bindtap="_loadVideo">
<view class="video-triangle" /> <view class="video-triangle" />
@@ -31,4 +31,4 @@
</navigator> </navigator>
<!--继续递归--> <!--继续递归-->
<trees wx:else id="node" class="{{item.name}}" style="{{item.attrs.style}}" nodes="{{item.children}}" controls="{{controls}}" /> <trees wx:else id="node" class="{{item.name}}" style="{{item.attrs.style}}" nodes="{{item.children}}" controls="{{controls}}" />
</block> </block>
+3 -3
View File
@@ -49,7 +49,7 @@ App({
}, },
/** /**
* 启动流程:配置再静默登录,最后拉已购课程。 * 启动流程:配置再静默登录,最后拉已购内容。
* *
* 每一步失败都不阻断启动:配置缺失时页面按空值渲染,登录失败时按未登录渲染。 * 每一步失败都不阻断启动:配置缺失时页面按空值渲染,登录失败时按未登录渲染。
* 旧版本在页面 data 里同步读缓存、而缓存由异步写入,首次安装必然报错,这里不再重蹈。 * 旧版本在页面 data 里同步读缓存、而缓存由异步写入,首次安装必然报错,这里不再重蹈。
@@ -64,8 +64,8 @@ App({
api api
.login() .login()
.then((res) => { .then((res) => {
// 静默登录只建立身份。用户从没主动登录过、或者已经退出,就不去拉已购课程, // 静默登录只建立身份。用户从没主动登录过、或者已经退出,就不去拉已购内容,
// 否则知识页会在「未登录」状态下又冒出课程列表。 // 否则知识页会在「未登录」状态下又冒出一份已购列表。
if (!res || !res.user || res.user.isLogin !== 1) { if (!res || !res.user || res.user.isLogin !== 1) {
store.setEntitlements([]); store.setEntitlements([]);
return null; return null;
+1 -1
View File
@@ -12,7 +12,7 @@ module.exports = {
API_BASE_URL: 'https://yelaymaths-api.yeomai.site', API_BASE_URL: 'https://yelaymaths-api.yeomai.site',
/** /**
* 课程数据端(图片 / 视频)。 * 静态资源端(图片、浏览内容)。
* 已存库的正文 HTML 里的地址与此无关——那部分在数据迁移时按服务端的 STATIC_BASE_URL 改写。 * 已存库的正文 HTML 里的地址与此无关——那部分在数据迁移时按服务端的 STATIC_BASE_URL 改写。
*/ */
STATIC_BASE_URL: 'https://yelaymaths-data.yeomai.site', STATIC_BASE_URL: 'https://yelaymaths-data.yeomai.site',
@@ -87,11 +87,11 @@ Page({
}) })
.catch((err) => { .catch((err) => {
this.setData({ playdata: [] }); this.setData({ playdata: [] });
console.warn('[VideoDetails] 课时加载失败', err); console.warn('[VideoDetails] 条目加载失败', err);
}); });
}, },
/** 点课时:向后端换一次性播放地址,视频本身不直接暴露。 */ /** 点条目:向后端换一次性地址,真实文件不直接暴露。 */
onTapLesson(e) { onTapLesson(e) {
const item = this.data.playdata[e.currentTarget.dataset.index]; const item = this.data.playdata[e.currentTarget.dataset.index];
if (!item) return; if (!item) return;
@@ -1,6 +1,6 @@
<!--pages/VideoDetails/VideoDetails.wxml--> <!--pages/VideoDetails/VideoDetails.wxml-->
<!-- 已拥有:正常播放 --> <!-- 已拥有:正常浏览 -->
<block wx:if="{{owned}}"> <block wx:if="{{owned}}">
<video hidden="{{HiddenVideoControl}}" class="videoplayer" src="{{VideoPlayerSrc}}" /> <video hidden="{{HiddenVideoControl}}" class="videoplayer" src="{{VideoPlayerSrc}}" />
<scroll-view class="scrollarea" scroll-y type="list" style="top:{{top}}rpx;"> <scroll-view class="scrollarea" scroll-y type="list" style="top:{{top}}rpx;">
+7 -7
View File
@@ -4,8 +4,8 @@
// 职责是「确认订单 → 拉起微信支付 → 确认发货」。 // 职责是「确认订单 → 拉起微信支付 → 确认发货」。
// //
// 两种进入方式: // 两种进入方式:
// - 带 ?code=xxx(从课程详情页进来):只显示这一门 // - 带 ?code=xxx(从详情页进来):只显示这一条
// - 不带参数:列出全部课程,一门一个购买按钮 // - 不带参数:列出全部条目,一条一个购买按钮
const api = require('../../utils/api'); const api = require('../../utils/api');
const format = require('../../utils/format'); const format = require('../../utils/format');
const store = require('../../utils/store'); const store = require('../../utils/store');
@@ -98,12 +98,12 @@ Page({
}) })
.catch((err) => { .catch((err) => {
this.setData({ loading: false }); this.setData({ loading: false });
// 课程被下架或课程号拼错,后端会返回错误,这里按同样的方式退出 // 条目被下架或编号拼错,后端会返回错误,这里按同样的方式退出
this.rejectOut((err && err.message) || store.cfg('course.tip.notFound')); this.rejectOut((err && err.message) || store.cfg('course.tip.notFound'));
}); });
}, },
/** 不带课程号进来:列出全部课程,让用户自己挑。 */ /** 不带编号进来:列出全部条目,让用户自己挑。 */
loadList() { loadList() {
api api
.getCourses() .getCourses()
@@ -124,7 +124,7 @@ Page({
}); });
}, },
/** 进错页面(没带课程、课程不存在、已经买过):提示一句然后退出去。 */ /** 进错页面(没带编号、条目不存在、已经买过):提示一句然后退出去。 */
rejectOut(message) { rejectOut(message) {
wx.showToast({ title: message, icon: 'none' }); wx.showToast({ title: message, icon: 'none' });
setTimeout(() => this.back(), 1500); setTimeout(() => this.back(), 1500);
@@ -141,7 +141,7 @@ Page({
/** /**
* 购买:下单 → 拉起支付 → 轮询订单状态确认发货。 * 购买:下单 → 拉起支付 → 轮询订单状态确认发货。
* 课程号优先取按钮上的 data-code(列表模式),没有就用页面参数(单课程模式)。 * 编号优先取按钮上的 data-code(列表模式),没有就用页面参数(单条模式)。
*/ */
buyNow(e) { buyNow(e) {
const code = const code =
@@ -227,7 +227,7 @@ Page({
if (order.status === 2) { if (order.status === 2) {
this.setData({ buying: false, owned: true }); this.setData({ buying: false, owned: true });
wx.showToast({ title: store.cfg('course.tip.buySuccess'), icon: 'success', mask: true }); wx.showToast({ title: store.cfg('course.tip.buySuccess'), icon: 'success', mask: true });
// 单课程模式从详情页进来,买完退回去;列表模式留在原地刷新 // 单条模式从详情页进来,买完退回去;列表模式留在原地刷新
if (this.data.code) { if (this.data.code) {
setTimeout(() => this.back(), 1500); setTimeout(() => this.back(), 1500);
} else { } else {
+1 -1
View File
@@ -1,7 +1,7 @@
<!--pages/buy/buy.wxml--> <!--pages/buy/buy.wxml-->
<view class="buy-page"> <view class="buy-page">
<!-- 带 ?code= 进来就只买这一门,否则列出全部课程、一门一个购买按钮。 <!-- 带 ?code= 进来就只买这一条,否则列出全部条目、一条一个购买按钮。
注意别在 wx:if 和 wx:else 之间插注释。 --> 注意别在 wx:if 和 wx:else 之间插注释。 -->
<view class="buy-single" wx:if="{{code}}"> <view class="buy-single" wx:if="{{code}}">
<text class="buy-title">{{title}}</text> <text class="buy-title">{{title}}</text>
+2 -2
View File
@@ -7,7 +7,7 @@
box-sizing: border-box; box-sizing: border-box;
} }
/* ── 单课程 ───────────────────────────────────────────── */ /* ── 单条 ─────────────────────────────────────────────── */
.buy-single { .buy-single {
background: #fff; background: #fff;
border-radius: 16rpx; border-radius: 16rpx;
@@ -31,7 +31,7 @@
font-weight: 600; font-weight: 600;
} }
/* ── 课程列表 ─────────────────────────────────────────── */ /* ── 列表 ─────────────────────────────────────────────── */
.buy-list { .buy-list {
background: #fff; background: #fff;
border-radius: 16rpx; border-radius: 16rpx;
+3 -3
View File
@@ -24,7 +24,7 @@ Page({
onShow() { onShow() {
if (store.isLoggedIn()) this.refresh(); if (store.isLoggedIn()) this.refresh();
// 未登录(含刚退出登录)不能留着一份别人的课程列表 // 未登录(含刚退出登录)不能留着一份别人的已购列表
else this.setData({ ownedCourses: [], hasCourse: false }); else this.setData({ ownedCourses: [], hasCourse: false });
}, },
@@ -39,7 +39,7 @@ Page({
}) })
.catch((err) => { .catch((err) => {
if (err && err.code === api.ACCOUNT_DISABLED) { if (err && err.code === api.ACCOUNT_DISABLED) {
// 服务端不返回权益了,本地缓存也一并清掉,免得露出上一次的课程 // 服务端不返回权益了,本地缓存也一并清掉,免得露出上一次的已购内容
store.setEntitlements([]); store.setEntitlements([]);
this.setData({ this.setData({
ownedCourses: [], ownedCourses: [],
@@ -50,7 +50,7 @@ Page({
}); });
return; return;
} }
console.warn('[kecheng] 已购课程加载失败', err); console.warn('[kecheng] 已购内容加载失败', err);
}); });
}, },
+2 -2
View File
@@ -195,7 +195,7 @@ const api = {
getArticle: (id) => request({ url: '/v1/articles/' + id, auth: false }), getArticle: (id) => request({ url: '/v1/articles/' + id, auth: false }),
getTips: () => request({ url: '/v1/tips', auth: false }), getTips: () => request({ url: '/v1/tips', auth: false }),
// ── 课程 ──────────────────────────────────────────────────────────── // ── 内容 ────────────────────────────────────────────────────────────
getCourses: () => request({ url: '/v1/courses' }), getCourses: () => request({ url: '/v1/courses' }),
getCourse: (code) => request({ url: '/v1/courses/' + code }), getCourse: (code) => request({ url: '/v1/courses/' + code }),
getLessons: (code) => request({ url: '/v1/courses/' + code + '/lessons' }), getLessons: (code) => request({ url: '/v1/courses/' + code + '/lessons' }),
@@ -231,7 +231,7 @@ const api = {
* 拉起虚拟支付。 * 拉起虚拟支付。
* *
* 支付成功只代表「钱付了」,权益由服务端收到发货推送后发放, * 支付成功只代表「钱付了」,权益由服务端收到发货推送后发放,
* 因此这里不直接开通课程,而是让调用方轮询订单状态。 * 因此这里不直接开通权益,而是让调用方轮询订单状态。
*/ */
function requestVirtualPayment(payData) { function requestVirtualPayment(payData) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
+1 -1
View File
@@ -92,7 +92,7 @@ const store = {
return value === undefined ? '' : value; return value === undefined ? '' : value;
}, },
// ── 已购课程 ───────────────────────────────────────────────────────── // ── 已购内容 ─────────────────────────────────────────────────────────
getEntitlements() { getEntitlements() {
if (memory.entitlements && memory.entitlements.length) return memory.entitlements; if (memory.entitlements && memory.entitlements.length) return memory.entitlements;
memory.entitlements = read(config.STORAGE.ENTITLEMENTS, []) || []; memory.entitlements = read(config.STORAGE.ENTITLEMENTS, []) || [];