Doc: dev i18n (#858)
* docs: add guide index table and i18n * docs: add dev i18n sample
This commit is contained in:
parent
82560b926b
commit
b9ce35d621
@ -185,8 +185,8 @@ export default {
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '@nocobase/acl',
|
||||
path: '/api/acl',
|
||||
title: '@nocobase/actions',
|
||||
path: '/api/actions',
|
||||
},
|
||||
{
|
||||
title: '@nocobase/client',
|
||||
@ -219,12 +219,12 @@ export default {
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '@nocobase/cli',
|
||||
path: '/api/cli',
|
||||
title: '@nocobase/acl',
|
||||
path: '/api/acl',
|
||||
},
|
||||
{
|
||||
title: '@nocobase/actions',
|
||||
path: '/api/actions',
|
||||
title: '@nocobase/cli',
|
||||
path: '/api/cli',
|
||||
},
|
||||
{
|
||||
title: '@nocobase/sdk',
|
||||
|
@ -57,6 +57,10 @@ const app = new Application({
|
||||
|
||||
## 实例成员
|
||||
|
||||
### `cli`
|
||||
|
||||
命令行工具实例,参考 npm 包 [Commander](https://www.npmjs.com/package/commander)。
|
||||
|
||||
### `db`
|
||||
|
||||
数据库实例,相关 API 参考 [Database](/api/database)。
|
||||
@ -105,6 +109,7 @@ NocoBase 默认对 context 注入了以下成员,可以在请求处理函数
|
||||
| `ctx.resourcer` | `Resourcer` | 资源路由管理器实例 |
|
||||
| `ctx.action` | `Action` | 资源操作相关对象实例 |
|
||||
| `ctx.i18n` | `I18n` | 国际化实例 |
|
||||
| `ctx.t` | `i18n.t` | 国际化翻译函数快捷方式 |
|
||||
| `ctx.getBearerToken()` | `Function` | 获取请求头中的 bearer token |
|
||||
|
||||
## 实例方法
|
||||
|
@ -1 +1,233 @@
|
||||
# I18n
|
||||
# 国际化
|
||||
|
||||
## 基础概念
|
||||
|
||||
多语言国际化支持根据服务端和客户端分为两大部分,各自有相应的实现。
|
||||
|
||||
语言配置均为普通的 JSON 对象键值对,如果没有翻译文件(或省略编写)的话会直接输出键名的字符串。
|
||||
|
||||
### 服务端
|
||||
|
||||
服务端的多语言国际化基于 npm 包 [i18next](https://npmjs.com/package/i18next) 实现,在服务端应用初始化时会创建一个 i18next 实例,同时也会将此实例注入到请求上下文(`context`)中,以供在各处方便的使用。
|
||||
|
||||
在创建服务端 Application 实例时可以传入配置对应的初始化参数:
|
||||
|
||||
```ts
|
||||
import { Application } from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
i18n: {
|
||||
defaultNS: 'test',
|
||||
resources: {
|
||||
'en-US': {
|
||||
test: {
|
||||
hello: 'Hello',
|
||||
},
|
||||
},
|
||||
'zh-CN': {
|
||||
test: {
|
||||
hello: '你好',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
或者在插件中对已存在的 app 实例添加语言数据至对应命名空间:
|
||||
|
||||
```ts
|
||||
app.i18n.addResources('zh-CN', 'test', {
|
||||
Hello: '你好',
|
||||
World: '世界',
|
||||
});
|
||||
|
||||
app.i18n.addResources('en-US', 'test', {
|
||||
Hello: 'Hello',
|
||||
World: 'World',
|
||||
});
|
||||
```
|
||||
|
||||
基于应用:
|
||||
|
||||
```ts
|
||||
app.i18n.t('World') // “世界”或“World”
|
||||
```
|
||||
|
||||
基于请求:
|
||||
|
||||
```ts
|
||||
app.resource({
|
||||
name: 'test',
|
||||
actions: {
|
||||
async get(ctx, next) {
|
||||
ctx.body = `${ctx.t('Hello')} ${ctx.t('World')}`;
|
||||
await next();
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
通常服务端的多语言处理主要用于错误信息的输出。
|
||||
|
||||
### 客户端
|
||||
|
||||
客户端的多语言国际化基于 npm 包 [react-i18next](https://npmjs.com/package/react-i18next) 实现,在应用顶层提供了 `<I18nextProvider>` 组件的包装,可以在任意位置直接使用相关的方法。
|
||||
|
||||
在组件中调用翻译函数:
|
||||
|
||||
```tsx | pure
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function MyComponent() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>{t('World')}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
其中 `compile()` 方法是专门针对 SchemaComponent 中纯 JSON 配置提供的编译方法,执行后也可以得到对应的翻译结果。
|
||||
|
||||
在 Schema 组件中使用:
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SchemaComponent } from '@nocobase/client';
|
||||
|
||||
export default function MySchemaComponent() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
type: 'string',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
value: '{{t("Hello")}}'
|
||||
},
|
||||
}}
|
||||
scope={{
|
||||
t
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
在 SchemaComponent 组件中使用时,可以在 schema 配置里直接使用 JSON 的可编译字符串模板表达要翻译的语言片段键名。
|
||||
|
||||
## 示例
|
||||
|
||||
### 服务端错误提示
|
||||
|
||||
例如用户在店铺对某个商品下单时,如果商品的库存不够,或者未上架,那么下单接口被调用时,应该返回相应的错误。
|
||||
|
||||
```ts
|
||||
const namespace = 'shop';
|
||||
|
||||
export default class ShopPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.i18n.addResources('zh-CN', namespace, {
|
||||
'No such product': '商品不存在',
|
||||
'Product not on sale': '商品已下架',
|
||||
'Out of stock': '库存不足',
|
||||
});
|
||||
|
||||
this.app.resource({
|
||||
name: 'orders',
|
||||
actions: {
|
||||
async create(ctx, next) {
|
||||
const productRepo = ctx.db.getRepository('products');
|
||||
const product = await productRepo.findOne({
|
||||
filterByTk: ctx.action.params.filterByTk
|
||||
});
|
||||
|
||||
if (!product) {
|
||||
return ctx.throw(404, ctx.t('No such product'));
|
||||
}
|
||||
|
||||
if (!product.enabled) {
|
||||
return ctx.throw(400, ctx.t('Product not on sale'));
|
||||
}
|
||||
|
||||
if (!product.inventory) {
|
||||
return ctx.throw(400, ctx.t('Out of stock'));
|
||||
}
|
||||
|
||||
const orderRepo = ctx.db.getRepository('orders');
|
||||
ctx.body = await orderRepo.create({
|
||||
values: {
|
||||
productId: product.id,
|
||||
quantity: 1,
|
||||
totalPrice: product.price,
|
||||
userId: ctx.state.currentUser.id
|
||||
}
|
||||
});
|
||||
|
||||
next();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 客户端组件多语言
|
||||
|
||||
例如订单状态的组件,根据不同值有不同的文本显示:
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { Select } from 'antd';
|
||||
import i18next from 'i18next';
|
||||
import { I18nextProvider, initReactI18next, useTranslation } from 'react-i18next';
|
||||
|
||||
const i18n = i18next.createInstance();
|
||||
i18n.use(initReactI18next).init({
|
||||
lng: 'zh-CN',
|
||||
defaultNS: 'client',
|
||||
resources: {
|
||||
'zh-CN': {
|
||||
client: {
|
||||
Pending: '已下单',
|
||||
Paid: '已支付',
|
||||
Delivered: '已发货',
|
||||
Received: '已签收'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const ORDER_STATUS_LIST = [
|
||||
{ value: -1, label: 'Canceled (untranslated)' },
|
||||
{ value: 0, label: 'Pending' },
|
||||
{ value: 1, label: 'Paid' },
|
||||
{ value: 2, label: 'Delivered' },
|
||||
{ value: 3, label: 'Received' },
|
||||
]
|
||||
|
||||
function OrderStatusSelect() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Select style={{ minWidth: '8em' }}>
|
||||
{ORDER_STATUS_LIST.map(item => (
|
||||
<Select.Option value={item.value}>{t(item.label)}</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
export default function () {
|
||||
return (
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<OrderStatusSelect />
|
||||
</I18nextProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
@ -1 +1,76 @@
|
||||
# Overview
|
||||
# 扩展点索引
|
||||
|
||||
## 内核模块
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>扩展点</th>
|
||||
<th>相关介绍</th>
|
||||
<th>相关 API</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>扩展数据库字段类型</td>
|
||||
<td rowspan="3"><a href="/development/guide/collections-fields">数据表与字段</a></td>
|
||||
<td><a href="/api/database#registerfieldtypes"><code>db.registerFieldType()</code></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>扩展已有数据库表的字段</td>
|
||||
<td><a href="/api/database#extendcollection"><code>db.extendcollection()</code></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>扩展查询比较运算符</td>
|
||||
<td><a href="/api/database#registeroperators"><code>db.registerOperators()</code></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>自定义数据仓库操作</td>
|
||||
<td></td>
|
||||
<td><a href="/api/database#registerrepositories"><code>db.registerRepositories()</code></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>扩展针对全局资源的操作</td>
|
||||
<td rowspan="3"><a href="/development/guide/resources-actions">资源与操作</a></td>
|
||||
<td><a href="/api/resourcer#registeractions"><code>resourcer.registerActions()</code></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>扩展针对全局资源的中间件</td>
|
||||
<td><a href="/api/resourcer#use"><code>resourcer.use()</code></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>自定义资源及操作</td>
|
||||
<td><a href="/api/resourcer#define"><code>resourcer.define()</code></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>自定义路由与页面</td>
|
||||
<td><a href="/development/guide/ui-router">界面路由</a></td>
|
||||
<td><a href="/api/client/route-switch"><code>RouteSwitch</code></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>扩展自定义组件</td>
|
||||
<td><a href="/development/guide/ui-schema-designer/extending-schema-components">扩展 Schema 组件</a></td>
|
||||
<td><a href="/api/client/schema-designer/schema-component"><code>SchemaComponent</code></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>扩展自定义区块类型</td>
|
||||
<td><a href="/development/guide/ui-schema-designer/designable">Schema 设计能力</a></td>
|
||||
<td><a href="/api/client/schema-designer/schema-initializer"><code>SchemaInitializer</code></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>多语言扩展</td>
|
||||
<td><a href="/development/guide/i18n">国际化</a></td>
|
||||
<td><a href="/api/server/application#i18n"><code>app.i18n</code></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>扩展子命令</td>
|
||||
<td><a href="/development/guide/commands">命令行工具</a></td>
|
||||
<td><a href="/api/server/application#cli"><code>app.i18n</code></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## 内置插件
|
||||
|
||||
TODO
|
||||
|
@ -2,25 +2,13 @@
|
||||
|
||||
## 基本概念
|
||||
|
||||
NocoBase 的核心是一个基于微内核、插件化设计的开发框架。在此基础上天然的支持任意的扩展开发。
|
||||
|
||||
## 特性
|
||||
|
||||
NocoBase 的核心是一个基于微内核、插件化设计的开发框架。在此基础上天然的支持任意的扩展开发。
|
||||
|
||||
## 扩展能力
|
||||
|
||||
基于 NocoBase 的微内核设计,在很多模块都考虑相应合理的可扩展性,并向外部暴露了一些扩展点。例如:
|
||||
|
||||
* 数据库字段类型
|
||||
* 已有数据库表的字段
|
||||
* 通用操作和对特定的资源的操作
|
||||
* 针对资源和操作的预处理和后处理
|
||||
* 字段的前端展示组件
|
||||
* 页面的区块类型
|
||||
* 用户插件的登入认证方法
|
||||
* 工作流插件的节点类型和触发类型
|
||||
* ……
|
||||
|
||||
在具体业务中可以根据需求基于以上扩展点进行相应的扩展。
|
||||
基于 NocoBase 的微内核设计,在很多模块都考虑相应的可扩展性,并向外部暴露了一些扩展点,几乎覆盖全部的应用生命周期,在具体业务中可以根据需求基于众多扩展点进行相应的扩展。详情参考 [扩展索引](/development/guide)。
|
||||
|
||||
## 插件生命周期
|
||||
|
||||
@ -40,3 +28,6 @@ NocoBase 的核心是一个基于微内核、插件化设计的开发框架。
|
||||
|
||||
## 学习路线
|
||||
|
||||
1. 编写第一个插件
|
||||
2. 数据表建模
|
||||
3.
|
||||
|
49
packages/samples/shop-i18n/README.md
Normal file
49
packages/samples/shop-i18n/README.md
Normal file
@ -0,0 +1,49 @@
|
||||
# Modeling for simple shop scenario
|
||||
|
||||
## Register
|
||||
|
||||
```ts
|
||||
yarn pm add sample-shop-modeling
|
||||
```
|
||||
|
||||
## Activate
|
||||
|
||||
```bash
|
||||
yarn pm enable sample-shop-modeling
|
||||
```
|
||||
|
||||
## Launch the app
|
||||
|
||||
```bash
|
||||
# for development
|
||||
yarn dev
|
||||
|
||||
# for production
|
||||
yarn build
|
||||
yarn start
|
||||
```
|
||||
|
||||
## Connect to the API
|
||||
|
||||
### Products API
|
||||
|
||||
```bash
|
||||
# create a product
|
||||
curl -X POST -d '{"title": "iPhone 14 Pro", "price": "7999", "enabled": true, "inventory": 10}' "http://localhost:13000/api/products"
|
||||
|
||||
# list products
|
||||
curl "http://localhost:13000/api/products"
|
||||
|
||||
# get product which id=1
|
||||
curl "http://localhost:13000/api/products?filterByTk=1"
|
||||
```
|
||||
|
||||
### Orders API
|
||||
|
||||
```bash
|
||||
# create a order
|
||||
curl -X POST -d '{"productId": 1, "quantity": 1, "totalPrice": "7999", "userId": 1}' 'http://localhost:13000/api/orders'
|
||||
|
||||
# list orders which userId=1 with product
|
||||
curl 'http://localhost:13000/api/orders?filter={"userId":1}&appends=product'
|
||||
```
|
4
packages/samples/shop-i18n/client.d.ts
vendored
Executable file
4
packages/samples/shop-i18n/client.d.ts
vendored
Executable file
@ -0,0 +1,4 @@
|
||||
// @ts-nocheck
|
||||
export * from './lib/client';
|
||||
export { default } from './lib/client';
|
||||
|
30
packages/samples/shop-i18n/client.js
Executable file
30
packages/samples/shop-i18n/client.js
Executable file
@ -0,0 +1,30 @@
|
||||
"use strict";
|
||||
|
||||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||
|
||||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||
|
||||
var _index = _interopRequireWildcard(require("./lib/client"));
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
var _exportNames = {};
|
||||
Object.defineProperty(exports, "default", {
|
||||
enumerable: true,
|
||||
get: function get() {
|
||||
return _index.default;
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(_index).forEach(function (key) {
|
||||
if (key === "default" || key === "__esModule") return;
|
||||
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
||||
if (key in exports && exports[key] === _index[key]) return;
|
||||
Object.defineProperty(exports, key, {
|
||||
enumerable: true,
|
||||
get: function get() {
|
||||
return _index[key];
|
||||
}
|
||||
});
|
||||
});
|
18
packages/samples/shop-i18n/package.json
Normal file
18
packages/samples/shop-i18n/package.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-sample-shop-i18n",
|
||||
"version": "0.7.4-alpha.7",
|
||||
"main": "lib/server/index.js",
|
||||
"dependencies": {
|
||||
"nodejs-snowflake": "2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nocobase/client": "0.7.4-alpha.7",
|
||||
"@nocobase/server": "0.7.4-alpha.7",
|
||||
"@nocobase/test": "0.7.4-alpha.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nocobase/client": "*",
|
||||
"@nocobase/server": "*",
|
||||
"@nocobase/test": "*"
|
||||
}
|
||||
}
|
4
packages/samples/shop-i18n/server.d.ts
vendored
Executable file
4
packages/samples/shop-i18n/server.d.ts
vendored
Executable file
@ -0,0 +1,4 @@
|
||||
// @ts-nocheck
|
||||
export * from './lib/server';
|
||||
export { default } from './lib/server';
|
||||
|
30
packages/samples/shop-i18n/server.js
Executable file
30
packages/samples/shop-i18n/server.js
Executable file
@ -0,0 +1,30 @@
|
||||
"use strict";
|
||||
|
||||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||
|
||||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||
|
||||
var _index = _interopRequireWildcard(require("./lib/server"));
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
var _exportNames = {};
|
||||
Object.defineProperty(exports, "default", {
|
||||
enumerable: true,
|
||||
get: function get() {
|
||||
return _index.default;
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(_index).forEach(function (key) {
|
||||
if (key === "default" || key === "__esModule") return;
|
||||
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
||||
if (key in exports && exports[key] === _index[key]) return;
|
||||
Object.defineProperty(exports, key, {
|
||||
enumerable: true,
|
||||
get: function get() {
|
||||
return _index[key];
|
||||
}
|
||||
});
|
||||
});
|
5
packages/samples/shop-i18n/src/client/index.tsx
Normal file
5
packages/samples/shop-i18n/src/client/index.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
export default React.memo((props) => {
|
||||
return <>{props.children}</>;
|
||||
});
|
1
packages/samples/shop-i18n/src/index.ts
Normal file
1
packages/samples/shop-i18n/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './server';
|
@ -0,0 +1,13 @@
|
||||
export default {
|
||||
name: 'categories',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title'
|
||||
},
|
||||
{
|
||||
type: 'hasMany',
|
||||
name: 'products',
|
||||
}
|
||||
]
|
||||
};
|
25
packages/samples/shop-i18n/src/server/collections/orders.ts
Normal file
25
packages/samples/shop-i18n/src/server/collections/orders.ts
Normal file
@ -0,0 +1,25 @@
|
||||
export default {
|
||||
name: 'orders',
|
||||
fields: [
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'product'
|
||||
},
|
||||
{
|
||||
type: 'integer',
|
||||
name: 'quantity'
|
||||
},
|
||||
{
|
||||
type: 'integer',
|
||||
name: 'totalPrice'
|
||||
},
|
||||
{
|
||||
type: 'integer',
|
||||
name: 'status'
|
||||
},
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'user'
|
||||
}
|
||||
]
|
||||
};
|
@ -0,0 +1,21 @@
|
||||
export default {
|
||||
name: 'products',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title'
|
||||
},
|
||||
{
|
||||
type: 'integer',
|
||||
name: 'price'
|
||||
},
|
||||
{
|
||||
type: 'boolean',
|
||||
name: 'enabled'
|
||||
},
|
||||
{
|
||||
type: 'integer',
|
||||
name: 'inventory'
|
||||
}
|
||||
]
|
||||
};
|
65
packages/samples/shop-i18n/src/server/index.ts
Normal file
65
packages/samples/shop-i18n/src/server/index.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import path from 'path';
|
||||
|
||||
import { InstallOptions, Plugin } from '@nocobase/server';
|
||||
|
||||
export class ShopPlugin extends Plugin {
|
||||
getName(): string {
|
||||
return this.getPackageName(__dirname);
|
||||
}
|
||||
|
||||
beforeLoad() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
async load() {
|
||||
await this.db.import({
|
||||
directory: path.resolve(__dirname, 'collections'),
|
||||
});
|
||||
|
||||
this.app.resource({
|
||||
name: 'order',
|
||||
actions: {
|
||||
async create(ctx, next) {
|
||||
const productRepo = ctx.db.getRepository('products');
|
||||
const product = await productRepo.findOne({
|
||||
filterByTk: ctx.action.params.filterByTk
|
||||
});
|
||||
|
||||
if (!product) {
|
||||
return ctx.throw(404, ctx.t('No such product'));
|
||||
}
|
||||
|
||||
if (!product.enabled) {
|
||||
return ctx.throw(400, ctx.t('Product not on sale'));
|
||||
}
|
||||
|
||||
if (!product.inventory) {
|
||||
return ctx.throw(400, ctx.t('Out of stock'));
|
||||
}
|
||||
|
||||
const orderRepo = ctx.db.getRepository('orders');
|
||||
ctx.body = await orderRepo.create({
|
||||
values: {
|
||||
productId: product.id,
|
||||
quantity: 1,
|
||||
totalPrice: product.price,
|
||||
userId: ctx.state.currentUser.id
|
||||
}
|
||||
});
|
||||
|
||||
next();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.app.acl.allow('products', '*');
|
||||
this.app.acl.allow('categories', '*');
|
||||
this.app.acl.allow('orders', '*');
|
||||
}
|
||||
|
||||
async install(options: InstallOptions) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
export default ShopPlugin;
|
Loading…
Reference in New Issue
Block a user