# 前端

前端不是作者专长,前端提供的方案不一定优雅但是够用了,建议在编写时候参考现有例子,这样更好理解。前端原则上只会采用AntdV 的组件,方案大家统一技术栈,Antdv例子全,文档细,本章节只会例举最佳实践相关。其余文档查阅 Antdv (opens new window) 官网,建议还是简单通读下Vue3和Antdv的文档。

# 数据字典

在登录后/src/api/sys/index.ts initAllDict 加载所有字典放入sessionStorage中,业务模块字典可用用于form表单, 或者页面回显数据时候使用,业务模块字典约定在data.ts中。

# 手动编写字典

export const templateTypes: Dict[] = [
  {
    label: '基础增删改查',
    value: 0,
  },
  {
    label: '树结构',
    value: 1,
    disabled: true,
  },
];

export const templateTypesMap = dictArray2Map(templateTypes);

# 加载DB字典

export const xxxDicts: Dict[] = getDict('xxxx')

export const xxxDictsMap = dictArray2Map(xxxDicts);

默认字典的value都是字符串类型,如果需要转整形,使用getDict('xxxx',true),表单校验对数类型很敏感。

# 字典使用

渲染select、radio、checkbox等。

  • 单选下拉
<a-select
      v-model:value="dataForm.xxx"
      :allowClear="true"
      :options="xxxDicts"
      placeholder="请选择下拉数据"
      style="width: 140px"
    />
  • 复选框
<a-checkbox-group v-model:value="dataForm.xxxArray" :options="xxxDicts" />
  • 单选框
<a-radio-group v-model:value="searchForm.status" :options="statusDicts" />
  • 下拉多选
<a-select
  v-model:value="dataForm.xxxxArray"
  :allowClear="true"
  :options="xxxxDicts"
  mode="multiple"
  placeholder="请选择下拉数据"
  style="width: 140px"
/>

数据转换数据回显的时候

xxxDictsMap.get('value')

# 分页表格

关于数据表格,框架已经封装成mixins,分页和不分页都支持,如果不分页或者需要默认参数,请查看queryTableMixin 中属性,自行覆盖即可。

# 查询表单

代码生成的查询表单为平铺方式,已参数管理为例:

<a-form
    ref="searchForm"
    :labelCol="this.labelCol"
    :model="searchForm"
    :wrapperCol="this.wrapperCol"
    layout="inline"
  >
    <a-form-item label="参数名" name="name">
      <a-input v-model:value="searchForm.name" :maxlength="50" placeholder="请输入参数名" />
    </a-form-item>
    <a-form-item label="" name="paramKey">
      <a-input v-model:value="searchForm.paramKey" :maxlength="50" placeholder="请输入唯一键" />
    </a-form-item>
    <a-form-item>
      <a-space>
        <a-button v-auth="'sys:param:query'" type="primary" @click="handleQuery()">查询</a-button>
        <a-button type="default" @click="this.$refs.searchForm.resetFields()">重置</a-button>
        <a-button
          v-auth="'sys:param:save'"
          type="default"
          @click="this.$refs.dataFormModal.open('添加')"
          >添加
        </a-button>
      </a-space>
    </a-form-item>
  </a-form>

如果条件较多,请使用Antdv 提供的栅格布局,将layout="inline"去掉,把a-form-item放入栅格中,栅格划分多少分自行决定,和为24。

 <a-row>
   <a-col :span="4"></a-col>
   <a-col :span="4"></a-col>
   <a-col :span="4"></a-col>
   <a-col :span="4"></a-col>
   <a-col :span="4"></a-col>
   <a-col :span="4"></a-col>
 </a-row>  

如果有多行默认行间隔比较宽,可以根据情况添加自定义样式:

<a-row style="margin-top: -20px">
</a-row>  

如果最后一个元素离按钮较近,通常select的内容过长会到时和最后的查询按钮挨的较近,这种情况则使用:

<a-col :span="4" class="mr-5">
</a-col>

# 表格

通过mixins方式,只需要编写如下代码即可,后台分页结构数据按框架现有的即可。如果离上面元素太近可以使用windcss 默认的样式mt-4,代表margin-top 4个标准单位。

如果表格字段较多,可以使用横向滚动在a-table:scroll="{ x: 1500 }",宽度自行定义,然后对 column 添加width:属性,综合不要超过这个即可以。根据经验,不要全部column设置宽度。

