likes
comments
collection
share

🔥【白哥开源社区】第二期完结篇

作者站长头像
站长
· 阅读数 17

【白哥开源社区】第二期已经完结,持续时间为2023.06.13-2023.06.29

1. 项目地址

github:github.com/xiumubai/li…

gitee:gitee.com/xiumubai/li…

预览地址:live-vue3-admin.vercel.app/#/index

目前开发是在gitee上推送代码,会同步到github中。github国内访问太慢了(科学上网无济于事)。

2. 接口文档

2.1. 接口

apifox:5z6ejvyqg4.apifox.cn

本次的接口全部使用apifox进行云端Mock。接口共计73个。

🔥【白哥开源社区】第二期完结篇

2.2. 功能

本次功能涉及到以下功能模块: 🔥【白哥开源社区】第二期完结篇

3. 数据格式

3.1. 最外层数据格式

{
  "code": 200,
  "message": "成功",
  "data": {
    
  }
  "ok": true
}

3.2. 分页数据格式

{
  "code": 200,
  "message": "成功",
  "data": {
    "list": [{}],
		"total": 7,
    "pageSize": 10,
    "pageNum": 1,
  }
  "ok": true
}

4. Mock

Mock基础语法

我们之所以使用apifox的原因是功能强大,方便协作和管理,后期便于跟后端对接。

4.1. 基础mock

我们先来创建一个基础的Mock接口。

比如登录接口。

🔥【白哥开源社区】第二期完结篇

🔥【白哥开源社区】第二期完结篇

直接点击添加接口即可。然后输入接口的配置:

  1. 请求方式:post
  2. 请求路径:/admin/acl/login
  3. 请求参数:Body
  4. 返回响应

在响应的数据这里,我们需要把每个返回数据的字段填写上,这样返回的数据字段就会根据我们设置的字段返回。

最后点击保存,我们就成功了Mock了一个接口了。写完了这个接口,如何在我们的项目中测试呢?就需要我们的云端Mock功能了。

4.2. 云端Mock

云端Mock可以为我们提供一个云端服务器,就跟后端一样,会给我们返回Mock的数据。

🔥【白哥开源社区】第二期完结篇

如果所示:apifox会给我们提供一个云端Mock的url地址,这个就是接口网管地址,加上我们写的接口请求路径,就可以完成的请求一个接口了。

云端Mock未开启的请点击开关按钮开启。

开放访问:任何人都能访问。

Token鉴权:需要在接口地址中携带一个Token才能访问这个接口。

有个这个Mock的url以后,回到前我们创建的登录接口,切换右上角未云端Mock即可:

🔥【白哥开源社区】第二期完结篇

然后我们点击发送,可以成功请求到Mock结果了。

4.3. 期望Mock

情景:当我们登录的时候,会涉及到不同的登录判断情况,比如密码错误了,账户名错误了等等。

基于以上情景,我们需要使用高级Mock中期望Mock功能。

点击高级Mock中的新建期望

🔥【白哥开源社区】第二期完结篇

然后新增一个期望

🔥【白哥开源社区】第二期完结篇

这里我们可以根据参数来做判断,当不满足或满足某种情况,返回某些特定结果。

如果你的返回情况判断比较多,你可以写多种:

🔥【白哥开源社区】第二期完结篇

最后记得把你的按钮打开。关闭则不生效。

4.4. 脚本Mock

情景:当我们Mock一个分页列表的时候,需要根据分页参数来返回数据,期望Mock的方式已经不能再满足了。这时候就需要使用脚本Mock的方式了。

在高级Mock选择脚本,开启Mock

🔥【白哥开源社区】第二期完结篇

注意:脚本Mock的优先级比期望Mock低,需要先把期望Mock关闭。

然后在自定义脚本中输入代码:

var MockJs = require('mockjs');

var pageNum = Number(fox.mockRequest.getParam('pageNum'));
var pageSize = Number(fox.mockRequest.getParam('pageSize'));

