iT邦幫忙

2022 iThome 鐵人賽

DAY 8
0

今天進入寫code環節!

先找一個不錯的頁面
http://yiming_chang.gitee.io/vue-pure-admin/#/system/role/index
發現這個看起來不錯~安排~~

我會下載兩份程式碼互相比對
vue-pure-admin 功能多,不好入門
pure-admin-thin 好入門,功能偏少

TLTR

我想新增一個"角色管理頁面"流程如下

  • 修改動態路由主檔案,決定 path 名稱
    • \pure-admin-thin\mock\asyncRoutes.ts
    • 多國語系yml檔案修改
      • \pure-admin-thin\locales
  • 新增假資料(/mock)
    • \pure-admin-thin\mock\system.ts
  • 新增前端api(/src/api)
    • \pure-admin-thin\src\api\system.ts
  • 新增components(/src/components)
    • \pure-admin-thin\src\components\ReTable\src\bar.tsx
    • \pure-admin-thin\src\components\ReTable\index.ts
  • 新增views(/src/views)
    • \pure-admin-thin\src\views\system\role\columns.tsx
    • \pure-admin-thin\src\views\system\role\index.vue

目錄
目錄截圖(片段)

修改動態路由

雖然說是動態路由,其實應該說是新增左邊的菜單定義以較好懂

眼尖的觀眾應該猜到 itle: "menus.hssysManagement", 其實是作者多國語系的規範
這邊就略過不展開討論
路由1

把定義好的 systemRouter 放在 info
路由2

這樣一來系統一進去就可以拿到菜單了
路由3

新增假資料

因為我們前面定義的path='/system'
在mock命名也叫做system.ts (實際上名稱取甚麼都ok不影響 MOCK API)

  //\pure-admin-thin\mock\system.ts

import { MockMethod } from "vite-plugin-mock";

export default [
  {
    url: "/role",
    method: "post",
    response: () => {
      return {
        code: 0,
        data: {
          list: [
            {
              createTime: 1609837428000,
              updateTime: 1645477701000,
              creator: "admin",
              updater: "",
              deleted: false,
              tenantId: 1,
              id: 1,
              name: "超级管理员",
              code: "super_admin",
              sort: 1,
              status: 0,
              type: 1,
              remark: "超级管理员",
              dataScope: 1,
              dataScopeDeptIds: null
            },
            {
              createTime: 1609837428000,
              updateTime: 1645477700000,
              creator: "admin",
              updater: "",
              deleted: false,
              tenantId: 1,
              id: 2,
              name: "普通角色",
              code: "common",
              sort: 2,
              status: 0,
              type: 1,
              remark: "普通角色",
              dataScope: 2,
              dataScopeDeptIds: null
            },
            {
              createTime: 1609912175000,
              updateTime: 1647698441000,
              creator: "",
              updater: "1",
              deleted: false,
              tenantId: 1,
              id: 101,
              name: "测试账号",
              code: "test",
              sort: 0,
              status: 0,
              type: 2,
              remark: "132",
              dataScope: 1,
              dataScopeDeptIds: []
            }
          ],
          total: 3
        }
      };
    }
  }
] as MockMethod[];

新增前端api

同上 新增一個 system.ts的檔案放在 /src/api資料夾底下

  //\pure-admin-thin\src\api\system.ts

import { http } from "../utils/http";

type Result = {
  data?: {
    list: Array<any>;
    total: number;
  };
  code?: number;
  msg?: string;
};

export const getRoleList = (data?: object) => {
  return http.request<Result>("post", "/role", { data });
};

新增 components ReTable

因為後面步驟會用到故先新增
import { TableProBar } from "/@/components/ReTable";

值得一提的是 這邊我卡關一段時間,Icon有bug載入一直失敗
雖然不影響功能後來還是拔掉了 dropdown 相關插槽的程式碼
拔掉後就成功執行

    //\pure-admin-thin\src\components\ReTable\src\bar.tsx

