引言
SAP UI5 Web Components 是一套基于原生 Web Components 标准构建的企业级 UI 组件库,它不依赖于特定框架,可以直接在 Vue、React、Angular 甚至原生 HTML 中使用。
结合 Vue 3 的响应式系统和组合式 API(Composition API),我们可以快速构建出语义清晰、风格统一的企业级应用。本文将带你使用 纯 JavaScript 从零开始,用 SAP UI5 Web Components 和 Vue 3 搭建一个简单的“员工查询”应用。
一、技术栈
- Vue 3(Composition API)
- SAP UI5 Web Components
- Vite(构建工具)
- JavaScript
二、初始化项目
- 使用 Vite 创建 Vue 项目(选择 JavaScript + Vue 模板):
npm create vite@latest es-ui5-app -- --template vue
cd es-ui5-app
npm install
- 安装 SAP UI5 Web Components:
npm install @ui5/webcomponents @ui5/webcomponents-fiori @ui5/webcomponents-icons
三、 构建员工查询页面
创建 src/components/EmployeeSearch.vue:
<template>
<div class="page-container">
<!-- 顶部导航 -->
<ui5-shellbar>
<ui5-shellbar-branding slot="branding">
员工信息管理系统
<img
slot="logo"
src="https://sap.github.io/ui5-webcomponents/images/sap-logo-svg.svg"
/>
</ui5-shellbar-branding>
<ui5-button slot="startButton" icon="menu"></ui5-button>
</ui5-shellbar>
<!-- 查询表单 -->
<div class="search-form">
<ui5-label for="nameInput">姓名</ui5-label>
<ui5-input
id="nameInput"
placeholder="请输入姓名"
v-model="filters.name"
></ui5-input>
<ui5-label for="deptSelect">部门</ui5-label>
<ui5-select id="deptSelect" v-model="filters.department">
<ui5-option value="">全部部门</ui5-option>
<ui5-option value="技术部">技术部</ui5-option>
<ui5-option value="销售部">销售部</ui5-option>
<ui5-option value="人事部">人事部</ui5-option>
<ui5-option value="财务部">财务部</ui5-option>
</ui5-select>
<ui5-label for="datePicker">入职时间</ui5-label>
<ui5-date-picker
id="datePicker"
v-model="filters.joinDate"
formatPattern="YYYY-MM-dd"
@change="onChangeDate"
></ui5-date-picker>
<ui5-button icon="search" @click="search" design="Emphasized"
>查询</ui5-button
>
<ui5-button icon="reset" @click="reset">重置</ui5-button>
</div>
<!-- 数据表格 -->
<div
class="table-container"
style="height: calc(100vh - 200px); overflow: auto"
>
<ui5-table aria-label="员工列表" no-data-text="暂无数据" mode="None">
<ui5-table-growing
mode="Button"
slot="features"
@load-more="onLoadMore"
v-if="hasMore"
></ui5-table-growing>
<!-- 表头 -->
<ui5-table-header-row slot="headerRow">
<ui5-table-header-cell><span>工号</span></ui5-table-header-cell>
<ui5-table-header-cell><span>姓名</span></ui5-table-header-cell>
<ui5-table-header-cell><span>部门</span></ui5-table-header-cell>
<ui5-table-header-cell><span>职位</span></ui5-table-header-cell>
<ui5-table-header-cell><span>入职时间</span></ui5-table-header-cell>
<ui5-table-header-cell><span>操作</span></ui5-table-header-cell>
</ui5-table-header-row>
<!-- 表体 -->
<ui5-table-row v-for="emp in visibleData" :key="emp.id">
<ui5-table-cell
><span>{{ emp.id }}</span></ui5-table-cell
>
<ui5-table-cell
><span>{{ emp.name }}</span></ui5-table-cell
>
<ui5-table-cell
><span>{{ emp.department }}</span></ui5-table-cell
>
<ui5-table-cell
><span>{{ emp.position }}</span></ui5-table-cell
>
<ui5-table-cell
><span>{{ emp.joinDate }}</span></ui5-table-cell
>
<ui5-table-cell>
<ui5-button
icon="action"
size="small"
title="更多操作"
></ui5-button>
</ui5-table-cell>
</ui5-table-row>
</ui5-table>
</div>
</div>
</template>
<script>
import { ref, computed, watch } from "vue";
import "@ui5/webcomponents-icons/dist/AllIcons.js";
import "@ui5/webcomponents/dist/Button.js";
import "@ui5/webcomponents/dist/Input.js";
import "@ui5/webcomponents/dist/Label.js";
import "@ui5/webcomponents/dist/Select.js";
import "@ui5/webcomponents/dist/Option.js";
import "@ui5/webcomponents/dist/DatePicker.js";
import "@ui5/webcomponents/dist/Table.js";
import "@ui5/webcomponents/dist/TableRow.js";
import "@ui5/webcomponents/dist/TableCell.js";
import "@ui5/webcomponents/dist/TableHeaderRow.js";
import "@ui5/webcomponents/dist/TableHeaderCell.js";
import "@ui5/webcomponents/dist/TableGrowing.js";
import "@ui5/webcomponents-fiori/dist/ShellBar.js";
import "@ui5/webcomponents-fiori/dist/ShellBarBranding.js";
export default {
setup() {
const logo =
"https://images.sj33.cn/uploads/allimg/201401/7-140131225442O6.png";
// 模拟员工数据
const employees = ref([
{
id: "E001",
name: "张伟",
department: "技术部",
position: "前端工程师",
joinDate: "2022-03-15",
},
{
id: "E002",
name: "李娜",
department: "销售部",
position: "客户经理",
joinDate: "2021-07-22",
},
{
id: "E003",
name: "王强",
department: "技术部",
position: "后端工程师",
joinDate: "2023-01-10",
},
{
id: "E004",
name: "陈芳",
department: "人事部",
position: "HR专员",
joinDate: "2020-11-05",
},
{
id: "E005",
name: "刘洋",
department: "财务部",
position: "会计",
joinDate: "2022-08-18",
},
{
id: "E006",
name: "赵敏",
department: "技术部",
position: "UI设计师",
joinDate: "2023-05-12",
},
{
id: "E007",
name: "孙磊",
department: "销售部",
position: "销售代表",
joinDate: "2021-09-30",
},
{
id: "E008",
name: "周婷",
department: "财务部",
position: "出纳",
joinDate: "2022-12-03",
},
]);
// 筛选条件
const filters = ref({
name: "",
department: "",
joinDate: "",
});
// 表格增长(代替分页)
const growingStep = 5;
const visibleCount = ref(growingStep);
// 计算筛选后的数据
const filteredData = computed(() => {
return employees.value.filter((emp) => {
const matchName =
!filters.value.name || emp.name.includes(filters.value.name);
const matchDept =
!filters.value.department ||
emp.department === filters.value.department;
const matchDate =
!filters.value.joinDate || emp.joinDate === filters.value.joinDate;
return matchName && matchDept && matchDate;
});
});
const onChangeDate = (e) => {
filters.value.joinDate = e.target.value;
};
// 可见数据(用于表格growing展示)
const visibleData = computed(() => {
return filteredData.value.slice(0, visibleCount.value);
});
// 是否还有更多数据
const hasMore = computed(
() => visibleCount.value < filteredData.value.length
);
// 处理加载更多
const onLoadMore = () => {
const next = Math.min(
visibleCount.value + growingStep,
filteredData.value.length
);
visibleCount.value = next;
};
// 查询
const search = () => {
visibleCount.value = growingStep; // 重置可见数量
};
// 重置
const reset = () => {
filters.value = {
name: "",
department: "",
joinDate: "",
};
visibleCount.value = growingStep;
};
// 监听筛选条件变化,自动重置可见数量
// 监听筛选条件变化,自动重置到第一页
watch(
filters,
() => {
visibleCount.value = growingStep;
},
{ deep: true }
);
return {
logo,
filters,
filteredData,
visibleData,
hasMore,
search,
reset,
onLoadMore,
onChangeDate,
};
},
};
</script>
<style scoped>
.page-container {
font-family: "Segoe UI", system-ui, sans-serif;
max-width: 1200px;
margin: 0 auto;
background: #f9f9f9;
}
/* 表单样式 */
.search-form {
display: grid;
grid-template-columns: auto 1fr auto 1fr auto 1fr auto auto;
gap: 12px;
padding: 16px;
background: white;
border-bottom: 1px solid #e5e5e5;
align-items: center;
}
.search-form ui5-label {
text-align: right;
font-weight: 500;
}
/* 表格容器 */
.table-container {
padding: 16px;
background: white;
margin: 0 16px;
border-radius: 8px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
}
/* 分页 */
.pagination {
display: flex;
justify-content: flex-end;
padding: 16px;
}
</style>
四、在 App.vue 中引入
<template>
<EmployeeSearch />
</template>
<script>
import EmployeeSearch from "./components/EmployeeSearch.vue";
export default {
components: {
EmployeeSearch,
},
};
</script>
五、检查vite.config.js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
// treat all tags with a ui5- as custom elements
isCustomElement: (tag) => tag.includes("ui5-"),
},
},
}),
],
});
六、运行项目
npm run dev
打开 http://localhost:5173,你将看到: