20261030 #16
@ -3,7 +3,8 @@ name: oms-auto-trigger-formula
|
||||
description: >-
|
||||
Writes OMS auto-trigger (自动触发) Chinese formulas for /channel/autoTrigger.
|
||||
Use when the user asks for 触发条件, 自动触发公式, 拦截/警告/问题件规则,
|
||||
oversize/国家/客户/销售产品条件, or formula verification sample data.
|
||||
oversize/国家/客户/销售产品条件, 字段非空/为空/不能为null 必填校验,
|
||||
or formula verification sample data.
|
||||
---
|
||||
|
||||
# OMS 自动触发公式编写
|
||||
@ -41,6 +42,8 @@ description: >-
|
||||
| 字符串字段用 `字段包含('值')` | `客户包含('阿里')` | `客户()=='阿里'` |
|
||||
| 数值/聚合用 `字段()` 再比较 | `实重()>5`、`单件最大长度()>60` | `实重>5`、`长度包含('60')` |
|
||||
| 布尔用 `字段()==1` / `==0` | `是否偏远()==1` | `是否偏远()==true` |
|
||||
| **非空 / 不能为 null** | `国家有值()` 或 `国家包含('')` | `国家!=null`、`国家()==null`、`国家!=''` |
|
||||
| **为空 / 未填** | `收件人邮编为空()` 或 `!收件人邮编有值()` | `邮编==null`、`邮编包含(null)` |
|
||||
| 字符串字面量单引号 | `'GB'`;内部单引号翻倍 `''` | `"GB"` |
|
||||
| 逻辑运算 | `&&` `\|\|` `!` | `and` `or` `not` / 中文「且」 |
|
||||
| 比较运算 | `==` `!=` `>` `>=` `<` `<=` | `=` `<>` |
|
||||
@ -60,14 +63,33 @@ description: >-
|
||||
!销售产品包含('EX_ASP_HKDHL_YD')
|
||||
```
|
||||
|
||||
### 空值 / 必填(不能为 null)
|
||||
|
||||
求值器**没有** `null` 字面量;禁止写 `字段==null` / `字段()!=null`。
|
||||
|
||||
| 意图 | 推荐写法 | 等价写法 |
|
||||
|------|----------|----------|
|
||||
| 字段有值(非 null、字符串非空白) | `国家有值()` | `国家包含('')` |
|
||||
| 字段为空(null 或空白) | `国家为空()` | `!国家有值()` |
|
||||
| 拦截「缺某字段」 | `收件人电话为空()` | `!收件人电话有值()` |
|
||||
| 拦截「某字段必须填」再叠加条件 | `国家有值()&&国家包含('GB')` | — |
|
||||
|
||||
语义说明:
|
||||
|
||||
- **字符串 / 日期**:`有值` = 非 null 且非空白;`为空` = null 或空白。
|
||||
- **数值 / 布尔**:`有值` = 底层值非 null(`0` / `false` 仍算有值);`为空` = null。
|
||||
- 件重尺若业务上「0 也算未填」,用 `单件最大长度()<=0` 等比较,**不要**只靠 `有值()`(因为 0 仍有值)。
|
||||
- `字段()` 把 null 当成 `0`,**不能**用 `字段()==0` 区分「未填」与「真是 0」。
|
||||
|
||||
## 写公式工作流
|
||||
|
||||
1. 从用户描述抽出:时机、操作、条件(国家/客户/产品/尺寸/重量/状态等)。
|
||||
1. 从用户描述抽出:时机、操作、条件(国家/客户/产品/尺寸/重量/状态、**必填/不能为空**等)。
|
||||
2. 映射到**中文字段**(见下方常用表;完整目录见 [field-catalog.md](field-catalog.md))。
|
||||
3. 按字段类型选 `包含('')` 或 `字段()`。
|
||||
4. 用 `&&` / `||` / `!` / `()` 组合;复杂条件先括号。
|
||||
5. 对照 [examples.md](examples.md) 自检语法。
|
||||
6. 输出「时机 + 操作 + 公式 + 说明」;需要时给验证样例。
|
||||
3. 按字段类型选:`包含('值')` / `字段()` / **`有值()` / `为空()`**。
|
||||
4. 用户说「不能为空 / 必填 / 未填拦截」→ 用 `字段为空()`(缺了就命中)或前置 `字段有值()&&…`。
|
||||
5. 用 `&&` / `||` / `!` / `()` 组合;复杂条件先括号。
|
||||
6. 对照 [examples.md](examples.md) 自检语法。
|
||||
7. 输出「时机 + 操作 + 公式 + 说明」;需要时给验证样例。
|
||||
|
||||
不确定字段名时:优先读 `formulaMeta.ts` 的 `ORDER_TABLE_FIELDS` / `VIRTUAL_FORMULA_FIELDS`,或后端 `AutoTriggerFormulaMeta`。**禁止编造未注册中文名**。
|
||||
|
||||
@ -95,10 +117,12 @@ description: >-
|
||||
| 是否偏远 / 是否COD / 是否拦截 | boolean | `是否偏远()==1` |
|
||||
| 实重 / 计费重 / 长度/宽度/高度 | number | `实重()>20`(件重尺;下单前优先用聚合字段) |
|
||||
|
||||
### 收件人(选填场景)
|
||||
### 收件人(选填 / 必填场景)
|
||||
|
||||
`收件人邮编包含('9')`、`收件人省州包含('CA')`、`收件人城市包含('London')`
|
||||
|
||||
必填拦截示例:`收件人手机为空()||收件人电话为空()`、`收件人邮编为空()`(手机=`收件人手机`,固话=`收件人电话`)
|
||||
|
||||
完整字段见 [field-catalog.md](field-catalog.md)。
|
||||
|
||||
## 时机与操作注意
|
||||
@ -112,6 +136,7 @@ description: >-
|
||||
|
||||
- [ ] 每个字段都是注册中文名
|
||||
- [ ] 字符串用 `包含('…')`,数字用 `字段() 比较`
|
||||
- [ ] 必填/非空用 `有值()` 或 `包含('')`,为空用 `为空()`;无 `null` 字面量
|
||||
- [ ] 括号匹配;`&&`/`||` 优先级按需加括号
|
||||
- [ ] 国家一般用二字码 `国家包含('XX')`
|
||||
- [ ] 排除条件用 `!字段包含('…')`
|
||||
|
||||
@ -72,6 +72,38 @@
|
||||
国家包含('GB')&&!(销售产品包含('EX_A')||销售产品包含('EX_B'))
|
||||
```
|
||||
|
||||
## 11. 收件人联系方式不能为空(缺手机拦截)
|
||||
|
||||
```text
|
||||
时机:下单
|
||||
操作:拦截
|
||||
公式:收件人手机为空()&&收件人电话为空()
|
||||
说明:手机与固话都未填时拦截
|
||||
验证样例:手机空、电话空 → 命中;填了手机 → 不命中
|
||||
```
|
||||
|
||||
仅校验手机:`收件人手机为空()`;等价非空写法:`!收件人手机有值()` / `!收件人手机包含('')`
|
||||
|
||||
## 12. 国家必填且仅为 GB 才警告
|
||||
|
||||
```text
|
||||
时机:下单
|
||||
操作:警告
|
||||
公式:国家有值()&&国家包含('GB')
|
||||
说明:已填国家且为国家码含 GB 时警告
|
||||
```
|
||||
|
||||
`国家包含('')` 与 `国家有值()` 等价(空串参数 = 有值校验)。
|
||||
|
||||
## 13. 邮编或城市缺一即问题件
|
||||
|
||||
```text
|
||||
时机:下单
|
||||
操作:问题件
|
||||
公式:收件人邮编为空()||收件人城市为空()
|
||||
说明:邮编或城市未填时记问题件
|
||||
```
|
||||
|
||||
## 常见写错对照
|
||||
|
||||
| 错误 | 正确 |
|
||||
@ -82,3 +114,5 @@
|
||||
| `是否偏远()==true` | `是否偏远()==1` |
|
||||
| `国家包含(GB)` | `国家包含('GB')` |
|
||||
| `长度()>60`(想表达单件最大) | `单件最大长度()>60` |
|
||||
| `国家!=null` / `国家()==null` | `国家为空()` / `!国家有值()` |
|
||||
| `电话!=''` | `收件人电话有值()` 或 `收件人电话包含('')` |
|
||||
|
||||
@ -8,6 +8,8 @@
|
||||
- `string` / `datetime` → `中文包含('…')`
|
||||
- `number` / 聚合 → `中文()` 再 `> >= < <= == !=`
|
||||
- `boolean` → `中文()==1` 或 `==0`
|
||||
- **非空 / 不能为 null** → `中文有值()` 或 `中文包含('')`(任意类型)
|
||||
- **为空 / 未填** → `中文为空()` 或 `!中文有值()`
|
||||
|
||||
## 常用 / 聚合 `_virtual`
|
||||
|
||||
|
||||
207
docs/IDE跨境物流-2个月获客营销方案-务实版.md
Normal file
207
docs/IDE跨境物流-2个月获客营销方案-务实版.md
Normal file
@ -0,0 +1,207 @@
|
||||
# IDE 跨境物流 · 2 个月获客方案(务实版)
|
||||
|
||||
> 基于现有 OMS:**渠道代理门户**(专属链接/海报、名下客户与运单、预估→待结算→已打款返佣)+ **会员/优惠券/C2C 裂变**。
|
||||
> 只写能马上干、系统已支持或运营线下能补齐的事;砍掉未上线能力(代理推代理终身分成、系统内提现、设备指纹四重防刷、毛利自动算销售提成等)。
|
||||
|
||||
---
|
||||
|
||||
## 一、目标怎么定(别只盯 2000)
|
||||
|
||||
原方案「0 广告、18 个种子用户 → 2 个月 2000 实名」属于冲刺上限,不是承诺线。
|
||||
|
||||
| 档位 | 2 个月实名客户 | 含义 |
|
||||
|------|----------------|------|
|
||||
| **底线** | **600~800** | 代理按节奏邀约 + 老客转介绍正常运转 |
|
||||
| **达标** | **1200~1500** | 代理周周有动作、销顾跟进到位 |
|
||||
| **冲刺** | **2000** | 代理拉满 + 销售外拓 + 券/会员刺激同时生效 |
|
||||
|
||||
**实名口径(建议统一):** 完成注册且资料齐全(手机/实名相关字段按你们现有校验),能下单。
|
||||
**归因口径:** 注册带 `?agent=` → 绑定代理;带 C2C 邀请码 → 走会员裂变;**二者互斥,代理优先于 C2C**(与系统一致)。
|
||||
|
||||
**核心 KPI(每周看,别堆一堆虚指标):**
|
||||
|
||||
1. 新增实名数(总 / 代理归因 / C2C / 自然)
|
||||
2. 活跃代理数(本周至少邀到 1 个新客,或名下有下单)
|
||||
3. 新客 7 日内首单率
|
||||
4. 待结算返佣金额(派送成功/失败后 pending)与当月应打款
|
||||
|
||||
---
|
||||
|
||||
## 二、你们手里真正能用的武器
|
||||
|
||||
### 1. 渠道代理(主引擎,建议承担 60%~70% 新增)
|
||||
|
||||
**系统已具备:**
|
||||
|
||||
- 专属注册链接 + 编号 + 海报/二维码(代理端「邀请」页)
|
||||
- 客户扫码/打开链接注册 → **自动绑到该代理名下**(长期有效,解绑/换绑走后台)
|
||||
- 代理工作台:名下客户、名下运单(按客户归属看,不是只看下单瞬间快照)
|
||||
- 返佣:下单写 **预估** → 运单派送成功/失败 → **待结算** → 运营线下打款标 **已打款**;取消/退款 → **作废**
|
||||
- 比例、计费基数(运费等)在后台按代理配置;**代理改不了卖价**
|
||||
|
||||
**务实玩法(不要搞「代理推代理 0.5% 终身」——系统没有):**
|
||||
|
||||
| 做法 | 说明 |
|
||||
|------|------|
|
||||
| 统一起步比例 | 例如运费(或应收)**5%**,后台写死到代理规则;谁做得好再**人工调到 6%~7%**,不要幻想系统按 GMV 自动跳档 |
|
||||
| 结算 | **每月固定日**按「待结算」明细 Excel/后台导出,对公或微信打款,再在系统点已打款;**不做 APP 提现** |
|
||||
| 阶梯激励(线下规则即可) | 例:当月名下新客 ≥20 且有首单 → 次月比例 +1%;连续两月不活跃 → 商务电话沟通,必要时换人,**不必上「沉睡自动解绑」功能** |
|
||||
| 代理画像 | 华人超市/货代同事/留学群主/同城跑腿——**能天天加人的人**,不是大 V |
|
||||
|
||||
**代理话术(一句话):**
|
||||
「用我的码注册,下单正常价,我帮你跟单;我按运费拿返佣,不另加你钱。」
|
||||
|
||||
### 2. 老客 C2C + 优惠券/会员(辅助,约 20%~25%)
|
||||
|
||||
**系统已具备:** 邀请有礼、新人券、会员等级。
|
||||
**注意:** 已被代理绑定的客户,不要再指望用 C2C 抢归因。
|
||||
|
||||
务实用法:
|
||||
|
||||
- 给**未绑代理**的老客发「邀请好友得券/积分」模板(企微/群发即可)
|
||||
- 新客注册送固定新人券(面额按毛利算死,如首单满 X 减 Y),避免无期叠太多券
|
||||
- 会员等级用来做「下单越多权益越好」的留存,**不要当成拉新主渠道**
|
||||
|
||||
### 3. 销售/顾问人工(约 10%~15%)
|
||||
|
||||
后台是「绑定代理」+ 顾问跟进,**销售提成可以继续 Excel 算**,不必等系统算毛利 30%。
|
||||
|
||||
务实要求:
|
||||
|
||||
- 每人每周固定外拓指标(微信群、老客户要名单、地推扫码)
|
||||
- 新客注册后 **24h 内**顾问加微信、催首单
|
||||
- 代理名下客户顾问可协助服务,**返佣仍归代理**;顾问绩效看首单率/投诉,别和代理抢账
|
||||
|
||||
---
|
||||
|
||||
## 三、数字怎么拆(按底线 700 / 冲刺 2000 两套)
|
||||
|
||||
假设种子:**代理 15 人 + 销售/顾问 3 人 + 老客池可触达约 200 人**(按你们实际改)。
|
||||
|
||||
### 底线情景(约 700 实名 / 8 周)
|
||||
|
||||
| 来源 | 周均新增 | 8 周合计 | 怎么做到 |
|
||||
|------|----------|----------|----------|
|
||||
| 代理 | ~55 | ~440 | 15 人 × 人均周有效邀约 ~4 人(注册) |
|
||||
| C2C/老客 | ~20 | ~160 | 老客每周发出去、核销券 |
|
||||
| 销售外拓 | ~12 | ~100 | 3 人 × 周均 ~4 个有效注册 |
|
||||
| **合计** | | **~700** | |
|
||||
|
||||
### 冲刺情景(约 2000)
|
||||
|
||||
| 来源 | 思路 |
|
||||
|------|------|
|
||||
| 代理扩到 40~50 人 | 第 2~3 周批量签约,人均仍按「每周 3~5 个注册」算,不按「一人拉 100」幻想 |
|
||||
| 单代理爆款周 | 个别代理有社群,单周 30+,用周榜红包激励(现金线下发) |
|
||||
| 销售集中地推/行业群 | 短期加码,不替代代理 |
|
||||
|
||||
**转化漏斗(盯运营,别只盯注册):**
|
||||
|
||||
```
|
||||
曝光/发卡 → 打开注册页 → 完成实名 → 7 日内首单 → 复购
|
||||
建议经验值:注册→首单 30%~45%(跨境偏决策慢,靠顾问+券推一把)
|
||||
```
|
||||
|
||||
没有首单的「实名」对现金流帮助有限——周会同时报 **首单数**,不要只报注册数。
|
||||
|
||||
---
|
||||
|
||||
## 四、8 周作战表(能执行的那种)
|
||||
|
||||
### 第 0 周(准备,3~5 天)
|
||||
|
||||
- [ ] 后台把现有/新签代理规则配齐(比例、计费项、启用)
|
||||
- [ ] 每人生成专属链接/海报,发到微信「仅文件传输助手」存档,再转发
|
||||
- [ ] 定结算日(如每月 5 日打上月「待结算」)
|
||||
- [ ] 新人券上架:规则写清楚、预算封顶(如 2 个月总券成本 ≤ X 万)
|
||||
- [ ] 建一个「代理作战群」:每日晒新增截图,不搞复杂 CRM
|
||||
|
||||
### 第 1~2 周:冷启动
|
||||
|
||||
- 目标:代理全员当周至少 **1 个新注册**;总实名冲击底线周进度
|
||||
- 动作:一对一教代理怎么发链接;销售帮代理写朋友圈文案(3 条轮换即可)
|
||||
- 系统:看代理工作台「名下客户」是否涨;返佣是否出现「预估」
|
||||
|
||||
### 第 3~4 周:加码与首单
|
||||
|
||||
- 目标:注册→首单率拉到 ≥35%
|
||||
- 动作:顾问对「已注册未下单」名单每日跟进;代理侧只负责继续拉人
|
||||
- 激励:周榜前 3 名代理发 **200~500 元现金红包**(线下,不做系统功能)
|
||||
|
||||
### 第 5~6 周:扩代理人数
|
||||
|
||||
- 从老代理、老客户里挖「能拉人」的,签成新代理(仍是一级,**没有多级分销**)
|
||||
- 差的代理当面谈,无效就停用链接,别养僵尸号
|
||||
|
||||
### 第 7~8 周:冲刺与清算
|
||||
|
||||
- 冲刺档看是否追 1500/2000;达不到就收在达标档,别为数字乱打折伤毛利
|
||||
- 第一次正式月结:导出待结算 → 打款 → 系统标已打款 → 代理群公示「已结清」建立信任
|
||||
|
||||
---
|
||||
|
||||
## 五、钱怎么算(务实版)
|
||||
|
||||
### 代理返佣
|
||||
|
||||
- **计费:** 与后台一致(常见:运费 × 约定比例);预估仅供代理查看,**以待结算为准**
|
||||
- **发放:** 仅「派送成功/失败」进入待结算的明细;取消/退款已作废的不发
|
||||
- **不要承诺:** 预估金额 = 最终到账(体积重/改价/退款都会变)
|
||||
|
||||
### 获客成本粗算(举例,请换成你们真实客单)
|
||||
|
||||
假设:客单价运费 300 元,代理佣金 5% = 15 元/单;新客首单率 40%。
|
||||
|
||||
| 项目 | 粗算 |
|
||||
|------|------|
|
||||
| 拉 1 个实名(若最终 40% 下单) | 佣金期望约 15×0.4 ≈ **6 元/实名**(仅佣金,未计券) |
|
||||
| 新人券 | 按核销计,建议单新客券成本压在 **10~20 元以内** |
|
||||
| 周榜红包 | 每周 1000 元封顶,8 周 ≤ 8000 |
|
||||
| **结论** | 比烧信息流便宜;关键是 **别把比例开到吃光毛利** |
|
||||
|
||||
销售提成、房租、系统成本另表,**本方案不假装「零成本」**——说的是「零付费广告」。
|
||||
|
||||
### 明确不做(避免和业务/研发扯皮)
|
||||
|
||||
| 原方案常见说法 | 务实处理 |
|
||||
|----------------|----------|
|
||||
| 代理推代理终身 0.5% | **不做**;要激励就给推荐人一次性奖金 |
|
||||
| 系统内提现/余额 | **线下打款** + 状态「已打款」 |
|
||||
| GMV 自动升档 5→6→7% | **运营改配置** |
|
||||
| 沉睡 6 个月自动解绑 | **人工停用** |
|
||||
| 设备指纹四重防刷 | 先靠手机号/实名+人工抽查;刷单直接清退 |
|
||||
| 销售毛利 30% 系统自动算 | **Excel** |
|
||||
|
||||
---
|
||||
|
||||
## 六、组织与日/周节奏
|
||||
|
||||
**日(代理群,15 分钟):** 昨日新增实名、首单、卡在注册页的问题。
|
||||
**周(内部会,30 分钟):** 三档目标进度、Top/Bottom 代理、待结算金额、券核销与毛利是否安全。
|
||||
**月:** 打款日 + 调比例/扩招/清退。
|
||||
|
||||
负责人建议就 **1 个运营盯代理群 + 顾问跟首单**,不要单独成立「增长中台」。
|
||||
|
||||
---
|
||||
|
||||
## 七、风险(实话)
|
||||
|
||||
1. **代理不发链接** — 最大风险。对策:周活代理数入 KPI,连续 2 周 0 新增就约谈。
|
||||
2. **只注册不下单** — 对策:顾问名单制 + 首单券,不看虚荣注册数。
|
||||
3. **返佣预期纠纷** — 对策:海报/群公告写清「以签收后待结算为准,取消不作数」。
|
||||
4. **和 C2C 抢人** — 对策:代理链接进来的一律归代理;销售口头承诺别和系统对着干。
|
||||
5. **冲刺 2000 伤价** — 对策:打折/加佣要有毛利底线审批,运营一句话能停。
|
||||
|
||||
---
|
||||
|
||||
## 八、一页纸结论
|
||||
|
||||
1. **主靠代理专属链接拉新**,系统已通;返佣按 **预估 → 待结算 → 线下已打款** 建立信任。
|
||||
2. **C2C 和券做补充**,会员做留存,销售做人肉补位。
|
||||
3. **目标分底线 / 达标 / 冲刺**;过程盯「活跃代理 + 首单」,不唯注册数。
|
||||
4. **激励用现金红包和人工调比例**,不上多级分销、不上提现钱包。
|
||||
5. **8 周按表执行**,每月结清一次待结算——比任何「增长模型」都管用。
|
||||
|
||||
---
|
||||
|
||||
*文档版本:务实版(对齐当前 OMS 代理/营销能力)。原 Word 方案中的多级分销、自动提现、指纹防刷、毛利自动提成等,一律降级为「不做或线下替代」。*
|
||||
@ -49,16 +49,24 @@ export default function setupPermissionGuard(router: Router) {
|
||||
!isLogin() &&
|
||||
(path.startsWith('/h5') || path.startsWith('/wechat/') || path.startsWith('/track/'));
|
||||
|
||||
// 渠道代理只能进代理门户;非代理不能进代理业务页
|
||||
// 渠道代理只能进代理门户;非代理不能进代理业务页。
|
||||
// 官网 `/` 始终可进(登录页 Logo 回官网);未登录时勿用残留的 rbac_login_type 误拦。
|
||||
const isAgent = rbacUserStore.loginPortalType === OmsLoginType.Agent;
|
||||
const isAgentPortalRoute =
|
||||
Boolean(to.meta?.agentPortalOnly) ||
|
||||
(isAgentPortalPath(path) && path !== AGENT_LOGIN_PATH);
|
||||
if (isAgent && !isAgentPortalRoute && !isLoginRouteName(to.name as string)) {
|
||||
const isOfficialSite = path === '/' || to.name === 'siteRoot';
|
||||
if (
|
||||
isLogin() &&
|
||||
isAgent &&
|
||||
!isAgentPortalRoute &&
|
||||
!isOfficialSite &&
|
||||
!isLoginRouteName(to.name as string)
|
||||
) {
|
||||
next({ path: getDefaultHomePathByType(OmsLoginType.Agent), replace: true });
|
||||
return;
|
||||
}
|
||||
if (!isAgent && isAgentPortalRoute) {
|
||||
if (isLogin() && !isAgent && isAgentPortalRoute) {
|
||||
Message.warning('该页面仅渠道代理可访问,请使用代理账号登录');
|
||||
next({
|
||||
path: getDefaultHomePathByType(rbacUserStore.loginPortalType),
|
||||
|
||||
@ -59,6 +59,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import HandsontablePro from '@/components/HandsontablePro/index.vue'
|
||||
import PaginationPro from '@/components/PaginationPro/index.vue'
|
||||
@ -69,6 +70,7 @@ import { AgentAdminApi, type AgentCommissionDto } from '@/api/agent'
|
||||
|
||||
defineOptions({ name: 'AgentAdminCommission' })
|
||||
|
||||
const { t, te } = useI18n()
|
||||
const { tableContainerRef, tableHeight } = useTableContainerHeight()
|
||||
const handsontableHeight = computed(() => Math.max(tableHeight.value || 0, 400))
|
||||
|
||||
@ -132,11 +134,20 @@ function mapRow(raw: AgentCommissionDto): CommissionRow {
|
||||
commissionAmount: Number(pick(r, 'commissionAmount', 'CommissionAmount') ?? 0),
|
||||
status: st,
|
||||
statusName: resolveStatusName(st),
|
||||
triggerOrderStatus: String(pick(r, 'triggerOrderStatus', 'TriggerOrderStatus') ?? ''),
|
||||
triggerOrderStatus: resolveTriggerOrderStatusName(
|
||||
String(pick(r, 'triggerOrderStatus', 'TriggerOrderStatus') ?? ''),
|
||||
),
|
||||
createTime: String(pick(r, 'createTime', 'CreateTime') ?? ''),
|
||||
}
|
||||
}
|
||||
|
||||
function resolveTriggerOrderStatusName(raw: string) {
|
||||
const code = String(raw || '').trim()
|
||||
if (!code) return '--'
|
||||
const key = `order.myOrders.status.${code}`
|
||||
return te(key) ? t(key) : code
|
||||
}
|
||||
|
||||
function resolveStatusName(st: string) {
|
||||
switch (st) {
|
||||
case 'estimated':
|
||||
|
||||
@ -64,6 +64,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import HandsontablePro from '@/components/HandsontablePro/index.vue'
|
||||
import PaginationPro from '@/components/PaginationPro/index.vue'
|
||||
@ -74,6 +75,7 @@ import { AgentPortalApi, type AgentCommissionDto } from '@/api/agent'
|
||||
defineOptions({ name: 'AgentPortalCommission' })
|
||||
|
||||
const route = useRoute()
|
||||
const { t, te } = useI18n()
|
||||
const { tableContainerRef, tableHeight } = useTableContainerHeight()
|
||||
const handsontableHeight = computed(() => Math.max(tableHeight.value || 0, 320))
|
||||
|
||||
@ -138,6 +140,13 @@ function resolveStatusTone(st: string): 'ok' | 'wait' | 'brand' | 'alert' | 'mut
|
||||
}
|
||||
}
|
||||
|
||||
function resolveTriggerOrderStatusName(raw: string) {
|
||||
const code = String(raw || '').trim()
|
||||
if (!code) return '--'
|
||||
const key = `order.myOrders.status.${code}`
|
||||
return te(key) ? t(key) : code
|
||||
}
|
||||
|
||||
const commissionColumns = [
|
||||
{ key: 'serviceNo', label: '运单号', width: 140 },
|
||||
{ key: 'customerName', label: '客户', width: 140 },
|
||||
@ -168,7 +177,9 @@ function mapCommission(raw: AgentCommissionDto): CommissionRow {
|
||||
commissionAmount: pick(r, 'commissionAmount', 'CommissionAmount') as number | string,
|
||||
status: st,
|
||||
statusName: resolveStatusName(st),
|
||||
triggerOrderStatus: String(pick(r, 'triggerOrderStatus', 'TriggerOrderStatus') ?? ''),
|
||||
triggerOrderStatus: resolveTriggerOrderStatusName(
|
||||
String(pick(r, 'triggerOrderStatus', 'TriggerOrderStatus') ?? ''),
|
||||
),
|
||||
createTime: String(pick(r, 'createTime', 'CreateTime') ?? ''),
|
||||
}
|
||||
}
|
||||
|
||||
@ -353,7 +353,7 @@ function resolveSystemPrompt(): string {
|
||||
if (!sendSkill.value) {
|
||||
return (
|
||||
props.shortSystemPrompt ||
|
||||
'你是 OMS 自动触发公式助手。只输出中文公式表达式;字符串用 字段包含(\'…\'),数值用 字段() 比较。'
|
||||
'你是 OMS 自动触发公式助手。只输出中文公式表达式;字符串用 字段包含(\'…\'),数值用 字段() 比较;非空用 字段有值() 或 字段包含(\'\'),为空用 字段为空()。'
|
||||
)
|
||||
}
|
||||
const fromList = skills.value.find((s) => s.skillKey === skillKey.value)
|
||||
|
||||
@ -17,12 +17,12 @@ export type ParsedAiFormula = {
|
||||
|
||||
function fieldUsage(def: OrderTableFieldDef): string {
|
||||
if (def.valueType === 'string' || def.valueType === 'datetime') {
|
||||
return `${def.label}包含('…')`
|
||||
return `${def.label}包含('…')|${def.label}有值()|${def.label}为空()`
|
||||
}
|
||||
if (def.valueType === 'boolean') {
|
||||
return `${def.label}()==1`
|
||||
return `${def.label}()==1|${def.label}有值()|${def.label}为空()`
|
||||
}
|
||||
return `${def.label}()`
|
||||
return `${def.label}()|${def.label}有值()|${def.label}为空()`
|
||||
}
|
||||
|
||||
function buildFieldCatalog(): string {
|
||||
@ -58,10 +58,14 @@ const SKILL_CORE = `你是 OMS「自动触发」公式助手。根据用户中
|
||||
|
||||
- 字段名必须是下方目录中的中文 label,禁止编造、禁止英文字段名
|
||||
- 字符串:字段包含('值');数值/聚合:字段() 再比较;布尔:字段()==1 / ==0
|
||||
- 非空/不能为 null:字段有值() 或 字段包含('');为空/未填:字段为空() 或 !字段有值()
|
||||
- 禁止 null 字面量、字段==null、字段()!=null、字段!=''(字符串用有值/为空)
|
||||
- 字面量用单引号;逻辑用 && || !;比较用 == != > >= < <=
|
||||
- 禁止 and/or/not、中文「且」、双引号、字段() 用于字符串相等
|
||||
- 国家一般用二字码:国家包含('GB')
|
||||
- 公式单行、无注释夹杂、长度 ≤ 10000
|
||||
- 数值 0 / 布尔 false 仍算有值;尺寸「未填当 0」用 字段()<=0,不要只靠有值()
|
||||
- 包含:子串匹配(非全等);多选用 ||;排除用 !字段包含('…')
|
||||
|
||||
## 示例
|
||||
|
||||
@ -70,6 +74,18 @@ const SKILL_CORE = `你是 OMS「自动触发」公式助手。根据用户中
|
||||
公式:(单件最大长度()>60||单件最大宽度()>46)&&国家包含('GB')
|
||||
说明:英国且超尺寸时拦截
|
||||
验证样例:国家 GB,长 70,宽 40,高 40
|
||||
|
||||
时机:下单
|
||||
操作:拦截
|
||||
公式:收件人手机为空()||收件人邮编为空()
|
||||
说明:手机或邮编未填时拦截
|
||||
验证样例:手机空、邮编 10001 → 命中
|
||||
|
||||
时机:下单
|
||||
操作:警告
|
||||
公式:国家有值()&&国家包含('GB')
|
||||
说明:已填国家且含 GB 时警告
|
||||
验证样例:国家 GB → 命中;国家空 → 不命中
|
||||
`
|
||||
|
||||
let cachedSystemPrompt: string | null = null
|
||||
@ -148,4 +164,5 @@ export const FORMULA_AI_QUICK_PROMPTS = [
|
||||
'偏远地区下单时警告',
|
||||
'申报金额 ≥ 2000 且国家 AU 时记问题件',
|
||||
'敏感货且收货渠道含 DHL 时拦截',
|
||||
'收件人手机与电话都为空,或邮编为空时下单拦截',
|
||||
]
|
||||
|
||||
@ -150,6 +150,10 @@
|
||||
:key="door.id"
|
||||
type="button"
|
||||
class="ai-intent-door"
|
||||
:class="{
|
||||
'is-agent-tall': door.id === 'agent',
|
||||
'is-growth-wide': door.id === 'newcomer',
|
||||
}"
|
||||
:disabled="busy"
|
||||
@click="onHomeDoor(door)"
|
||||
>
|
||||
@ -157,8 +161,12 @@
|
||||
<SiteIllusIcon :name="door.icon" />
|
||||
</span>
|
||||
<span class="ai-intent-door__copy">
|
||||
<span class="ai-intent-door__title">{{ isZh ? door.zh.title : door.en.title }}</span>
|
||||
<span class="ai-intent-door__hint">{{ isZh ? door.zh.hint : door.en.hint }}</span>
|
||||
<span class="ai-intent-door__title">{{
|
||||
isZh ? door.zh.title : door.en.title
|
||||
}}</span>
|
||||
<span class="ai-intent-door__hint">{{
|
||||
isZh ? door.zh.hint : door.en.hint
|
||||
}}</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -223,6 +223,43 @@
|
||||
],
|
||||
};
|
||||
}
|
||||
if (card.type === 'agent-rules') {
|
||||
return {
|
||||
kicker: isZh ? '代理招募' : 'Channel agents',
|
||||
title: isZh ? '有客源就合作,按运费拿返佣' : 'Bring clients — earn freight commission',
|
||||
body: isZh
|
||||
? '适合有华人社群、货代资源或稳定客源的人。客户扫你的码注册即绑定名下;你看不到也不改卖价。返佣比例由运营配置,明细在代理门户可查。'
|
||||
: 'For people with communities or steady leads. Clients register via your link and bind to you. You never change sell prices. Ops sets your rate; details show in the agent portal.',
|
||||
items: [
|
||||
{
|
||||
title: isZh ? '专属链接 / 海报邀客' : 'Personal invite link & poster',
|
||||
hint: isZh
|
||||
? '客户注册自动归你名下,长期有效(换绑走后台)。'
|
||||
: 'Signups bind to you until ops reassigns.',
|
||||
},
|
||||
{
|
||||
title: isZh ? '运费比例返佣' : 'Freight-share commission',
|
||||
hint: isZh
|
||||
? '下单可见预估;派送成功/失败后变待结算;取消退款作废。'
|
||||
: 'Estimate on place-order; pending after delivery success/fail; void on cancel/refund.',
|
||||
},
|
||||
{
|
||||
title: isZh ? '线下月结打款' : 'Monthly offline payout',
|
||||
hint: isZh
|
||||
? '不对客户加价;不做代理推代理多级分成;结算日对公/微信打款。'
|
||||
: 'No markup on clients; no multi-level agent tree; payout on settlement day.',
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{ key: 'human', intent: 'human', label: human, primary: true },
|
||||
{
|
||||
key: 'agent-login',
|
||||
href: '/agent',
|
||||
label: isZh ? '已有代理账号,去登录' : 'Already an agent? Sign in',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
if (card.type === 'member-rules') {
|
||||
return {
|
||||
kicker: isZh ? '会员成长' : 'Member levels',
|
||||
|
||||
@ -40,6 +40,7 @@ export function detectIntent(text: string) {
|
||||
if (/price|cost|quote|报价|价格|多少钱/.test(lower)) return 'quote';
|
||||
if (/human|人工|顾问|电话|联系我们|联系/.test(lower)) return 'human';
|
||||
if (/who are you|about ide|你们是谁|关于我们|关于|公司|靠谱/.test(lower)) return 'company';
|
||||
if (/代理|渠道代理|返佣|招募代理|become an agent|channel agent/.test(lower)) return 'agent';
|
||||
if (/会员|等级|成长|v0|v1|v5|积分定级|运费折扣/.test(lower)) return 'member';
|
||||
if (/邀新|邀请|分成|裂变|拉新|referral|invite/.test(lower)) return 'invite';
|
||||
if (/新人|注册福利|首单券|第二单|备货券|newcomer/.test(lower)) return 'newcomer';
|
||||
@ -180,6 +181,17 @@ export function replyForIntent(
|
||||
},
|
||||
};
|
||||
}
|
||||
if (intentId === 'agent') {
|
||||
return {
|
||||
flow: null,
|
||||
reply: {
|
||||
text: zh
|
||||
? '渠道代理用专属链接邀客户注册绑定,客户下单后按约定运费比例计提返佣;派送成功/失败后进入待结算,由运营线下打款。不改卖价、不做多级分销。'
|
||||
: 'Channel agents invite clients with a personal link. Commission accrues on agreed freight share after delivery success/failure, then ops pays out offline. No price markup, no multi-level MLS.',
|
||||
card: { type: 'agent-rules' },
|
||||
},
|
||||
};
|
||||
}
|
||||
if (intentId === 'member') {
|
||||
return {
|
||||
flow: null,
|
||||
|
||||
@ -167,6 +167,21 @@ export const SITE_INTENTS = [
|
||||
label: '给我一个专属顾问,别拉群',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'agent',
|
||||
icon: 'human',
|
||||
followup: false,
|
||||
en: {
|
||||
title: 'Become an agent',
|
||||
hint: 'Your own portal & clients — earn up to 10% freight commission',
|
||||
label: 'How does the channel agent program work?',
|
||||
},
|
||||
zh: {
|
||||
title: '代理招募',
|
||||
hint: '独立管理系统管客户,运费分成最高 10%',
|
||||
label: '渠道代理怎么合作?返佣怎么算?',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'member',
|
||||
icon: 'growth',
|
||||
@ -244,11 +259,27 @@ export const HOME_DOORS = [
|
||||
zh: { title: '报关退税', hint: '正式报关、退税,或没有专票怎么出。' },
|
||||
en: { title: 'Customs & VAT', hint: 'Declaration, refund, or shipping without invoices.' },
|
||||
},
|
||||
{
|
||||
id: 'agent',
|
||||
icon: 'human',
|
||||
intentId: 'agent' as const,
|
||||
primary: false,
|
||||
featured: true,
|
||||
zh: {
|
||||
title: '代理招募',
|
||||
hint: '独立管理系统管客户,运费分成最高 10%。',
|
||||
},
|
||||
en: {
|
||||
title: 'Become an agent',
|
||||
hint: 'Your own portal & clients — earn up to 10% freight commission.',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'member',
|
||||
icon: 'growth',
|
||||
intentId: 'member' as const,
|
||||
primary: false,
|
||||
featured: false,
|
||||
zh: { title: '会员成长', hint: '近 12 个月发货积分定级,基础运费最高 95 折。' },
|
||||
en: { title: 'Member tiers', hint: '12-month shipping points — up to 95% on base freight.' },
|
||||
},
|
||||
@ -257,6 +288,7 @@ export const HOME_DOORS = [
|
||||
icon: 'coin',
|
||||
intentId: 'invite' as const,
|
||||
primary: false,
|
||||
featured: false,
|
||||
zh: { title: '邀新分成', hint: '好友派送成功,你拿运费余额 + 10 元券。' },
|
||||
en: { title: 'Invite & earn', hint: 'Friends deliver → freight credit + ¥10 coupon.' },
|
||||
},
|
||||
@ -265,6 +297,7 @@ export const HOME_DOORS = [
|
||||
icon: 'coupon',
|
||||
intentId: 'newcomer' as const,
|
||||
primary: false,
|
||||
featured: false,
|
||||
zh: { title: '新人福利', hint: '注册送券:首单、备货、第二单都有。' },
|
||||
en: { title: 'New-seller deals', hint: 'Coupons for first ship, stock-up, and second ship.' },
|
||||
},
|
||||
|
||||
@ -2756,6 +2756,22 @@ body.command-home #navbar .site-nav-avatar:hover {
|
||||
|
||||
.ai-intent-grid--growth {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
grid-template-rows: auto auto;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.ai-intent-grid--growth .ai-intent-door.is-agent-tall {
|
||||
grid-row: span 2;
|
||||
align-self: stretch;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.ai-intent-grid--growth .ai-intent-door.is-growth-wide {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.ai-welcome__command .ai-intent-grid {
|
||||
@ -2870,6 +2886,44 @@ body.command-home #navbar .site-nav-avatar:hover {
|
||||
color: var(--ai-text-2);
|
||||
}
|
||||
|
||||
.ai-welcome__command .ai-intent-grid--growth .ai-intent-door.is-agent-tall {
|
||||
gap: 12px;
|
||||
padding: 18px 16px;
|
||||
background: color-mix(in srgb, #fff 55%, #e8eef3);
|
||||
box-shadow: var(--ai-paper-shadow-lift);
|
||||
}
|
||||
|
||||
.ai-welcome__command .ai-intent-grid--growth .ai-intent-door.is-agent-tall .ai-intent-door__icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.ai-welcome__command .ai-intent-grid--growth .ai-intent-door.is-agent-tall .ai-icon--illus {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
}
|
||||
|
||||
.ai-welcome__command .ai-intent-grid--growth .ai-intent-door.is-agent-tall .ai-intent-door__copy {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.ai-welcome__command .ai-intent-grid--growth .ai-intent-door.is-agent-tall .ai-intent-door__title {
|
||||
font-size: 0.98rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.ai-welcome__command .ai-intent-grid--growth .ai-intent-door.is-agent-tall .ai-intent-door__hint {
|
||||
font-size: 0.8rem;
|
||||
color: var(--ai-text-2);
|
||||
-webkit-line-clamp: 4;
|
||||
line-clamp: 4;
|
||||
}
|
||||
|
||||
.ai-welcome-plane {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
@ -4351,9 +4405,22 @@ body.command-home #navbar .site-nav-avatar:hover {
|
||||
width: min(420px, 52vw);
|
||||
}
|
||||
|
||||
.ai-intent-grid--primary,
|
||||
.ai-intent-grid--primary {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.ai-intent-grid--growth {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
grid-template-rows: auto;
|
||||
}
|
||||
|
||||
.ai-intent-grid--growth .ai-intent-door.is-agent-tall,
|
||||
.ai-intent-grid--growth .ai-intent-door.is-growth-wide {
|
||||
grid-row: auto;
|
||||
grid-column: auto;
|
||||
height: auto;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.ai-intent-grid--primary .ai-intent-door:last-child {
|
||||
@ -4391,9 +4458,22 @@ body.command-home #navbar .site-nav-avatar:hover {
|
||||
}
|
||||
|
||||
.ai-intent-grid--growth .ai-intent-door {
|
||||
flex: 0 0 min(78vw, 260px);
|
||||
flex: 0 0 min(72vw, 240px);
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
|
||||
.ai-intent-grid--growth .ai-intent-door.is-agent-tall,
|
||||
.ai-intent-grid--growth .ai-intent-door.is-growth-wide {
|
||||
grid-row: auto;
|
||||
grid-column: auto;
|
||||
height: auto;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.ai-welcome__command .ai-intent-grid--growth .ai-intent-door.is-agent-tall {
|
||||
padding: 14px;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user