# 东臣 Web 端基础框架

## Vite + Vue3 + TypeScript + Pinia

## 版本命名规范

- 版本需要发布到私仓 `npm publish` 
- 

### 规范说明

1. 文件命名规则
    - Vue 文件采用驼峰大写命名，例如：`HelloWorld.vue`
    - js/ts 文件采用小写命名，例如：`hello-world.js`
    - `assets/images`下的图片文件，采用小写中划线格式，例如：`hello-world.png`
2. 目录分包规则
    - `src/view`下的目录按照业务模块来进行划分,例如：`system/uesr`
    - 增改查分包分别使用`Add.vue`、`Update.vue`、`Detail.vue`， 审批、生成使用`Approval.vue`、`Generate.vue`。  
      例如：`system/uesr`目录：
        - `Manage.vue` 表示管理页，一般是表格的页面
        - `Add.vue` 表示新增用户
        - `Update.vue` 表示修改用户（可以和新增合并）
        - `Detail.vue` 表示查看用户详情用户
3. 路由说明
    - 路由的可以不写`name`,`path`必写,对应菜单的`菜单地址：linkUrl`字段，`path`建议带上前缀，例如：`/account/user`、`/system/menu`

### 使用技术

（推荐使用`pnpm`进行包管理）  
`npm install -g pnpm`

-   Vite2
-   Vue3
-   TypeScript
-   Pinia
-   VueRouter
-   Eslint
-   Ant Design Vue

## 框架说明

### 网络请求

默认写法

```typescript
import { PostRequestModel, GetRequestModel, PutRequestModel, DeleteRequestModel } from '@/utils/model/request-model'
import { PageModel } from '@/utils/model/result-model'
import { defaultConfig } from '@/http/json-config'
import { FactorVo } from '@/entity/eess/FactorVo'
import { FactorMapVo } from '@/entity/eess/FactorMapVo'

// 当前模块通用请求前缀
const MODULE_API_PREFIX = '/system/factor'

export default {
	page: (query?: {}) => new PostRequestModel<PageModel<FactorVo>>(`${MODULE_API_PREFIX}/page`, query).request(),
	getById: (id: string) => new GetRequestModel<FactorVo>(`${MODULE_API_PREFIX}/${id}`, {}).request(),
	add: (data: {}) => new PostRequestModel<FactorVo>(`${MODULE_API_PREFIX}/`, data).request(),
	update: (id: string, data: {}) => new PutRequestModel<FactorVo>(`${MODULE_API_PREFIX}/${id}`, data).request(),
	delete: (id: string) => new DeleteRequestModel<boolean>(`${MODULE_API_PREFIX}/${id}`, {}).request()
}
```

修改配置（配置需要继承已有的配置信息）

```typescript
import { PostRequestModel, GetRequestModel, PutRequestModel, DeleteRequestModel } from '@/utils/model/request-model'
import { PageModel } from '@/utils/model/result-model'
import { defaultConfig } from '@/http/json-config'
import { FactorVo } from '@/entity/eess/FactorVo'
import { FactorMapVo } from '@/entity/eess/FactorMapVo'
import { getDefaultConfig } from '@/http/config/custom-config'

// 当前模块通用请求前缀
const MODULE_API_PREFIX = '/system/factor'

const defaultConfig = getDefaultConfig<any>()
// 修改配置中的值
// defaultConfig。。。

export default {
	page: (query?: {}) => new PostRequestModel<PageModel<FactorVo>>(`${MODULE_API_PREFIX}/page`, query, defaultConfig).request(),
	add: (data: {}) => new PostRequestModel<FactorVo>(`${MODULE_API_PREFIX}/`, data, defaultConfig).request()
}
```

## 目录结构

使用`tree`命令生成

    ├─api                       所有的接口api
    │  ├─account                用户管理模块
    │  ├─dynamic                动态表单模块
    │  ├─login                  登录模块
    │  ├─system                 系统模块
    │  └─system-log             系统日志模块
    ├─assets                    资源文件
    │  ├─images                 图片资源
    │  │  ├─index               首页的资源文件
    │  │  └─login               登录模块的资源文件
    │  └─scss                   通用的css部分
    ├─components                组件
    │  ├─form                   表单组件的公共方法
    │  ├─form-item              表单的item
    │  │  └─props               item的属性
    │  ├─rich-text              富文本
    │  └─table                  表格
    │      └─components         表格的组件部分
    ├─entity                    实体类
    │  ├─account                用户管理模块
    │  ├─common                 公告的实体类
    │  ├─dynamic                动态表单模块
    │  ├─login                  登录模块
    │  ├─system                 系统模块
    │  └─system-log             系统日志模块
    ├─http                      网络请求工具类
    │  └─config                 请求的配置
    ├─router                    路由信息
    │  └─modules                路由配置
    ├─store                     缓存
    ├─utils                     工具类
    └─views                     视图
        ├─account               用户管理模块
        │  ├─department         部门管理
        │  ├─information        个人信息
        │  ├─organization       单位管理
        │  ├─role               角色管理
        │  │  └─components
        │  └─user               用户管理
        ├─dynamic               动态表单
        │  └─table              表格
        │      ├─table-columns
        │      └─table-record
        ├─exception             意外的界面，403/404/500
        ├─index                 首页
        │  └─components
        ├─login                 登录页面
        │  └─components
        ├─system                系统界面
        │  ├─dictionary         数据字典
        │  ├─dictionary-type    数据字典类型
        │  ├─menu               菜单
        │  ├─message            消息
        │  └─parameter          系统参数
        └─system-log            系统日志
            ├─backup            系统备份日志
            ├─backup-file       备份文件日志
            ├─file-upload       文件上传日志
            ├─login             登录日志
            ├─message           短信发送日志
            ├─operation         操作日志
            └─version           版本日志

