34 KiB
34 KiB
Uni-App-X 页面结构规范 - CRMEB 风格
📚 概述
本文档定义了 mall 项目中所有 admin 页面的标准结构和编写方法,参考 CRMEB 的专业设计模式。
1. 页面基本结构
1.1 完整模板
所有 admin 页面都应遵循此结构:
<template>
<AdminLayout :currentPage="pageName">
<!-- 1. 页面标题和操作按钮 -->
<view class="page-header">
<view class="header-left">
<text class="page-title">{{ pageTitle }}</text>
<text class="page-subtitle">{{ pageSubtitle }}</text>
</view>
<view class="header-right">
<button class="btn btn-primary" @click="handleCreate">
<text class="icon">+</text>
<text>新增</text>
</button>
</view>
</view>
<!-- 2. 搜索和过滤区域 -->
<view class="search-section">
<view class="search-card">
<form class="search-form">
<view class="form-row">
<view class="form-item">
<text class="form-label">搜索:</text>
<input
v-model="searchForm.keyword"
class="input"
placeholder="请输入关键词"
/>
</view>
<view class="form-actions">
<button class="btn btn-primary" @click="handleSearch">搜索</button>
<button class="btn btn-default" @click="handleReset">重置</button>
</view>
</view>
</form>
</view>
</view>
<!-- 3. 内容区域 -->
<view class="content-section">
<!-- 列表或详情内容 -->
<view class="content-card">
<!-- 内容 -->
</view>
</view>
<!-- 4. 底部操作区 -->
<view class="page-footer">
<!-- 分页或其他底部操作 -->
</view>
</AdminLayout>
</template>
<script setup lang="uts">
import { ref, onShow, onMounted } from 'vue'
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
// ============ 页面元数据 ============
const pageName = ref('page-name')
const pageTitle = ref('页面标题')
const pageSubtitle = ref('页面描述')
// ============ 状态数据 ============
const searchForm = ref({
keyword: '',
status: '',
})
// ============ 生命周期 ============
onShow(() => {
// 页面显示时执行
loadData()
})
onMounted(() => {
// 组件挂载后执行
initializeData()
})
// ============ 数据加载 ============
const loadData = () => {
// 从 API 加载数据
}
const initializeData = () => {
// 初始化数据
}
// ============ 事件处理 ============
const handleCreate = () => {
// 新增操作
}
const handleSearch = () => {
// 搜索操作
}
const handleReset = () => {
// 重置表单
searchForm.value = {
keyword: '',
status: '',
}
}
</script>
<style scoped lang="scss">
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $space-lg;
padding-bottom: $space-md;
border-bottom: 1px solid $border-light;
}
.header-left {
display: flex;
flex-direction: column;
}
.page-title {
font-size: $font-size-lg;
font-weight: $font-weight-bold;
color: $text-primary;
margin-bottom: $space-xs;
}
.page-subtitle {
font-size: $font-size-sm;
color: $text-secondary;
}
.header-right {
display: flex;
gap: $space-sm;
}
.search-section {
margin-bottom: $space-lg;
}
.search-card {
background: $background-primary;
border-radius: $radius;
padding: $space-md;
box-shadow: $shadow;
}
.search-form {
display: flex;
flex-direction: column;
gap: $space-md;
}
.form-row {
display: flex;
gap: $space-md;
flex-wrap: wrap;
align-items: flex-end;
}
.form-item {
display: flex;
align-items: center;
gap: $space-md;
flex: 1;
min-width: 200px;
}
.form-label {
color: $text-secondary;
font-size: $font-size-sm;
white-space: nowrap;
}
.input {
height: $input-height;
padding: 0 $space-sm;
border: 1px solid $border-color;
border-radius: $radius-xs;
flex: 1;
}
.form-actions {
display: flex;
gap: $space-sm;
}
.content-section {
margin-bottom: $space-lg;
}
.content-card {
background: $background-primary;
border-radius: $radius;
overflow: hidden;
box-shadow: $shadow;
}
.page-footer {
display: flex;
justify-content: center;
padding: $space-lg 0;
}
</style>
2. 列表页面(ListPage)
2.1 结构说明
列表页面的典型结构:
┌─────────────────────────────────────┐
│ 页面标题 [+ 新增] [导出] │ <- page-header
├─────────────────────────────────────┤
│ ┌───────────────────────────────────┐│
│ │ 搜索 [输入框] [搜索] [重置] ││ <- search-section
│ └───────────────────────────────────┘│
├─────────────────────────────────────┤
│ ┌───────────────────────────────────┐│
│ │ 表格头 | 表格头 | 表格头 | 操作 ││
│ ├───────────────────────────────────┤│
│ │ 行数据 | 行数据 | 行数据 | 编辑删除 ││ <- list-card
│ │ 行数据 | 行数据 | 行数据 | 编辑删除 ││
│ │ 行数据 | 行数据 | 行数据 | 编辑删除 ││
│ └───────────────────────────────────┘│
├─────────────────────────────────────┤
│ [上一页] 第 1 页,共 10 页 [下一页] │ <- pagination
└─────────────────────────────────────┘
2.2 完整示例代码
<template>
<AdminLayout :currentPage="pageName">
<!-- 标题栏 -->
<view class="page-header">
<view class="header-left">
<text class="page-title">系统管理</text>
<text class="page-subtitle">管理系统配置和参数</text>
</view>
<view class="header-right">
<button class="btn btn-primary" @click="handleCreate">
<text>+ 新增</text>
</button>
<button class="btn btn-default" @click="handleExport">
<text>导出</text>
</button>
</view>
</view>
<!-- 搜索区 -->
<view class="search-section">
<view class="search-card">
<view class="form-row">
<view class="form-item">
<text class="form-label">名称:</text>
<input
v-model="searchForm.name"
class="input"
placeholder="请输入名称"
/>
</view>
<view class="form-item">
<text class="form-label">状态:</text>
<select v-model="searchForm.status" class="input">
<option value="">全部</option>
<option value="1">启用</option>
<option value="0">禁用</option>
</select>
</view>
<view class="form-actions">
<button class="btn btn-primary" @click="handleSearch">搜索</button>
<button class="btn btn-default" @click="handleReset">重置</button>
</view>
</view>
</view>
</view>
<!-- 列表内容 -->
<view class="content-section">
<view class="list-card">
<!-- 列表头 -->
<view class="list-header">
<view class="list-col col-checkbox">
<input type="checkbox" v-model="selectAll" />
</view>
<view class="list-col col-id">ID</view>
<view class="list-col col-name">名称</view>
<view class="list-col col-status">状态</view>
<view class="list-col col-time">创建时间</view>
<view class="list-col col-action">操作</view>
</view>
<!-- 列表体 -->
<view v-if="items.length > 0" class="list-body">
<view class="list-item" v-for="item in items" :key="item.id">
<view class="list-col col-checkbox">
<input type="checkbox" :value="item.id" v-model="selectedIds" />
</view>
<view class="list-col col-id">{{ item.id }}</view>
<view class="list-col col-name">
<text class="item-name">{{ item.name }}</text>
</view>
<view class="list-col col-status">
<view
:class="['status-badge', `status-${item.status}`]"
>
{{ statusMap[item.status] }}
</view>
</view>
<view class="list-col col-time">{{ formatTime(item.createTime) }}</view>
<view class="list-col col-action">
<button class="btn-link" @click="handleEdit(item)">编辑</button>
<button class="btn-link btn-danger" @click="handleDelete(item)">删除</button>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-else class="list-empty">
<text>暂无数据</text>
</view>
</view>
</view>
<!-- 分页 -->
<view class="pagination">
<button
class="btn btn-sm"
:disabled="pagination.page <= 1"
@click="handlePrevPage"
>
上一页
</button>
<text class="page-info">
第 {{ pagination.page }} 页,共 {{ pagination.total }} 页
</text>
<button
class="btn btn-sm"
:disabled="pagination.page >= pagination.total"
@click="handleNextPage"
>
下一页
</button>
</view>
</AdminLayout>
</template>
<script setup lang="uts">
import { ref, onShow } from 'vue'
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
const pageName = ref('system-info')
const selectAll = ref(false)
const selectedIds = ref<string[]>([])
const searchForm = ref({
name: '',
status: '',
})
const pagination = ref({
page: 1,
limit: 10,
total: 0,
})
const items = ref<any[]>([])
const statusMap = {
'1': '启用',
'0': '禁用',
}
onShow(() => {
loadData()
})
const loadData = () => {
// 调用 API 加载数据
// this.$api.list(searchForm.value, pagination.value).then(res => {
// items.value = res.data.items
// pagination.value.total = res.data.total
// })
}
const handleCreate = () => {
// 跳转到新增页面
uni.navigateTo({
url: `/pages/mall/admin/system/system-info-edit?mode=create`,
})
}
const handleEdit = (item: any) => {
uni.navigateTo({
url: `/pages/mall/admin/system/system-info-edit?id=${item.id}&mode=edit`,
})
}
const handleDelete = (item: any) => {
// 确认删除
uni.showModal({
title: '删除确认',
content: `确定删除 "${item.name}" 吗?`,
success(res) {
if (res.confirm) {
// this.$api.delete(item.id).then(() => {
// uni.showToast({ title: '删除成功' })
// loadData()
// })
}
},
})
}
const handleSearch = () => {
pagination.value.page = 1
loadData()
}
const handleReset = () => {
searchForm.value = { name: '', status: '' }
handleSearch()
}
const handleExport = () => {
// 导出数据
}
const handlePrevPage = () => {
if (pagination.value.page > 1) {
pagination.value.page--
loadData()
}
}
const handleNextPage = () => {
if (pagination.value.page < pagination.value.total) {
pagination.value.page++
loadData()
}
}
const formatTime = (time: string) => {
return time.split(' ')[0]
}
</script>
<style scoped lang="scss">
// 页面头部
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $space-lg;
padding-bottom: $space-md;
border-bottom: 1px solid $border-light;
}
.header-left {
display: flex;
flex-direction: column;
gap: $space-xs;
}
.page-title {
font-size: $font-size-lg;
font-weight: $font-weight-bold;
color: $text-primary;
}
.page-subtitle {
font-size: $font-size-sm;
color: $text-secondary;
}
.header-right {
display: flex;
gap: $space-sm;
}
// 搜索区域
.search-section {
margin-bottom: $space-lg;
}
.search-card {
background: $background-primary;
border-radius: $radius;
padding: $space-md;
box-shadow: $shadow;
}
.form-row {
display: flex;
gap: $space-md;
align-items: flex-end;
flex-wrap: wrap;
}
.form-item {
display: flex;
align-items: center;
gap: $space-md;
flex: 1;
min-width: 250px;
}
.form-label {
color: $text-secondary;
font-size: $font-size-sm;
white-space: nowrap;
width: 60px;
}
.input {
height: $input-height;
padding: 0 $space-sm;
border: 1px solid $border-color;
border-radius: $radius-xs;
font-size: $font-size;
flex: 1;
}
.form-actions {
display: flex;
gap: $space-sm;
}
// 列表卡片
.content-section {
margin-bottom: $space-lg;
}
.list-card {
background: $background-primary;
border-radius: $radius;
overflow: hidden;
box-shadow: $shadow;
}
.list-header {
display: flex;
background: $background-secondary;
border-bottom: 1px solid $border-light;
padding: $space-md;
font-weight: $font-weight-semibold;
color: $text-primary;
font-size: $font-size-sm;
}
.list-col {
display: flex;
align-items: center;
padding: 0 $space-sm;
&.col-checkbox {
width: 40px;
flex: none;
}
&.col-id {
width: 60px;
flex: none;
}
&.col-name {
flex: 2;
}
&.col-status {
flex: 1;
}
&.col-time {
flex: 1;
}
&.col-action {
width: 120px;
flex: none;
justify-content: flex-end;
}
}
.list-body {
display: flex;
flex-direction: column;
}
.list-item {
display: flex;
align-items: center;
padding: $space-md;
border-bottom: 1px solid $border-light;
transition: background-color $transition-duration $transition-timing;
&:hover {
background-color: $background-secondary;
}
&:last-child {
border-bottom: none;
}
}
.item-name {
color: $primary-color;
font-weight: $font-weight-medium;
}
.status-badge {
display: inline-block;
padding: $space-xs $space-sm;
border-radius: $radius-xs;
font-size: $font-size-xs;
text-align: center;
white-space: nowrap;
&.status-1 {
background: rgba($success-color, 0.1);
color: $success-color;
}
&.status-0 {
background: rgba($error-color, 0.1);
color: $error-color;
}
}
.btn-link {
background: none;
border: none;
color: $primary-color;
padding: 0;
font-size: $font-size-sm;
cursor: pointer;
margin-right: $space-sm;
&:last-child {
margin-right: 0;
}
&.btn-danger {
color: $error-color;
}
}
.list-empty {
padding: $space-xl;
text-align: center;
color: $text-tertiary;
font-size: $font-size;
}
// 分页
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: $space-md;
padding: $space-lg 0;
}
.page-info {
color: $text-secondary;
font-size: $font-size-sm;
}
</style>
3. 表单页面(FormPage)
3.1 结构说明
表单页面的典型结构:
┌─────────────────────────────────────┐
│ 页面标题(新增/编辑) │ <- page-header
├─────────────────────────────────────┤
│ ┌───────────────────────────────────┐│
│ │ 基本信息 ││
│ │ ├─ 名称 [输入框] ││
│ │ ├─ 描述 [文本框] ││
│ │ ├─ 分类 [下拉选择] ││
│ │ └─ 状态 [单选按钮] ││ <- form-card
│ └───────────────────────────────────┘│
│ ┌───────────────────────────────────┐│
│ │ 详细配置 ││
│ │ ├─ 参数1 [输入框] ││
│ │ └─ 参数2 [输入框] ││
│ └───────────────────────────────────┘│
├─────────────────────────────────────┤
│ [保存] [取消] │ <- form-footer
└─────────────────────────────────────┘
3.2 完整示例代码
<template>
<AdminLayout :currentPage="pageName">
<!-- 页面标题 -->
<view class="page-header">
<view class="header-left">
<text class="page-title">{{ isEdit ? '编辑' : '新增' }}系统信息</text>
</view>
</view>
<!-- 表单区域 -->
<view class="form-section">
<!-- 基本信息卡片 -->
<view class="form-card">
<view class="form-card-header">
<text class="form-section-title">基本信息</text>
</view>
<view class="form-card-body">
<!-- 名称 -->
<view class="form-group">
<label class="form-label required">名称</label>
<input
v-model="form.name"
class="input"
placeholder="请输入名称"
@blur="validateField('name')"
/>
<text v-if="errors.name" class="form-error">{{ errors.name }}</text>
</view>
<!-- 描述 -->
<view class="form-group">
<label class="form-label">描述</label>
<textarea
v-model="form.description"
class="textarea"
placeholder="请输入描述(可选)"
/>
</view>
<!-- 分类 -->
<view class="form-group">
<label class="form-label required">分类</label>
<select v-model="form.category" class="input">
<option value="">请选择分类</option>
<option value="system">系统</option>
<option value="user">用户</option>
<option value="product">商品</option>
</select>
<text v-if="errors.category" class="form-error">{{ errors.category }}</text>
</view>
<!-- 状态 -->
<view class="form-group">
<label class="form-label">状态</label>
<view class="radio-group">
<label class="radio-item">
<input type="radio" value="1" v-model="form.status" />
<text class="radio-label">启用</text>
</label>
<label class="radio-item">
<input type="radio" value="0" v-model="form.status" />
<text class="radio-label">禁用</text>
</label>
</view>
</view>
</view>
</view>
<!-- 高级设置卡片 -->
<view class="form-card">
<view class="form-card-header">
<text class="form-section-title">高级设置</text>
</view>
<view class="form-card-body">
<!-- 参数1 -->
<view class="form-group">
<label class="form-label">参数1</label>
<input
v-model="form.param1"
class="input"
placeholder="请输入参数1"
/>
</view>
<!-- 参数2 -->
<view class="form-group">
<label class="form-label">参数2</label>
<input
v-model="form.param2"
class="input"
placeholder="请输入参数2"
/>
</view>
</view>
</view>
</view>
<!-- 表单底部 -->
<view class="form-footer">
<button class="btn btn-primary" @click="handleSubmit" :disabled="loading">
<text>{{ loading ? '保存中...' : '保存' }}</text>
</button>
<button class="btn btn-default" @click="handleCancel">
<text>取消</text>
</button>
</view>
</AdminLayout>
</template>
<script setup lang="uts">
import { ref, computed, onMounted } from 'vue'
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
const pageName = ref('system-info-edit')
const loading = ref(false)
const isEdit = ref(false)
const form = ref({
name: '',
description: '',
category: '',
status: '1',
param1: '',
param2: '',
})
const errors = ref({
name: '',
category: '',
})
onMounted(() => {
// 获取路由参数
const route = getCurrentPages()[0].$route
const id = route?.query?.id
const mode = route?.query?.mode
isEdit.value = mode === 'edit'
if (isEdit.value && id) {
loadData(id)
}
})
const loadData = (id: string) => {
// 调用 API 加载数据
// this.$api.get(id).then(res => {
// form.value = res.data
// })
}
const validateField = (field: string) => {
switch (field) {
case 'name':
errors.value.name = form.value.name ? '' : '名称不能为空'
break
case 'category':
errors.value.category = form.value.category ? '' : '分类不能为空'
break
}
}
const validateForm = () => {
validateField('name')
validateField('category')
return !errors.value.name && !errors.value.category
}
const handleSubmit = async () => {
if (!validateForm()) {
return
}
loading.value = true
try {
// const res = await this.$api[isEdit.value ? 'update' : 'create'](form.value)
// uni.showToast({ title: '保存成功' })
// setTimeout(() => {
// uni.navigateBack()
// }, 1000)
} catch (error) {
uni.showToast({ title: '保存失败', icon: 'error' })
} finally {
loading.value = false
}
}
const handleCancel = () => {
uni.showModal({
title: '提示',
content: '放弃编辑并返回?',
success(res) {
if (res.confirm) {
uni.navigateBack()
}
},
})
}
</script>
<style scoped lang="scss">
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $space-lg;
padding-bottom: $space-md;
border-bottom: 1px solid $border-light;
}
.header-left {
display: flex;
flex-direction: column;
}
.page-title {
font-size: $font-size-lg;
font-weight: $font-weight-bold;
color: $text-primary;
}
// 表单区域
.form-section {
margin-bottom: $space-lg;
display: flex;
flex-direction: column;
gap: $space-lg;
}
.form-card {
background: $background-primary;
border-radius: $radius;
overflow: hidden;
box-shadow: $shadow;
}
.form-card-header {
padding: $space-md $space-md;
border-bottom: 1px solid $border-light;
background: $background-secondary;
}
.form-section-title {
font-size: $font-size-md;
font-weight: $font-weight-semibold;
color: $text-primary;
}
.form-card-body {
padding: $space-md;
display: flex;
flex-direction: column;
gap: $space-lg;
}
.form-group {
display: flex;
flex-direction: column;
gap: $space-sm;
}
.form-label {
font-size: $font-size-sm;
color: $text-primary;
font-weight: $font-weight-medium;
&.required::before {
content: '*';
color: $error-color;
margin-right: $space-xs;
}
}
.input {
height: $input-height;
padding: 0 $space-sm;
border: 1px solid $border-color;
border-radius: $radius-xs;
font-size: $font-size;
color: $text-primary;
&:focus {
outline: none;
border-color: $primary-color;
box-shadow: 0 0 0 3px rgba($primary-color, 0.1);
}
&::placeholder {
color: $text-tertiary;
}
}
.textarea {
padding: $space-sm;
border: 1px solid $border-color;
border-radius: $radius-xs;
font-size: $font-size;
color: $text-primary;
min-height: 100px;
resize: vertical;
font-family: inherit;
&:focus {
outline: none;
border-color: $primary-color;
box-shadow: 0 0 0 3px rgba($primary-color, 0.1);
}
&::placeholder {
color: $text-tertiary;
}
}
.form-error {
font-size: $font-size-xs;
color: $error-color;
}
.radio-group {
display: flex;
gap: $space-md;
flex-wrap: wrap;
}
.radio-item {
display: flex;
align-items: center;
gap: $space-xs;
cursor: pointer;
user-select: none;
}
.radio-label {
font-size: $font-size-sm;
color: $text-primary;
}
// 表单底部
.form-footer {
display: flex;
justify-content: center;
gap: $space-md;
padding: $space-lg 0;
border-top: 1px solid $border-light;
}
</style>
4. 详情页面(DetailPage)
4.1 结构说明
详情页面的典型结构:
┌─────────────────────────────────────┐
│ [< 返回] 页面标题 [编辑] [删除] │ <- page-header
├─────────────────────────────────────┤
│ ┌───────────────────────────────────┐│
│ │ 基本信息 ││
│ │ ├─ 名称: 某某 ││
│ │ ├─ 描述: 描述内容 ││
│ │ └─ 创建时间: 2024-01-01 ││ <- info-card
│ └───────────────────────────────────┘│
│ ┌───────────────────────────────────┐│
│ │ 操作日志 ││
│ │ ├─ 2024-01-01 10:00 - 创建 ││ <- log-card
│ │ └─ 2024-01-02 11:00 - 更新 ││
│ └───────────────────────────────────┘│
└─────────────────────────────────────┘
4.2 完整示例代码
<template>
<AdminLayout :currentPage="pageName">
<!-- 页面标题 -->
<view class="page-header">
<view class="header-left">
<button class="btn-icon" @click="handleGoBack">
<text>← 返回</text>
</button>
<text class="page-title">{{ item.name }}</text>
</view>
<view class="header-right">
<button class="btn btn-default" @click="handleEdit">编辑</button>
<button class="btn btn-danger" @click="handleDelete">删除</button>
</view>
</view>
<!-- 基本信息 -->
<view class="detail-card">
<view class="card-header">
<text class="card-title">基本信息</text>
</view>
<view class="card-body">
<view class="info-row">
<text class="info-label">ID:</text>
<text class="info-value">{{ item.id }}</text>
</view>
<view class="info-row">
<text class="info-label">名称:</text>
<text class="info-value">{{ item.name }}</text>
</view>
<view class="info-row">
<text class="info-label">描述:</text>
<text class="info-value">{{ item.description }}</text>
</view>
<view class="info-row">
<text class="info-label">状态:</text>
<view class="status-badge" :class="[`status-${item.status}`]">
{{ statusMap[item.status] }}
</view>
</view>
<view class="info-row">
<text class="info-label">创建时间:</text>
<text class="info-value">{{ formatTime(item.createTime) }}</text>
</view>
<view class="info-row">
<text class="info-label">更新时间:</text>
<text class="info-value">{{ formatTime(item.updateTime) }}</text>
</view>
</view>
</view>
<!-- 操作日志 -->
<view class="detail-card">
<view class="card-header">
<text class="card-title">操作日志</text>
</view>
<view class="card-body">
<view class="timeline">
<view class="timeline-item" v-for="log in logs" :key="log.id">
<view class="timeline-marker"></view>
<view class="timeline-content">
<view class="log-time">{{ formatTime(log.createTime) }}</view>
<view class="log-action">{{ log.action }}</view>
<view class="log-operator">操作人: {{ log.operator }}</view>
</view>
</view>
</view>
</view>
</view>
</AdminLayout>
</template>
<script setup lang="uts">
import { ref, onMounted } from 'vue'
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
const pageName = ref('system-info-detail')
const item = ref({
id: '',
name: '',
description: '',
status: '1',
createTime: '',
updateTime: '',
})
const logs = ref<any[]>([])
const statusMap = {
'1': '启用',
'0': '禁用',
}
onMounted(() => {
const route = getCurrentPages()[0].$route
const id = route?.query?.id
if (id) {
loadData(id)
}
})
const loadData = (id: string) => {
// 调用 API 加载详情
// this.$api.getDetail(id).then(res => {
// item.value = res.data
// logs.value = res.data.logs
// })
}
const handleGoBack = () => {
uni.navigateBack()
}
const handleEdit = () => {
uni.navigateTo({
url: `/pages/mall/admin/system/system-info-edit?id=${item.value.id}&mode=edit`,
})
}
const handleDelete = () => {
uni.showModal({
title: '删除确认',
content: `确定删除吗?`,
success(res) {
if (res.confirm) {
// this.$api.delete(item.value.id).then(() => {
// uni.showToast({ title: '删除成功' })
// setTimeout(() => {
// uni.navigateBack()
// }, 1000)
// })
}
},
})
}
const formatTime = (time: string) => {
return time
}
</script>
<style scoped lang="scss">
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $space-lg;
padding-bottom: $space-md;
border-bottom: 1px solid $border-light;
}
.header-left {
display: flex;
align-items: center;
gap: $space-md;
}
.btn-icon {
background: none;
border: none;
color: $primary-color;
padding: 0;
cursor: pointer;
}
.page-title {
font-size: $font-size-lg;
font-weight: $font-weight-bold;
color: $text-primary;
}
.header-right {
display: flex;
gap: $space-sm;
}
// 详情卡片
.detail-card {
background: $background-primary;
border-radius: $radius;
overflow: hidden;
box-shadow: $shadow;
margin-bottom: $space-lg;
}
.card-header {
padding: $space-md;
border-bottom: 1px solid $border-light;
background: $background-secondary;
}
.card-title {
font-size: $font-size-md;
font-weight: $font-weight-semibold;
color: $text-primary;
}
.card-body {
padding: $space-md;
}
.info-row {
display: flex;
gap: $space-md;
padding: $space-sm 0;
border-bottom: 1px solid $border-light;
&:last-child {
border-bottom: none;
}
}
.info-label {
width: 120px;
color: $text-secondary;
font-size: $font-size-sm;
font-weight: $font-weight-medium;
white-space: nowrap;
}
.info-value {
color: $text-primary;
font-size: $font-size;
flex: 1;
}
.status-badge {
display: inline-block;
padding: $space-xs $space-sm;
border-radius: $radius-xs;
font-size: $font-size-xs;
&.status-1 {
background: rgba($success-color, 0.1);
color: $success-color;
}
&.status-0 {
background: rgba($error-color, 0.1);
color: $error-color;
}
}
// 时间线
.timeline {
display: flex;
flex-direction: column;
gap: $space-lg;
}
.timeline-item {
display: flex;
gap: $space-md;
}
.timeline-marker {
width: 12px;
height: 12px;
border-radius: $radius-full;
background: $primary-color;
margin-top: $space-xs;
flex: none;
}
.timeline-content {
flex: 1;
padding-bottom: $space-lg;
border-left: 2px solid $border-light;
padding-left: $space-md;
}
.log-time {
font-size: $font-size-xs;
color: $text-tertiary;
margin-bottom: $space-xs;
}
.log-action {
font-size: $font-size;
color: $text-primary;
margin-bottom: $space-xs;
}
.log-operator {
font-size: $font-size-sm;
color: $text-secondary;
}
</style>
5. 布局规范
5.1 FlexBox 布局规则
// 水平排列
.flex-row {
display: flex;
flex-direction: row;
align-items: center;
gap: $space-md;
}
// 垂直排列
.flex-col {
display: flex;
flex-direction: column;
gap: $space-md;
}
// 间隔排列
.space-between {
display: flex;
justify-content: space-between;
align-items: center;
}
// 居中排列
.center {
display: flex;
justify-content: center;
align-items: center;
}
5.2 Grid 栅格
// 栅格容器
.grid {
display: grid;
gap: $space-lg;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
// 响应式栅格
@media (min-width: $breakpoint-lg) {
.grid {
grid-template-columns: repeat(3, 1fr);
}
}
6. 常见问题
Q1: 如何处理长文本溢出?
// 单行溢出
.text-truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
// 多行溢出
.text-clamp {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
Q2: 如何实现响应式页面?
始终采用"移动优先"策略:
- 先为移动设备设计样式
- 再使用
@media为更大屏幕添加样式 - 使用断点变量,不要硬编码断点值
Q3: 如何管理颜色主题?
所有颜色值必须使用 uni.scss 中定义的变量。如需更换主题,只需修改变量值。
总结
✅ 页面开发核心原则:
- 统一结构 - 所有页面遵循相同的结构模板
- 设计系统 - 所有样式使用
uni.scss变量 - 组件复用 - 使用
AdminLayout等通用组件 - 交互一致 - 遵循相同的交互和验证模式
- 响应式设计 - 移动优先,逐步增强
❌ 禁止做法:
- 不要创建不遵循模板的页面
- 不要使用硬编码的样式值
- 不要重复代码,尽量复用组件
- 不要创建孤立的页面样式
文档版本: 1.0
最后更新: 2026-01-31
维护者: AI Assistant