feat: 更新租户申请和管理页面的路由及布局信息
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
# 页面设计: 租户申请/成为创作者 (Tenant Application)
|
# 页面设计: 租户申请/成为创作者 (Tenant Application)
|
||||||
|
|
||||||
> **路由**: `/me/tenant/apply`
|
> **路由**: `/creator/apply` > **布局**: `LayoutMain` (头部增加引导展示)
|
||||||
> **布局**: `LayoutMain` (头部增加引导展示)
|
|
||||||
> **入口**: `TopNavbar` 右侧 [成为创作者] 按钮 (仅对非租户显示)
|
> **入口**: `TopNavbar` 右侧 [成为创作者] 按钮 (仅对非租户显示)
|
||||||
|
|
||||||
## 1. 流程结构 (Flow)
|
## 1. 流程结构 (Flow)
|
||||||
@@ -17,16 +16,19 @@
|
|||||||
## 2. 详细设计 (Detailed Specs)
|
## 2. 详细设计 (Detailed Specs)
|
||||||
|
|
||||||
### 2.1 入驻引导页 (Landing)
|
### 2.1 入驻引导页 (Landing)
|
||||||
|
|
||||||
用户首次点击 [成为创作者] 时展示。
|
用户首次点击 [成为创作者] 时展示。
|
||||||
|
|
||||||
- **视觉 (Hero)**:
|
- **视觉 (Hero)**:
|
||||||
- 简洁、大气的插画/背景,文案: "开启您的内容创作之旅"。
|
- 简洁、大气的插画/背景,文案: "开启您的内容创作之旅"。
|
||||||
- 核心权益 (3个大图标):
|
- 核心权益 (3 个大图标):
|
||||||
1. **内容变现**: 设置付费阅读,直接获得收益。
|
1. **内容变现**: 设置付费阅读,直接获得收益。
|
||||||
2. **租户主页**: 拥有独立的品牌/个人展示空间。
|
2. **租户主页**: 拥有独立的品牌/个人展示空间。
|
||||||
3. **数据管理**: 清晰的内容与订单统计。
|
3. **数据管理**: 清晰的内容与订单统计。
|
||||||
- **操作**: 底部显著的 [立即申请] 大按钮。
|
- **操作**: 底部显著的 [立即申请] 大按钮。
|
||||||
|
|
||||||
### 2.2 申请表单页 (Application Form)
|
### 2.2 申请表单页 (Application Form)
|
||||||
|
|
||||||
**容器**: `max-w-3xl mx-auto bg-white rounded-lg shadow-sm p-10 my-8`。
|
**容器**: `max-w-3xl mx-auto bg-white rounded-lg shadow-sm p-10 my-8`。
|
||||||
|
|
||||||
- **表单字段 (适老化大输入框)**:
|
- **表单字段 (适老化大输入框)**:
|
||||||
@@ -42,6 +44,7 @@
|
|||||||
- **操作**: [提交审核] (Primary Button, h-14, text-xl)。
|
- **操作**: [提交审核] (Primary Button, h-14, text-xl)。
|
||||||
|
|
||||||
### 2.3 审核结果页 (Review Status)
|
### 2.3 审核结果页 (Review Status)
|
||||||
|
|
||||||
- **提交成功**:
|
- **提交成功**:
|
||||||
- 绿色对勾图标 + "申请已提交"。
|
- 绿色对勾图标 + "申请已提交"。
|
||||||
- 文案: "平台将在 1-3 个工作日内完成审核,结果将通过系统通知告知您。"。
|
- 文案: "平台将在 1-3 个工作日内完成审核,结果将通过系统通知告知您。"。
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# 页面设计: 租户/个人主页 (Tenant Profile)
|
# 页面设计: 租户/个人主页 (Tenant Profile)
|
||||||
|
|
||||||
> **路由**: `/tenants/:tenantId`
|
> **路由**: `/t/:tenantId` > **布局**: `LayoutMain` (Header + Footer)
|
||||||
> **布局**: `LayoutMain` (Header + Footer)
|
|
||||||
> **结构**: 固定宽度 (1280px) 封面头部 + 切换内容区
|
> **结构**: 固定宽度 (1280px) 封面头部 + 切换内容区
|
||||||
|
|
||||||
## 1. 页面结构 (Structure)
|
## 1. 页面结构 (Structure)
|
||||||
@@ -25,6 +24,7 @@
|
|||||||
## 2. 详细设计 (Detailed Specs)
|
## 2. 详细设计 (Detailed Specs)
|
||||||
|
|
||||||
### 2.1 头部区域 (Profile Header)
|
### 2.1 头部区域 (Profile Header)
|
||||||
|
|
||||||
- **封面图 (Cover)**:
|
- **封面图 (Cover)**:
|
||||||
- 尺寸: **1280x320px** (4:1 比例)。
|
- 尺寸: **1280x320px** (4:1 比例)。
|
||||||
- **适配**: `object-cover`,定位 `center center`。
|
- **适配**: `object-cover`,定位 `center center`。
|
||||||
@@ -41,11 +41,13 @@
|
|||||||
- **内容**: 点击自动切换至 "全部内容" Tab。
|
- **内容**: 点击自动切换至 "全部内容" Tab。
|
||||||
|
|
||||||
### 2.2 导航与筛选 (Sticky Navigation & Search)
|
### 2.2 导航与筛选 (Sticky Navigation & Search)
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
### 2.3 内容展示逻辑 (Content Area)
|
### 2.3 内容展示逻辑 (Content Area)
|
||||||
|
|
||||||
#### B. "全部内容" Tab (All Posts)
|
#### B. "全部内容" Tab (All Posts)
|
||||||
|
|
||||||
- **卡片内容类型标识 (Media Markers)**:
|
- **卡片内容类型标识 (Media Markers)**:
|
||||||
- **位置**: 封面图左下角。
|
- **位置**: 封面图左下角。
|
||||||
- **视频**: `Play Icon` + `12:05` (时长)。
|
- **视频**: `Play Icon` + `12:05` (时长)。
|
||||||
@@ -59,20 +61,25 @@
|
|||||||
- **兜底**: 若价格数据异常缺失,默认显示为 "免费" 或 "价格待定"。
|
- **兜底**: 若价格数据异常缺失,默认显示为 "免费" 或 "价格待定"。
|
||||||
|
|
||||||
### 2.4 "关于" Tab & 更多操作
|
### 2.4 "关于" Tab & 更多操作
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
### 2.5 响应式与移动端适配 (lg 以下)
|
### 2.5 响应式与移动端适配 (lg 以下)
|
||||||
|
|
||||||
- **操作按钮布局**:
|
- **操作按钮布局**:
|
||||||
- 当屏幕宽度 `< 768px` 时,关注与私信按钮由水平排列改为 **通栏吸底 (Sticky Bottom)**,以保证单手操作便捷。
|
- 当屏幕宽度 `< 768px` 时,关注与私信按钮由水平排列改为 **通栏吸底 (Sticky Bottom)**,以保证单手操作便捷。
|
||||||
- 更多操作 (`...`) 变为右上角固定菜单。
|
- 更多操作 (`...`) 变为右上角固定菜单。
|
||||||
- **统计数据**: 缩放字号并横向等分排列。
|
- **统计数据**: 缩放字号并横向等分排列。
|
||||||
|
|
||||||
#### D. "关于" Tab (About)
|
#### D. "关于" Tab (About)
|
||||||
|
|
||||||
- **内容层级**:
|
- **内容层级**:
|
||||||
- **排版**: 使用 `prose` 规范,阅读行宽限制在 **800px** 以内。
|
- **排版**: 使用 `prose` 规范,阅读行宽限制在 **800px** 以内。
|
||||||
- **图片**: 图片说明文字居中 `text-sm text-slate-400 mt-2`。
|
- **图片**: 图片说明文字居中 `text-sm text-slate-400 mt-2`。
|
||||||
- **联系方式**: 脱敏展示,点击 [查看] 需验证。
|
- **联系方式**: 脱敏展示,点击 [查看] 需验证。
|
||||||
|
|
||||||
### 2.5 侧边引导与辅助
|
### 2.5 侧边引导与辅助
|
||||||
|
|
||||||
- **分享入口**: 右下角悬浮分享按钮 (FAB)。
|
- **分享入口**: 右下角悬浮分享按钮 (FAB)。
|
||||||
- **推荐区块**: 若采用 9:3 布局,右侧展示 "推荐关注"。
|
- **推荐区块**: 若采用 9:3 布局,右侧展示 "推荐关注"。
|
||||||
- **订阅提醒**: 未关注用户滚到底部时弹出轻量订阅 Toast。
|
- **订阅提醒**: 未关注用户滚到底部时弹出轻量订阅 Toast。
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
# 页面设计: 租户管理/创作者中心 (Tenant Management)
|
# 页面设计: 租户管理/创作者中心 (Tenant Management)
|
||||||
|
|
||||||
> **路由**: `/me/management/*`
|
> **路由**: `/creator/*` > **布局**: `LayoutMain` (复用门户布局,采用侧边栏分栏结构)
|
||||||
> **布局**: `LayoutMain` (复用门户布局,采用侧边栏分栏结构)
|
|
||||||
> **结构**: 与个人中心一致的左侧导航布局,保持视觉与操作连续性
|
> **结构**: 与个人中心一致的左侧导航布局,保持视觉与操作连续性
|
||||||
|
|
||||||
## 1. 布局结构 (Structure)
|
## 1. 布局结构 (Structure)
|
||||||
@@ -17,7 +16,8 @@
|
|||||||
|
|
||||||
## 2. 核心功能模块
|
## 2. 核心功能模块
|
||||||
|
|
||||||
### 2.1 管理概览 (Dashboard) `/me/management`
|
### 2.1 管理概览 (Dashboard) `/creator`
|
||||||
|
|
||||||
- **核心指标卡片 (Key Metrics)**:
|
- **核心指标卡片 (Key Metrics)**:
|
||||||
- **总关注用户**: 显示累计粉丝数及较前一日的增长变化 (+/-)。
|
- **总关注用户**: 显示累计粉丝数及较前一日的增长变化 (+/-)。
|
||||||
- **订单收益**:
|
- **订单收益**:
|
||||||
@@ -28,12 +28,13 @@
|
|||||||
- [待处理订单]: 待发货或退款申请中的实时数量提示。
|
- [待处理订单]: 待发货或退款申请中的实时数量提示。
|
||||||
- **内容表现**: 累计阅读量 (PV) 与 获赞总数。
|
- **内容表现**: 累计阅读量 (PV) 与 获赞总数。
|
||||||
- **趋势分析图表 (Charts)**:
|
- **趋势分析图表 (Charts)**:
|
||||||
- **收益/订单趋势**: 支持切换 [近7天] / [近30天] 的柱状图,直观展示收入波动。
|
- **收益/订单趋势**: 支持切换 [近 7 天] / [近 30 天] 的柱状图,直观展示收入波动。
|
||||||
- **粉丝增长曲线**: 折线图展示每日新增粉丝趋势。
|
- **粉丝增长曲线**: 折线图展示每日新增粉丝趋势。
|
||||||
- **快捷入口**: [发布新内容] [处理退款申请]。
|
- **快捷入口**: [发布新内容] [处理退款申请]。
|
||||||
- **公告通知**: 创作者专属公告(如:平台分成比例调整通知)。
|
- **公告通知**: 创作者专属公告(如:平台分成比例调整通知)。
|
||||||
|
|
||||||
### 2.2 内容管理 (Content Management) `/me/management/contents`
|
### 2.2 内容管理 (Content Management) `/creator/contents`
|
||||||
|
|
||||||
- **列表展示**:
|
- **列表展示**:
|
||||||
- **字段**: 封面 | **曲种/剧目** | 价格 | 状态 | 阅读/收益 | 操作。
|
- **字段**: 封面 | **曲种/剧目** | 价格 | 状态 | 阅读/收益 | 操作。
|
||||||
- **状态说明 (Audit Status)**:
|
- **状态说明 (Audit Status)**:
|
||||||
@@ -48,12 +49,12 @@
|
|||||||
- [剧目名] (必填, 如《锁麟囊》)
|
- [剧目名] (必填, 如《锁麟囊》)
|
||||||
- [选段名] (可选, 如"春秋亭")
|
- [选段名] (可选, 如"春秋亭")
|
||||||
- [附加信息] (可选, 如"程砚秋")
|
- [附加信息] (可选, 如"程砚秋")
|
||||||
- *预览*: 自动拼接展示 `《锁麟囊》春秋亭 (程砚秋)`。
|
- _预览_: 自动拼接展示 `《锁麟囊》春秋亭 (程砚秋)`。
|
||||||
- **工具栏功能**:
|
- **工具栏功能**:
|
||||||
- **基础排版**: H1/H2/H3, 加粗, 下划线, 列表, 居中/对齐。
|
- **基础排版**: H1/H2/H3, 加粗, 下划线, 列表, 居中/对齐。
|
||||||
- **戏曲专用**:
|
- **戏曲专用**:
|
||||||
- **剧本模式**: 专门的 [角色: 台词] 格式块,自动悬挂缩进。
|
- **剧本模式**: 专门的 [角色: 台词] 格式块,自动悬挂缩进。
|
||||||
- **唱段属性**: 插入音频时可选填 **定调 (Key)** (如 C大调/D大调) 和 **板式** (如 原板/流水)。
|
- **唱段属性**: 插入音频时可选填 **定调 (Key)** (如 C 大调/D 大调) 和 **板式** (如 原板/流水)。
|
||||||
- **注音工具**: 支持给生僻字添加拼音标注 (Ruby),解决尖团音等阅读障碍。
|
- **注音工具**: 支持给生僻字添加拼音标注 (Ruby),解决尖团音等阅读障碍。
|
||||||
- **媒体 (仅支持上传)**: 图片, 视频 (支持断点续传), 音频, 附件。**不支持外链**。
|
- **媒体 (仅支持上传)**: 图片, 视频 (支持断点续传), 音频, 附件。**不支持外链**。
|
||||||
- **操作**: 撤销, 重做, 清除格式, 插入分割线。
|
- **操作**: 撤销, 重做, 清除格式, 插入分割线。
|
||||||
@@ -78,7 +79,8 @@
|
|||||||
- 输入时长: 默认 `60` 秒 (支持自定义,针对戏曲长视频建议设为 3-5 分钟)。
|
- 输入时长: 默认 `60` 秒 (支持自定义,针对戏曲长视频建议设为 3-5 分钟)。
|
||||||
- **操作**: [存草稿] [立即发布]。
|
- **操作**: [存草稿] [立即发布]。
|
||||||
|
|
||||||
### 2.3 订单管理 (Order Management) `/me/management/orders`
|
### 2.3 订单管理 (Order Management) `/creator/orders`
|
||||||
|
|
||||||
- **列表展示**:
|
- **列表展示**:
|
||||||
- 记录所有购买该租户内容的订单。
|
- 记录所有购买该租户内容的订单。
|
||||||
- **字段**: 订单号 | 内容标题 | 买家昵称 | 实付金额 | 下单时间 | 状态。
|
- **字段**: 订单号 | 内容标题 | 买家昵称 | 实付金额 | 下单时间 | 状态。
|
||||||
@@ -91,21 +93,24 @@
|
|||||||
- [处理退款]: (仅针对退款申请) 弹出审核框,选择 [同意退款] 或 [拒绝]。
|
- [处理退款]: (仅针对退款申请) 弹出审核框,选择 [同意退款] 或 [拒绝]。
|
||||||
- [发送私信]: 快速联系买家。
|
- [发送私信]: 快速联系买家。
|
||||||
|
|
||||||
### 2.4 租户资料设置 (Tenant Settings) `/me/management/settings`
|
### 2.4 租户资料设置 (Tenant Settings) `/creator/settings`
|
||||||
|
|
||||||
- **内容**:
|
- **内容**:
|
||||||
- 修改租户主页的封面图 (Cover)、名称、简介。
|
- 修改租户主页的封面图 (Cover)、名称、简介。
|
||||||
- 资质材料上传(用于等级提升或认证)。
|
- 资质材料上传(用于等级提升或认证)。
|
||||||
- **同步**: 此处修改后,租户公开主页 (`/tenants/:id`) 同步更新。
|
- **同步**: 此处修改后,租户公开主页 (`/t/:id`) 同步更新。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. 交互细节与权限
|
## 3. 交互细节与权限
|
||||||
|
|
||||||
### 3.1 权限拦截
|
### 3.1 权限拦截
|
||||||
- **访问控制**: 非租户管理员访问 `/me/management` 直接跳转至“租户申请”页或 403。
|
|
||||||
|
- **访问控制**: 非租户管理员访问 `/creator` 直接跳转至“租户申请”页或 403。
|
||||||
- **身份显示**: 在管理页面顶部显著位置显示“当前正在管理:[租户名称]”,防止多账号混淆。
|
- **身份显示**: 在管理页面顶部显著位置显示“当前正在管理:[租户名称]”,防止多账号混淆。
|
||||||
|
|
||||||
### 3.2 适老化管理体验
|
### 3.2 适老化管理体验
|
||||||
|
|
||||||
- **大按钮**: 所有的“发布”、“编辑”按钮均采用 `h-12` 以上高度。
|
- **大按钮**: 所有的“发布”、“编辑”按钮均采用 `h-12` 以上高度。
|
||||||
- **状态感知**: 每一个操作(如保存草稿)都有明确的 Toast 反馈。
|
- **状态感知**: 每一个操作(如保存草稿)都有明确的 Toast 反馈。
|
||||||
- **自动保存**: 编辑器支持本地离线保存,防止误关闭导致内容丢失。
|
- **自动保存**: 编辑器支持本地离线保存,防止误关闭导致内容丢失。
|
||||||
|
|||||||
@@ -1,798 +0,0 @@
|
|||||||
# Sakai Vue - Admin Dashboard Template
|
|
||||||
|
|
||||||
Sakai Vue is a modern, production-ready admin dashboard template built with Vue 3, Vite, and PrimeVue. It provides a comprehensive set of UI components, pre-built layouts, and data services for rapidly developing enterprise-grade web applications. The template follows Vue 3 Composition API patterns with reactive state management and includes extensive component showcases demonstrating best practices.
|
|
||||||
|
|
||||||
The application features a modular architecture with five mock data services (Product, Customer, Country, Node, and Photo), a flexible theming system with three presets and 17 color schemes, and responsive layouts optimized for both desktop and mobile devices. It includes a complete CRUD implementation, dashboard with analytics widgets, 15+ UI component demo pages, authentication pages, and a public landing page, making it an ideal starting point for building admin panels, dashboards, and data-driven applications.
|
|
||||||
|
|
||||||
## Application Initialization
|
|
||||||
|
|
||||||
Vue 3 application setup with PrimeVue integration and global services
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { createApp } from 'vue';
|
|
||||||
import App from './App.vue';
|
|
||||||
import router from './router';
|
|
||||||
|
|
||||||
import Aura from '@primeuix/themes/aura';
|
|
||||||
import PrimeVue from 'primevue/config';
|
|
||||||
import ConfirmationService from 'primevue/confirmationservice';
|
|
||||||
import ToastService from 'primevue/toastservice';
|
|
||||||
|
|
||||||
import '@/assets/styles.scss';
|
|
||||||
|
|
||||||
const app = createApp(App);
|
|
||||||
|
|
||||||
app.use(router);
|
|
||||||
app.use(PrimeVue, {
|
|
||||||
theme: {
|
|
||||||
preset: Aura,
|
|
||||||
options: {
|
|
||||||
darkModeSelector: '.app-dark'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.use(ToastService);
|
|
||||||
app.use(ConfirmationService);
|
|
||||||
|
|
||||||
app.mount('#app');
|
|
||||||
|
|
||||||
// Expected result: Application mounts to #app with PrimeVue Aura theme,
|
|
||||||
// Toast and Confirmation services available globally
|
|
||||||
```
|
|
||||||
|
|
||||||
## Product Service API
|
|
||||||
|
|
||||||
Mock data service providing product inventory with 30 sample products across four categories
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { ProductService } from '@/service/ProductService';
|
|
||||||
|
|
||||||
// Fetch all products (30 items)
|
|
||||||
ProductService.getProducts()
|
|
||||||
.then(products => {
|
|
||||||
console.log(products);
|
|
||||||
// Returns array of products with structure:
|
|
||||||
// {
|
|
||||||
// id: '1000',
|
|
||||||
// code: 'f230fh0g3',
|
|
||||||
// name: 'Bamboo Watch',
|
|
||||||
// description: 'Product Description',
|
|
||||||
// image: 'bamboo-watch.jpg',
|
|
||||||
// price: 65,
|
|
||||||
// category: 'Accessories',
|
|
||||||
// quantity: 24,
|
|
||||||
// inventoryStatus: 'INSTOCK',
|
|
||||||
// rating: 5
|
|
||||||
// }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fetch products with order history
|
|
||||||
ProductService.getProductsWithOrders()
|
|
||||||
.then(products => {
|
|
||||||
products.forEach(product => {
|
|
||||||
console.log(`${product.name} has ${product.orders.length} orders`);
|
|
||||||
// Each order contains:
|
|
||||||
// { id, productCode, date, amount, quantity, customer, status }
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fetch limited product sets
|
|
||||||
ProductService.getProductsMini() // First 5 products
|
|
||||||
.then(products => console.log(products.length)); // Output: 5
|
|
||||||
|
|
||||||
ProductService.getProductsSmall() // First 10 products
|
|
||||||
.then(products => console.log(products.length)); // Output: 10
|
|
||||||
|
|
||||||
// Available inventory statuses: 'INSTOCK', 'LOWSTOCK', 'OUTOFSTOCK'
|
|
||||||
// Available categories: 'Accessories', 'Fitness', 'Clothing', 'Electronics'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Layout State Management Composable
|
|
||||||
|
|
||||||
Reactive layout configuration for theme switching, menu management, and dark mode
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { useLayout } from '@/layout/composables/layout';
|
|
||||||
|
|
||||||
const {
|
|
||||||
layoutConfig,
|
|
||||||
layoutState,
|
|
||||||
toggleMenu,
|
|
||||||
isSidebarActive,
|
|
||||||
isDarkTheme,
|
|
||||||
getPrimary,
|
|
||||||
getSurface,
|
|
||||||
setActiveMenuItem,
|
|
||||||
toggleDarkMode
|
|
||||||
} = useLayout();
|
|
||||||
|
|
||||||
// Access current configuration
|
|
||||||
console.log(layoutConfig.preset); // 'Aura'
|
|
||||||
console.log(layoutConfig.primary); // 'emerald'
|
|
||||||
console.log(layoutConfig.menuMode); // 'static' or 'overlay'
|
|
||||||
console.log(layoutConfig.darkTheme); // false
|
|
||||||
|
|
||||||
// Toggle dark mode with view transitions
|
|
||||||
toggleDarkMode();
|
|
||||||
// Result: Adds/removes 'app-dark' class from document element
|
|
||||||
// Uses View Transition API if supported for smooth animation
|
|
||||||
|
|
||||||
// Toggle menu (responsive behavior)
|
|
||||||
toggleMenu();
|
|
||||||
// Desktop (>991px): Toggles staticMenuDesktopInactive
|
|
||||||
// Mobile: Toggles staticMenuMobileActive
|
|
||||||
// Overlay mode: Toggles overlayMenuActive
|
|
||||||
|
|
||||||
// Check sidebar state
|
|
||||||
if (isSidebarActive.value) {
|
|
||||||
console.log('Sidebar is currently visible');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set active menu item
|
|
||||||
setActiveMenuItem({ value: 'dashboard' });
|
|
||||||
// Result: layoutState.activeMenuItem = 'dashboard'
|
|
||||||
|
|
||||||
// Access computed values
|
|
||||||
console.log(isDarkTheme.value); // true/false
|
|
||||||
console.log(getPrimary.value); // 'emerald'
|
|
||||||
console.log(getSurface.value); // null or surface palette name
|
|
||||||
```
|
|
||||||
|
|
||||||
## Router Configuration
|
|
||||||
|
|
||||||
Vue Router setup with lazy-loaded components and nested routes
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { createRouter, createWebHistory } from 'vue-router';
|
|
||||||
import AppLayout from '@/layout/AppLayout.vue';
|
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
history: createWebHistory(),
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
component: AppLayout,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
name: 'dashboard',
|
|
||||||
component: () => import('@/views/Dashboard.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/pages/crud',
|
|
||||||
name: 'crud',
|
|
||||||
component: () => import('@/views/pages/Crud.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/uikit/table',
|
|
||||||
name: 'table',
|
|
||||||
component: () => import('@/views/uikit/TableDoc.vue')
|
|
||||||
}
|
|
||||||
// ... 15+ UI kit routes
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/landing',
|
|
||||||
name: 'landing',
|
|
||||||
component: () => import('@/views/pages/Landing.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/auth/login',
|
|
||||||
name: 'login',
|
|
||||||
component: () => import('@/views/pages/auth/Login.vue')
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Navigate programmatically
|
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
router.push({ name: 'dashboard' });
|
|
||||||
router.push('/pages/crud');
|
|
||||||
router.push({ name: 'table' });
|
|
||||||
|
|
||||||
// All routes use lazy loading for optimal code splitting
|
|
||||||
// Routes under AppLayout include sidebar and topbar navigation
|
|
||||||
```
|
|
||||||
|
|
||||||
## CRUD Operations Implementation
|
|
||||||
|
|
||||||
Complete product management with create, read, update, delete operations using PrimeVue DataTable
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
<script setup>
|
|
||||||
import { ProductService } from '@/service/ProductService';
|
|
||||||
import { FilterMatchMode } from '@primevue/core/api';
|
|
||||||
import { useToast } from 'primevue/usetoast';
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
const toast = useToast();
|
|
||||||
const products = ref([]);
|
|
||||||
const product = ref({});
|
|
||||||
const productDialog = ref(false);
|
|
||||||
const selectedProducts = ref();
|
|
||||||
const filters = ref({
|
|
||||||
global: { value: null, matchMode: FilterMatchMode.CONTAINS }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Load products on component mount
|
|
||||||
onMounted(() => {
|
|
||||||
ProductService.getProducts().then((data) => {
|
|
||||||
products.value = data;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create new product
|
|
||||||
function openNew() {
|
|
||||||
product.value = {};
|
|
||||||
productDialog.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save product (create or update)
|
|
||||||
function saveProduct() {
|
|
||||||
if (product.value.name?.trim()) {
|
|
||||||
if (product.value.id) {
|
|
||||||
// Update existing product
|
|
||||||
const index = products.value.findIndex(p => p.id === product.value.id);
|
|
||||||
products.value[index] = product.value;
|
|
||||||
toast.add({
|
|
||||||
severity: 'success',
|
|
||||||
summary: 'Successful',
|
|
||||||
detail: 'Product Updated',
|
|
||||||
life: 3000
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Create new product
|
|
||||||
product.value.id = createId();
|
|
||||||
product.value.code = createId();
|
|
||||||
product.value.inventoryStatus = 'INSTOCK';
|
|
||||||
products.value.push(product.value);
|
|
||||||
toast.add({
|
|
||||||
severity: 'success',
|
|
||||||
summary: 'Successful',
|
|
||||||
detail: 'Product Created',
|
|
||||||
life: 3000
|
|
||||||
});
|
|
||||||
}
|
|
||||||
productDialog.value = false;
|
|
||||||
product.value = {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update existing product
|
|
||||||
function editProduct(prod) {
|
|
||||||
product.value = { ...prod };
|
|
||||||
productDialog.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete single product
|
|
||||||
function deleteProduct() {
|
|
||||||
products.value = products.value.filter((val) => val.id !== product.value.id);
|
|
||||||
toast.add({
|
|
||||||
severity: 'success',
|
|
||||||
summary: 'Successful',
|
|
||||||
detail: 'Product Deleted',
|
|
||||||
life: 3000
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete multiple products
|
|
||||||
function deleteSelectedProducts() {
|
|
||||||
products.value = products.value.filter((val) => !selectedProducts.value.includes(val));
|
|
||||||
selectedProducts.value = null;
|
|
||||||
toast.add({
|
|
||||||
severity: 'success',
|
|
||||||
summary: 'Successful',
|
|
||||||
detail: 'Products Deleted',
|
|
||||||
life: 3000
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate unique ID
|
|
||||||
function createId() {
|
|
||||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
||||||
return Array.from({ length: 5 }, () =>
|
|
||||||
chars.charAt(Math.floor(Math.random() * chars.length))
|
|
||||||
).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expected workflow:
|
|
||||||
// 1. Component loads products from service
|
|
||||||
// 2. User clicks "New" -> openNew() shows dialog
|
|
||||||
// 3. User fills form and saves -> saveProduct() creates product, shows toast
|
|
||||||
// 4. User clicks edit icon -> editProduct() pre-fills dialog with product data
|
|
||||||
// 5. User updates and saves -> saveProduct() updates product, shows toast
|
|
||||||
// 6. User clicks delete icon -> deleteProduct() removes product, shows toast
|
|
||||||
// 7. User selects multiple products and clicks delete -> deleteSelectedProducts()
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Dashboard Component Structure
|
|
||||||
|
|
||||||
Dashboard page composing five analytics widgets in responsive grid layout
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
import BestSellingWidget from '@/components/dashboard/BestSellingWidget.vue';
|
|
||||||
import NotificationsWidget from '@/components/dashboard/NotificationsWidget.vue';
|
|
||||||
import RecentSalesWidget from '@/components/dashboard/RecentSalesWidget.vue';
|
|
||||||
import RevenueStreamWidget from '@/components/dashboard/RevenueStreamWidget.vue';
|
|
||||||
import StatsWidget from '@/components/dashboard/StatsWidget.vue';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="grid grid-cols-12 gap-8">
|
|
||||||
<!-- Stats cards spanning full width -->
|
|
||||||
<StatsWidget />
|
|
||||||
|
|
||||||
<!-- Two-column layout on extra large screens -->
|
|
||||||
<div class="col-span-12 xl:col-span-6">
|
|
||||||
<RecentSalesWidget />
|
|
||||||
<BestSellingWidget />
|
|
||||||
</div>
|
|
||||||
<div class="col-span-12 xl:col-span-6">
|
|
||||||
<RevenueStreamWidget />
|
|
||||||
<NotificationsWidget />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Responsive behavior:
|
|
||||||
- Mobile/Tablet: Single column, widgets stack vertically
|
|
||||||
- Desktop (xl): Two-column layout with widgets side by side
|
|
||||||
- StatsWidget shows 4 KPI cards: Orders, Revenue, Customers, Comments
|
|
||||||
- RecentSalesWidget shows table of recent sales transactions
|
|
||||||
- BestSellingWidget displays chart of best-selling products
|
|
||||||
- RevenueStreamWidget visualizes revenue breakdown
|
|
||||||
- NotificationsWidget lists recent system notifications
|
|
||||||
-->
|
|
||||||
```
|
|
||||||
|
|
||||||
## Customer Service API
|
|
||||||
|
|
||||||
Mock data service providing customer records with representative and activity data
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { CustomerService } from '@/service/CustomerService';
|
|
||||||
|
|
||||||
CustomerService.getCustomers()
|
|
||||||
.then(customers => {
|
|
||||||
customers.forEach(customer => {
|
|
||||||
console.log({
|
|
||||||
id: customer.id,
|
|
||||||
name: customer.name,
|
|
||||||
country: customer.country, // { name: 'Algeria', code: 'dz' }
|
|
||||||
company: customer.company,
|
|
||||||
date: customer.date, // ISO date string
|
|
||||||
status: customer.status, // 'unqualified', 'negotiation', 'qualified', 'new'
|
|
||||||
verified: customer.verified, // boolean
|
|
||||||
activity: customer.activity, // 0-100
|
|
||||||
representative: {
|
|
||||||
name: customer.representative.name,
|
|
||||||
image: customer.representative.image
|
|
||||||
},
|
|
||||||
balance: customer.balance // number
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Usage in DataTable with filtering
|
|
||||||
<DataTable
|
|
||||||
:value="customers"
|
|
||||||
:filters="filters"
|
|
||||||
filterDisplay="row"
|
|
||||||
paginator
|
|
||||||
:rows="10"
|
|
||||||
>
|
|
||||||
<Column field="name" header="Name" sortable />
|
|
||||||
<Column field="country.name" header="Country" sortable />
|
|
||||||
<Column field="company" header="Company" sortable />
|
|
||||||
<Column field="status" header="Status">
|
|
||||||
<template #body="{ data }">
|
|
||||||
<Tag
|
|
||||||
:value="data.status"
|
|
||||||
:severity="getStatusSeverity(data.status)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Column>
|
|
||||||
<Column field="verified" header="Verified" dataType="boolean" />
|
|
||||||
<Column field="balance" header="Balance" sortable>
|
|
||||||
<template #body="{ data }">
|
|
||||||
{{ formatCurrency(data.balance) }}
|
|
||||||
</template>
|
|
||||||
</Column>
|
|
||||||
</DataTable>
|
|
||||||
|
|
||||||
// Expected data structure: Array of customer objects with nested country
|
|
||||||
// and representative objects, suitable for DataTable display with badges
|
|
||||||
```
|
|
||||||
|
|
||||||
## Country Service API
|
|
||||||
|
|
||||||
Mock data service providing 249 world countries with ISO codes
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { CountryService } from '@/service/CountryService';
|
|
||||||
|
|
||||||
CountryService.getCountries()
|
|
||||||
.then(countries => {
|
|
||||||
console.log(countries.length); // Output: 249
|
|
||||||
|
|
||||||
// Sample country structure
|
|
||||||
countries.forEach(country => {
|
|
||||||
console.log(country);
|
|
||||||
// { name: 'Afghanistan', code: 'af' }
|
|
||||||
// { name: 'United States', code: 'us' }
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Usage in Dropdown component
|
|
||||||
<script setup>
|
|
||||||
import { ref, onMounted } from 'vue';
|
|
||||||
import { CountryService } from '@/service/CountryService';
|
|
||||||
|
|
||||||
const countries = ref([]);
|
|
||||||
const selectedCountry = ref(null);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
CountryService.getCountries().then(data => {
|
|
||||||
countries.value = data;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Dropdown
|
|
||||||
v-model="selectedCountry"
|
|
||||||
:options="countries"
|
|
||||||
optionLabel="name"
|
|
||||||
placeholder="Select a Country"
|
|
||||||
filter
|
|
||||||
class="w-full"
|
|
||||||
>
|
|
||||||
<template #value="slotProps">
|
|
||||||
<div v-if="slotProps.value">
|
|
||||||
{{ slotProps.value.name }}
|
|
||||||
</div>
|
|
||||||
<span v-else>
|
|
||||||
{{ slotProps.placeholder }}
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<template #option="slotProps">
|
|
||||||
<div>{{ slotProps.option.name }}</div>
|
|
||||||
</template>
|
|
||||||
</Dropdown>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
// Expected result: Dropdown with 249 countries, filterable search,
|
|
||||||
// returns selected country object with name and code properties
|
|
||||||
```
|
|
||||||
|
|
||||||
## Node Service API
|
|
||||||
|
|
||||||
Mock data service providing hierarchical tree structures for file browsers and navigation
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { NodeService } from '@/service/NodeService';
|
|
||||||
|
|
||||||
// Tree structure for Tree component
|
|
||||||
NodeService.getTreeNodes()
|
|
||||||
.then(nodes => {
|
|
||||||
console.log(nodes);
|
|
||||||
// Returns 3 root nodes: Documents, Events, Movies
|
|
||||||
// Each with nested children representing folders/files
|
|
||||||
// Structure: { key, label, data, icon, children[] }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Tree table structure with detailed data
|
|
||||||
NodeService.getTreeTableNodes()
|
|
||||||
.then(nodes => {
|
|
||||||
nodes.forEach(node => {
|
|
||||||
console.log({
|
|
||||||
key: node.key, // Unique identifier
|
|
||||||
data: {
|
|
||||||
name: node.data.name,
|
|
||||||
size: node.data.size,
|
|
||||||
type: node.data.type
|
|
||||||
},
|
|
||||||
children: node.children // Nested nodes
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Usage in TreeTable component
|
|
||||||
<script setup>
|
|
||||||
import { ref, onMounted } from 'vue';
|
|
||||||
import { NodeService } from '@/service/NodeService';
|
|
||||||
|
|
||||||
const nodes = ref([]);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
NodeService.getTreeTableNodes().then(data => {
|
|
||||||
nodes.value = data;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<TreeTable :value="nodes" :expandedKeys="expandedKeys">
|
|
||||||
<Column field="name" header="Name" expander />
|
|
||||||
<Column field="size" header="Size" />
|
|
||||||
<Column field="type" header="Type" />
|
|
||||||
</TreeTable>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
// Expected result: 9 root nodes (Applications, Cloud, Desktop, Documents,
|
|
||||||
// Downloads, Main, Movies, Photos, Work) with nested children
|
|
||||||
// Perfect for file manager UIs and hierarchical data display
|
|
||||||
```
|
|
||||||
|
|
||||||
## Photo Service API
|
|
||||||
|
|
||||||
Mock data service providing gallery images hosted on CDN
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { PhotoService } from '@/service/PhotoService';
|
|
||||||
|
|
||||||
PhotoService.getImages()
|
|
||||||
.then(images => {
|
|
||||||
console.log(images.length); // Output: 15
|
|
||||||
|
|
||||||
images.forEach(image => {
|
|
||||||
console.log({
|
|
||||||
itemImageSrc: image.itemImageSrc,
|
|
||||||
thumbnailImageSrc: image.thumbnailImageSrc,
|
|
||||||
alt: image.alt,
|
|
||||||
title: image.title
|
|
||||||
});
|
|
||||||
// Example output:
|
|
||||||
// {
|
|
||||||
// itemImageSrc: 'https://primefaces.org/cdn/primevue/images/galleria/galleria1.jpg',
|
|
||||||
// thumbnailImageSrc: 'https://primefaces.org/cdn/primevue/images/galleria/galleria1s.jpg',
|
|
||||||
// alt: 'Description for Image 1',
|
|
||||||
// title: 'Title 1'
|
|
||||||
// }
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Usage in Galleria component
|
|
||||||
<script setup>
|
|
||||||
import { ref, onMounted } from 'vue';
|
|
||||||
import { PhotoService } from '@/service/PhotoService';
|
|
||||||
|
|
||||||
const images = ref([]);
|
|
||||||
const activeIndex = ref(0);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
PhotoService.getImages().then(data => {
|
|
||||||
images.value = data;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Galleria
|
|
||||||
:value="images"
|
|
||||||
v-model:activeIndex="activeIndex"
|
|
||||||
:numVisible="5"
|
|
||||||
containerStyle="max-width: 640px"
|
|
||||||
:showThumbnails="true"
|
|
||||||
:showItemNavigators="true"
|
|
||||||
>
|
|
||||||
<template #item="slotProps">
|
|
||||||
<img
|
|
||||||
:src="slotProps.item.itemImageSrc"
|
|
||||||
:alt="slotProps.item.alt"
|
|
||||||
style="width: 100%; display: block"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template #thumbnail="slotProps">
|
|
||||||
<img
|
|
||||||
:src="slotProps.item.thumbnailImageSrc"
|
|
||||||
:alt="slotProps.item.alt"
|
|
||||||
style="display: block"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Galleria>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
// Expected result: Image gallery with 15 photos, thumbnail navigation,
|
|
||||||
// full-size image display, navigation arrows, responsive layout
|
|
||||||
```
|
|
||||||
|
|
||||||
## Theme Configuration and Dynamic Switching
|
|
||||||
|
|
||||||
Real-time theme customization with preset, color, and surface palette options
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { useLayout } from '@/layout/composables/layout';
|
|
||||||
|
|
||||||
const { layoutConfig } = useLayout();
|
|
||||||
|
|
||||||
// Available theme presets
|
|
||||||
const presets = ['Aura', 'Lara', 'Nora'];
|
|
||||||
|
|
||||||
// Available primary colors
|
|
||||||
const primaryColors = [
|
|
||||||
'noir', 'emerald', 'green', 'lime', 'orange', 'amber',
|
|
||||||
'yellow', 'teal', 'cyan', 'sky', 'blue', 'indigo',
|
|
||||||
'violet', 'purple', 'fuchsia', 'pink', 'rose'
|
|
||||||
];
|
|
||||||
|
|
||||||
// Available surface palettes
|
|
||||||
const surfacePalettes = [
|
|
||||||
'slate', 'gray', 'zinc', 'neutral',
|
|
||||||
'stone', 'soho', 'viva', 'ocean'
|
|
||||||
];
|
|
||||||
|
|
||||||
// Change theme preset
|
|
||||||
import { updatePreset } from '@primeuix/themes';
|
|
||||||
layoutConfig.preset = 'Lara';
|
|
||||||
updatePreset({
|
|
||||||
preset: 'Lara',
|
|
||||||
primary: layoutConfig.primary,
|
|
||||||
surface: layoutConfig.surface
|
|
||||||
});
|
|
||||||
|
|
||||||
// Change primary color
|
|
||||||
layoutConfig.primary = 'blue';
|
|
||||||
updatePreset({
|
|
||||||
preset: layoutConfig.preset,
|
|
||||||
primary: 'blue',
|
|
||||||
surface: layoutConfig.surface
|
|
||||||
});
|
|
||||||
|
|
||||||
// Change surface palette
|
|
||||||
import { updateSurfacePalette } from '@primeuix/themes';
|
|
||||||
layoutConfig.surface = 'slate';
|
|
||||||
updateSurfacePalette('slate');
|
|
||||||
|
|
||||||
// Toggle dark mode
|
|
||||||
import { toggleDarkMode } from '@/layout/composables/layout';
|
|
||||||
toggleDarkMode();
|
|
||||||
// Result: Adds/removes 'app-dark' class with smooth View Transition API animation
|
|
||||||
|
|
||||||
// Complete theme change example
|
|
||||||
function applyTheme(preset, primary, surface) {
|
|
||||||
layoutConfig.preset = preset;
|
|
||||||
layoutConfig.primary = primary;
|
|
||||||
layoutConfig.surface = surface;
|
|
||||||
|
|
||||||
updatePreset({ preset, primary, surface });
|
|
||||||
if (surface) {
|
|
||||||
updateSurfacePalette(surface);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
applyTheme('Nora', 'purple', 'ocean');
|
|
||||||
// Result: Theme instantly updates across entire application
|
|
||||||
// All PrimeVue components reflect new colors and styling
|
|
||||||
```
|
|
||||||
|
|
||||||
## PrimeVue DataTable with Filtering and Pagination
|
|
||||||
|
|
||||||
Advanced data table with global search, column filtering, sorting, and pagination
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
import { ref, onMounted } from 'vue';
|
|
||||||
import { FilterMatchMode } from '@primevue/core/api';
|
|
||||||
import { ProductService } from '@/service/ProductService';
|
|
||||||
|
|
||||||
const products = ref([]);
|
|
||||||
const filters = ref({
|
|
||||||
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
|
|
||||||
name: { value: null, matchMode: FilterMatchMode.STARTS_WITH },
|
|
||||||
'category': { value: null, matchMode: FilterMatchMode.IN }
|
|
||||||
});
|
|
||||||
const categories = ref(['Accessories', 'Fitness', 'Clothing', 'Electronics']);
|
|
||||||
const selectedProducts = ref([]);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
ProductService.getProducts().then(data => {
|
|
||||||
products.value = data;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function formatCurrency(value) {
|
|
||||||
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStatusSeverity(status) {
|
|
||||||
switch (status) {
|
|
||||||
case 'INSTOCK': return 'success';
|
|
||||||
case 'LOWSTOCK': return 'warn';
|
|
||||||
case 'OUTOFSTOCK': return 'danger';
|
|
||||||
default: return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<DataTable
|
|
||||||
v-model:selection="selectedProducts"
|
|
||||||
v-model:filters="filters"
|
|
||||||
:value="products"
|
|
||||||
paginator
|
|
||||||
:rows="10"
|
|
||||||
:rowsPerPageOptions="[5, 10, 20, 50]"
|
|
||||||
filterDisplay="row"
|
|
||||||
:globalFilterFields="['name', 'category', 'description']"
|
|
||||||
sortMode="multiple"
|
|
||||||
>
|
|
||||||
<template #header>
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<span class="text-xl">Products</span>
|
|
||||||
<InputText
|
|
||||||
v-model="filters.global.value"
|
|
||||||
placeholder="Global Search"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<Column selectionMode="multiple" headerStyle="width: 3rem" />
|
|
||||||
|
|
||||||
<Column field="name" header="Name" sortable>
|
|
||||||
<template #filter="{ filterModel, filterCallback }">
|
|
||||||
<InputText
|
|
||||||
v-model="filterModel.value"
|
|
||||||
@input="filterCallback()"
|
|
||||||
placeholder="Search by name"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Column>
|
|
||||||
|
|
||||||
<Column field="category" header="Category" sortable>
|
|
||||||
<template #filter="{ filterModel, filterCallback }">
|
|
||||||
<MultiSelect
|
|
||||||
v-model="filterModel.value"
|
|
||||||
@change="filterCallback()"
|
|
||||||
:options="categories"
|
|
||||||
placeholder="Any"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Column>
|
|
||||||
|
|
||||||
<Column field="price" header="Price" sortable>
|
|
||||||
<template #body="{ data }">
|
|
||||||
{{ formatCurrency(data.price) }}
|
|
||||||
</template>
|
|
||||||
</Column>
|
|
||||||
|
|
||||||
<Column field="inventoryStatus" header="Status" sortable>
|
|
||||||
<template #body="{ data }">
|
|
||||||
<Tag
|
|
||||||
:value="data.inventoryStatus"
|
|
||||||
:severity="getStatusSeverity(data.inventoryStatus)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Column>
|
|
||||||
|
|
||||||
<Column header="Actions">
|
|
||||||
<template #body="{ data }">
|
|
||||||
<Button icon="pi pi-pencil" class="mr-2" @click="editProduct(data)" />
|
|
||||||
<Button icon="pi pi-trash" severity="danger" @click="deleteProduct(data)" />
|
|
||||||
</template>
|
|
||||||
</Column>
|
|
||||||
</DataTable>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Features demonstrated:
|
|
||||||
- Global search across multiple fields
|
|
||||||
- Column-specific filters (text input, multiselect)
|
|
||||||
- Multi-column sorting
|
|
||||||
- Row selection with checkboxes
|
|
||||||
- Pagination with configurable rows per page
|
|
||||||
- Custom cell rendering (currency, badges, action buttons)
|
|
||||||
- Responsive layout
|
|
||||||
-->
|
|
||||||
```
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Sakai Vue serves as a comprehensive foundation for building modern admin dashboards and data-driven web applications with Vue 3. The template demonstrates enterprise-grade patterns including service-based architecture for data management, reactive state with composables, and extensive PrimeVue component integration. The five mock data services (Product, Customer, Country, Node, Photo) provide realistic sample data with proper structure for immediate prototyping, while the complete CRUD implementation showcases best practices for data manipulation with user feedback through toasts and confirmations.
|
|
||||||
|
|
||||||
The template's standout features include its flexible theming system supporting three presets (Aura, Lara, Nora) with 17 primary colors and 8 surface palettes, all switchable in real-time without page reloads. The responsive layout system adapts seamlessly from mobile to desktop with static and overlay menu modes, dark mode support with smooth view transitions, and a component library of 15+ UI showcase pages demonstrating every major PrimeVue component. Whether building internal tools, customer portals, or SaaS applications, Sakai Vue provides production-ready code, professional design, and extensive examples that accelerate development from concept to deployment.
|
|
||||||
Reference in New Issue
Block a user