# MES CRUD 模块设计: {{Entity}}

> 模块: {{module}} | 业务中心: {{module}}-center | 表: {{table_name}}

---

## 1. 数据库设计（Oracle）

### 建表 SQL

```sql
CREATE TABLE {{table_name}} (
  id                    NUMBER(19)      NOT NULL,
  -- ============================================================
  -- 业务字段（根据提案中的字段清单补充）
  -- ============================================================
  is_effect             NUMBER(1)       DEFAULT 1,
  -- ============================================================
  -- 审计字段（BaseModel / TimeModel 自动填充）
  -- ============================================================
  created_by            VARCHAR2(64)    DEFAULT '',
  created_date          TIMESTAMP       DEFAULT SYSTIMESTAMP,
  last_updated_by       VARCHAR2(64)    DEFAULT '',
  last_updated_date     TIMESTAMP       DEFAULT SYSTIMESTAMP,
  -- ============================================================
  CONSTRAINT pk_{{table_name}} PRIMARY KEY (id)
);

COMMENT ON TABLE {{table_name}} IS '{{table_comment}}';
-- COMMENT ON COLUMN {{table_name}}.xxx IS 'xxx';

-- 雪花算法 ID 序列（可选，若由应用层生成则不需要）
-- CREATE SEQUENCE seq_{{table_name}} START WITH 1 INCREMENT BY 1 NOCACHE;
```

---

## 2. 后端代码结构

### 目录布局

```
modules-center/{{module}}-center/{{module}}-service/
└── src/main/java/com/twsz/mom/{{module}}/
    ├── controller/
    │   └── {{Entity}}Controller.java
    ├── service/
    │   ├── {{Entity}}Service.java
    │   └── impl/
    │       └── {{Entity}}ServiceImpl.java
    ├── mapper/
    │   └── {{Entity}}Mapper.java
    └── model/
        └── {{Entity}}.java

└── src/main/resources/
    └── mapper/
        └── {{Entity}}Mapper.xml
```

### Entity（继承 BaseModel）

```java
@Data
@EqualsAndHashCode(callSuper = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
@TableName("{{table_name}}")
public class {{Entity}} extends BaseModel {
    // 业务字段（审计字段由 BaseModel → TimeModel 提供）
    private Integer isEffect;
    // ... 根据提案补充
}
```

> **BaseModel 继承链**: `ViewModel`（fields/columns/orderBy/动态查询）→ `TimeModel`（审计字段自动填充）→ `BaseModel`（id 雪花算法）

### Mapper（继承 BaseMapper）

```java
@Mapper
public interface {{Entity}}Mapper extends BaseMapper<{{Entity}}> {
    IPage<{{Entity}}> pageSearch(Page<{{Entity}}> page, @Param("entity") {{Entity}} entity);
    List<{{Entity}}> list(@Param("entity") {{Entity}} entity);
}
```

### Mapper XML（标准片段模板）

```xml
<mapper namespace="com.twsz.mom.{{module}}.mapper.{{Entity}}Mapper">
    <resultMap id="BaseResultMap" type="com.twsz.mom.{{module}}.model.{{Entity}}"/>

    <!-- 动态列（支持 ViewModel.fields 部分列查询） -->
    <sql id="{{Entity}}Columns">
        <choose>
            <when test="entity.fields != null">
                <foreach collection="entity.columns" item="it" separator=",">t.${it}</foreach>
            </when>
            <otherwise>t.*</otherwise>
        </choose>
    </sql>

    <!-- 动态 WHERE（每个字段一个 if 判断） -->
    <sql id="{{Entity}}Where">
        <where>
            <if test="entity.id != null">AND t.id = #{entity.id}</if>
            <if test="entity.isEffect != null">AND t.is_effect = #{entity.isEffect}</if>
            <!-- 根据字段清单补充 -->
        </where>
        <choose>
            <when test="entity.orderBy != null and entity.orderBy != ''">
                ORDER BY t.${entity.orderBy}
            </when>
            <otherwise>ORDER BY t.id DESC</otherwise>
        </choose>
    </sql>

    <sql id="{{Entity}}Joins"></sql>

    <select id="pageSearch" resultMap="BaseResultMap">
        SELECT <include refid="{{Entity}}Columns"/>
        FROM {{table_name}} t
        <include refid="{{Entity}}Joins"/>
        <include refid="{{Entity}}Where"/>
    </select>

    <select id="list" resultMap="BaseResultMap">
        SELECT * FROM (
            SELECT <include refid="{{Entity}}Columns"/>
            FROM {{table_name}} t
            <include refid="{{Entity}}Joins"/>
            <include refid="{{Entity}}Where"/>
        ) tt WHERE rownum &lt;=
        <choose>
            <when test="entity.rowNum != null">#{entity.rowNum}</when>
            <otherwise>10000</otherwise>
        </choose>
    </select>
</mapper>
```

