[TBID:ECX-8800] chore: merge master for v4.6.8 release

- fix: 修复运费模板保存后重复请求列表接口
- fix(ECX-8289): 菜单保存 API 权限只传 shopmenu_id 和 apis
- fix(ECX-8797): 补充新 PC 商城环境变量
- fix: 开屏广告热区配置更新
- chore: 内购模板优化及新增参数
- docs: 更新 git-commit skill 为 GitLab TBID 规范

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
maqian
2026-06-12 15:13:30 +08:00
parent 533cdc620f
commit 02a37b8f1e
22 changed files with 325 additions and 137 deletions

View File

@@ -0,0 +1,70 @@
---
description: Behavioral guidelines to reduce common LLM coding mistakes. Use when writing, reviewing, or refactoring code to avoid overcomplication, make surgical changes, surface assumptions, and define verifiable success criteria.
alwaysApply: true
---
# Karpathy behavioral guidelines
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
## 1. Think Before Coding
**Don't assume. Don't hide confusion. Surface tradeoffs.**
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
## 2. Simplicity First
**Minimum code that solves the problem. Nothing speculative.**
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
## 3. Surgical Changes
**Touch only what you must. Clean up only your own mess.**
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
## 4. Goal-Driven Execution
**Define success criteria. Loop until verified.**
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
```
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
```
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
---
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.

View File

@@ -0,0 +1,170 @@
---
name: git-commit
description: 按 GitLab TBID 强制校验规范提交 Git 变更并在对话中总结。提交信息必须以 [TBID:项目ID-任务编号] 开头。用户说「提交」「提交并总结」「帮我 commit」时使用不写 .cursor/commit-summary 文件。
---
# Git 提交TBID 规范)
**2026 年 6 月 12 日起**GitLab 仓库开启 Commit Message 强制校验:**所有提交必须包含 `[TBID:项目ID-任务编号]`,否则推送将被拒绝。**
## 何时触发
- 「提交并总结」「帮我 commit」「直接提交」「记录并提交」
- 「总结这次改了什么并提交」(只对话总结 + commit不写 md 文件)
- 「修正提交信息」「改 commit message」按下方「错误提交修正」处理
## Commit Message 格式(强制)
```
[TBID:项目ID-任务编号] <提交类型> <问题/功能简述>
```
类型后可加冒号(如 `feat:`),与不加冒号(如 `fix `)均可;推荐统一为 `type:` 后接简述。
**正确示例:**
```
[TBID:PROJ-1001] fix 解决用户登录超时问题
[TBID:PROJ-1002] feat: 新增用户注册验证码功能
[TBID:WA-641] style: 赠品缩进改为固定 20px
```
### [TBID:项目ID-任务编号](必填,放开头)
- 格式固定:`[TBID:项目ID-任务编号]`,关联 Teambition 任务
- 示例:`[TBID:ECX-8754]``[TBID:WA-641]``[TBID:LAXZE-7377]`
- **用户未提供完整 TBID 时必须先询问**,禁止猜测或使用占位符
### 提交类型(必填)
| type | 说明 |
|------|------|
| `feat` | 新功能、新特性 |
| `fix` | 修改 bug |
| `perf` | 性能优化(不改变代码行为) |
| `refactor` | 代码重构(不改变行为与功能) |
| `docs` | 文档修改 |
| `style` | 代码格式修改(**非 CSS**;删多余行、缩进等) |
| `test` | 测试用例新增、修改 |
| `build` | 影响构建或依赖(如 pom 依赖) |
| `revert` | 恢复上一次提交 |
| `ci` | CI 相关Dockerfile 等) |
| `chore` | 其他(不在上述类型中) |
| `release` | 发布新版本 |
| `workflow` | 工作流相关文件修改 |
用户指定 type 时以用户为准。
### 问题/功能简述(必填)
- 一句话描述本次提交,**不超过 50 字**
- 写「做了什么」,不要堆文件路径列表
## 流程
### 1. 获取变更
```bash
git status -sb
git diff --stat
git diff
```
若有 staged 变更,再执行 `git diff --cached`
### 2. 对话总结(不写文件)
用中文简要说明:
- 涉及哪些文件/模块
- 主要改了什么
- 拟用的完整 commit message含 TBID、type、简述
**禁止**创建或更新 `.cursor/commit-summary/` 下任何 md 文件。
**发现不合理代码时**:只指出问题与建议,**不得擅自修改**;先询问用户是否需要一并处理,待确认后再动代码。
### 3. 是否提交
| 用户意图 | 动作 |
|----------|------|
| 仅「总结」「看看改了啥」 | 只输出总结,不 `git add` / `git commit` |
| 「并提交」「直接 commit」 | 继续步骤 4 |
### 4. 执行提交
1. **只提交已有 diff**:不得顺带改代码;有问题在提交前说明
2. **不改无关代码**:先向用户说明并询问,**禁止擅自改动**
3. **暂存**:未指定文件时 add 相关变更;用户指定路径则只 add 指定项
4. **排除敏感文件**`.env`、密钥等勿加入暂存,并提醒用户
5. **提交**message 必须含 TBID
```bash
git commit -m "$(cat <<'EOF'
[TBID:项目ID-任务编号] type: 问题/功能简述
EOF
)"
```
6. **确认**:回复「已提交」+ commit hash + 一句话总结
7. **不 push**:除非用户明确要求
## 错误提交修正(未推送前)
推送被拦截或发现 message 不符合规范时:
### 仅最近 1 次提交写错
```bash
git commit --amend -m "[TBID:XXXX-123] fix: 正确描述"
```
### 最近 N 次提交都写错
```bash
git rebase -i HEAD~N
```
将需修改的提交前的 `pick` 改为 `reword`,保存后依次修改每条 message。
> 交互式 rebase 需用户确认Agent 仅在用户明确要求修正历史提交信息时执行,且不使用 `-i` 以外的 destructive 操作。
### 查看未推送的提交
```bash
git log --oneline origin/<分支名>..HEAD
```
## 代码改动边界
- 提交、总结、review 过程中发现不合理代码:**先问用户,再改**
- 用户未确认前,不得把「顺手修复」混进本次 commit
## 安全约束
- 不修改 git config
- 不执行 destructive 命令force push、hard reset 等),除非用户明确要求
- 不 skip hooks`--no-verify` 等),除非用户明确要求
- **`--amend` / `rebase -i reword`**:仅用于修正 commit messageTBID 规范),且符合用户 amend 规则HEAD 为自己创建、未 push 等)
## 示例对话
**用户**`提交 WA-641赠品缩进改 20px`
**Agent**
1. 查看 diff → 3 个 trade scss 文件
2. 总结:订单列表/结算/详情赠品 margin-left 改为 20px
3. `git commit -m "[TBID:WA-641] style: 赠品缩进改为固定 20px"`
4. 回复:已提交 `515c73472`
**用户**`推送被拒,上次 commit 没写 TBID`
**Agent**
1. 确认未 push → `git commit --amend -m "[TBID:WA-641] style: 赠品缩进改为固定 20px"`
2. 提醒用户再次 push
**用户**`先总结我改了啥`
**Agent**:只输出 diff 总结,不 commit并问是否需要提交及 TBID。