import { defineComponent, ref, PropType } from "vue";
import { IconifyIconOffline } from "../../ReIcon";

export const loadingSvg = `
  <path class="path" d="
    M 30 15
    L 28 17
    M 25.61 25.61
    A 15 15, 0, 0, 1, 15 30
    A 15 15, 0, 1, 1, 27.99 7.5
    L 15 15
  "
    style="stroke-width: 4px; fill: rgba(0, 0, 0, 0)"
  />
`;

const props = {
  // 標題
  title: {
    type: String,
    default: "列表"
  },
  // data
  dataList: {
    type: Array,
    default: () => {
      return [];
    }
  },
  // 摺疊
  tableRef: {
    type: Object as PropType<any>,
    default() {
      return {};
    }
  },
  // loading 動畫
  loading: {
    type: Boolean,
    default: false
  }
};

export default defineComponent({
  name: "TableProBar",
  props,
  emits: ["refresh"],
  setup(props, { emit, slots, attrs }) {
    const buttonRef = ref();
    const checkList = ref([]);
    const size = ref("default");
    const isExpandAll = ref(true);

    function onExpand() {
      isExpandAll.value = !isExpandAll.value;
      toggleRowExpansionAll(props.dataList, isExpandAll.value);
    }

    function toggleRowExpansionAll(data, isExpansion) {
      data.forEach(item => {
        props.tableRef.toggleRowExpansion(item, isExpansion);
        if (item.children !== undefined && item.children !== null) {
          toggleRowExpansionAll(item.children, isExpansion);
        }
      });
    }

    const reference = {
      reference: () => (
        <IconifyIconOffline
          class="cursor-pointer"
          icon="setting"
          width="16"
          color="text_color_regular"
          onMouseover={e => (buttonRef.value = e.currentTarget)}
        />
      )
    };

    return () => (
      <>
        <div
          {...attrs}
          class="w-[99/100] mt-6 p-2 bg-bg_color"
          v-loading={props.loading}
          element-loading-svg={loadingSvg}
          element-loading-svg-view-box="-10, -10, 50, 50"
        >
          <div class="flex justify-between w-full h-[60px] p-4">
            <p class="font-bold truncate">{props.title}</p>
            <div class="flex items-center justify-around">
              <div class="flex mr-4">{slots?.buttons()}</div>
              {props.tableRef?.size ? (
                <>
                  <el-tooltip
                    effect="dark"
                    content={isExpandAll.value ? "折叠" : "展开"}
                    placement="top"
                  >
                    <IconifyIconOffline
                      class="cursor-pointer"
                      icon={isExpandAll.value ? "unExpand" : "expand"}
                      width="16"
                      color="text_color_regular"
                      onClick={() => onExpand()}
                    />
                  </el-tooltip>
                  <el-divider direction="vertical" />
                </>
              ) : undefined}
              <el-tooltip effect="dark" content="刷新" placement="top">
                <IconifyIconOffline
                  class="cursor-pointer"
                  icon="refresh-right"
                  width="16"
                  color="text_color_regular"
                  onClick={() => emit("refresh")}
                />
              </el-tooltip>
              <el-divider direction="vertical" />

              <el-popover v-slots={reference} width="200" trigger="click">
                <el-checkbox-group v-model={checkList.value}>
                  <el-checkbox label="序号列" />
                  <el-checkbox label="勾选列" />
                </el-checkbox-group>
              </el-popover>
            </div>

            <el-tooltip
              popper-options={{
                modifiers: [
                  {
                    name: "computeStyles",
                    options: {
                      adaptive: false,
                      enabled: false
                    }
                  }
                ]
              }}
              placement="top"
              virtual-ref={buttonRef.value}
              virtual-triggering
              trigger="hover"
              content="列设置"
            />
          </div>
          {props.dataList.length > 0 ? (
            slots.default({ size: size.value, checkList: checkList.value })
          ) : (
            <el-empty description="暂无数据" />
          )}
        </div>
      </>
    );
  }
});

    // \pure-admin-thin\src\components\ReTable\index.ts

