iT邦幫忙

2025 iThome 鐵人賽

DAY 22
0
Software Development

全端工程師團隊的養成計畫系列 第 22

Day22 Vue 模板改造實戰:動態功能列

  • 分享至 

  • xImage
  •  

在前面幾篇文章裡,提到這次專案選擇透過外購套版來處理 UI/UX,藉此省下不少介面設計與元件開發的時間。本篇就延續這個話題,來分享如何利用模板進行改造與引用。Day7 已經整理過套版的專案結構,因此只要依照分類,就能快速找到需要的元件或頁面。接下來,先談談這次實際操作套版的心得,專案檔案主要分成 dashboard 與 components 兩大區塊,並逐步說明我們如何在專案中加以應用。

dashboard:代表整體頁面,包含各種 page、form、table、chart 與 layout 排版設計範例。可依照上方對應的 Route 路徑,在 MainRoutes.ts 中找到原始的 Vue 檔案。

  • 圖22-1:Dashboard 分類頁:可瀏覽頁面、表單、表格、圖表與版面配置等排版範例。
    圖22-1

  • 圖22-2:在 MainRoutes.ts 中查找對應的 Dashboard 範例路由設定。
    圖22-2

components:著重於獨立元件的使用方式,提供如 Autocomplete、Button、Chip、Progress 等物件的使用與調用範例。

  • 圖22-3:Components 分類總覽:Autocomplete、Button、Chip、Progress 等獨立元件示例。
    圖22-3

  • 圖22-4:在 ComponentRoutes.ts 中查找各元件範例的路由設定位置。
    圖22-4

改造成我們想要的樣子吧

  1. 首先,將購買回來的 starter-kit 資料夾複製到專案中,接著啟動服務。從這個基礎環境開始,我們就能一步一步進行改造,逐漸把畫面調整成理想的樣子。本篇的第一步,先來實作左側的功能列。
npm install // 首次執行就可以了
npm run dev

圖22-5:starter-kit 啟動後首頁;紅框為本篇要改造的左側功能清單區域。
圖22-5

  1. 在專案中找到左側功能列的模板 sidebarItem.ts,並檢視其綁定的 interface 物件結構。
    了解套版中左側功能列的物件結構與對應的模擬資料,先用來測試功能列的呈現效果,確保排版與互動符合預期。
    圖22-6 : sidebarItem.ts 的功能列物件結構與模擬資料,用於先行驗證排版與互動。
    圖22-6

  2. 將物件結構移至後端,由 backend API 負責處理。當使用者登入時,API 會依照身份與功能授權動態產生功能清單,並回傳給前端;前端再依據回應內容呈現功能列表。

//UserAccessDto.cs
namespace Core.Models.Dto
{
    public class UserAccessDto
    {
        public List<Menu> Menu { get; set; } = new();// 新增這個屬性

    }
    public class Menu
    {
        public string? Header { get; set; } // 功能群組
        public string? Title { get; set; } // 功能名稱
        public string? Icon { get; set; } //使用的 Icon 
        public string? To { get; set; } // route 導頁 vue 標的
        public bool? Divider { get; set; }
        public string? Chip { get; set; }
        public string? ChipColor { get; set; }
        public string? ChipVariant { get; set; }
        public string? ChipIcon { get; set; }
        public List<Menu>? Children { get; set; }
        public bool? Disabled { get; set; }
        public string? Type { get; set; }
        public string? SubCaption { get; set; }
    
    }

}
  1. 開啟 backend API 專案,新增 PermissionController.cs,作為處理功能授權與回應清單的控制器。
//PermissionController.cs
using Core.Models.Dto;
using backendAPI.Services;
using Microsoft.AspNetCore.Authorization;
namespace backendAPI.Controllers
{
    [Authorize] // 需有基本身分驗證
    [ApiController]
    [Route("api/[controller]")]
    [EnableCors("AllowSpecificOrigin")]

    public class PermissionController : ControllerBase
    {
    private readonly UserAccessService _userAccessService;
    public PermissionController(UserAccessService userAccessService)
    {
            //服務取得登入帳號授權清單的service
            _userAccessService = userAccessService ?? throw new ArgumentNullException(nameof(userAccessService))
    }
    [HttpGet]
    [ProducesResponseType(typeof(UserAccessDto), 200)]
    public async Task<IActionResult> GetPermission()
    { 
           var emailClaim = User.FindFirst(ClaimTypes.Upn)?.Value;
           UserAccessDto ?userAccess = new UserAccessDto();
           //至資料庫撈取帳號被授權的功能清單,並且將資料組何符合套版設計的物件結構存放資料
           List<Menu> menusList = await _userAccessService.GetSideBarItems(emailClaim); 
           userAccess.Menu = menusList;
           return Ok(userAccess);
      }
    }
}

補充說明:其實資料庫的欄位設計有依照套版進行微幅調整,目的是在組合功能列資料時,能更容易匯整成前端套版所需的樣式。不過,並非所有功能都會完全配合套版,仍需依需求與實務設計來決定。以下資料欄位為例,新增了 RoutePath 欄位,讓前端的 Menu.To 能正確導向指定的 Vue Page。

AppId AppName RoutePath OrderNo GroupType ShowOnMenu
1 一般硬體 App1 1 新增資產 1
2 軟體 App2 5 新增資產 1
3 資料庫 App3 4 新增資產 1
4 伺服器 App4 2 新增資產 1
5 網路設備 App5 3 新增資產 1
6 出入機房維護作業 App6 1 機房管理 1
7 進出機房查詢 App7 2 ServerRoom 1

Menu DTO 與實際 table 的對照:

Menu DTO屬性 Table欄位 說明
Title AppName 應用程式名稱
To RoutePath 路由路徑
Header GroupType 群組類型
OrderNo OrderNo 排序(List 會依此數值排序)
Disabled ShowOnMenu 是否顯示(僅會顯示 Disabled 的功能)
GroupName GroupType 功能群組名稱
  1. 回到前端專案,新增 auth.ts,用來呼叫後端 API,並將回傳結果存放於 LocalStorage
import { defineStore } from 'pinia';
import { router } from '@/router';
import type { AuthenticationResult } from '@azure/msal-browser';
import msalInstance from '@/stores/msalConfig';
import { fetchWrapper ,type ApiRspMessage } from '@/utils/helpers/fetch-wrapper';//Day10 建立的 fetch-wrapper.ts
const baseUrl = `${import.meta.env.VITE_API_URL}/Permission`;

export interface menu {
  header?: string;
  title?: string;
  icon?: string;
  to?: string;
  divider?: boolean;
  chip?: string;
  chipColor?: string;
  chipVariant?: string;
  chipIcon?: string;
  children?: menu[];
  disabled?: boolean;
  type?: string;
  subCaption?: string;

}
export const useAuthStore = defineStore({
  id: 'auth',
  state: () => ({
    user :  JSON.parse(localStorage.getItem('user') || 'null') ,
  }),
  actions: {
    async feachUserInfo() {
      try {
        const respData: UserAccessDto = await fetchWrapper.get(baseUrl) as UserAccessDto;
        this.user.menu = respData.menu;
        localStorage.setItem('user', JSON.stringify(this.user));
      } catch (error: any) {
        console.error('Failed to fetch user info', error);
      }

    } ,

  }

});
  1. 開啟前端的 VerticalSidebar.vue,在此頁面實際引用 sidebarItem 元件。
  • script 區域 (onMounted):執行 auth,同步後端的功能清單,並將資料透過 authStore 回寫到套版中的 sidebarMenu
  • template 區域:不需做任何調整,不需要的元素可以自行移除(哈!使用套版就是這麼方便)。
<script setup lang="ts">
import { onMounted,ref } from 'vue';
import { useCustomizerStore } from '../../../stores/customizer';
import { useAuthStore ,type  menu} from '@/stores/auth';
import NavGroup from './NavGroup/NavGroup.vue';
import NavItem from './NavItem/NavItem.vue';
import NavCollapse from './NavCollapse/NavCollapse.vue';
import Logo from '../logo/LogoMain.vue';

const customizer = useCustomizerStore();
const userStore = useAuthStore();
const sidebarMenu = ref(<menu[]>([]))
const authStore = useAuthStore();

onMounted(async () => {
  try {
    await authStore.fetchUserAccessDto(); // 觸發authStore.fetchUserAccessDto 同步後端授權的功能清單
    sidebarMenu.value = userStore.user.menu; // 取用authStore中的user.menu 資料

  } catch (error) {

    console.error('Failed to fetch menu:', error);

  }

});

</script>

<template>
  <v-navigation-drawer
    left
    v-model="customizer.sidebarDrawer"
    elevation="0"
    rail-width="90"
    mobile-breakpoint="lg"
    app
    class="leftSidebar"
    width="279"
    :rail="customizer.miniSidebar"
    expand-on-hover
  >

    <!---Logo part -->

    <div class="pa-5">
      <Logo />
    </div>
    <!-- ---------------------------------------------- -->

    <!---Navigation -->

    <!-- ---------------------------------------------- -->

    <perfect-scrollbar class="scrollnavbar">
      <v-list aria-busy="true" class="px-2" aria-label="menu list">
        <!---Menu Loop -->
        <template v-for="(item, i) in userStore.user.menu" :key="i">
          <!---Item Sub Header -->
          <NavGroup :item="item" v-if="item.header" :key="item.title" />
          <!---Item Divider -->
          <v-divider class="my-3" v-else-if="item.divider" />
          <!---If Has Child -->
          <NavCollapse class="leftPadding" :item="item" :level="0" v-else-if="item.children" />
          <!---Single Item-->
          <NavItem :item="item" v-else />
          <!---End Single Item-->
        </template>
      </v-list>
      <!-- <div class="pa-4">
        <ExtraBox />
      </div> -->
    </perfect-scrollbar>
  </v-navigation-drawer>
</template>

驗證結果

圖22-7:登入後由 API 回傳權限,前端動態產生左側功能列(不同帳號顯示不同選單)。
圖22-7

Ending Remark

本日的示範展示了如何將套版中原本的靜態選單改造成由後端權限驅動的動態選單。透過 Menu.ToRoutePath 的對應,以及 DTO 與資料表欄位的設計調整,前端能正確渲染對應的頁面。整體改造遵循「最小改動原則」,沿用套版的渲染邏輯,僅需在資料結構與 API 串接上下功夫,即可實現依使用者身份動態生成的功能清單。

最後,以圖示化呈現整體流程:從前端 VerticalSidebar.vue 在初始化時發出請求,經由 auth.ts 呼叫後端 API,由 UserAccessService 連接資料庫取得授權功能清單,再回傳給前端並寫入 LocalStorage,最終由前端完成功能列的動態渲染。這個流程清楚地展示了前後端資料如何流轉與互動。

圖22-8
圖22-8:功能清單初始化與授權流程圖


上一篇
Day21 Vue 元件:Easy Data Table 進階用應用
下一篇
Day23 Vue 模板改造實戰:使用者群組授權功能(一)
系列文
全端工程師團隊的養成計畫26
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言