بنقرة واحدة
huo15-owl-dev
OWL (Odoo Web Library) 开发技能 — Odoo 前端组件开发框架,基于 QWeb 模板、响应式状态管理、OWL 组件生命周期
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
OWL (Odoo Web Library) 开发技能 — Odoo 前端组件开发框架,基于 QWeb 模板、响应式状态管理、OWL 组件生命周期
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
【青岛火一五信息科技有限公司】企业级 Word 文档生成技能,支持两种模式:规则模式(默认)和模板模式。触发词:写word,写文档、生成word、生成文档、创建文档、.docx、Word文档、写合同、写方案、写报告、写会议纪要、按模板生成、导出PDF、Word转PDF、生成PDF。
辉火 Token 优化器 — 在不降低 OpenClaw 性能的前提下,系统化扫描、清理、监控工作区 token 消耗。安全模式:默认 dry-run,备份先行,AGENTS 永不自动替换。
用宝塔(BT-Panel)HTTP API 操控火一五的服务器——查系统状态/内存/磁盘/负载、管网站 (停站/启站/重载)、数据库、SSL 证书、计划任务、Docker,以及调用任意宝塔接口。 当用户说"看下 xx 服务器状态/负载/磁盘""重启/停掉某网站""宝塔面板""续证书" "列计划任务""管一下服务器""bt panel"等运维意图时使用。支持多面板。 凭据在 ~/.huo15/baota.json,脚本 scripts/bt_api.py。
Use this skill whenever the user wants to manage their work in the 火一五/辉火云 company Odoo system (www.huo15.com, db=huo15) — to-dos / tasks ("记个待办" "加任务" "我的待办"), projects, timesheets ("记工时"), CRM leads & opportunities ("新建商机" "跟进客户" "销售管道"), follow-up activities & reminders ("提醒我" "3天后回访"), calendar events & meetings ("安排会议" "我这周日程" "提前30分钟提醒"), knowledge-base articles ("查/写知识库" "产品手册"), or document files ("找文档" "上传文件"). Also a one-stop "我今天/本周要做什么" briefing. Triggers on odoo, 辉火云企业套件, 公司系统, /odoo/to-do /project /timesheets /crm /calendar /knowledge /documents /sale /purchase /inventory, or "登录公司系统/保存 odoo 账号". Treat bare work-context mentions of 待办/任务/项目/工时/客户/商机/会议/日程/提醒/知识库/文档/销售/采购/库存/订单/发货/收货 as this skill. First run: login.py init (地址/数据库/账号/密码 → ~/.huo15/tools.md). Pure standard library, zero dependencies.
火一五多智能体协同 - 基于 OpenClaw sessions_spawn 的多 Agent 并行工作系统。支持协调者模式、任务分配、结果汇总。触发词:多智能体协同、多 Agent、并行任务、协调者模式。
Use this skill when the user says "你好世界" / "你好世界初始化" / "初始化龙虾" / "龙虾初始化" / "初始化" / "bootstrap" / "hello world" / "onboarding" / "重新认识一下" / "重置我的偏好" / "更新画像", OR when the current working directory contains a BOOTSTRAP.md file. This skill guides users through a 4-step onboarding process to initialize their OpenClaw workspace by creating IDENTITY.md, USER.md, SOUL.md, TOOLS.md, and AGENTS.md files.
| name | huo15-owl-dev |
| version | 1.0.0 |
| slug | owl-dev |
| description | OWL (Odoo Web Library) 开发技能 — Odoo 前端组件开发框架,基于 QWeb 模板、响应式状态管理、OWL 组件生命周期 |
| aliases | ["OWL组件开发","Odoo前端","OWL","odoo web library","OWL dev"] |
用户提到以下内容时激活本技能:
odoo.define / owl.componentOWL(Odoo Web Library)是 Odoo 15+ 引入的新一代前端框架,用于替代旧的 web.old(backbone-based)架构。它基于:
| 特性 | OWL(新) | web.old(旧) |
|---|---|---|
| 模板 | QWeb XML | QWeb XML |
| 状态 | useState / useRefs | 不存在 |
| 组件 | 类 + 装饰器 | Widget 基类 |
| 生命周期 | PoP (Props on Props) | 不存在 |
| TypeScript | 原生支持 | 不支持 |
| 响应式 | 自动追踪依赖 | 手动绑定 |
| 性能 | 虚拟DOM + 懒加载 | 完整重渲染 |
/** @odoo-module **/
import { Component, useState, useEnv } from "@odoo/owl";
export class MyComponent extends Component {
static template = "my_module.MyComponent";
// 响应式状态
state = useState({
value: 0,
items: [],
loading: false,
});
// 组件挂载时调用
onWillMount() {
this.loadData();
}
async loadData() {
this.state.loading = true;
try {
const result = await this.rpc("/my_module/data");
this.state.items = result;
} finally {
this.state.loading = false;
}
}
increment() {
this.state.value++;
}
onClick() {
this.trigger("custom-event", { payload: this.state.value });
}
}
my_module/
├── static/
│ └── src/
│ ├── js/
│ │ ├── components/
│ │ │ ├── my_component/
│ │ │ │ ├── my_component.xml # QWeb 模板
│ │ │ │ └── my_component.ts # 组件逻辑
│ │ │ └── other_component/
│ │ │ ├── other_component.xml
│ │ │ └── other_component.ts
│ │ └── pages/
│ │ └── my_page.xml
│ └── css/
│ └── my_component.scss
├── views/
│ └── templates.xml # Web 入口
└── __manifest__.py
__manifest__.py 配置{
'name': '我的模块',
'depends': ['web'],
'assets': {
'web.assets_backend': [
('include', 'my_module.static.src.js.components.my_component.my_component_xml'),
'my_module/static/src/js/components/my_component/my_component.ts',
'my_module/static/src/css/my_component.scss',
],
},
}
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets_backend" name="my_module assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/my_module/static/src/css/my_component.scss"/>
<script type="module" src="/my_module/static/src/js/components/my_component/my_component.es.js"/>
</xpath>
</template>
</odoo>
// my_component.ts
import { Component } from "@odoo/owl";
import { registry } from "@web/core/registry";
export class MyComponent extends Component {
static template = "my_module.MyComponent";
}
registry.category("components").add("my_component", MyComponent);
<-- parent.xml -->
<MyComponent t-component="'my_component'" />
import { Component } from "@odoo/owl";
import { Props } from "@odoo/owl";
interface MyProps {
title: string;
count: number;
onUpdate: (value: number) => void;
}
export class MyComponent extends Component<Props & MyProps> {
static template = "my_module.MyComponent";
static props = {
title: { type: String, optional: true },
count: { type: Number },
onUpdate: { type: Function },
};
updateCount(newCount: number) {
this.props.onUpdate(newCount);
}
}
// 简单类型
static props = {
name: String, // string
active: Boolean, // boolean
count: Number, // number
items: Array, // any[]
}
// 可选
static props = {
title: { type: String, optional: true },
}
// 对象类型
static props = {
config: { type: Object },
}
// 函数(回调)
static props = {
onChange: Function,
onSubmit: { type: Function, optional: true },
}
// 数组对象
static props = {
items: {
type: Array,
validate: (items) => items.every(i => typeof i === 'object'),
},
}
import { useState } from "@odoo/owl";
export class MyComponent extends Component {
state = useState({
// 原始类型
count: 0,
name: "",
active: false,
// 嵌套对象
user: {
name: "张三",
age: 30,
},
// 数组
items: [],
// 特殊值
loading: false,
error: null,
});
}
// 直接赋值(OWL 自动追踪)
this.state.count = 5;
this.state.user.name = "李四";
this.state.items.push({ id: 1, name: "item" });
// 数组操作
this.state.items = [...this.state.items, newItem]; // 替换触发更新
this.state.items.filter(i => i.id !== deleteId); // 删除
// 批量更新(函数式)
this.state = {
...this.state,
count: this.state.count + 1,
loading: false,
};
export class MyComponent extends Component {
// ===== 创建阶段 =====
/** 组件即将挂载(异步) */
async onWillMount() {
// 可返回 Promise
}
/** 组件即将渲染 */
onWillRender() {}
// ===== 挂载阶段 =====
/** 组件 DOM 已挂载 */
onMounted() {
// 可添加 DOM 事件监听
const el = this.el;
}
/** 组件渲染完成 */
onRendered() {}
// ===== 更新阶段 =====
/** Props 或 State 即将更新 */
onWillUpdateProps(nextProps) {
// 准备数据
}
/** 组件即将重新渲染 */
onWillPatch() {}
/** 组件 DOM patch 完成 */
onPatched() {
// DOM 更新后执行
}
// ===== 卸载阶段 =====
/** 组件即将卸载 */
onWillUnmount() {
// 清理定时器/事件监听
}
/** 组件已卸载 */
onUnmounted() {}
}
组件创建 → onWillMount → 渲染 → onMounted
↓
props/state 变化 → onWillUpdateProps → onWillPatch → patch → onPatched
↓
组件卸载 → onWillUnmount → onUnmounted
<templates>
<t t-name="my_module.MyComponent">
<div class="my-component">
<h1 t-esc="props.title"/>
<!-- 条件渲染 -->
<span t-if="state.loading">加载中...</span>
<!-- 循环 -->
<ul>
<li t-foreach="state.items" t-as="item" t-key="item.id">
<span t-esc="item.name"/>
</li>
</ul>
<!-- 事件绑定 -->
<button t-on-click="increment">点击</button>
<button t-on-click.self="doSomething">仅自身</button>
<!-- 子组件 -->
<SubComponent t-component="'sub_component'" t-props="subProps"/>
</div>
</t>
</templates>
| 指令 | 用法 | 说明 |
|---|---|---|
t-esc | <span t-esc="value"/> | 转义文本 |
t-raw | <span t-raw="html"/> | 原始 HTML |
t-if | <div t-if="state.show"/> | 条件渲染 |
t-else | <div t-elif="state.show2"/> | 否则 |
t-foreach | <li t-foreach="arr" t-as="i"/> | 循环 |
t-key | t-key="item.id" | 循环 key |
t-on-* | t-on-click="handler" | 事件绑定 |
t-model | t-model="state.inputValue" | 双向绑定 |
t-ref | <div t-ref="myDiv"/> | DOM 引用 |
t-set | <t t-set="val" t-value="123"/> | 设置变量 |
t-call | <t t-call="other_template"/> | 引用模板 |
t-translation | t-translation="on" | 翻译标记 |
<!-- 基础 -->
<button t-on-click="onClick">点击</button>
<!-- 阻止冒泡 -->
<button t-on-click.stop="onClick">阻止冒泡</button>
<!-- 阻止默认 -->
<a href="#" t-on-click.prevent="onLink">阻止默认</a>
<!-- 修饰符组合 -->
<button t-on-click.stop.prevent="onClick">组合</button>
<!-- 传参数 -->
<button t-on-click="(ev) => onClick(42, ev)">传参</button>
<button t-on-click="() => onClick(42)">箭头函数</button>
<!-- 事件对象 -->
<button t-on-click="onClick">访问事件: ev.target</button>
<!-- 输入框 -->
<input type="text" t-model="state.inputValue"/>
<!-- 多行文本 -->
<textarea t-model="state.description"/>
<!-- 复选框 -->
<input type="checkbox" t-model="state.checked"/>
<!-- 下拉选择 -->
<select t-model="state.selectedId">
<option value="">请选择</option>
<option t-foreach="state.options"
t-as="opt"
t-att-value="opt.id"
t-esc="opt.name"/>
</select>
import {
useState, // 响应式状态
useRef, // DOM 引用
useEffect, // 副作用
useEnv, // 环境(env)
useDispatch, // dispatch (action-service)
useSelectors, // selectors
} from "@odoo/owl";
import { useRef } from "@odoo/owl";
export class MyComponent extends Component {
myInput = useRef("myInput"); // 声明 ref
onMounted() {
// 访问 DOM
this.myInput.el.focus();
}
getValue() {
return this.myInput.el.value;
}
}
// 模板中使用
<input t-ref="myInput" type="text"/>
import { useEffect } from "@odoo/owl";
export class MyComponent extends Component {
state = useState({ data: null });
setup() {
// 每次渲染后执行
useEffect(
() => console.log("count changed:", this.state.count),
() => [this.state.count] // 依赖数组
);
// 清理函数
useEffect(
() => {
const timer = setInterval(() => this.tick(), 1000);
return () => clearInterval(timer); // 返回清理函数
},
() => []
);
}
}
import { useEnv } from "@odoo/owl";
export class MyComponent extends Component {
setup() {
const env = useEnv();
// 访问 Odoo env
const user = env.session.user;
const db = env.session.db;
}
}
import { useService } from "@web/core/utils/hooks";
import { registry } from "@web/core/registry";
export class MyComponent extends Component {
setup() {
this.actionService = useService("action");
this.ormService = useService("orm");
}
async doAction() {
// 触发窗口动作
await this.actionService.doAction("my_module.action_wizard");
}
async searchData() {
const result = await this.ormService.call(
"my.model", // 模型
"search_read", // 方法
[[['active', '=', true]], ['name', 'id']], // domain, fields
{ context: { ... } }
);
return result;
}
}
// Action 服务
this.actionService.doAction({
type: "ir.actions.act_window",
res_model: "my.model",
view_mode: "form",
views: [[false, "form"]],
target: "new", // new/current/main
});
// 通知服务
useService("notification").notify("标题", "内容", "success");
// 对话框服务
useService("dialog").add(ConfirmDialog, {
title: "确认删除",
body: "确定要删除吗?",
confirmLabel: "删除",
cancelLabel: "取消",
confirmCallback: () => this.doDelete(),
});
// RPC 服务
const result = await useService("rpc").call("/my_route", params);
// my_service.js
import { registry } from "@web/core/registry";
export const myService = {
start(env) {
return {
async getData(id) {
return await env.services.orm.call(
"my.model", "read", [id]
);
},
};
},
};
registry.category("services").add("myService", myService);
// __manifest__.py
'assets': {
'web.assets_backend': [
'my_module/static/src/js/routes.js',
],
},
// routes.js
import { registry } from "@web/core/registry";
import { route } from "@web/core/router/route";
registry.category("main").add("my_module.dashboard",
route("/my_module/dashboard", MyComponent, {
load: async (env) => {
// 加载数据
return await env.services.orm.call("my.model", "get_data", []);
},
})
);
<span>这是中文文本</span>
<span t-esc="env._t('Hello World')"/>
import { useEnv } from "@odoo/owl";
export class MyComponent extends Component {
setup() {
const env = useEnv();
const msg = env._t("Hello");
const formatted = env._t("You have %s items", [count]);
}
}
// static/src/css/my_component.scss
.my-component {
padding: 16px;
background: #fff;
border-radius: 4px;
&__header {
font-size: 18px;
font-weight: 600;
}
&__list {
list-style: none;
&-item {
padding: 8px;
&--active {
background: #f0f0f0;
}
}
}
&--loading {
opacity: 0.6;
pointer-events: none;
}
}
.o_my_component {
--my-color: #{$primary};
--my-spacing: 16px;
padding: var(--my-spacing);
color: var(--my-color);
}
// my_component.test.js
import { describe, expect, test } from "@odoo/owl";
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
import { MyComponent } from "./my_component";
describe("MyComponent", () => {
test("renders correctly", async () => {
const env = await makeTestEnv();
const wrapper = new MyComponent(null, { env });
await wrapper.mount(fixture);
expect(wrapper.el.textContent).toContain("标题");
});
test("increments counter on click", async () => {
const env = await makeTestEnv();
const comp = new MyComponent(null, { env });
comp.state.count = 0;
await comp.mount(fixture);
await comp.onClick();
expect(comp.state.count).toBe(1);
});
});
import { makeTestEnv, makeFakeCookieService } from "@web/../tests/helpers/mock_env";
import { dom } from "@web/../tests/helpers/dom";
// 模拟 RPC
await mockServer.mockModel("my.model", {
search_read: () => [{ id: 1, name: "Test" }],
});
Odoo Web Developer 浏览器插件console.log 替换为 console.debug 减少干扰| 错误 | 原因 | 解决 |
|---|---|---|
| Template not found | __manifest__.py 未注册 assets | 检查 assets_backend 配置 |
| Component not rendering | t-component 名称错误 | 确认 registry.category("components").add() 名称 |
| State not updating | 直接修改对象而非赋值 | this.state.items.push() 不触发更新,需替换 |
| Event not working | t-on-click 语法错误 | 检查是否少了括号 t-on-click="handler" |
| Props undefined | 子组件 Props 未定义 | 检查 static props 定义 |
# Odoo 开发者模式已启用热重载
# 修改 .ts/.xml 文件后,刷新浏览器即可
# 如需完全重载:Ctrl+Shift+R (强制刷新)
components/my_comp/my_comp.xml + my_comp.tsMyComponentonClick, getData// ✅ 使用 useState 细粒度更新
state = useState({ count: 0, items: [] });
// ✅ 避免在 render 中创建函数
class MyComponent extends Component {
onClick = (id) => {
// 使用箭头函数避免重复创建
};
}
// ✅ 使用 t-key 帮助 OWL 高效更新
<li t-foreach="items" t-as="item" t-key="item.id">
t-esc 而非 t-rawt-raw 处理用户输入import { Component, useState, useRef, useEffect, useEnv, useService } from "@odoo/owl";
import { registry } from "@web/core/registry";
import { ormService } from "@web/core/orm_service";
import { actionService } from "@web/core/action_service";
// 可选:类组件装饰器
@registerComponent("my_component")
export class MyComponent extends Component {
// ...
}
static props = {
// 必需
title: String,
count: Number,
// 可选
subtitle: { type: String, optional: true },
// 回调
onChange: Function,
// 复杂类型
config: { type: Object },
items: Array,
}
本技能基于 OWL v2.x(Odoo 16+),适配辉火云企业套件前端开发环境。