import tableProBar from "./src/bar";
import { withInstall } from "@pureadmin/utils";

/** table-crud */
export const TableProBar = withInstall(tableProBar);

最後一步新增views

    // \pure-admin-thin\src\views\system\role\columns.tsx
import { ref } from "vue";
import dayjs from "dayjs";
import { ElMessageBox } from "element-plus";
import { Switch, message } from "@pureadmin/components";

export function useColumns() {
  const switchLoadMap = ref({});

  const columns = ref([
    {
      type: "selection",
      width: 55,
      hide: ({ checkList }) => !checkList.includes("勾选列")
    },
    {
      label: "序号",
      type: "index",
      width: 70,
      hide: ({ checkList }) => !checkList.includes("序号列")
    },
    {
      label: "角色编号",
      prop: "id"
    },
    {
      label: "角色名称",
      prop: "name"
    },
    {
      label: "角色标识",
      prop: "code"
    },
    {
      label: "角色类型",
      prop: "type",
      cellRenderer: ({ row, props }) => (
        <el-tag
          size={props.size}
          type={row.type === 1 ? "danger" : ""}
          effect="plain"
        >
          {row.type === 1 ? "内置" : "自定义"}
        </el-tag>
      )
    },
    {
      label: "显示顺序",
      prop: "sort"
    },
    {
      label: "状态",
      prop: "status",
      width: 130,
      cellRenderer: scope => (
        <Switch
          size={scope.props.size === "small" ? "small" : "default"}
          loading={switchLoadMap.value[scope.index]?.loading}
          v-model:checked={scope.row.status}
          checkedValue={1}
          unCheckedValue={0}
          checked-children="已开启"
          un-checked-children="已关闭"
          onChange={() => onChange(scope)}
        />
      )
    },
    {
      label: "创建时间",
      width: 180,
      prop: "createTime",
      formatter: ({ createTime }) =>
        dayjs(createTime).format("YYYY-MM-DD HH:mm:ss")
    },
    {
      label: "操作",
      fixed: "right",
      width: 180,
      slot: "operation"
    }
  ]);

  function onChange({ row, index }) {
    ElMessageBox.confirm(
      `确认要<strong>${
        row.status === 0 ? "停用" : "启用"
      }</strong><strong style='color:var(--el-color-primary)'>${
        row.name
      }</strong>角色吗?`,
      "系统提示",
      {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
        dangerouslyUseHTMLString: true,
        draggable: true
      }
    )
      .then(() => {
        switchLoadMap.value[index] = Object.assign(
          {},
          switchLoadMap.value[index],
          {
            loading: true
          }
        );
        setTimeout(() => {
          switchLoadMap.value[index] = Object.assign(
            {},
            switchLoadMap.value[index],
            {
              loading: false
            }
          );
          message.success("已成功修改角色状态");
        }, 300);
      })
      .catch(() => {
        row.status === 0 ? (row.status = 1) : (row.status = 0);
      });
  }

  return {
    columns
  };
}

    // \pure-admin-thin\src\views\system\role\index.vue
<script setup lang="ts">
import { useColumns } from "./columns";
import { getRoleList } from "/@/api/system";
import { reactive, ref, onMounted } from "vue";
import { TableProBar } from "/@/components/ReTable";
import { PaginationProps } from "@pureadmin/table";
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";

//一定要引入
import { PureTable } from "@pureadmin/table";

defineOptions({
  name: "Role"
});

let dataList = ref([]);
let loading = ref(true);
const { columns } = useColumns();

const pagination = reactive<PaginationProps>({
  total: 0,
  pageSize: 10,
  currentPage: 1,
  background: true
});

function handleUpdate(row) {
  console.log(row);
}

function handleDelete(row) {
  console.log(row);
}

function handleCurrentChange(val: number) {
  console.log(`current page: ${val}`);
}