// 生成中国手机号断前三位
function generateChinaMobilePrefix() {
  const prefixes = [
    '134', '135', '136', '137', '138', '139', '150', '151', '152', '157',
    '158', '159', '165', '1703', '1705', '1706', '178', '182', '183', '184',
    '187', '188', '195', '197', '198'
  ];
  const randomIndex = Math.floor(Math.random() * prefixes.length);
  return prefixes[randomIndex];
}

// 扩展手机号
MockJs.Random.extend({
  phone: function () {
    return generateChinaMobilePrefix() + MockJs.mock(/\d{8}/)
  }
});

// 生成分页列表数据
function generatePageList(pageNum, pageSize, total) {
  const results = [];
  for (let i = 0; i < pageSize; i++) {
    const id = (pageNum - 1) * pageSize + i + 1;
    results.push({ 
      id,
      "username": MockJs.mock('@first'),
      "nickName": MockJs.mock('@cname'),
      "roleName": '系统管理员 游客',
      "createTime": MockJs.mock('@datetime'),
      "updateTime": MockJs.mock('@datetime'),
      "phone": MockJs.mock("@phone")
    });
  }

  return {
    code: 200,
    message: "成功",
    data: {
        list: results,
        pageNum,
        pageSize,
        total,
    },
    ok: true
  };
}

fox.mockResponse.setBody(generatePageList(pageNum, pageSize, 100))

fox.mockRequest.getParam()会获取包括 Path 参数、Body 参数、Query 参数

fox.mockResponse.setBody()会把最终生成的数据返回。

其他的逻辑就是需要自己去判断和添加了。跟我们的js一样的。

4.5. Mock优先级

在apixfox的Mock中,默认Mock<脚本Mock<期望Mock。

4.6. Mock语法扩展

有时候我们想使用Mock中不存在的语法,比如生成手机号@phone,我们可以扩展:

var MockJs = require('mockjs');

// 扩展手机号
MockJs.Random.extend({
  phone: function () {
    var phonePrefixs = ['132', '135', '189']
    return this.pick(phonePrefixs) + MockJs.mock(/\d{8}/)
  }
});

// 使用
"phone": MockJs.mock("@phone")

4.7. Mock奇门遁甲

4.7.1. mock头像

"avatar": MockJs.mock(`@image('100x100', @color, @cname)`),

🔥【白哥开源社区】第二期完结篇

4.7.2. Mock枚举值

...MockJs.mock({"status|1": [0, 1]})
// 或者
status: Mockjs.mock('@integer(0, 1)')

5. 权限

5.1. 菜单权限

5.1.1. 添加mock数据

菜单权限的数据放在用户信息接口当中:/admin/acl/info,数据接口如下:

{
  "code": 200,
  "message": "成功",
  "data": {
    "routes": [
      "User",
      "Acl",
      "Role",
      "Permission",
      "UserManage",
      "UserNormal",
      "UserCreator",
      "UserAnchor",
      "UserManager",
      "UserActor",
      "MarketManager",
      "AdvertisementSetting",
      "AdvertisementSpaceSetting",
      "VIPManager",
      "ProxyManager",
      "FreeVideoPermissionManager",
      "GiftManager"
    ],
    "buttons": [
      "btn.User.assgin",
      "btn.User.add",
      "btn.User.remove",
      "btn.User.update",
      "btn.User.remove",
      "btn.Role.add",
      "btn.Role.assgin",
      "btn.Role.update",
      "btn.Role.remove"
    ],
    "roles": [
      "系统管理员",
      "游客"
    ],
    "name": "admin",
    "avatar": "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif"
  },
  "ok": true
}

其中

  • routes表示路由权限
  • buttons表示按钮权限

那么如果新增一个菜单权限,就需要在apifox中去修改routes的数据:

🔥【白哥开源社区】第二期完结篇

按照上面的步骤,在高级Mock的期望中,点击详情

🔥【白哥开源社区】第二期完结篇