### Service 接口

```java
public interface {{Entity}}Service extends IService<{{Entity}}> {
    ResponseWrapper<String> insert({{Entity}} entity);
    ResponseWrapper<String> update({{Entity}} entity);
    ResponseWrapper<String> deleteByIds(Collection<Long> ids);
    ResponseWrapper<Page<{{Entity}}>> search(PageForm<{{Entity}}> pageForm);
    List<{{Entity}}> list({{Entity}} entity);
}
```

### Service 实现

```java
@Slf4j
@Service
public class {{Entity}}ServiceImpl extends ServiceImpl<{{Entity}}Mapper, {{Entity}}>
        implements {{Entity}}Service {

    @Override
    public ResponseWrapper<String> insert({{Entity}} entity) {
        if (super.save(entity)) {
            return ResponseWrapper.defaultSuccess();
        }
        return ResponseWrapper.ofStatus(HttpStatus.OBJECT_INSERT_FAIL);
    }

    @Override
    public ResponseWrapper<String> update({{Entity}} entity) {
        if (super.updateById(entity)) {
            return ResponseWrapper.defaultSuccess();
        }
        return ResponseWrapper.ofStatus(HttpStatus.OBJECT_UPDATE_FAIL);
    }

    @Override
    public ResponseWrapper<String> deleteByIds(Collection<Long> ids) {
        if (super.removeByIds(ids)) {
            return ResponseWrapper.defaultSuccess();
        }
        return ResponseWrapper.ofStatus(HttpStatus.OBJECT_DELETE_FAIL);
    }

    @Override
    public ResponseWrapper<Page<{{Entity}}>> search(PageForm<{{Entity}}> pageForm) {
        Page<{{Entity}}> p = new Page<>(pageForm.getCurrent(), pageForm.getSize());
        baseMapper.pageSearch(p, pageForm.getCondition());
        return ResponseWrapper.ofSuccess(p);
    }

    @Override
    public List<{{Entity}}> list({{Entity}} entity) {
        return baseMapper.list(entity);
    }
}
```

### Controller

```java
@Slf4j
@RestController
@RequestMapping("/{{entity}}")
public class {{Entity}}Controller {

    @Resource
    private {{Entity}}Service {{entity}}Service;

    @PostMapping(value = "add")
    public ResponseWrapper<String> add(@RequestBody {{Entity}} entity) {
        return {{entity}}Service.insert(entity);
    }

    @PutMapping(value = "update")
    public ResponseWrapper<String> update(@RequestBody {{Entity}} entity) {
        return {{entity}}Service.update(entity);
    }

    @PostMapping(value = "search")
    public ResponseWrapper<Page<{{Entity}}>> search(@RequestBody PageForm<{{Entity}}> pageForm) {
        return {{entity}}Service.search(pageForm);
    }

    @DeleteMapping(value = "delete")
    public ResponseWrapper<String> delete(@RequestBody Long[] ids) {
        return {{entity}}Service.deleteByIds(Arrays.asList(ids));
    }

    @GetMapping(value = "get/{id}")
    public ResponseWrapper<{{Entity}}> get(@PathVariable Long id) {
        return ResponseWrapper.ofSuccess({{entity}}Service.getById(id));
    }

    @PostMapping(value = "list")
    public ResponseWrapper<List<{{Entity}}>> list(@RequestBody {{Entity}} entity) {
        return ResponseWrapper.ofSuccess({{entity}}Service.list(entity));
    }

    @PostMapping(value = "export")
    public void export(@RequestBody {{Entity}} condition, HttpServletResponse response) throws IOException {
        // EasyExcel 导出
    }
}
```

---

## 3. 前端代码结构

### 目录布局

```
src/
├── api/{{domain}}/
│   └── {{entity}}.js
└── views/{{domain}}/{{entity}}/
    ├── index.vue          # 列表页
    └── {{entity}}-form.vue  # 表单页
```

### API 接口文件

```js
import axios from '@/libs/request'
import { exportExcel } from '../file'

const apiPrefix = '/{{entity}}'

const search{{Entity}} = data => {
    return axios.request({ url: `${apiPrefix}/search`, method: 'POST', data })
}

const save{{Entity}} = data => {
    const url = data.id ? `${apiPrefix}/update` : `${apiPrefix}/add`
    const method = data.id ? 'PUT' : 'POST'
    return axios.request({ url, method, data })
}

const delete{{Entity}} = ids => {
    return axios.request({
        url: `${apiPrefix}/delete`,
        method: 'DELETE',
        data: Array.isArray(ids) ? ids : [ids]
    })
}

const list{{Entity}} = data => {
    return axios.request({ url: `${apiPrefix}/list`, method: 'POST', data })
}

const export{{Entity}} = data => {
    return exportExcel(`${apiPrefix}/export`, data)
}

export default { search{{Entity}}, save{{Entity}}, delete{{Entity}}, list{{Entity}}, export{{Entity}} }
```

