示例应用

上手最快的办法是根据本教程来快速编写一个应用模块。

创建后端模块

在根目录下使用 dux 命令行工具来创建一个名为 Example 的应用模块:

Linux + Macos
windows
./dux generate:app example

此时会在 app 目录中创建以下目录和文件:

└─ Example  # 应用模块 (示例)
   └─ App.php             # <- 模块的入口文件

创建数据模型

在根目录下使用 dux 命令行工具在 Example 应用模块中来创建一个名为 Example 的数据模型:

Linux + Macos
windows
./dux generate:model Example

执行命令后会出现如下询问:

# 询问模型名
Please enter a model name:
# 此处输入模型类名
> Example

生成的模型文件位于应用的 Models 目录下,如下:

<?php

declare(strict_types=1);

namespace App\Example\Models;

use Dux\Database\Attribute\AutoMigrate;

#[AutoMigrate]
class Example extends \Dux\Database\Model
{
    // 数据库表名
    public $table = 'example';

    // 数据表同步结构
    public function migration(\Illuminate\Database\Schema\Blueprint $table)
    {
        $table->id();
        
        // 自定义数据表字段
        $table->string('name')->comment('名称');
        
        $table->timestamps();
    }
}

在 migration 方法中定义需要的字段,该字段对应的数据表中的结构。

因为数据模型使用了 eloquent ORM 所以在编写字段配置时可以参考对应文档 数据库迁移字段 来定义字段配置。

接下来使用以下命令将字段同步至数据库,无需手动去操作数据库:

Linux + Macos
windows
./dux db:sync example

创建后端资源

在根目录下使用 dux 命令行工具在 Example 应用模块中来创建一个名为 Example 的后端资源类:

Linux + Macos
windows
./dux generate:manage Example

执行命令后会出现如下询问:

# 1. 询问控制器层
Please enter a dir name:
# 此处输入后台目录名
> Admin

# 2. 询问控制器名
Please enter a class name:
# 此处输入控制器类名
> Example

生成的控制器文件位于应用的 Admin 目录下,需手动更改指定模型:

<?php

declare(strict_types=1);

namespace App\Example\Admin;

use Dux\Resources\Action\Resources;
use Dux\Resources\Attribute\Resource;
use Dux\Validator\Data;
use Illuminate\Database\Eloquent\Model;
use Psr\Http\Message\ServerRequestInterface;

#[Resource(app: 'admin', route: '/example/example', name: 'example.example')]
class Example extends Resources
{
    // 此处需要手动更改为对应的模型类
    protected string $model = \App\Example\Models\Example::class; 

    public function transform(object $item): array
    {
        return [
            "id" => $item->id,
        ];
    }


    protected function validator(array $data, ServerRequestInterface $request, array $args): array
    {
        return [
            "name" => ["required", "please enter name"],
        ];
    }


    protected function format(Data $data, ServerRequestInterface $request, array $args): array
    {
        return [
            "name" => $data->name,
        ];
    }
}

控制器使用了注解方法 Resource 来将一个控制器声明为资源,资源会自动注册该控制器的路由和权限。

后台应用模块创建完毕后接下来需要创建前端页面来供用户进行操作使用。

创建前端模块

请手动创建以下前端目录结构。

└─ web  # 后台前端目录
   ├─ pages  # 页面模块根目录
   |  ├─ example                # <- 应用模块 (示例)
   |  |  ├─ admin               # <- 后台前端页面目录
   |  |  |  └─ example          # <- 页面分组目录
   |  |  |  |  └─ list.tsx      # <- 列表页面文件
   |  |  |  |  └─ save.tsx      # <- 添加编辑页面文件
   |  |  ├─ config              # <- 后台前端配置目录
   |  |  |  └─ resources.tsx    # <- 前端资源配置文件
   |  |  ├─ locales             # <- 语言包目录
   |  |  |  └─ zh-CN.json       # <- 中文语言文件
   |  |  └─ index.ts            # <- 模块入口文件

配置入口文件

请在入口文件中编写以下代码:

index.ts
import { appConfig, appContext } from '@duxweb/dux-refine'
import { adminResources } from './config/resources'

const init = (context: appContext) => {
  // 加载当前模块的所有语言包
  const data = import.meta.glob('./locales/*.json', { eager: true })
  context.addI18ns(data)
}