## 常见问题

1. 使用 BaseFormItem，文件上传校验问题，已经上传了还是提示不能为空。  
   设置校验触发时机为`trigger: 'blur'`
2. 使用 BaseFormItem，比如使用选择框，我即想要`label`又想要`value`，我要怎么写？  
   推荐使用`update:extraValue`方法进行实现, 可以使用`extraNames`指定返回字段

    ```vue
    <template>
        <base-form-item v-for="item of fields" :key="item.name" v-model:value="formState[item.name]" v-bind="item" @update:extraValue="extraValueChange" />
    </template>
   
    <script setup lang="ts">
    import fields from '/src/views/index/fields'
    import { reactive } from 'vue'
    import BaseFormItem from 'src/views/components/form-item/BaseFormItem.vue'
    import { ExtraProps } from '/src/components/form-item/field-props'
    import { BaseFormDTO } from '/src/entity/common/base'
   
    const formState = reactive<BaseFormDTO>({})
   
    function extraValueChange(name: string, extra: ExtraProps) {
        // 需要注意的是赋值以后和 onFinish 里面拿不到这个赋值， 两个不是一个对象
   
        // 这样赋值
        Object.assign(formState, extra)
   
        // 这样赋值，返回的key可以使用extraNames
        Object.keys(extra).forEach(key => {
            formState[key] = extra[key]
        })
   
        // 或者通过 name 找到， 然后手动赋值
        // fields.find(value => value.name == name)
        if (name == 'upload-images') {
            //  手动进行操作或者判断
        }
   
        console.log(extra)
    }
    </script>
    ```
   
3. 使用 BaseFormItem，如何设置自动完成（AutoComplete）/下拉项（Select）/级联项（Cascader）/自动完成（AutoComplete）值？   
    首先，**需要对`fields`设置可观察（`ref`/`reactive`）**,例如：

    ```typescript
    // 先设置可观察，否则界面不会及时更新
    const fieldList = reactive(fields)
    // 当界面挂载后进行数据处理
    onMounted(async () => {
        // 通过name字段进行查找，这个比较健壮一点，对typeValue没填时进行了处理
        const find = fieldList.find((value: FieldProps) => value.name == 'select')
        if (find) {
            const typeValue = find?.typeValue as SelectProps
            const { data } = await requestMethod.getListRootCode('building_trade_tag')
            if (typeValue) typeValue.options = data
            // fieldNames 用来指定label和value对应的字段名称
            else find.typeValue = { options: data, fieldNames: { label: 'name', value: 'id', children: 'children' } }
        }
    
        // 如果你在`fields`中给`select`设置了`typeValue`，那么这里可以更简单点，使用这段的前提是在`fields`中给`select`设置了`typeValue`
        const typeValue = fieldList.find((value: FieldProps) => value.name == 'select')?.typeValue as SelectProps
        if (typeValue) {
            const { data } = await requestMethod.getListRootCode('building_trade_tag')
            typeValue.options = data
        }
    })
    ```

4. 使用 BaseFormItem，怎么设置文件上传默认值？  
   （参考第 3 点）

    ```typescript
    // 先设置可观察，否则界面不会及时更新
    onMounted(async () => {
        if (!props.id) return
   
        const typeValue = fieldList.find((value: FieldProps) => value.name == 'upload')?.typeValue as UploadProps
        if (typeValue && props.id) {
            const { data } = await requestMehod.getById(props.id)
            typeValue.fileList = [{ id: data.imageId, uploadPath: data.imageUrl, oldFileName: '可填' }]
        }
    })
    ```

5. Api 请求，怎么设置`application/x-www-form-urlencoded`？
    ```typescript
     update: (data: QueryType) => {
        const defaultConfig = getDefaultConfig<AccountUser>()
        defaultConfig.contentType = 'application/x-www-form-urlencoded'
        return new PutRequestModel<AccountUser>('/accountUser/updateMyPass', data, defaultConfig).request()
    }
    ```