<a-table
    :columns="columns"
    :data-source="data"
    :loading="loading"
    :pagination="pagination"
    :row-key="(record) => record.id"
    bordered
    class="mt-4 pr-4"
    size="small"
    @change="handleTableChange"
  >
    <template #status="{ text }">
      <a-tag :color="text == 1 ? 'blue' : 'red'">
        {{ text == 1 ? '有效' : '无效' }}
      </a-tag>
    </template>
    <template #action="{ record }">
      <a v-auth="'sys:param:update'" @click="this.$refs.dataFormModal.open('编辑', record.id)"
        >编辑</a
      >
      <a-divider type="vertical" />
      <a-popconfirm
        placement="left"
        title="确定删除?"
        @confirm="handleDelete('/sys/param/delete', record.id)"
      >
        <a v-auth="'sys:param:delete'">删除</a>
      </a-popconfirm>
    </template>
  </a-table>

组件中加入如下代码即可以。

mixins: [queryTableMixin],
data() {
      return {
        url: '/sys/param/query',
        columns: [
          {
            title: '参数名',
            dataIndex: 'name',
          },
          {
            title: '键',
            dataIndex: 'paramKey',
          },
          {
            title: '值',
            dataIndex: 'paramValue',
          },
          {
            title: '状态',
            dataIndex: 'status',
            slots: { customRender: 'status' },
          },
          {
            title: '创建时间',
            dataIndex: 'createTime',
          },
          {
            title: '修改时间',
            dataIndex: 'updateTime',
            sorter: true,
          },
          {
            title: '操作',
            fixed: 'right',
            width: 120,
            slots: { customRender: 'action' },
          },
        ],
      };
    },
    mounted() {
      this.handleQuery();
    },

# 数据编辑

类似的使用mixins ,封装dataFormModalMixin ,将新增、修改、参数验证,回调等统一封装。

<template>
  <a-modal
    v-model:visible="visible"
    :confirm-loading="confirmLoading"
    :destroyOnClose="true"
    :height="this.height"
    :maskClosable="false"
    :title="title"
    :width="this.width"
    okText="保存"
    @ok="handleOk(this.dataForm.id === undefined ? '/sys/param/save' : '/sys/param/update')"
  >
    <a-form
      ref="dataForm"
      :label-col="this.labelCol"
      :model="dataForm"
      :wrapper-col="this.wrapperCol"
    >
      <a-input v-model:value="dataForm.id" type="hidden" />
      <a-row>
        <a-col :md="12" :xs="24">
          <a-form-item
            :rules="[
              { required: true, message: '参数名不能为空', whitespace: true },
              { min: 1, max: 50, message: '参数名长度1-50' },
            ]"
            label="参数名"
            name="name"
          >
            <a-input v-model:value="dataForm.name" :maxlength="50" placeholder="参数名" />
          </a-form-item>
        </a-col>
        <a-col :md="12" :xs="24">
          <a-form-item
            :rules="[
              { required: true, message: '唯一键不能为空', whitespace: true },
              { min: 1, max: 50, message: '唯一键长度1-50' },
              { validator: checkParamKey, trigger: 'blur' },
            ]"
            label="唯一键"
            name="paramKey"
          >
            <a-input v-model:value="dataForm.paramKey" :maxlength="50" placeholder="唯一键" />
          </a-form-item>
        </a-col>
      </a-row>
      <a-row>
        <a-col :md="12" :xs="24">
          <a-form-item
            :rules="[
              { required: true, message: '参数值不能为空', whitespace: true },
              { min: 1, max: 100, message: '参数值长度1-100' },
            ]"
            label="参数值"
            name="paramValue"
          >
            <a-input v-model:value="dataForm.paramValue" :maxlength="100" placeholder="参数值" />
          </a-form-item>
        </a-col>
        <a-col :md="12" :xs="24">
          <a-form-item
            :rules="[
              {
                required: true,
                type: 'number',
                message: '状态不能为空',
                whitespace: true,
              },
            ]"
            label="状态"
            name="status"
          >
            <a-radio-group v-model:value="dataForm.status" name="status">
              <a-radio :value="1">
                <a-badge status="success" text="有效" />
              </a-radio>
              <a-radio :value="0">
                <a-badge status="warning" text="无效" />
              </a-radio>
            </a-radio-group>
          </a-form-item>
        </a-col>
      </a-row>
      <a-row>
        <a-col :span="24">
          <a-form-item
            :label-col="{ span: 3 }"
            :wrapper-col="{ span: 21 }"
            label="备注"
            name="remarks"
          >
            <a-textarea
              v-model:value="dataForm.remarks"
              :auto-size="{ minRows: 3, maxRows: 5 }"
              :maxlength="255"
              placeholder="备注"
            />
          </a-form-item>
        </a-col>
      </a-row>
    </a-form>
  </a-modal>