### 列表页（index.vue）

```vue
<template>
    <div class="master-index">
        <search-table v-show="showIndexPage" ref="searchTable" :option="option" :columns="tableColumns">
            <template #condition-form>
                <Form ref="conditionForm" class="search-condition-form" label-colon @keyup.enter.native="refresh">
                    <!-- 搜索条件表单字段 -->
                </Form>
            </template>
        </search-table>
        <tw-card v-show="!showIndexPage" :back="triggerBack">
            <{{Entity}}Form :readonly="readonly" :form="formData" :key="instanceKey"
                @on-success="handleSuccess" />
        </tw-card>
    </div>
</template>

<script>
import { indexPage } from '_c/table-form/index-mixin'
import {{Entity}}Api from '@/api/{{domain}}/{{entity}}'
import {{Entity}}Form from './{{entity}}-form'

export default {
    mixins: [indexPage],
    components: { {{Entity}}Form },
    data() {
        return {
            option: {
                tableName: '{{entity}}',
                searchForm: {},
                fixedTableHeight: true,
                add:    { enable: true, method: this.add },
                edit:   { enable: true, method: this.edit },
                view:   { enable: true, method: this.view },
                delete: { enable: true, method: {{Entity}}Api.delete{{Entity}} },
                export: { enable: true, method: {{Entity}}Api.export{{Entity}} },
                search: { query: {{Entity}}Api.search{{Entity}} },
            },
            tableColumns: [
                { type: 'selection' },
                // 表格列定义
                { title: this.$t('operate||操作'), slot: 'operate' }
            ]
        }
    }
}
</script>
```

### 表单页（{{entity}}-form.vue）

```vue
<template>
    <master-sub :readonly="readonly">
        <template v-if="!readonly" #master-header-toolbar>
            <Button type="primary" :loading="loading" @click="doSubmit">{{ $t('submit||提交') }}</Button>
        </template>
        <template #master-form>
            <Form ref="mform" :model="mform" :label-width="120" :disabled="readonly" class="form">
                <!-- 表单字段 -->
            </Form>
        </template>
    </master-sub>
</template>

<script>
import { BaseMixin } from '@/mixin'
import {{Entity}}Api from '@/api/{{domain}}/{{entity}}'

export default {
    mixins: [BaseMixin],
    props: {
        readonly: { type: Boolean, default: false },
        form: { type: Object, default: () => ({}) }
    },
    data() { return { mform: {}, default: {} } },
    created() {
        this.mform = Object.assign({}, this.default, this.form)
    },
    methods: {
        doSubmit() {
            this.$refs.mform.validate(valid => {
                if (valid) {
                    this.asyncLoading({{Entity}}Api.save{{Entity}}(this.mform)).then(res => {
                        this.$Message.success({ background: true, content: this.$t('submit.success||提交成功') })
                        this.$emit('on-success', this.mform)
                    })
                }
            })
        }
    }
}
</script>
```

---

## 4. 接口定义

| Method | Path | 描述 | 请求体 | 响应 |
|--------|------|------|--------|------|
| POST | /{{entity}}/search | 分页查询 | `PageForm<{{Entity}}>` | `ResponseWrapper<Page<{{Entity}}>>` |
| POST | /{{entity}}/add | 新增 | `{{Entity}}` | `ResponseWrapper<String>` |
| PUT | /{{entity}}/update | 修改 | `{{Entity}}` | `ResponseWrapper<String>` |
| DELETE | /{{entity}}/delete | 批量删除 | `Long[]` | `ResponseWrapper<String>` |
| GET | /{{entity}}/get/{id} | 详情 | — | `ResponseWrapper<{{Entity}}>` |
| POST | /{{entity}}/list | 不分页列表 | `{{Entity}}` | `ResponseWrapper<List<{{Entity}}>>` |
| POST | /{{entity}}/export | Excel 导出 | `{{Entity}}` | Excel 文件流 |

---

## 5. 前后端分工

| 功能 | 前端 | 后端 |
|------|------|------|
| 列表页 | search-table + indexPage mixin | POST /search（PageForm 分页） |
| 新增 | master-sub 表单 + save（无 id → add） | POST /add |
| 编辑 | master-sub 表单 + save（有 id → update） | PUT /update |
| 删除 | 确认弹窗 + delete(ids) | DELETE /delete |
| 导出 | exportExcel Blob 下载 | POST /export（EasyExcel） |
| 菜单 | 动态路由（后端返回 component 路径） | c_sys_resource 注册 |