const register = (context: appContext) => {
  // 注册当前模块资源到 admin 端
  const admin = context.getApp('admin')
  adminResources(admin)
}

const boot = (context: appContext) => {
}

const config: appConfig = {
  init: init,
  register: register,
  boot: boot,
}

export default config

该入口文件会在前端启动时自动进行全局加载引入,无需手动加载。

配置资源文件

编写以下代码来定义资源与页面组件路径。

config/resources.tsx
import {App, lazyComponent} from '@duxweb/dux-refine'

export const adminResources = (app: App) => {
  app.addResources([
    {
      name: 'exampleApp',                 // 资源标识,全局唯一
      meta: {
        label: 'exampleApp',              // 菜单名称,对应语言包键名
        sort: 90,                         // 菜单优先级
        icon: 'i-tabler:tools',           // 菜单图标
      },
    },
    {
      name: 'example.example',            // 资源标识,全局唯一
      list: 'example/example',            // 前端 url 路径
      listComponent: lazyComponent(() => import('../admin/example/list')),
      meta: {
        label: 'exampleApp.example',       // 菜单名称,对应语言包键名
        icon: 'i-tabler:link',             // 菜单图标
        parent: 'exampleApp',              // 上级资源标识
      },
    },
  ])
}

创建页面组件

编写以下代码用于列表和编辑组件。

admin/example/list.tsx
import React from 'react'
import { useTranslate } from '@refinedev/core'
import { PrimaryTableCol } from 'tdesign-react/esm'
import { PageTable, CreateButtonModal, EditLinkModal, DeleteLink } from '@duxweb/dux-refine'

const List = () => {
  const translate = useTranslate()

  const columns = React.useMemo<PrimaryTableCol[]>(
    () => [
      {
        colKey: 'id',
        sorter: true,
        sortType: 'all',
        title: 'ID',
        width: 150,
      },
      {
        colKey: 'name',
        title: '名称',
        ellipsis: true,
      },
      {
        colKey: 'link',
        title: translate('table.actions'), // 调用语言包文字
        fixed: 'right',
        align: 'center',
        width: 160,
        cell: ({ row }) => {
          return (
            <div className='flex justify-center gap-4'>
              <EditLinkModal rowId={row.id} component={() => import('./save')} />
              <DeleteLink rowId={row.id} />
            </div>
          )
        },
      },
    ],
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [translate],
  )

  return (
    <PageTable
      columns={columns}
      table={{
        rowKey: 'id',
      }}
      actionRender={() => <CreateButtonModal component={() => import('./save')} />}
    />
  )
}

export default List
admin/example/save.tsx
import { FormModal } from '@duxweb/dux-refine'
import { Form, Input } from 'tdesign-react/esm'

const Page = (props: Record<string, any>) => {

  return (
    <FormModal id={props?.id}>
      <Form.FormItem label={'名称'} name='name'>
        <Input />
      </Form.FormItem>
    </FormModal>
  )
}

export default Page

创建语言包文件

编写以下 json 代码来定义前端语言包:

locales/zh-CN.json
{
  "exampleApp": {
    "name": "示例",
    "example": {
      "name": "示例列表"
    }
  }
}

语言包中每个资源标识内都有一个 name 值来对应各自的菜单名称。

预览并调试

修改位于项目配置文件 config/use.yaml 的配置信息,打开前端调试。

app:
    name: Dux
    debug: true
    cache: false
    secret: a9dfda61e965d5101dfbf31e959fe096
    domain: 'http://duxcms.test'
clock: false
lang: zh-CN
vite:
    dev: true    port: 5173
manage:
    indexName: cms
    baiduMap:

打开后在 web 目录中安装前端依赖与运行前端调试模式。

1、安装依赖,已安装可忽略该步骤。

shell
yarn install

2、启动开发者调试模式。

shell
> yarn dev
# 或
> yarn dev --force

运行成功会出下如下提示:

VITE v5.2.11  ready in 1512 ms

  Local:   http://localhost:5173/web/
  Network: http://10.0.0.219:5173/web/

此时通过浏览器打开 php 部署后的后台地址,即可进入开发者调试模式,登录后可浏览您刚开发的示例应用。

http://域名/manage

结束语

该示例为基础并且经常使用的创建方法,基本流程为创建后端应用 -> 创建模型 -> 创建控制器 -> 创建前端应用 -> 创建前端页面 -> 创建前端配置 -> 开发调试,掌握该流程可供您流畅进行开发。