</template>

<script>
  import { dataFormModalMixin } from '../../../mixins/common/data-form-mixin-modal.js';
  import { defHttp } from '../../../utils/http/axios';

  export default {
    name: 'DataFormModal',
    mixins: [dataFormModalMixin],
    emits: ['refreshQuery'],
    methods: {
      checkParamKey(rule, value) {
        return this.uniqueFieldSimpleValidation(
          '/sys/param/check_param_key',
          value,
          {
            id: this.dataForm.id,
            paramKey: value,
          },
          `唯一键 ${value} 已存在`
        );
      },
      open(title, id) {
        this.visible = true;
        this.title = title;
        if (null != id) {
          defHttp.get({ url: '/sys/param/query/' + id }).then((data) => {
            this.dataForm = data;
          });
        } else {
          this.dataForm = { status: 1 };
        }
      },
      // 保存后回调
      handleOkCb() {
        this.$emit('refreshQuery');
      },
    },
  };
</script>

# 数据查看

利用Antdv descriptions,响应式组件,查看数据很方便,利用数据字典组件,回显无烦恼。

<template>
  <a-modal
    v-model:visible="visible"
    :destroyOnClose="true"
    :height="600"
    title="查看"
    :width="750"
    :footer="null"
  >
    <a-descriptions bordered size="small">
      <a-descriptions-item label="文本">{{ data.inputText }}</a-descriptions-item>
      <a-descriptions-item label="下拉">
        {{ inputSelectDictsMap.get(data.inputSelect) }}
      </a-descriptions-item>
      <a-descriptions-item label="单选">
        {{ inputRadioDictsMap.get(data.inputRadio) }}
      </a-descriptions-item>
      <a-descriptions-item label="多选"> {{ data.inputCheckbox }} </a-descriptions-item>
      <a-descriptions-item label="文本域"> {{ data.inputTextarea }} </a-descriptions-item>
      <a-descriptions-item label="日期"> {{ data.inputDate }} </a-descriptions-item>
      <a-descriptions-item label="整数"> {{ data.inputZhengshu }} </a-descriptions-item>
      <a-descriptions-item label="小数"> {{ data.inputXiaoshu }} </a-descriptions-item>
      <a-descriptions-item label="图片">
        <s-uploader v-model:value="data.image" :disabled="true" listType="picture-card" />
      </a-descriptions-item>
      <a-descriptions-item label="文件">
        <s-uploader v-model:value="data.file" :disabled="true" />
      </a-descriptions-item>
      <a-descriptions-item label="富文本"><p v-html="data.richText"></p> </a-descriptions-item>
      <a-descriptions-item label="备注"> {{ data.remarks }} </a-descriptions-item>
    </a-descriptions>
  </a-modal>
</template>

<script>
  import { ref } from 'vue';
  import { defHttp } from '../../../utils/http/axios';
  import { inputRadioDictsMap, inputSelectDictsMap } from './data';
  import SUploader from '../../../components/SUploader/index.vue';

  export default {
    name: 'DataViewModal',
    components: { SUploader },
    setup() {
      const visible = ref(false);
      const data = ref({});
      const open = function (id) {
        visible.value = true;
        defHttp.get({ url: '/sys/demo/query/' + id }).then((_data) => {
          data.value = _data;
        });
      };
      return {
        visible,
        open,
        data,
        inputSelectDictsMap,
        inputRadioDictsMap,
      };
    },
  };
</script>

# 图片&文件

这里自行封装SUploader组件,在数据查看和数据编辑场景都可以使用,重要参数如下:

组件使用的是相对地址

参数名 是否必须 说明
value 绑定的相对路径
disabled 是否禁用,通常查看时候是true
listType 展示类型:text(文件名), picture(图片和文件名) 和 picture-card(照片墙),默认是text
limit 上传数量,默认1
multiple 是否允许多文件,默认false.注意和limit搭配。
accept 允许类型,和原生input type='file' 用法一致,如image/*

数据编辑中例子:

<s-uploader v-model:value="dataForm.image" accept="image/*" listType="picture-card" />

在数据查看中的例子:

<s-uploader v-model:value="data.image" :disabled="true" listType="picture-card" />

# 全局配置

涉及到几个可能变更配置文件,一看懂。

.env.development
.env.production
src/settings/projectSetting.ts
src/settings/localeSetting.ts