function handleSizeChange(val: number) {
  console.log(`${val} items per page`);
}

function handleSelectionChange(val) {
  console.log("handleSelectionChange", val);
}

async function onSearch() {
  loading.value = true;
  let { data } = await getRoleList();
  dataList.value = data.list;
  pagination.total = data.total;
  setTimeout(() => {
    loading.value = false;
  }, 500);
}

onMounted(() => {
  onSearch();
});
</script>

<template>
  <div class="main">
    <TableProBar
      title="角色列表"
      :loading="loading"
      :dataList="dataList"
      @refresh="onSearch"
    >
      <template #buttons>
        <el-button type="primary" :icon="useRenderIcon('add')">
          新增角色
        </el-button>
      </template>
      <template v-slot="{ size, checkList }">
        <PureTable
          border
          align="center"
          showOverflowTooltip
          table-layout="auto"
          :size="size"
          :data="dataList"
          :columns="columns"
          :checkList="checkList"
          :pagination="pagination"
          :paginationSmall="size === 'small' ? true : false"
          :header-cell-style="{
            background: 'var(--el-table-row-hover-bg-color)',
            color: 'var(--el-text-color-primary)'
          }"
          @selection-change="handleSelectionChange"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
        >
          <template #operation="{ row }">
            <el-button
              class="reset-margin"
              link
              type="primary"
              :size="size"
              @click="handleUpdate(row)"
              :icon="useRenderIcon('edits')"
            >
              修改
            </el-button>
            <el-popconfirm title="是否确认删除?">
              <template #reference>
                <el-button
                  class="reset-margin"
                  link
                  type="primary"
                  :size="size"
                  :icon="useRenderIcon('delete')"
                  @click="handleDelete(row)"
                >
                  删除
                </el-button>
              </template>
            </el-popconfirm>
            <el-dropdown>
              <el-button
                class="ml-3"
                link
                type="primary"
                :size="size"
                @click="handleUpdate(row)"
                :icon="useRenderIcon('more')"
              />
              <template #dropdown>
                <el-dropdown-menu>
                  <el-dropdown-item>
                    <el-button
                      class="reset-margin !h-[20px] !text-gray-500 dark:!text-white dark:hover:!text-primary"
                      link
                      type="primary"
                      :size="size"
                      :icon="useRenderIcon('menu')"
                    >
                      菜单权限
                    </el-button>
                  </el-dropdown-item>
                  <el-dropdown-item>
                    <el-button
                      class="reset-margin !h-[20px] !text-gray-500 dark:!text-white dark:hover:!text-primary"
                      link
                      type="primary"
                      :size="size"
                      :icon="useRenderIcon('database')"
                    >
                      数据权限
                    </el-button>
                  </el-dropdown-item>
                </el-dropdown-menu>
              </template>
            </el-dropdown>
          </template>
        </PureTable>
      </template>
    </TableProBar>
  </div>
</template>

<style scoped lang="scss">
// :deep(.el-dropdown-menu__item i) {
//   margin: 0;
// }
</style>

完成

總結

這篇文章把程式碼都貼上來的用意是希望能夠"完整地"
將作者用心製作的 vue-pure-admin 部分功能頁面整合至 pure-admin-thin

因為在網路上要做這麼多截圖和複製程式碼不是簡單幾分鐘就能完成的~

希望這個流程對於有需要的讀者有所幫助囉!

/images/emoticon/emoticon06.gif

小插曲

今天發現在使用作者封裝的 PureTable components 後開啟 Vue.js devtools
Win10 操作網頁變成非常卡頓!(還以為在看簡報)

關掉Vue.js devtools 後就正常了


上一篇
第七天 盤點 pure-admin-thin 用了哪些套件?
下一篇
第九天 閱讀 pure-admin-table
系列文
教練我想做一個後台管理系統,阿我忘記我只有一個人沒有教練,那用試著以vue-pure-admin為基底做做看31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言