然后在routes下面添加你需要的菜单权限名称。

注意:首字母全部大写,一级菜单和子菜单都需要提供对应的权限。

5.1.2. 添加路由菜单

接下来,我们回到代码中,找到route/dynamicRoutes.ts文件,这里面是所有的权限路由菜单。

export const dynamicRoutes: RouteRecordRaw[] = [
  // 权限管理
  {
    name: 'Acl',
    path: '/acl',
    component: Layout,
    redirect: '/acl/user',
    meta: {
      title: '权限管理',
      icon: 'Setting',
    },
    children: [
      {
        name: 'User',
        path: '/acl/user',
        component: () => import('@/views/acl/user/index.vue'),
        meta: {
          title: '用户管理',
          icon: 'UserFilled',
        },
      },
      {
        name: 'Role',
        path: '/acl/role',
        component: () => import('@/views/acl/role/index.vue'),
        meta: {
          title: '角色管理',
          icon: 'Avatar',
        },
      },
      {
        name: 'Permission',
        path: '/acl/permission',
        component: () => import('@/views/acl/permission/index.vue'),
        meta: {
          title: '菜单管理',
          icon: 'Menu',
        },
      },
    ],
  },
]

上面的示例中,没个路由都包含一个name字段,这个值会跟接口中的routes里面的值做匹配,如果匹配上,就会出现在左侧的菜单栏中。

5.1.3. 添加页面

最后,component中是页面路径,我们需要在views目录下添加对应的页面。

5.2. 按钮权限

5.2.1. 添加mock数据

在菜单权限中,我们已经知道了按钮的数据格式,是添加在buttons中的。现在我们需要给页面中增删改按钮添加对应的权限,同样的,还是在apifox中相同的位置:

🔥【白哥开源社区】第二期完结篇

在buttons下面添加对应的按钮权限值,btn开头,中间是菜单权限值,结尾是操作值。

5.2.2. 添加按钮逻辑

添加完成以后,现在我们去页面控制按钮。我把按钮分了下面两种情况,我更偏向于第二种。

一、按钮无权限,隐藏按钮

第一种情况就是按钮没有权限,直接在页面中隐藏掉,不让用户看见。

我在代码中添加了三种控制按钮的方法:

  1. 指令方式
<el-button
  type="primary"
  icon="Plus"
  v-auth="['btn.User.add']"
  @click="openDrawer('新增')"
>

通过v-auth传递一个按钮权限值。注意这里可以添加多个权限值。满足其中一个即可。

指令的底层代码在目录:src/directives/modules/auth.ts中,感兴趣的可以自己去看看逻辑。

  1. 组件方式
<Auth :value="['btn.Role.add']">
  <el-button type="primary" icon="Plus" @click="openDialog('新增')">
    添加
  </el-button>
</Auth>

这里我封装了一个Auth组件,传递一个按钮权限值,同样的可以添加多个权限值。

组件的底层代码在目录:src/components/Auth/src/Auth.tsx中,感兴趣的可以自己去看看逻辑。

  1. hooks方式
<template>
  <el-button
    type="primary"
    link
    v-if="BUTTONS['btn.Permission.update']"
    icon="Edit"
    @click="openDialog(2, scope.row)"
  >
    编辑
  </el-button>
</template>

<script setup lang="ts">
  
import { useAuthButtons } from '@/hooks/useAuthButtons'

const { BUTTONS } = useAuthButtons()
</script>

这里我封装了一个useAuthButtons的hooks函数,然后通过v-if的方式去控制按钮。同样的可以添加多个权限值。

hooks的底层代码在目录:src/hooks/useAuthButtons.ts中,感兴趣的可以自己去看看逻辑。

二、按钮无权限,按钮展示,禁用按钮

为什么说我更偏向于这样的方式来控制按钮呢?如果在页面有某个操作不让用户操作,应该禁用掉,而不是直接隐藏掉。还有一个问题就是当某个按钮隐藏掉以后会影响布局。比如表格中的操作栏:

🔥【白哥开源社区】第二期完结篇

当然这种方式写起来更繁琐和复杂。

假设一个业务场景:我需要对新增按钮进行权限的控制,无权限的时候我可以让用户看到这个按钮,但是设置一个disabled属性不让用户点击,代码如下:

<template #tableHeader>
  <el-button
    type="primary"
    icon="Plus"
    :disabled="!BUTTONS['btn.goodsInfo.add']"
    @click="openDialog('新增')"
  >
    添加
  </el-button>
</template>

这里使用了useAuthButtons这个hooks函数来控制。

仅仅这样做还是不够的,如果会打开浏览器调试工具的,直接把这个disabled干掉了不就行了?所以我们还需要在新增逻辑中控制一下:

<template #tableHeader>
  <el-button
    type="primary"
    icon="Plus"
    :disabled="!BUTTONS['btn.goodsInfo.add']"
    @click="openDialog('新增')"
  >
    添加
  </el-button>
</template>

<script setup lang="ts">
import { useAuth, hasAuth } from '@/hooks/useAuth'
import { useAuthButtons } from '@/hooks/useAuthButtons'
const { BUTTONS } = useAuthButtons()
// 点击新增按钮
const openDialog = async (
  title: string,
  rowData: Partial<GoodsInfo.ResGoodsInfoItem> = {},
) => {
  // 检查是否有操作权限
  await useAuth(hasAuth('btn.goodsInfo.add'))
  // 剩余逻辑
  ...
}
  </script>

这里我使用了两个hooks,hasAuth先判断是否有权限,然后使用useAuth来提示用户无权限。逻辑可以看这里:

// src/hooks/useAuth.ts
import { ElMessage } from 'element-plus'
import { useAuthStore } from '@/store/modules/auth'

// 无权限的时候提示
export const useAuth = (hasAuth: boolean) => {
  return new Promise((resolve) => {
    if (!hasAuth) {
      ElMessage({
        message: '你没有权限!!!',
        type: 'warning',
      })
    } else {
      resolve('success')
    }
  })
}

// 判断是否有权限
export const hasAuth = (value: string) => {
  const authStore = useAuthStore()
  const authButtons = authStore.authButtonList || []
  return authButtons.includes(value)
}

之所以使用这么多方式去实现按钮权限的控制,是想让大家在项目中灵活运用,掌握各种招式,条条大路通罗马,掌握其中一个,也不错。

6. Git规范

6.1. commit规范

前缀解释示例
feat新功能feat: 添加新功能
fix修复fix: 修改bug
docs文档变更docs: 更新文档
style代码样式变更style: 修改样式
refactor重构refactor: 重构代码
perf性能优化perf: 优化了性能
test增加测试test: 单元测试
revert回退revert: 回退代码
build打包build: 打包代码
chore构建过程或辅助工具的变动chore: 修改构建|

示例:

feat: 添加数据子典模块

6.2. 代码分支规范

分支管理命名规范解释
mastermaster稳定版本分支,上线完成回归后后,由项目技术负责人从 release 分支合并进来,并打 tag
feature/xiumubaifeature/功能名称比如你领取的任务是数据字典,分支就可以命名为:feature/dict,具体可以看看路由name的名字新功能开发使用分支,基于master建立
bugbug/功能名称示例:bug/style紧急线上bug修复使用分支,基于master建立

注意,在代码提交之前一定先pull主分支(master)的代码,然后merge到你的自己的分支,确保代码没有任何冲突,在提交到PR。

示例:

🔥【白哥开源社区】第二期完结篇

7. 开源贡献列表

朽木白:www.yuque.com/xiumubai

codebo: gitee.com/li-haibo-19…

wy: gitee.com/wy0531

cyhcnn:gitee.com/cyhcnn

8. 最后

如果你也想参加开源项目,可以添加我的wx:xiumubai01,我邀请你进【白哥开源社区】群。