3
.env
View File

@@ -11,7 +11,6 @@ VUE_APP_OSS_CDN=false
VUE_APP_MAP_KEY=
VUE_APP_LOCAL_DELIVERY_DIRVER=dada
VUE_APP_DEFAULT_LANG=zhcn
VUE_APP_WEBSITE=
# PC 商城预览 originpostMessage 目标,与 dev-plan 一致;未设时回退 VUE_APP_WEBSITE
# VUE_APP_SHOP_ORIGIN=https://your-shop-domain.com

View File

@@ -15,28 +15,31 @@ export function getShopMenu(query) {
})
}
function buildMenuParams(query) {
const params = { ...query }
if (query.is_menu !== undefined) {
params.is_menu = query.is_menu ? 'true' : 'false'
}
if (query.is_show !== undefined) {
params.is_show = query.is_show ? 'true' : 'false'
}
return params
}
export function saveMenu(isEdit, query) {
let data, msg
if (isEdit) {
data = fetch({
url: '/shopmenu',
method: 'put',
params: {
...query,
is_menu: query.is_menu ? 'true' : 'false',
is_show: query.is_show ? 'true' : 'false'
}
params: buildMenuParams(query)
})
msg = i18n.t('995dc510.7d662b')
} else {
data = fetch({
url: '/shopmenu',
method: 'post',
params: {
...query,
is_menu: query.is_menu ? 'true' : 'false',
is_show: query.is_show ? 'true' : 'false'
}
params: buildMenuParams(query)
})
msg = i18n.t('995dc510.5e6ef1')
}

View File

@@ -75,48 +75,7 @@
</el-input>
</el-form-item>
<el-form-item :label="$t('94b9c64d.bf490f')">
<!-- <el-input type="text" v-model="form.ad_url" placeholder="请输入URL" ></el-input>-->
<div class="uploader-setting">
<div class="goods-select">
<div v-if="JSON.stringify(form.ad_url) !== '{}'" class="link-content">
<span @click="handleGoodsChange()">
<template v-if="form.ad_url.linkPage === 'goods'">{{
$t('94b9c64d.10fe9c')
}}</template>
<template v-if="form.ad_url.linkPage === 'category'">{{
$t('94b9c64d.e7d2e8')
}}</template>
<template v-if="form.ad_url.linkPage === 'article'">{{
$t('94b9c64d.8cb9b8')
}}</template>
<template v-if="form.ad_url.linkPage === 'planting'">{{
$t('94b9c64d.9dcd91')
}}</template>
<template v-if="form.ad_url.linkPage === 'link'">{{
$t('94b9c64d.ffd741')
}}</template>
<template v-if="form.ad_url.linkPage === 'marketing'">{{
$t('94b9c64d.c78a2f')
}}</template>
<template v-if="form.ad_url.linkPage === 'custom_page'">{{
$t('94b9c64d.2a4e32')
}}</template>
{{ form.ad_url.title }}
</span>
<span style="margin-left: 10px">
<i
v-if="JSON.stringify(form.ad_url) !== '{}'"
style="color: #f56c6c"
class="el-icon-delete"
@click="clear_ad_url"
/>
</span>
</div>
<div v-else class="content-center" @click="handleGoodsChange()">
<i class="el-icon-link" />{{ $t('94b9c64d.4f2c29') }}
</div>
</div>
</div>
<SpPathSelector v-model="adUrlSelector" />
</el-form-item>
<!-- <el-form-item label="应用端">
<el-checkbox-group v-model="is_app">
@@ -128,17 +87,6 @@
</el-form>
</div>
<linkSetter
:links="linksArr"
:visible="linksVisible"
:show_article="false"
:show_planting="false"
:show_page="false"
:show_marketing="false"
:show_store="false"
@setLink="setLink"
@closeDialog="closeDialog"
/>
</el-card>
<template slot="page-footer">
<div class="text-center">
@@ -149,21 +97,18 @@
</SpPage>
</template>
<script>
import linkSetter from '@/components/template_links' // 添加导航连接
import SpPathSelector from '@/components/sp-path-selector/index.vue'
import videoPicker from '@/components/videoselect'
import { mapGetters } from 'vuex'
import { getOpenScreenADSet, saveOpenScreenADSet } from '../../../api/openscreenad'
export default {
components: {
linkSetter,
SpPathSelector,
videoPicker
},
data() {
return {
// linksArr: ['goods', 'store', 'custom_page', 'category'],
linksArr: [],
linksVisible: false, // 路径设置组件
itemVideo: {},
loading: false,
ad_pic: '',
@@ -190,22 +135,9 @@ export default {
this.getInfo()
},
methods: {
clear_ad_url() {
this.form.ad_url = {}
},
input_waiting_time() {
this.form.waiting_time = Number(this.form.waiting_time.replace(/\D+/, ''))
},
handleGoodsChange() {
this.linksVisible = true
},
setLink(data, type) {
let obj = Object.assign(data, { linkPage: type })
this.form.ad_url = obj
},
closeDialog() {
this.linksVisible = false
},
async handleImgChange() {
try {
const { data } = await this.$picker.image({
@@ -311,7 +243,18 @@ export default {
}
},
computed: {
...mapGetters(['wheight'])
...mapGetters(['wheight']),
adUrlSelector: {
get() {
const url = this.form.ad_url
if (!url || typeof url !== 'object') return null
if (!(url.linkPage || url.title || url.id)) return null
return url
},
set(val) {
this.form.ad_url = val || {}
}
}
}
}
</script>

View File

@@ -432,6 +432,10 @@ export default {
const { setting } = wgt.config
return setting
},
resolveCustomPageVersion() {
const { version } = this.$route.query || {}
return version || 'v1.0.1'
},
async getTemplateDetial() {
const { id } = this.$route.query
let list = []
@@ -446,7 +450,7 @@ export default {
const resTemplate = await this.$api.wxa.getParamByTempName({
template_name: 'yykweishop',
page_name: `custom_${id}`,
version: 'v1.0.1'
version: this.resolveCustomPageVersion()
})
list = resTemplate?.list || []
} else {
@@ -677,7 +681,7 @@ export default {
await this.$api.wxa.savePageParams({
template_name: 'yykweishop',
page_name: `custom_${id}`,
version: 'v1.0.1',
version: this.resolveCustomPageVersion(),
config: JSON.stringify(data)
})
} else {

View File

@@ -1615,13 +1615,9 @@ export default {
}
this.submitLoading = false
this.isLeave = true
// 安全调用父组件方法
if (this.$parent && typeof this.$parent.onActivated === 'function') {
this.$parent.onActivated()
}
setTimeout(() => {
this.$router.go(-1)
}, 200)
const parent = this.$parent
this.$router.go(-1)
setTimeout(() => parent?.onActivated?.(), 100)
} catch (e) {
this.submitLoading = false
console.log(e)

View File

@@ -1464,7 +1464,7 @@ export default {
this.getBaseSetting()
this.$activated = () => {
this.$refs['finder'].refresh()
this.$refs['finder']?.refresh?.()
}
},
methods: {

View File

@@ -822,7 +822,6 @@ import { getAddress } from '../../../../api/common'
// import this.district from '../../../../common/this.district.json'
//匹配省市区
export default {
inject: ['refresh'],
data() {
return {
loading: true,
@@ -1053,9 +1052,8 @@ export default {
this.$message({
message: this.$t('e06d340d.55aa63'),
type: 'success',
duration: 2 * 1000,
duration: 1500,
onClose() {
that.refresh()
that.$router.go(-1)
}
})
@@ -1067,9 +1065,8 @@ export default {
this.$message({
message: this.$t('e06d340d.3fdaea'),
type: 'success',
duration: 2 * 1000,
duration: 1500,
onClose() {
that.refresh()
that.$router.go(-1)
}
})

View File

@@ -13,19 +13,19 @@
</el-button>
</div>
<el-tabs v-model="activeName" type="card" @tab-click="handleClick">
<el-tab-pane :label="$t('c8abf6d9.882490')" name="first">
<el-tab-pane lazy :label="$t('c8abf6d9.882490')" name="first">
<buyerTemplates ref="buyerTemplates" :get-status="buyerTemplates" />
</el-tab-pane>
<el-tab-pane :label="$t('c8abf6d9.843135')" name="second">
<el-tab-pane lazy :label="$t('c8abf6d9.843135')" name="second">
<weightTemplates ref="weightTemplates" :get-status="weightTemplates" />
</el-tab-pane>
<el-tab-pane :label="$t('c8abf6d9.5555b9')" name="third">
<el-tab-pane lazy :label="$t('c8abf6d9.5555b9')" name="third">
<numberTemplates ref="numberTemplates" :get-status="numberTemplates" />
</el-tab-pane>
<el-tab-pane :label="$t('c8abf6d9.877ec9')" name="fourth">
<el-tab-pane lazy :label="$t('c8abf6d9.877ec9')" name="fourth">
<priceTemplates ref="priceTemplates" :get-status="priceTemplates" />
</el-tab-pane>
<el-tab-pane :label="$t('c8abf6d9.a95569')" name="fifth">
<el-tab-pane lazy :label="$t('c8abf6d9.a95569')" name="fifth">
<volumeTemplates ref="volumeTemplates" :get-status="volumeTemplates" />
</el-tab-pane>
</el-tabs>
@@ -49,11 +49,6 @@ export default {
priceTemplates,
volumeTemplates
},
provide() {
return {
refresh: this.getList
}
},
data() {
return {
loading: false,
@@ -70,6 +65,7 @@ export default {
if ('undefined' != typeof this.$route.query.activeName) {
this.activeName = this.$route.query.activeName
}
this.$nextTick(() => this.getList())
},
watch: {
$route(to, from) {
@@ -95,19 +91,22 @@ export default {
this.volumeTemplates = true
}
},
getActiveTabRef() {
const refMap = {
first: 'buyerTemplates',
second: 'weightTemplates',
third: 'numberTemplates',
fourth: 'priceTemplates',
fifth: 'volumeTemplates'
}
const refName = refMap[this.activeName]
return refName ? this.$refs[refName] : null
},
getList() {
const refs = [
this.$refs.buyerTemplates,
this.$refs.weightTemplates,
this.$refs.numberTemplates,
this.$refs.priceTemplates,
this.$refs.volumeTemplates
]
refs.forEach((ref) => {
if (ref && typeof ref.getShippingTemplatesList === 'function') {
ref.getShippingTemplatesList()
}
})
const ref = this.getActiveTabRef()
if (ref && typeof ref.getShippingTemplatesList === 'function') {
ref.getShippingTemplatesList()
}
}
}
}

View File

@@ -78,9 +78,6 @@ export default {
}
}
},
mounted() {
this.getShippingTemplatesList()
},
methods: {
handleCurrentChange(pageNum) {
this.params.page = pageNum

View File

@@ -86,7 +86,6 @@ export default {
}
},
mounted() {
this.getShippingTemplatesList()
this.getAddress()
},
methods: {

View File

@@ -82,7 +82,6 @@ export default {
},
mounted() {
this.getAddress()
this.getShippingTemplatesList()
},
methods: {
handleCurrentChange(pageNum) {

View File

@@ -89,7 +89,6 @@ export default {
},
mounted() {
this.getAddress()
this.getShippingTemplatesList()
},
methods: {
handleCurrentChange(pageNum) {

View File

@@ -89,7 +89,6 @@ export default {
},
mounted() {
this.getAddress()
this.getShippingTemplatesList()
},
methods: {
handleCurrentChange(pageNum) {

View File

@@ -188,11 +188,13 @@ export default {
this.$message.warning(this.$t('9d1e03c7.c1d902'))
return
}
const distributorId = Number(row.distributor_id)
this.$router.push({
path: '/wxapp/manage/decorate',
query: {
id: String(cid),
scene: '1010'
scene: '1010',
version: distributorId > 0 ? `shop_${distributorId}` : 'v1.0.1'
}
})
},

View File

@@ -60,6 +60,8 @@
<SpFinder
ref="finder"
no-selection
fixed-row-action
row-actions-width="220px"
:setting="setting"
:row-actions-align="'left'"
:hooks="{

View File

@@ -431,7 +431,10 @@ export default {
this.setApisForm.is_menu = row.is_menu
},
actionSetMenuApis() {
saveMenu(true, this.setApisForm).then((res) => {
saveMenu(true, {
shopmenu_id: this.setApisForm.shopmenu_id,
apis: this.setApisForm.apis
}).then((res) => {
this.editApisVisible = false
this.getMenuTrees()
})

View File

@@ -431,7 +431,10 @@ export default {
this.setApisForm.is_menu = row.is_menu
},
actionSetMenuApis() {
saveMenu(true, this.setApisForm).then((res) => {
saveMenu(true, {
shopmenu_id: this.setApisForm.shopmenu_id,
apis: this.setApisForm.apis
}).then((res) => {
this.editApisVisible = false
this.getMenuTrees()
})

View File

@@ -431,7 +431,10 @@ export default {
this.setApisForm.is_menu = row.is_menu
},
actionSetMenuApis() {
saveMenu(true, this.setApisForm).then((res) => {
saveMenu(true, {
shopmenu_id: this.setApisForm.shopmenu_id,
apis: this.setApisForm.apis
}).then((res) => {
this.editApisVisible = false
this.getMenuTrees()
})

View File

@@ -431,7 +431,10 @@ export default {
this.setApisForm.is_menu = row.is_menu
},
actionSetMenuApis() {
saveMenu(true, this.setApisForm).then((res) => {
saveMenu(true, {
shopmenu_id: this.setApisForm.shopmenu_id,
apis: this.setApisForm.apis
}).then((res) => {
this.editApisVisible = false
this.getMenuTrees()
})

View File

@@ -201,8 +201,8 @@ export default {
},
data() {
return {
activeDomestic: 'wxpay',
activeInternational: 'paypal',
activeDomestic: '',
activeInternational: '',
isHfpay: false,
loading: false,
@@ -421,10 +421,8 @@ export default {
const res = await hfpayVersionStatus()
const data = res.data.data
if (data.hfpay_version_status) {
this.activeDomestic = 'hfpay'
this.isHfpay = true
} else {
this.activeDomestic = 'offline_pay'
this.isHfpay = false
}
} catch (error) {