1.合并前段时间写的页面

This commit is contained in:
2025-07-24 16:00:29 +08:00
parent 33ef7f5cdb
commit 658d21399d
89 changed files with 7649 additions and 243 deletions

View File

@@ -24,6 +24,6 @@ const config = {
}
// 设置后台接口服务的基础地址
config.baseUrl = 'http://192.168.0.104:8080';
config.baseUrl = 'http://tc.cqsznc.com:7080/api';
export default config;

View File

@@ -12,11 +12,12 @@ const install = (Vue, vm) => {
login: (params = {}) => vm.$u.post(config.adminPath+'/auth/login', params),
getUserInfo: (params = {}) => vm.$u.get(config.adminPath+'/system/user/profile', params),
// 基础服务:登录登出、身份信息、菜单授权、切换系统、字典数据等
lang: (params = {}) => vm.$u.get('/lang/'+params.lang),
index: (params = {}) => vm.$u.get(config.adminPath+'/mobile/index', params),
getUserInfo: (params = {}) => vm.$u.get(config.adminPath+'/mobile/user/getUserInfo', params),
// getUserInfo: (params = {}) => vm.$u.get(config.adminPath+'/mobile/user/getUserInfo', params),
// login: (params = {}) => vm.$u.post(config.adminPath+'/mobile/login/loginByPassword', params),
sendCode: (params = {}) => vm.$u.post(config.adminPath+'/mobile/login/sendCode', params),
registerUser: (params = {}) => vm.$u.post(config.adminPath+'/mobile/user/registerUser', params),

View File

@@ -30,10 +30,10 @@ const install = (Vue, vm) => {
if (!req.header[ajaxHeader]){
req.header[ajaxHeader] = 'json';
}
console.log('t1', req.url);
// 设定传递 Token 认证参数 aidex
if (!req.header[sessionIdHeader] && vm.vuex_token){
req.header[sessionIdHeader] = vm.vuex_token;
if (req.url!="/auth/login"&&!req.header[sessionIdHeader] && vm.vuex_token){
req.header[sessionIdHeader] = "Bearer "+vm.vuex_token;
}
// 为节省流量,记住我数据不是每次都发送的,当会话失效后,尝试重试登录 aidex

View File

@@ -2,207 +2,405 @@
"easycom": {
"^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
},
"pages": [
{
"path": "pages/sys/login/index",
"style": {
"navigationBarTitleText": "登录"
}
},
{
"path": "pages/sys/login/forget",
"style": {
"navigationBarTitleText": "忘记密码"
}
},
{
"path": "pages/sys/login/reg",
"style": {
"navigationBarTitleText": "注册账号"
}
},
{
"path": "pages/sys/msg/index",
"style": {
"navigationBarTitleText": "消息"
}
},
{
"path": "pages/sys/msg/form",
"style": {
"navigationBarTitleText": "查看详情"
}
},
{
"path": "pages/sys/home/index",
"style": {
"navigationBarTitleText": "工作台",
"navigationStyle": "custom" // 隐藏系统导航栏
//案列页面
// "pages": [
// {
// "path": "pages/sys/login/index",
// "style": {
// "navigationBarTitleText": "登录"
// }
// },
// {
// "path": "pages/sys/login/forget",
// "style": {
// "navigationBarTitleText": "忘记密码"
// }
// },
// {
// "path": "pages/sys/login/reg",
// "style": {
// "navigationBarTitleText": "注册账号"
// }
// },
// {
// "path": "pages/sys/msg/index",
// "style": {
// "navigationBarTitleText": "消息"
// }
// },
// {
// "path": "pages/sys/msg/form",
// "style": {
// "navigationBarTitleText": "查看详情"
// }
// },
// {
// "path": "pages/sys/home/index",
// "style": {
// "navigationBarTitleText": "工作台",
// "navigationStyle": "custom" // 隐藏系统导航栏
}
},
// }
// },
// {
// "path": "pages/sys/user/index",
// "style": {
// "navigationBarTitleText": "我的",
// "navigationBarBackgroundColor":"#5b95ff",
// "navigationBarTextStyle": "white"
// }
// },
// {
// "path": "pages/sys/user/info",
// "style": {
// "navigationBarTitleText": "个人信息"
// }
// },
// {
// "path": "uview-ui/components/u-avatar-cropper/u-avatar-cropper",
// "style": {
// "navigationBarTitleText": "头像裁剪",
// "navigationBarBackgroundColor": "#000000"
// }
// },
// {
// "path": "pages/sys/user/help",
// "style": {
// "navigationBarTitleText": "帮助中心"
// }
// },
// {
// "path": "pages/sys/user/pwd",
// "style": {
// "navigationBarTitleText": "修改密码"
// }
// },
// {
// "path": "pages/sys/user/setting",
// "style": {
// "navigationBarTitleText": "系统设置"
// }
// },
// {
// "path": "pages/sys/user/comment",
// "style": {
// "navigationBarTitleText": "意见反馈"
// }
// },
// {
// "path": "pages/sys/user/about",
// "style": {
// "navigationBarTitleText": "关于我们"
// }
// },
// {
// "path": "pages/testData/form",
// "style": {
// "navigationBarTitleText": "新增编辑"
// }
// },
// {
// "path": "pages/testData/index",
// "style": {
// "navigationBarTitleText": "增删改查"
// }
// },
// {
// "path": "pages/common/webview",
// "style": {
// "navigationBarTitleText": "浏览网页"
// }
// },
// {
// "path": "pages/sys/login/code",
// "style": {
// "navigationBarTitleText": "验证码"
// }
// },
// {
// "path": "pages/sys/login/registerCode",
// "style": {
// "navigationBarTitleText": "验证码"
// }
// },
// {
// "path": "pages/sys/user/service",
// "style": {
// "navigationBarTitleText": "联系客服"
// }
// },
// {
// "path": "pages/sys/user/problem",
// "style": {
// "navigationBarTitleText": "常见问题"
// }
// },
// {
// "path": "pages/sys/user/currency",
// "style": {
// "navigationBarTitleText": "通用"
// }
// },
// {
// "path": "pages/sys/user/clear-cache",
// "style": {
// "navigationBarTitleText": "清除缓存"
// }
// },
// {
// "path": "pages/sys/workbench/index",
// "style": {
// "navigationBarTitleText": "工作台"
// }
// },
// {
// "path": "pages/sys/book/index",
// "style": {
// "navigationBarTitleText": "通讯录"
// }
// },
// {
// "path": "pages/sys/book/personal-details",
// "style": {
// "navigationBarTitleText": "详情"
// }
// },
// {
// "path": "pages/sys/msg/list-item",
// "style": {
// "navigationBarTitleText": "列表"
// }
// },
// {
// "path": "pages/sys/workbench/add-form",
// "style": {
// "navigationBarTitleText": "请假申请"
// }
// },
// {
// "path": "pages/sys/msg/examine-item",
// "style": {
// "navigationBarTitleText": "网上报销"
// }
// },
// {
// "path": "pages/sys/msg/details",
// "style": {
// "navigationBarTitleText": "详情"
// }
// },
// {
// "path": "pages/sys/user/modify",
// "style": {
// "navigationBarTitleText": "修改"
// }
// },
// {
// "path": "pages/sys/workbench/install",
// "style": {
// "navigationBarTitleText": "常用设置",
// "navigationStyle": "custom" // 隐藏系统导航栏
// }
// }
// ],
// "tabBar": {
// "color": "#333333",
// "selectedColor": "#4094ff",
// "backgroundColor": "#ffffff",
// "borderStyle": "white",
// "list": [
// {
// "pagePath": "pages/sys/msg/index",
// "iconPath": "static/aidex/tabbar/msg_1.png",
// "selectedIconPath": "static/aidex/tabbar/msg_2.png",
// "text": "消息"
// },
// // {
// // "pagePath": "pages/sys/home/index",
// // "iconPath": "static/aidex/tabbar/home_1.png",
// // "selectedIconPath": "static/aidex/tabbar/home_2.png",
// // "text": "首页"
// // },
// {
// "pagePath": "pages/sys/workbench/index",
// "iconPath": "static/aidex/tabbar/apply_1.png",
// "selectedIconPath": "static/aidex/tabbar/apply_2.png",
// "text": "工作台"
// },
// {
// "pagePath": "pages/sys/book/index",
// "iconPath": "static/aidex/tabbar/book_1.png",
// "selectedIconPath": "static/aidex/tabbar/book_2.png",
// "text": "通讯录"
// },
// {
// "pagePath": "pages/sys/user/index",
// "iconPath": "static/aidex/tabbar/my_1.png",
// "selectedIconPath": "static/aidex/tabbar/my_2.png",
// "text": "我的"
// }
// ]
// },
"pages":[
{
"path": "pages/sys/user/index",
"path": "pages/sys/login/login",
"style": {
"navigationBarTitleText": "我的",
"navigationBarBackgroundColor":"#5b95ff",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/sys/user/info",
"style": {
"navigationBarTitleText": "个人信息"
}
},
{
"path": "uview-ui/components/u-avatar-cropper/u-avatar-cropper",
"style": {
"navigationBarTitleText": "头像裁剪",
"navigationBarBackgroundColor": "#000000"
}
},
{
"path": "pages/sys/user/help",
"style": {
"navigationBarTitleText": "帮助中心"
}
},
{
"path": "pages/sys/user/pwd",
"style": {
"navigationBarTitleText": "修改密码"
}
},
{
"path": "pages/sys/user/setting",
"style": {
"navigationBarTitleText": "系统设置"
}
},
{
"path": "pages/sys/user/comment",
"style": {
"navigationBarTitleText": "意见反馈"
}
},
{
"path": "pages/sys/user/about",
"style": {
"navigationBarTitleText": "关于我们"
}
},
{
"path": "pages/testData/form",
"style": {
"navigationBarTitleText": "新增编辑"
}
},
{
"path": "pages/testData/index",
"style": {
"navigationBarTitleText": "增删改查"
}
},
{
"path": "pages/common/webview",
"style": {
"navigationBarTitleText": "浏览网页"
}
},
{
"path": "pages/sys/login/code",
"style": {
"navigationBarTitleText": "验证码"
}
},
{
"path": "pages/sys/login/registerCode",
"style": {
"navigationBarTitleText": "验证码"
}
},
{
"path": "pages/sys/user/service",
"style": {
"navigationBarTitleText": "联系客服"
}
},
{
"path": "pages/sys/user/problem",
"style": {
"navigationBarTitleText": "常见问题"
}
},
{
"path": "pages/sys/user/currency",
"style": {
"navigationBarTitleText": "通用"
}
},
{
"path": "pages/sys/user/clear-cache",
"style": {
"navigationBarTitleText": "清除缓存"
}
},
{
"path": "pages/sys/workbench/index",
"style": {
"navigationBarTitleText": "工作台"
}
},
{
"path": "pages/sys/book/index",
"style": {
"navigationBarTitleText": "通讯录"
}
},
{
"path": "pages/sys/book/personal-details",
"style": {
"navigationBarTitleText": "详情"
}
},
{
"path": "pages/sys/msg/list-item",
"style": {
"navigationBarTitleText": "列表"
}
},
{
"path": "pages/sys/workbench/add-form",
"style": {
"navigationBarTitleText": "请假申请"
}
},
{
"path": "pages/sys/msg/examine-item",
"style": {
"navigationBarTitleText": "网上报销"
}
},
{
"path": "pages/sys/msg/details",
"style": {
"navigationBarTitleText": "详情"
}
},
{
"path": "pages/sys/user/modify",
"style": {
"navigationBarTitleText": "修改"
}
},
{
"path": "pages/sys/workbench/install",
"style": {
"navigationBarTitleText": "常用设置",
"navigationBarTitleText": "登录",
"navigationStyle": "custom" // 隐藏系统导航栏
}
},
{
"path": "pages/sys/home/home",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/sys/workbench/workbench",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/sys/user/mine",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/sys/user/changeInfo/changeInfo",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/sys/user/message/message",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/sys/user/serviceCenter/serviceCenter",
"style": {
"navigationBarTitleText": "服务中心"
}
},
{
"path": "pages/sys/user/serviceCenter/questionDetail",
"style": {
"navigationBarTitleText": "服务中心"
}
},
{
"path": "pages/sys/user/myVisitor/myVisitor",
"style": {
"navigationBarTitleText": "我的访客"
}
},
{
"path": "pages/sys/user/myVisitor/creatVisitor",
"style": {
"navigationBarTitleText": "发起邀约"
}
},
{
"path": "pages/sys/user/myVisitor/visitorInfo",
"style": {
"navigationStyle": "custom"
}
},
{
"path" : "pages/sys/user/myPayment/myPayment",
"style" :
{
"navigationBarTitleText": "停车缴费"
}
},
{
"path" : "pages/sys/user/myPayment/paymentRecords",
"style" :
{
"navigationBarTitleText": "缴费记录"
}
},
{
"path" : "pages/sys/user/myRepair/myRepair",
"style" :
{
"navigationBarTitleText": "报事报修"
}
},
{
"path" : "pages/sys/user/myRepair/addRepair",
"style" :
{
"navigationBarTitleText": "新增"
}
},
{
"path" : "pages/sys/user/myRepair/repaired",
"style" :
{
"navigationStyle" : "custom"
}
},
{
"path" : "pages/sys/user/myRepair/repairEvaluate",
"style" :
{
"navigationBarTitleText": "服务评价"
}
},
{
"path" : "pages/sys/user/myRecord/myRecord",
"style" :
{
"navigationStyle" : "custom"
}
},
{
"path" : "pages/sys/workbench/oa/oa",
"style" :
{
"navigationStyle" : "custom"
}
},
{
"path" : "pages/sys/workbench/oa/oaDetail",
"style" :
{
"navigationStyle" : "custom"
}
}
],
"tabBar": {
"color": "#232323",
"selectedColor": "#0652FF",
"backgroundColor": "#fff",
"borderStyle": "black",
"list": [{
"pagePath": "pages/sys/home/home",
"iconPath": "static/ic_main_home.png",
"selectedIconPath": "static/ic_main_home_selected.png",
"text": "首页"
},
{
"pagePath": "pages/sys/workbench/workbench",
"iconPath": "static/ic_main_work.png",
"selectedIconPath": "static/ic_main_work_selected.png",
"text": "工作台"
},
{
"pagePath": "pages/sys/user/mine",
"iconPath": "static/ic_main_mine.png",
"selectedIconPath": "/static/ic_main_mine_selected.png",
"text": "我的"
}
]
},
"subPackages": [
],
@@ -213,43 +411,5 @@
"navigationBarTextStyle": "black",
"navigationBarTitleText": "Aidex",
"navigationBarBackgroundColor": "#ffffff"
},
"tabBar": {
"color": "#333333",
"selectedColor": "#4094ff",
"backgroundColor": "#ffffff",
"borderStyle": "white",
"list": [
{
"pagePath": "pages/sys/msg/index",
"iconPath": "static/aidex/tabbar/msg_1.png",
"selectedIconPath": "static/aidex/tabbar/msg_2.png",
"text": "消息"
},
// {
// "pagePath": "pages/sys/home/index",
// "iconPath": "static/aidex/tabbar/home_1.png",
// "selectedIconPath": "static/aidex/tabbar/home_2.png",
// "text": "首页"
// },
{
"pagePath": "pages/sys/workbench/index",
"iconPath": "static/aidex/tabbar/apply_1.png",
"selectedIconPath": "static/aidex/tabbar/apply_2.png",
"text": "工作台"
},
{
"pagePath": "pages/sys/book/index",
"iconPath": "static/aidex/tabbar/book_1.png",
"selectedIconPath": "static/aidex/tabbar/book_2.png",
"text": "通讯录"
},
{
"pagePath": "pages/sys/user/index",
"iconPath": "static/aidex/tabbar/my_1.png",
"selectedIconPath": "static/aidex/tabbar/my_2.png",
"text": "我的"
}
]
}
}

409
pages/sys/home/home.vue Normal file
View File

@@ -0,0 +1,409 @@
<template>
<view class="home-container">
<!-- 顶部Banner区域包含所有顶部内容 -->
<view class="home-header">
<swiper class="banner-swiper" :current="current" @change="onBannerChange" :autoplay="true" :interval="3000" :circular="true">
<swiper-item v-for="(item, idx) in banners" :key="idx">
<image :src="item.img" class="banner-bg-img" mode="aspectFill" />
</swiper-item>
</swiper>
<!-- 自定义指示器 -->
<view class="banner-indicator">
<view v-for="(item, idx) in banners" :key="idx" :class="['banner-dot', current === idx ? 'active' : '']"></view>
</view>
<view class="home-top">
<image class="icon3" src="/static/ic_tq.png" />
<text class="weather">21-36</text>
<view class="search-bar">
<image src="/static/ic_search.png" class="search-icon-img" />
<input class="search-input" placeholder="搜索" />
</view>
<view class="top-icons">
<image class="icon1" src="/static/ic_scan.png" />
<image class="icon2" src="/static/ic_msg.png" />
</view>
</view>
</view>
<!-- 白色圆角虚线面板 -->
<view class="main-panel">
<!-- 九宫格功能区 -->
<view class="grid-area">
<view class="grid-item" v-for="(item, idx) in gridList" :key="idx">
<image :src="item.icon" class="grid-icon" />
<text class="grid-text">{{ item.text }}</text>
</view>
</view>
<!-- 滚动资讯条 -->
<view class="news-bar">
<text class="news-label">头条</text>
<scroll-view scroll-x class="news-scroll">
<text v-for="(item, idx) in newsList" :key="idx" class="news-item">{{ item }}</text>
</scroll-view>
<text class="news-arrow"></text>
</view>
<!-- 热门活动区 -->
<view class="hot-section">
<view class="hot-title-row">
<text class="hot-title">热门活动</text>
<text class="hot-more">全部热门活动 ></text>
</view>
<view class="hot-list">
<view class="hot-card" v-for="(item, idx) in hotList" :key="idx">
<image :src="item.img" class="hot-img" mode="aspectFill" />
<view class="hot-info">
<text class="hot-tag">#热门活动</text>
<text class="hot-desc">{{ item.title }}</text>
<view class="hot-meta">
<text class="hot-date">{{ item.date }}</text>
<text class="hot-status">进行中</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'Home',
data() {
return {
banners: [{
img: 'https://picsum.photos/750/300?random=1'
},
{
img: 'https://picsum.photos/750/300?random=2'
}
],
current: 0,
gridList: [{
icon: 'https://picsum.photos/80/80?random=3',
text: '报事报修'
},
{
icon: 'https://picsum.photos/80/80?random=4',
text: '停车缴费'
},
{
icon: 'https://picsum.photos/80/80?random=5',
text: '生活服务'
},
{
icon: 'https://picsum.photos/80/80?random=6',
text: '服务中心'
},
{
icon: 'https://picsum.photos/80/80?random=7',
text: '会议预约'
},
{
icon: 'https://picsum.photos/80/80?random=8',
text: '工单管理'
},
{
icon: 'https://picsum.photos/80/80?random=9',
text: '访客管理'
},
{
icon: 'https://picsum.photos/80/80?random=10',
text: '敬请期待'
}
],
newsList: [
'数智南川|最新资讯1',
'数智南川|最新资讯2',
'数智南川|最新资讯3'
],
hotList: [{
img: 'https://picsum.photos/700/280?random=13',
title: '世界骑行日 低碳出行 让城市更美好',
date: '2025-07-03'
},
{
img: 'https://picsum.photos/700/280?random=14',
title: '仲夏之夜低碳出行·绿色生活让城市更美好',
date: '2025-07-03'
}
]
}
},
methods: {
onBannerChange(e) {
this.current = e.detail.current;
}
}
}
</script>
<style scoped>
.home-container {
display: flex;
flex-direction: column;
height: 100vh;
background: #f8f8f8;
padding-bottom: 40rpx;
}
.home-header {
position: relative;
height: 446rpx;
width: 100vw;
margin-bottom: -40rpx;
flex-shrink: 0;
}
.banner-swiper {
width: 100vw;
height: 446rpx;
position: absolute;
left: 0;
top: 0;
z-index: 0;
}
.banner-bg-img {
width: 100vw;
height: 446rpx;
object-fit: cover;
}
.banner-indicator {
position: absolute;
left: 0;
bottom: 64rpx;
width: 100vw;
display: flex;
justify-content: center;
z-index: 2;
}
.banner-dot {
width: 14rpx;
height: 14rpx;
border-radius: 50%;
background: rgba(255,255,255,0.6);
margin: 0 8rpx;
transition: background 0.2s;
}
.banner-dot.active {
background: #3B7BFF;
}
.home-top {
position: relative;
z-index: 3;
display: flex;
align-items: center;
color: #fff;
padding: 80rpx 20rpx 0 20rpx;
}
.weather {
font-size: 24rpx;
margin-left: 10rpx;
margin-right: 16rpx;
}
.search-bar {
flex: 1;
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.3);
border-radius: 30rpx;
padding: 0 20rpx;
height: 56rpx;
margin-right: 16rpx;
}
.search-input::placeholder {
color: #f0f0f0;
}
.search-icon-img {
width: 28rpx;
height: 28rpx;
margin-right: 8rpx;
}
.search-input {
border: none;
background: transparent;
font-size: 26rpx;
flex: 1;
color: #fff;
}
.top-icons {
display: flex;
align-items: center;
}
.icon1 {
width: 33rpx;
height: 33rpx;
margin-left: 19rpx;
}
.icon2 {
width: 38rpx;
height: 38rpx;
margin-left: 23rpx;
}
.icon3 {
width: 40rpx;
height: 40rpx;
}
.main-panel {
flex: 1;
overflow-y: auto;
background: #fff;
border-radius: 20rpx;
position: relative;
z-index: 10;
padding: 20rpx;
}
.grid-area {
display: flex;
flex-wrap: wrap;
padding: 20rpx 0 10rpx 0;
}
.grid-item {
width: 20%;
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20rpx;
}
.grid-icon {
width: 80rpx;
height: 80rpx;
margin-bottom: 8rpx;
}
.grid-text {
font-size: 24rpx;
color: #1D1D1D;
}
.news-bar {
display: flex;
align-items: center;
padding: 10rpx 0;
border-top: 1rpx solid #f5f5f5;
margin-top: 10rpx;
}
.news-label {
color: #FF6A00;
font-size: 28rpx;
font-weight: bold;
margin-right: 16rpx;
border: 1rpx solid #FF6A00;
border-radius: 4rpx;
padding: 2rpx 6rpx;
}
.news-scroll {
flex: 1;
white-space: nowrap;
}
.news-item {
display: inline-block;
margin-right: 30rpx;
color: #666;
font-size: 24rpx;
}
.news-arrow {
color: #999;
font-size: 32rpx;
}
.hot-section {
margin-top: 20rpx;
}
.hot-title-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.hot-title {
font-size: 32rpx;
font-weight: bold;
color: #222;
position: relative;
padding-left: 16rpx;
}
.hot-title::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 6rpx;
height: 30rpx;
background: #3B7BFF;
border-radius: 3rpx;
}
.hot-more {
font-size: 24rpx;
color: #999;
}
.hot-list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.hot-card {
background: #fff;
border-radius: 20rpx;
overflow: hidden;
display: flex;
flex-direction: column;
border: 1rpx solid #f0f0f0;
}
.hot-img {
width: 100%;
height: 280rpx;
object-fit: cover;
}
.hot-info {
padding: 16rpx 20rpx 20rpx 20rpx;
}
.hot-tag {
color: #3B7BFF;
font-size: 22rpx;
margin-bottom: 6rpx;
display: block;
}
.hot-desc {
font-size: 28rpx;
color: #222;
margin-bottom: 10rpx;
display: block;
font-weight: 500;
}
.hot-meta {
display: flex;
justify-content: space-between;
align-items: center;
}
.hot-date {
color: #999;
font-size: 22rpx;
}
.hot-status {
color: #fff;
background: #3B7BFF;
font-size: 22rpx;
border-radius: 8rpx;
padding: 4rpx 12rpx;
}
</style>

266
pages/sys/login/login.vue Normal file
View File

@@ -0,0 +1,266 @@
<template>
<view class="login-container">
<!-- 顶部渐变背景和标题 -->
<view class="login-header">
<image class="login-bg-img" src="/static/ic_login_topbg.png" mode="widthFix" />
<view class="login-title">登录注册</view>
<view class="login-subtitle">欢迎使用数智南川</view>
</view>
<!-- 表单区域 -->
<view class="login-form">
<view class="input-row">
<image class="iconfont" src="/static/ic_login_phone.png" />
<input class="login-input" type="text" placeholder="输入手机号" v-model="username" />
</view>
<view class="input-row">
<image class="iconfont2" src="/static/ic_login_code.png" />
<input class="login-input" type="text" placeholder="请输入验证码" />
<button class="code-btn">获取校验码</button>
</view>
<view class="protocol-row">
<label class="custom-checkbox-label">
<input type="checkbox" :checked="checked" @change="handleCheckboxChange"
class="custom-checkbox-input" />
<image :src="checked ? '/static/ic_login_agree.png' : '/static/ic_login_dis.png'"
class="custom-checkbox-img" @click="handleCheckboxChange({ target: { checked: !checked }})" />
</label>
<text>同意</text>
<text class="protocol-link">用户协议</text>
<text></text>
<text class="protocol-link">隐私政策</text>
</view>
<button class="login-btn" @click="submit">登录</button>
</view>
</view>
</template>
<script>
import base64 from '@/common/base64.js';
export default {
data() {
return {
phoneNo: '',
username: 'admin',
password: 'admin123',
loginType: 'currentPhone',
showPassword: false,
remember: true,
isValidCodeLogin: false,
validCode: '',
imgValidCodeSrc: null,
list: [{
name: '用户名'
}, {
name: '手机号'
}],
current: 0,
activeColor: '#007aff',
checked: false
}
},
methods: {
async submit() {
if (this.username.length == 0) {
this.$u.toast('请输入账号');
return;
}
if (this.password.length == 0) {
this.$u.toast('请输入密码');
return;
}
let res = await this.$u.api.login({
"tenantId": "000000", // 把单引号换成双引号
"username": this.username,
"password": this.password,
"grantType": "password",
"uuid": "7cb3ea4fe2ae4f08bbd561c94ef0191b",
"clientId": "dab457a1ea14411787c240db05bb0832"
});
console.log(res)
if (res.code == "200") {
this.$u.toast("登录成功")
this.$store.commit('$uStore', {
name: 'vuex_token',
value: res.data.access_token
});
setTimeout(() => {
uni.reLaunch({
url: '/pages/sys/home/home'
});
}, 500);
}
},
handleCheckboxChange(e) {
this.checked = e.target.checked;
}
}
}
</script>
<style scoped>
.login-container {
min-height: 100vh;
background: #fafbfc;
display: flex;
flex-direction: column;
align-items: center;
}
.login-header {
width: 100%;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-bottom: 40rpx;
}
.login-bg-img {
position: absolute;
top: 0;
left: 0;
width: 100%;
z-index: 0;
}
.login-title,
.login-subtitle {
position: relative;
top: 164rpx;
z-index: 2;
}
.login-title {
color: #fff;
font-size: 48rpx;
font-weight: bold;
margin-top: 40rpx;
}
.login-subtitle {
color: #fff;
font-size: 24rpx;
margin-top: 10rpx;
}
.login-form {
width: 80%;
margin-top: 400rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.input-row {
width: 100%;
display: flex;
align-items: center;
border-bottom: 1px solid #eee;
margin-bottom: 30rpx;
padding-bottom: 10rpx;
}
.iconfont {
font-size: 32rpx;
margin-right: 16rpx;
width: 27rpx;
height: 43rpx;
}
.iconfont2 {
font-size: 32rpx;
margin-right: 16rpx;
width: 31rpx;
height: 35rpx;
}
.login-input {
flex: 1;
border: none;
background: transparent;
font-size: 28rpx;
padding: 10rpx 0;
margin-right: 10rpx;
}
.code-btn {
background: none;
color: #2e6cf6;
border: none;
font-size: 26rpx;
padding: 0 10rpx;
}
.protocol-row {
width: 100%;
display: flex;
align-items: center;
margin: 20rpx 0 40rpx 0;
font-size: 24rpx;
}
.protocol-checkbox {
margin-right: 8rpx;
}
.protocol-link {
color: #2e6cf6;
margin: 0 4rpx;
}
.login-btn {
width: 100%;
height: 80rpx;
background: linear-gradient(90deg, #2e6cf6 0%, #4fc3f7 100%);
color: #fff;
font-size: 32rpx;
border: none;
border-radius: 40rpx;
margin-top: 268rpx;
}
.custom-checkbox-label {
display: flex;
align-items: center;
margin-right: 8rpx;
}
.custom-checkbox-input {
display: none;
}
.custom-checkbox-span {
width: 28rpx;
height: 28rpx;
border-radius: 50%;
border: 2rpx solid #2e6cf6;
background: #fff;
display: inline-block;
position: relative;
transition: border-color 0.2s;
}
.custom-checkbox-span.checked {
background: #2e6cf6;
border-color: #2e6cf6;
}
.custom-checkbox-span.checked::after {
content: '';
position: absolute;
left: 7rpx;
top: 7rpx;
width: 12rpx;
height: 12rpx;
background: #fff;
border-radius: 50%;
}
.custom-checkbox-img {
width: 28rpx;
height: 28rpx;
margin-right: 8rpx;
}
</style>

View File

@@ -70,7 +70,7 @@ page {
overflow: hidden;
font-size: 30rpx;
line-height: 50rpx;
/deep/ p {
::v-deep p {
margin-bottom: 20rpx;
text-indent: 60rpx;
}

View File

@@ -0,0 +1,236 @@
<template>
<view class="change-info-container">
<image src="/static/ic_back.png" class="back-arrow" @click="goBack"/>
<view class="title-main">完善你的资料</view>
<view class="title-sub">让大家更好地了解你</view>
<view class="avatar-section">
<image class="avatar-img" src="/static/avatar.png" />
<view class="avatar-camera">
<image src="/static/ic_camera.png" class="camera-icon" />
</view>
</view>
<view class="form-section">
<view class="form-label">取个昵称</view>
<view class="input-row">
<input class="form-input" v-model="nickname" placeholder="请输入昵称" />
<view class="input-suffix">
<image src="/static/ic_i_c_01.png" class="random-icon" />
<text class="random-text">随机</text>
</view>
</view>
<view class="form-label">你的手机号</view>
<view class="input-row">
<input class="form-input" v-model="phone" placeholder="请输入您的手机号" />
<image src="/static/ic_arrow_right.png" class="arrow-icon" />
</view>
<view class="form-label">你的性别不可修改</view>
<view class="gender-row">
<view class="gender-item selected">
<image src="/static/ic_i_c_02.png" class="gender-icon" />
</view>
<view class="gender-item">
<image src="/static/ic_i_c_03.png" class="gender-icon" />
</view>
</view>
<view class="info-row"><text class="info-label">部门</text>生活服务部</view>
<view class="info-row"><text class="info-label">岗位</text>客服</view>
<view class="info-row"><text class="info-label">工号</text>A10235</view>
</view>
<button class="submit-btn">确认修改</button>
</view>
</template>
<script>
export default {
data() {
return {
nickname: '帅气的小南瓜',
phone: ''
}
},
methods: {
goBack() {
uni.navigateBack();
}
}
}
</script>
<style scoped>
.change-info-container {
position: relative;
padding-bottom: 60rpx;
padding-top: 118rpx;
min-height: 100vh;
background: linear-gradient(180deg, #d2edff 0%, #ffffff 100%);
}
.back-arrow {
width: 24rpx;
height: 42rpx;
left: 45rpx;
}
.title-main {
font-size: 50rpx;
font-weight: bold;
color: #000;
margin-top: 45rpx;
margin-left: 65rpx;
}
.title-sub {
font-size: 50rpx;
color: #000;
font-weight: bold;
margin-left: 65rpx;
margin-bottom: 30rpx;
}
.avatar-section {
display: flex;
justify-content: flex-start;
align-items: center;
margin-left: 68rpx;
margin-bottom: 62rpx;
position: relative;
}
.avatar-img {
width: 200rpx;
height: 200rpx;
border-radius: 50%;
background: #fff;
}
.avatar-camera {
position: absolute;
left: 100rpx;
top: 90rpx;
width: 48rpx;
height: 48rpx;
background: #bbb;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.camera-icon {
width: 32rpx;
height: 32rpx;
}
.form-section {
margin: 0 68rpx;
background: transparent;
}
.form-label {
color: #737373;
font-size: 28rpx;
margin-bottom: 17rpx;
margin-top: 32rpx;
font-weight: bold;
}
.input-row {
display: flex;
align-items: center;
background: #fff;
border-radius: 14rpx;
border: 2rpx solid #e0e0e0;
margin-bottom: 18rpx;
padding: 0 20rpx;
height: 98rpx;
}
.form-input {
flex: 1;
border: none;
font-weight: bold;
font-size: 33rpx;
color: #000000;
}
.input-suffix {
display: flex;
align-items: center;
margin-left: 10rpx;
}
.random-icon {
width: 31rpx;
height: 27rpx;
margin-right: 6rpx;
}
.random-text {
color: #636363;
font-size: 25rpx;
}
.arrow-icon {
width: 22rpx;
height: 22rpx;
margin-left: 10rpx;
}
.gender-row {
display: flex;
align-items: center;
margin-bottom: 50rpx;
padding-top: 15rpx;
}
.gender-item {
width: 188rpx;
height: 97rpx;
border: 2rpx dashed #bbb;
border-radius: 10rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 24rpx;
background: #fff;
}
.gender-item.selected {
background: #e0e0e0;
border-color: #bbb;
}
.gender-icon {
width: 38rpx;
height: 38rpx;
}
.info-row {
display: flex;
align-items: center;
font-size: 28rpx;
color: #737373;
margin-bottom: 20rpx;
}
.info-label {
color: #737373;
width: 134rpx;
font-size: 28rpx;
font-weight: bold;
display: inline-block;
}
.submit-btn {
width: 90vw;
height: 80rpx;
background: linear-gradient(90deg, #2e6cf6 0%, #4fc3f7 100%);
color: #fff;
font-size: 32rpx;
border: none;
border-radius: 40rpx;
margin: 80rpx auto 40rpx auto;
display: block;
box-shadow: 0 8rpx 24rpx rgba(46, 108, 246, 0.12);
}
</style>

View File

@@ -39,7 +39,7 @@ page {
background-color: #f8f8f8;
}
/deep/ .u-cell-title {
::v-deep .u-cell-title {
padding: 25rpx 30rpx;
font-size: 30rpx;
}

View File

@@ -77,7 +77,7 @@ page {
background-color: #f8f8f8;
}
/deep/ .u-cell-title {
::v-deep .u-cell-title {
padding: 25rpx 30rpx;
font-size: 30rpx;
}

View File

@@ -0,0 +1,164 @@
<template>
<view class="msg-container">
<!-- 顶部导航栏 -->
<view class="msg-navbar">
<image src="/static/ic_msg_01.png" class="msg-navbar-bg" mode="aspectFill" />
<image src="/static/ic_back_white.webp" class="msg-back" @click="goBack" />
<text class="msg-title">消息中心</text>
</view>
<!-- 可滚动内容区 -->
<view class="msg-scroll-content">
<!-- 消息列表 -->
<view v-if="msgList.length" class="msg-list">
<view class="msg-item" v-for="(item, idx) in msgList" :key="idx">
<image :src="item.icon" class="msg-icon" />
<view class="msg-content">
<view class="msg-row">
<text class="msg-main-title">{{ item.title }}</text>
<text class="msg-time">{{ item.time }}</text>
</view>
<view class="msg-desc">{{ item.desc }}</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-else class="msg-empty">
<image src="/static/ic_msg_empty.png" class="msg-empty-img" />
<text class="msg-empty-text">暂无更多消息</text>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
msgList: [
{
icon: '/static/ic_msg_sys.png',
title: '系统消息',
desc: '欢迎注册!更多好礼等你来....',
time: '12:20'
},
{
icon: '/static/ic_msg_feedback.png',
title: '反馈进度',
desc: '查看更多反馈进度....',
time: '12:20'
}
]
}
},
methods: {
goBack() {
uni.navigateBack();
}
}
}
</script>
<style scoped>
.msg-container {
height: 100vh;
background: #F0F3FF;
display: flex;
flex-direction: column;
}
.msg-navbar {
width: 100%;
height: 142rpx;
position: relative;
padding-top: 40rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.msg-navbar-bg {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 142rpx;
z-index: 0;
}
.msg-back {
position: absolute;
left: 32rpx;
width: 20rpx;
height: 36rpx;
z-index: 1;
}
.msg-title {
color: #fff;
font-size: 36rpx;
z-index: 1;
}
.msg-scroll-content {
flex: 1;
overflow-y: auto;
}
.msg-list {
background: #fff;
margin: 0 0 0 0;
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.04);
padding: 0 0 0 0;
}
.msg-item {
display: flex;
align-items: flex-start;
padding: 20rpx;
border-bottom: 1px solid #f7f7f7;
}
.msg-item:last-child {
border-bottom: none;
}
.msg-icon {
width: 64rpx;
height: 64rpx;
margin-right: 24rpx;
border-radius: 16rpx;
}
.msg-content {
flex: 1;
display: flex;
flex-direction: column;
}
.msg-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.msg-main-title {
font-size: 30rpx;
color: #333;
}
.msg-time {
font-size: 24rpx;
color: #333;
}
.msg-desc {
font-size: 24rpx;
color: #999;
margin-top: 12rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.msg-empty {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 120rpx;
}
.msg-empty-img {
width: 320rpx;
height: 220rpx;
margin-bottom: 32rpx;
}
.msg-empty-text {
color: #bbb;
font-size: 28rpx;
}
</style>

232
pages/sys/user/mine.vue Normal file
View File

@@ -0,0 +1,232 @@
<template>
<view class="mine-container">
<!-- 顶部蓝色背景和个人信息 -->
<view class="mine-header">
<image class="mine-bg" src="/static/ic_mine_topbg.png" mode="widthFix" />
<view class="mine-info-row">
<image class="mine-avatar" src="/static/ic_mine_head.png" />
<view class="mine-userinfo">
<view class="mine-nick">昵称</view>
<view class="mine-phone">1789548878</view>
</view>
</view>
<view class="mine-header-icons">
<image class="mine-header-icon" src="/static/ic_mine_notice.png" @click="handleItemClick(-1)"/>
<image class="mine-header-icon2" src="/static/ic_mine_setting.png" @click="handleItemClick(-2)"/>
</view>
<view class="mine-header-wave"></view>
</view>
<!-- 白色圆角面板 -->
<view class="mine-panel">
<view class="mine-list">
<view class="mine-list-item" v-for="(item, idx) in list" :key="idx" @click="handleItemClick(idx)">
<image class="mine-list-icon" :src="item.icon" />
<text class="mine-list-text">{{ item.text }}</text>
<text v-if="item.extra" class="mine-list-extra">{{ item.extra }}</text>
<image v-if="!item.extra" class="mine-list-arrow" src="/static/ic_arrow_gray.webp" />
</view>
</view>
<button class="logout-btn">退出登录</button>
</view>
</view>
</template>
<script>
export default {
name: 'Mine',
data() {
return {
list: [
{ icon: '/static/ic_mine_info.png', text: '我的信息' },
{ icon: '/static/ic_mine_pay.png', text: '我的缴费' },
{ icon: '/static/ic_mine_repair.png', text: '我的报修' },
{ icon: '/static/ic_mine_visitor.png', text: '我的访客' },
{ icon: '/static/ic_mine_check.png', text: '我的考勤' },
{ icon: '/static/ic_mine_pwd.png', text: '修改密码' },
{ icon: '/static/ic_mine_version.png', text: '系统版本', extra: 'v1.00.01' },
{ icon: '/static/ic_mine_setting2.png', text: '设置' }
]
}
},
onLoad() {
this.$u.api.getUserInfo().then(res => {
if (res.code == '200'){
}
});
},
methods: {
handleItemClick(idx) {
if (idx === 0) {
uni.navigateTo({ url: '/pages/sys/user/changeInfo/changeInfo' });
}else if(idx === -1){
uni.navigateTo({ url: '/pages/sys/user/message/message' });
}else if(idx === -2){
uni.navigateTo({ url: '/pages/sys/user/serviceCenter/serviceCenter' });
}else if(idx === 1){
uni.navigateTo({ url: '/pages/sys/user/myPayment/myPayment' });
}else if(idx === 2){
uni.navigateTo({ url: '/pages/sys/user/myRepair/myRepair' });
}else if(idx === 3){
uni.navigateTo({ url: '/pages/sys/user/myVisitor/myVisitor' });
}else if(idx === 4){
uni.navigateTo({ url: '/pages/sys/user/myRecord/myRecord' });
// uni.navigateTo({ url: '/pages/workbench/oa/oa' });
}
}
}
}
</script>
<style scoped>
.mine-container {
height: 100vh;
background: #f8f8f8;
display: flex;
flex-direction: column;
}
.mine-header {
width: 100%;
height: 343rpx;
position: relative;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding-bottom: 0;
flex-shrink: 0; /* 防止被压缩 */
}
.mine-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 343rpx;
z-index: 0;
}
.mine-header-wave {
position: absolute;
left: 0;
bottom: -18rpx;
width: 100%;
height: 36rpx;
background: #fff;
border-bottom-left-radius: 36rpx;
border-bottom-right-radius: 36rpx;
z-index: 2;
}
.mine-info-row {
display: flex;
align-items: center;
position: relative;
z-index: 3;
margin-left: 32rpx;
bottom: 127rpx;
}
.mine-header-icons {
position: absolute;
right: 22rpx;
top: 86rpx;
display: flex;
z-index: 4;
}
.mine-avatar {
width: 90rpx;
height: 90rpx;
border-radius: 50%;
background: #fff;
margin-right: 24rpx;
border: 4rpx solid #fff;
}
.mine-userinfo {
display: flex;
flex-direction: column;
}
.mine-nick {
color: #fff;
font-size: 32rpx;
font-weight: bold;
margin-bottom: 6rpx;
}
.mine-phone {
color: #fff;
font-size: 24rpx;
}
.mine-header-icon {
width: 30rpx;
height: 35rpx;
margin-left: 24rpx;
}
.mine-header-icon2 {
width: 33rpx;
height: 33rpx;
margin-left: 24rpx;
}
.mine-panel {
position: relative;
z-index: 10;
background: #fff;
border-top-left-radius: 30rpx;
border-top-right-radius: 30rpx;
margin-top: -51rpx;
box-shadow: 0 -2rpx 16rpx rgba(0,0,0,0.04);
padding: 0 0 40rpx 0;
display: flex;
flex-direction: column;
align-items: center;
flex: 1; /* 占据剩余空间 */
overflow-y: auto; /* 开启滚动 */
}
.mine-list {
width: 92vw;
background: transparent;
border-radius: 0;
margin: 51rpx auto;
box-shadow: none;
padding: 0;
}
.mine-list-item {
display: flex;
align-items: center;
padding: 0 0 0 0;
height: 96rpx;
position: relative;
background: transparent;
}
.mine-list-item:last-child {
border-bottom: none;
}
.mine-list-icon {
width: 38rpx;
height: 38rpx;
margin: 0 24rpx 0 30rpx;
}
.mine-list-text {
flex: 1;
font-size: 28rpx;
color: #1A1A1A;
}
.mine-list-extra {
color: #999;
font-size: 24rpx;
margin-right: 10rpx;
}
.mine-list-arrow {
width: 18rpx;
height: 28rpx;
margin-right: 24rpx;
}
.logout-btn {
width: 88vw;
height: 80rpx;
background: #0090FF;
color: #fff;
font-size: 32rpx;
border: none;
border-radius: 40rpx;
margin: 100rpx auto 0 auto;
display: block;
box-shadow: 0 18rpx 24rpx rgba(0,0,0,0.18);
font-weight: bold;
}
</style>

View File

@@ -0,0 +1,537 @@
<template>
<view class="page-wrapper">
<!-- 顶部title栏 -->
<view class="pay-container">
<!-- 未绑定车牌状态 -->
<view v-if="status === 'unbound'" class="pay-unbound">
<image src="/static/ic_my_payment_01.png" class="pay-empty-img" />
<view class="pay-unbound-tip">你还未绑定车牌哦~</view>
<view class="pay-unbound-link" @click="showBindDialog = true">现在去绑定</view>
</view>
<!-- 已绑定车牌状态 -->
<view v-else class="pay-binded">
<view class="pay-car-card">
<view class="pay-car-row">
<image src="/static/ic_my_payment_02.png" class="pay-car-icon" />
<view class="pay-car-num">{{ carInfo.number }}</view>
<view class="pay-car-status">{{ carInfo.status }}</view>
</view>
<view class="pay-car-addr">{{ carInfo.addr }}</view>
<view class="pay-car-info">
<view class="pay-car-time">
<view>停放时长</view>
<view class="pay-car-info-val">{{ carInfo.duration }}</view>
</view>
<view class="pay-car-fee">
<view>预计费用</view>
<view class="pay-car-fee-val">{{ carInfo.fee }}</view>
</view>
</view>
<button class="pay-car-btn">立即缴费</button>
</view>
<view class="pay-list-entry">
<view class="pay-list-item">缴费标准
<image class="mine-list-arrow" src="/static/ic_arrow_gray.webp" />
</view>
<view class="pay-list-item" @click="goToRecords">缴费记录
<image class="mine-list-arrow" src="/static/ic_arrow_gray.webp"/>
</view>
</view>
<button class="pay-add-btn">新增停车</button>
</view>
<!-- 绑定车牌弹窗 -->
<view v-if="showBindDialog" class="pay-dialog-mask" @click.self="showBindDialog = false">
<view class="pay-dialog-box">
<view class="pay-dialog-title-row">
<view class="pay-dialog-title">输入车牌号</view>
<image src="/static/ic_close_01.png" class="pay-dialog-close" @click="showBindDialog = false" />
</view>
<view class="pay-dialog-input-row">
<!-- 省份简称输入框 -->
<input class="pay-dialog-input province-input" :value="carNumArr[0]" readonly
:focus="focusIndex === -1" @click="showProvinceKeyboard = true; focusIndex = -1" />
<!-- 后6位 -->
<input v-for="(item, idx) in 6" :key="idx" maxlength="1" class="pay-dialog-input"
v-model="carNumArr[idx+1]" :focus="focusIndex === idx" @input="onCarInput($event, idx)"
@keydown.native="onCarKeydown($event, idx)" @paste="onCarPaste($event)" />
</view>
<button class="pay-dialog-btn" @click="bindCar">保存</button>
</view>
</view>
<!-- 省份简称自定义键盘弹窗 -->
<view v-if="showProvinceKeyboard" class="province-keyboard-mask" @click.self="showProvinceKeyboard = false">
<view class="province-keyboard-box">
<view class="province-keyboard-title">选择省份简称</view>
<view class="province-keyboard-grid">
<view v-for="(item, idx) in provinceList" :key="idx" class="province-keyboard-btn"
@click="selectProvince(item)">{{ item }}</view>
</view>
<button class="province-keyboard-cancel" @click="showProvinceKeyboard = false">取消</button>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
status: 'unbound', // unbound, binded
showBindDialog: false,
carNumArr: ['渝', '', '', '', '', '', ''],
showProvinceKeyboard: false,
focusIndex: 0,
isBackspace: false,
// 假数据
carInfo: {
number: '渝A-65891',
status: '停放中',
addr: '服务中心1栋A区五层-018号',
duration: '01:25:33',
fee: 12
},
provinceList: [
'京', '津', '沪', '渝', '冀', '豫', '云', '辽', '黑', '湘',
'皖', '鲁', '新', '苏', '浙', '赣', '鄂', '桂', '甘', '晋',
'蒙', '陕', '吉', '闽', '贵', '粤', '青', '藏', '川', '宁', '琼'
]
}
},
methods: {
bindCar() {
this.showBindDialog = false;
this.status = 'binded';
},
goBack() {
uni.navigateBack();
},
onCarInput(e, idx) {
let val = e.detail.value.toUpperCase().replace(/[^A-Z0-9]/g, '');
this.$set(this.carNumArr, idx + 1, val);
// 只在输入时自动跳到下一个,避免键盘频繁弹出
if (val && idx < 5) {
this.focusIndex = idx + 1;
}
},
onCarKeydown(e, idx) {
// 记录是否是删除键,但删除时不自动回退聚焦
this.isBackspace = (e.key === 'Backspace' || e.keyCode === 8);
},
onCarPaste(e) {
let paste = (e.clipboardData || window.clipboardData).getData('text').toUpperCase().replace(/[^A-Z0-9]/g,
'');
if (paste.length > 0) {
for (let i = 0; i < 6; i++) {
this.$set(this.carNumArr, i + 1, paste[i] || '');
}
this.focusIndex = 5;
}
e.preventDefault();
},
selectProvince(item) {
this.$set(this.carNumArr, 0, item);
this.showProvinceKeyboard = false;
// 自动聚焦第二位
this.focusIndex = 0;
},
goToRecords() {
uni.navigateTo({
url: '/pages/sys/user/myPayment/paymentRecords'
});
}
}
}
</script>
<style scoped>
.page-wrapper {
height: 100vh;
display: flex;
flex-direction: column;
}
.pay-navbar {
width: 100%;
height: 160rpx;
padding-top: 70rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
background: #fff;
flex-shrink: 0;
}
.pay-back {
position: absolute;
left: 37rpx;
width: 15rpx;
height: 33rpx;
}
.pay-title {
font-size: 36rpx;
color: #000;
}
.pay-container {
background: #F7F8FA;
padding-bottom: 120rpx;
display: flex;
flex-direction: column;
flex: 1;
overflow-y: auto;
}
.pay-unbound {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 160rpx;
}
.pay-empty-img {
width: 320rpx;
height: 180rpx;
margin-bottom: 40rpx;
}
.pay-unbound-tip {
font-size: 30rpx;
color: #222;
margin-bottom: 18rpx;
}
.pay-unbound-link {
color: #0090FF;
font-size: 30rpx;
font-weight: bold;
margin-top: 10rpx;
}
.pay-binded {
margin-top: 80rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.pay-car-card {
position: relative;
width: 80vw;
background: #fff;
border-radius: 20rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
padding: 56rpx 40rpx 30rpx 40rpx;
margin-bottom: 40rpx;
}
.pay-car-row {
display: flex;
align-items: center;
margin-bottom: 54rpx;
}
.pay-car-icon {
width: 43rpx;
height: 35rpx;
margin-right: 18rpx;
}
.pay-car-num {
font-size: 32rpx;
font-weight: 600;
color: #000;
margin-right: 18rpx;
}
.pay-car-status {
font-size: 24rpx;
color: #FEFEFE;
background: linear-gradient(90deg, #05C58C 0%, #73FFC9 100%);
border-radius: 12rpx;
padding: 8rpx 20rpx;
margin-left: auto;
}
.pay-car-addr {
position: absolute;
top: 94rpx;
left: 98rpx;
font-size: 24rpx;
color: #878787;
}
.pay-car-info {
position: relative;
width: 70vw;
height: 113rpx;
display: flex;
align-items: center;
margin-bottom: 54rpx;
background: #E9F4FF;
border: 1rpx solid #2E93FF;
border-radius: 10rpx;
margin-left: auto;
margin-right: auto;
}
.pay-car-time {
position: absolute;
top: 16rpx;
left: 33rpx;
align-items: center;
display: flex;
flex-direction: column;
align-items: center;
}
.pay-car-info-val {
font-size: 28rpx;
color: #222;
font-weight: bold;
margin-top: 8rpx;
}
.pay-car-fee {
position: absolute;
top: 16rpx;
right: 58rpx;
align-items: center;
display: flex;
flex-direction: column;
align-items: center;
}
.pay-car-fee-val {
color: #F3831F;
font-size: 28rpx;
font-weight: bold;
margin-top: 8rpx;
}
.pay-car-btn {
width: 283rpx;
height: 57rpx;
background: linear-gradient(90deg, #005DE9 0%, #4B9BFF 100%);
color: #fff;
font-size: 28rpx;
border-radius: 15rpx;
font-weight: 600;
text-align: center;
/* 新增flex居中 */
display: flex;
align-items: center;
justify-content: center;
}
.pay-list-entry {
width: 92vw;
}
.pay-list-item {
background: #fff;
border-radius: 12rpx;
font-size: 28rpx;
color: #222;
padding: 28rpx 32rpx;
margin-bottom: 18rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.pay-list-arrow {
color: #bbb;
font-size: 32rpx;
}
.pay-add-btn {
width: 90vw;
height: 80rpx;
background: #0090FF;
color: #fff;
font-size: 32rpx;
border: none;
border-radius: 40rpx;
position: fixed;
left: 0;
right: 0;
bottom: 200rpx;
margin: 0 auto;
display: block;
font-weight: bold;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.18);
z-index: 100;
}
.pay-dialog-mask {
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.5);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
.pay-dialog-box {
width: 70vw;
background: #fff;
border-radius: 28rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.12);
padding: 48rpx 36rpx 36rpx 36rpx;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
.pay-dialog-title-row {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 80rpx;
}
.pay-dialog-title {
font-size: 36rpx;
color: #000;
}
.pay-dialog-close {
position: absolute;
right: 0;
top: 0;
width: 32rpx;
height: 32rpx;
}
.pay-dialog-input-row {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 80rpx;
gap: 28rpx;
}
.pay-dialog-input {
width: 43rpx;
height: 53rpx;
background: #EEEEEE;
border-radius: 5rpx;
font-size: 32rpx;
color: #222;
text-align: center;
border: none;
outline: none;
}
.pay-dialog-btn {
width: 320rpx;
height: 70rpx;
margin-bottom: 40rpx;
background: linear-gradient(90deg, #0090FF 0%, #2E9FFF 100%);
color: #fff;
font-size: 28rpx;
border: none;
border-radius: 35rpx;
display: block;
font-weight: bold;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.12);
}
.province-input {
background: #EEEEEE;
border-radius: 5rpx;
width: 43rpx;
height: 53rpx;
margin-right: 28rpx;
text-align: center;
font-size: 32rpx;
color: #222;
border: none;
}
.province-keyboard-mask {
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.4);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
.province-keyboard-box {
width: 600rpx;
background: #fff;
border-radius: 28rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.12);
padding: 48rpx 36rpx 36rpx 36rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.province-keyboard-title {
font-size: 32rpx;
color: #222;
font-weight: bold;
margin-bottom: 32rpx;
}
.province-keyboard-grid {
display: flex;
flex-wrap: wrap;
gap: 18rpx;
justify-content: center;
margin-bottom: 32rpx;
}
.province-keyboard-btn {
width: 80rpx;
height: 60rpx;
background: #F7F8FA;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: #222;
margin-bottom: 8rpx;
cursor: pointer;
border: 1rpx solid #eee;
transition: background 0.2s;
}
.province-keyboard-btn:active {
background: #e6f0ff;
}
.province-keyboard-cancel {
width: 320rpx;
height: 60rpx;
background: #eee;
color: #222;
font-size: 28rpx;
border: none;
border-radius: 30rpx;
margin-top: 10rpx;
}
.mine-list-arrow {
width: 18rpx;
height: 28rpx;
margin-right: 24rpx;
}
</style>

View File

@@ -0,0 +1,127 @@
<template>
<view class="page-wrapper">
<!-- 可滚动内容区 -->
<view class="scroll-content">
<!-- 列表区 -->
<view class="records-list">
<view v-for="(item, idx) in records" :key="idx" class="records-card">
<view class="records-row">
<view class="records-car">{{ item.car }}</view>
<view class="records-fee">{{ item.fee }}</view>
</view>
<view class="records-time">{{ item.time }}<text v-if="idx === 0">{{ item.time }}</text></view>
</view>
</view>
<!-- 底部客服 -->
<view class="records-service">
<image src="/static/ic_payment_record_01.png" class="records-service-icon" />
<text class="records-service-text">有疑问请联系客服</text>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
records: [
{ car: '渝A·B8889', fee: '11.84', time: '2025-07-04 15:23:45' },
{ car: '渝A·B8889', fee: '11.84', time: '2025-07-04 15:23:45' },
{ car: '渝A·B8889', fee: '11.84', time: '2025-07-04 15:23:45' },
{ car: '渝A·B8889', fee: '11.84', time: '2025-07-04 15:23:45' },
{ car: '渝A·B8889', fee: '11.84', time: '2025-07-04 15:23:45' },
{ car: '渝A·B8889', fee: '11.84', time: '2025-07-04 15:23:45' }
]
}
},
methods: {
}
}
</script>
<style scoped>
.page-wrapper {
height: 100vh;
display: flex;
flex-direction: column;
}
.scroll-content {
flex: 1;
overflow-y: auto;
background: #f7f7f7;
padding-bottom: 40rpx;
}
.records-navbar {
width: 100%;
height: 120rpx;
padding-top: 40rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
background: #fff;
flex-shrink: 0;
}
.records-back {
position: absolute;
left: 37rpx;
width: 15rpx;
height: 33rpx;
}
.records-title {
font-size: 36rpx;
color: #000;
font-weight: 500;
}
.records-list {
margin: 32rpx 0 0 0;
padding: 40rpx 24rpx;
}
.records-card {
background: #fff;
border-radius: 12rpx;
margin-bottom: 24rpx;
padding: 32rpx 32rpx 24rpx 32rpx;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.03);
border: none;
}
.records-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12rpx;
}
.records-car {
font-size: 32rpx;
font-weight: bold;
color: #222;
}
.records-fee {
font-size: 28rpx;
color: #222;
font-weight: 500;
}
.records-time {
font-size: 24rpx;
color: #888;
margin-top: 2rpx;
}
.records-service {
margin-top: 60rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.records-service-icon {
width: 26rpx;
height: 29rpx;
margin-right: 6rpx;
}
.records-service-text {
color: #2186FF;
font-size: 24rpx;
}
</style>

View File

@@ -0,0 +1,507 @@
<template>
<view class="my-record-container">
<!-- 顶部导航栏 -->
<view class="header">
<image class="back-btn" src="/static/ic_back.png" @click="goBack" />
<text class="page-title">我的考勤</text>
</view>
<!-- 月份标题和切换 -->
<view class="month-header">
<view class="month-nav">
<view class="month-arrow" @click="prevMonth">
<image class="arrow-icon" src="/static/ic_arrow_gray.webp" mode="aspectFit" style="transform: rotate(180deg)" />
</view>
<text class="month-title">{{ currentYear }}{{ currentMonth }}</text>
<view class="month-arrow" @click="nextMonth">
<image class="arrow-icon" src="/static/ic_arrow_gray.webp" mode="aspectFit" />
</view>
</view>
</view>
<!-- 考勤统计 -->
<view class="attendance-stats">
<view class="stat-item">
<text class="stat-value">17</text>
<text class="stat-label">出勤天数</text>
</view>
<view class="stat-item">
<text class="stat-value orange">2</text>
<text class="stat-label">迟到</text>
</view>
<view class="stat-item">
<text class="stat-value orange">1</text>
<text class="stat-label">早退</text>
</view>
<view class="stat-item">
<text class="stat-value">0</text>
<text class="stat-label">补卡</text>
</view>
</view>
<!-- 日历 -->
<view class="calendar">
<view class="weekdays">
<text v-for="day in weekdays" :key="day" class="weekday">{{ day }}</text>
</view>
<view class="dates" v-if="calendarExpanded">
<view
v-for="(date, index) in allDates"
:key="index"
:class="getDateClass(date)"
@click="selectDate(date)"
>
<text v-if="date.value" class="date-text">{{ date.value }}</text>
<view v-if="date.hasRecord" class="record-dot"></view>
</view>
</view>
<view class="dates" v-else>
<view
v-for="(date, index) in currentWeekDates"
:key="index"
:class="getDateClass(date)"
@click="selectDate(date)"
>
<text v-if="date.value" class="date-text">{{ date.value }}</text>
<view v-if="date.hasRecord" class="record-dot"></view>
</view>
</view>
<view class="calendar-toggle" @click="toggleCalendar">
<text class="toggle-text">{{ calendarExpanded ? '收起' : '展开' }}</text>
</view>
</view>
<!-- 固定班次 -->
<view class="fixed-shifts">
<text class="shifts-title">固定班次</text>
<view class="shift-item">
<view class="shift-time">
<view class="time-dot"></view>
<text class="time-text">应上班 09:00</text>
</view>
<view class="record leave-record">
<text class="record-text">已请假 09:0012:00</text>
</view>
</view>
<view class="shift-item">
<view class="shift-time">
<view class="time-dot"></view>
<text class="time-text">应下班 18:00</text>
</view>
<view class="record clock-record">
<text class="record-text">已打卡 18:02:18</text>
<text class="location">@某综合服务中心1栋</text>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 我的考勤页面
* @author lyc
* @description 显示用户考勤记录,包含可展开收缩的日历
*/
export default {
data() {
return {
// 星期标题
weekdays: ['日', '一', '二', '三', '四', '五', '六'],
// 日历是否展开
calendarExpanded: false,
// 当前选中的日期
selectedDate: 8,
// 当前年份
currentYear: 2025,
// 当前月份
currentMonth: 7,
// 完整月份日期数据
allDates: []
};
},
computed: {
// 当前周的日期(收缩状态显示)
currentWeekDates() {
// 找到选中日期所在的周
const selectedIndex = this.allDates.findIndex(date => date.selected);
if (selectedIndex === -1) return this.allDates.slice(0, 7);
const startOfWeek = Math.floor(selectedIndex / 7) * 7;
return this.allDates.slice(startOfWeek, startOfWeek + 7);
}
},
created() {
// 初始化日历数据
this.generateCalendarDates();
},
methods: {
/**
* 返回上一页
*/
goBack() {
uni.navigateBack();
},
/**
* 切换日历展开/收缩状态
*/
toggleCalendar() {
this.calendarExpanded = !this.calendarExpanded;
},
/**
* 选择日期
* @param {Object} date 日期对象
*/
selectDate(date) {
if (!date.value) return;
// 清除之前选中的日期
this.allDates.forEach(d => d.selected = false);
// 设置新选中的日期
date.selected = true;
this.selectedDate = date.value;
},
/**
* 获取日期样式类名
* @param {Object} date 日期对象
* @returns {Object} 样式类名对象
*/
getDateClass(date) {
return {
'date-item': true,
'selected': date.selected,
'has-record': date.hasRecord,
'empty': !date.value
};
},
/**
* 切换到上一个月
*/
prevMonth() {
if (this.currentMonth === 1) {
this.currentYear--;
this.currentMonth = 12;
} else {
this.currentMonth--;
}
this.generateCalendarDates();
},
/**
* 切换到下一个月
*/
nextMonth() {
if (this.currentMonth === 12) {
this.currentYear++;
this.currentMonth = 1;
} else {
this.currentMonth++;
}
this.generateCalendarDates();
},
/**
* 生成日历数据
*/
generateCalendarDates() {
const year = this.currentYear;
const month = this.currentMonth;
// 获取当月第一天是星期几
const firstDay = new Date(year, month - 1, 1).getDay();
// 获取当月总天数
const daysInMonth = new Date(year, month, 0).getDate();
// 清空日历数据
this.allDates = [];
// 添加上月的空白日期
for (let i = 0; i < firstDay; i++) {
this.allDates.push({ value: null });
}
// 添加当月日期
for (let i = 1; i <= daysInMonth; i++) {
// 模拟考勤记录数据这里假设前20天有考勤记录
const hasRecord = i <= 20;
// 默认选中当月8号
const selected = i === 8 && month === 7 && year === 2025;
this.allDates.push({
value: i,
hasRecord,
selected
});
}
// 计算需要添加的下月空白日期数量使总数为7的倍数
const remainingDays = 7 - (this.allDates.length % 7);
if (remainingDays < 7) {
for (let i = 0; i < remainingDays; i++) {
this.allDates.push({ value: null });
}
}
}
}
};
</script>
<style scoped>
.my-record-container {
min-height: 100vh;
background-color: #f5f5f5;
}
/* 顶部导航栏 */
.header {
display: flex;
align-items: center;
padding: 20rpx 30rpx;
background-color: #fff;
position: relative;
}
.back-btn {
width: 40rpx;
height: 40rpx;
margin-right: 20rpx;
}
.page-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
position: absolute;
left: 50%;
transform: translateX(-50%);
}
/* 月份标题和导航 */
.month-header {
padding: 20rpx 30rpx;
background-color: #fff;
}
.month-nav {
display: flex;
align-items: center;
justify-content: space-between;
}
.month-arrow {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.arrow-icon {
width: 32rpx;
height: 32rpx;
}
.month-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
/* 考勤统计 */
.attendance-stats {
display: flex;
justify-content: space-around;
padding: 40rpx 30rpx;
margin: 20rpx 30rpx;
background-color: #fff;
border-radius: 16rpx;
border: 2rpx dashed #e0e0e0;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
}
.stat-value {
font-size: 48rpx;
font-weight: bold;
color: #333;
margin-bottom: 8rpx;
}
.stat-value.orange {
color: #ff6b35;
}
.stat-label {
font-size: 24rpx;
color: #666;
}
/* 日历 */
.calendar {
margin: 20rpx 30rpx;
background-color: #fff;
border-radius: 16rpx;
border: 2rpx dashed #e0e0e0;
overflow: hidden;
}
.weekdays {
display: flex;
background-color: #f8f8f8;
padding: 20rpx 0;
}
.weekday {
flex: 1;
text-align: center;
font-size: 28rpx;
color: #666;
}
.dates {
display: flex;
flex-wrap: wrap;
padding: 20rpx 0;
}
.date-item {
width: 14.28%;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 20rpx;
}
.date-item.empty {
color: #ccc;
}
.date-item.selected {
background-color: #007aff;
border-radius: 50%;
width: 60rpx;
height: 60rpx;
margin: 10rpx auto;
}
.date-item.selected .date-text {
color: #fff;
}
.date-text {
font-size: 28rpx;
color: #333;
}
.record-dot {
position: absolute;
bottom: 8rpx;
left: 50%;
transform: translateX(-50%);
width: 8rpx;
height: 8rpx;
background-color: #007aff;
border-radius: 50%;
}
.date-item.selected .record-dot {
background-color: #fff;
}
.calendar-toggle {
padding: 20rpx;
text-align: center;
border-top: 1rpx solid #e0e0e0;
background-color: #f8f8f8;
}
.toggle-text {
font-size: 28rpx;
color: #666;
}
/* 固定班次 */
.fixed-shifts {
margin: 20rpx 30rpx;
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
}
.shifts-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 30rpx;
}
.shift-item {
margin-bottom: 40rpx;
}
.shift-item:last-child {
margin-bottom: 0;
}
.shift-time {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.time-dot {
width: 12rpx;
height: 12rpx;
background-color: #ff6b35;
border-radius: 50%;
margin-right: 16rpx;
}
.time-text {
font-size: 28rpx;
color: #333;
}
.record {
border: 2rpx dashed #e0e0e0;
border-radius: 12rpx;
padding: 24rpx;
margin-left: 28rpx;
}
.leave-record {
background-color: #fff7f0;
border-color: #ffb366;
}
.clock-record {
background-color: #f0f9ff;
border-color: #66b3ff;
}
.record-text {
font-size: 28rpx;
color: #333;
margin-bottom: 8rpx;
}
.location {
font-size: 24rpx;
color: #007aff;
display: block;
}
</style>

View File

@@ -0,0 +1,405 @@
<template>
<view class="my-record-container">
<!-- 顶部导航栏 -->
<view class="header">
<image class="back-btn" src="/static/ic_back.png" @click="goBack" />
<text class="page-title">我的考勤</text>
</view>
<!-- 月份标题 -->
<view class="month-header">
<text class="month-title">2025年7月</text>
</view>
<!-- 考勤统计 -->
<view class="attendance-stats">
<view class="stat-item">
<text class="stat-value">17</text>
<text class="stat-label">出勤天数</text>
</view>
<view class="stat-item">
<text class="stat-value orange">2</text>
<text class="stat-label">迟到</text>
</view>
<view class="stat-item">
<text class="stat-value orange">1</text>
<text class="stat-label">早退</text>
</view>
<view class="stat-item">
<text class="stat-value">0</text>
<text class="stat-label">补卡</text>
</view>
</view>
<!-- 日历 -->
<view class="calendar">
<view class="weekdays">
<text v-for="day in weekdays" :key="day" class="weekday">{{ day }}</text>
</view>
<view class="dates" v-if="calendarExpanded">
<view
v-for="(date, index) in allDates"
:key="index"
:class="getDateClass(date)"
@click="selectDate(date)"
>
<text v-if="date.value" class="date-text">{{ date.value }}</text>
<view v-if="date.hasRecord" class="record-dot"></view>
</view>
</view>
<view class="dates" v-else>
<view
v-for="(date, index) in currentWeekDates"
:key="index"
:class="getDateClass(date)"
@click="selectDate(date)"
>
<text v-if="date.value" class="date-text">{{ date.value }}</text>
<view v-if="date.hasRecord" class="record-dot"></view>
</view>
</view>
<view class="calendar-toggle" @click="toggleCalendar">
<text class="toggle-text">{{ calendarExpanded ? '收起' : '展开' }}</text>
</view>
</view>
<!-- 固定班次 -->
<view class="fixed-shifts">
<text class="shifts-title">固定班次</text>
<view class="shift-item">
<view class="shift-time">
<view class="time-dot"></view>
<text class="time-text">应上班 09:00</text>
</view>
<view class="record leave-record">
<text class="record-text">已请假 09:0012:00</text>
</view>
</view>
<view class="shift-item">
<view class="shift-time">
<view class="time-dot"></view>
<text class="time-text">应下班 18:00</text>
</view>
<view class="record clock-record">
<text class="record-text">已打卡 18:02:18</text>
<text class="location">@某综合服务中心1栋</text>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 我的考勤页面
* @author lyc
* @description 显示用户考勤记录,包含可展开收缩的日历
*/
export default {
data() {
return {
// 星期标题
weekdays: ['日', '一', '二', '三', '四', '五', '六'],
// 日历是否展开
calendarExpanded: false,
// 当前选中的日期
selectedDate: 8,
// 完整月份日期数据
allDates: [
{ value: null }, { value: null }, { value: 1, hasRecord: true }, { value: 2, hasRecord: true }, { value: 3, hasRecord: true }, { value: 4, hasRecord: true }, { value: 5, hasRecord: true },
{ value: 6, hasRecord: true }, { value: 7, hasRecord: true }, { value: 8, hasRecord: true, selected: true }, { value: 9, hasRecord: true }, { value: 10, hasRecord: true }, { value: 11, hasRecord: true }, { value: 12, hasRecord: true },
{ value: 13, hasRecord: true }, { value: 14, hasRecord: true }, { value: 15, hasRecord: true }, { value: 16, hasRecord: true }, { value: 17 }, { value: 18 }, { value: 19 },
{ value: 20 }, { value: 21 }, { value: 22 }, { value: 23 }, { value: 24 }, { value: 25 }, { value: 26 },
{ value: 27 }, { value: 28 }, { value: 29 }, { value: 30 }, { value: 31 }, { value: null }, { value: null }
]
};
},
computed: {
// 当前周的日期(收缩状态显示)
currentWeekDates() {
// 找到选中日期所在的周
const selectedIndex = this.allDates.findIndex(date => date.selected);
const startOfWeek = Math.floor(selectedIndex / 7) * 7;
return this.allDates.slice(startOfWeek, startOfWeek + 7);
}
},
methods: {
/**
* 返回上一页
*/
goBack() {
uni.navigateBack();
},
/**
* 切换日历展开/收缩状态
*/
toggleCalendar() {
this.calendarExpanded = !this.calendarExpanded;
},
/**
* 选择日期
* @param {Object} date 日期对象
*/
selectDate(date) {
if (!date.value) return;
// 清除之前选中的日期
this.allDates.forEach(d => d.selected = false);
// 设置新选中的日期
date.selected = true;
this.selectedDate = date.value;
},
/**
* 获取日期样式类名
* @param {Object} date 日期对象
* @returns {Object} 样式类名对象
*/
getDateClass(date) {
return {
'date-item': true,
'selected': date.selected,
'has-record': date.hasRecord,
'empty': !date.value
};
}
}
};
</script>
<style scoped>
.my-record-container {
min-height: 100vh;
background-color: #f5f5f5;
}
/* 顶部导航栏 */
.header {
display: flex;
align-items: center;
padding: 20rpx 30rpx;
background-color: #fff;
position: relative;
}
.back-btn {
width: 40rpx;
height: 40rpx;
margin-right: 20rpx;
}
.page-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
position: absolute;
left: 50%;
transform: translateX(-50%);
}
/* 月份标题 */
.month-header {
padding: 30rpx;
background-color: #fff;
}
.month-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
/* 考勤统计 */
.attendance-stats {
display: flex;
justify-content: space-around;
padding: 40rpx 30rpx;
margin: 20rpx 30rpx;
background-color: #fff;
border-radius: 16rpx;
border: 2rpx dashed #e0e0e0;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
}
.stat-value {
font-size: 48rpx;
font-weight: bold;
color: #333;
margin-bottom: 8rpx;
}
.stat-value.orange {
color: #ff6b35;
}
.stat-label {
font-size: 24rpx;
color: #666;
}
/* 日历 */
.calendar {
margin: 20rpx 30rpx;
background-color: #fff;
border-radius: 16rpx;
border: 2rpx dashed #e0e0e0;
overflow: hidden;
}
.weekdays {
display: flex;
background-color: #f8f8f8;
padding: 20rpx 0;
}
.weekday {
flex: 1;
text-align: center;
font-size: 28rpx;
color: #666;
}
.dates {
display: flex;
flex-wrap: wrap;
padding: 20rpx 0;
}
.date-item {
width: 14.28%;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 20rpx;
}
.date-item.empty {
color: #ccc;
}
.date-item.selected {
background-color: #007aff;
border-radius: 50%;
width: 60rpx;
height: 60rpx;
margin: 10rpx auto;
}
.date-item.selected .date-text {
color: #fff;
}
.date-text {
font-size: 28rpx;
color: #333;
}
.record-dot {
position: absolute;
bottom: 8rpx;
left: 50%;
transform: translateX(-50%);
width: 8rpx;
height: 8rpx;
background-color: #007aff;
border-radius: 50%;
}
.date-item.selected .record-dot {
background-color: #fff;
}
.calendar-toggle {
padding: 20rpx;
text-align: center;
border-top: 1rpx solid #e0e0e0;
background-color: #f8f8f8;
}
.toggle-text {
font-size: 28rpx;
color: #666;
}
/* 固定班次 */
.fixed-shifts {
margin: 20rpx 30rpx;
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
}
.shifts-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 30rpx;
}
.shift-item {
margin-bottom: 40rpx;
}
.shift-item:last-child {
margin-bottom: 0;
}
.shift-time {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.time-dot {
width: 12rpx;
height: 12rpx;
background-color: #ff6b35;
border-radius: 50%;
margin-right: 16rpx;
}
.time-text {
font-size: 28rpx;
color: #333;
}
.record {
border: 2rpx dashed #e0e0e0;
border-radius: 12rpx;
padding: 24rpx;
margin-left: 28rpx;
}
.leave-record {
background-color: #fff7f0;
border-color: #ffb366;
}
.clock-record {
background-color: #f0f9ff;
border-color: #66b3ff;
}
.record-text {
font-size: 28rpx;
color: #333;
margin-bottom: 8rpx;
}
.location {
font-size: 24rpx;
color: #007aff;
display: block;
}
</style>

View File

@@ -0,0 +1,299 @@
<template>
<view class="add-repair-container">
<!-- 可滚动内容区 -->
<view class="add-repair-scroll-content">
<!-- 地址选择 -->
<view class="add-repair-section">
<view class="add-repair-address-btn">
<view class="add-repair-address-text">请选择房屋所在地址</view>
<image class="add-repair-address-img" src="/static/ic_add_repair_01.png" />
</view>
<view class="add-repair-detail">
<view class="add-repair-detail1">详细地址 </view>
<view class="add-repair-detail2">例1栋2单元101室1</view>
</view>
</view>
<!-- 报事报修类型 -->
<view class="add-repair-section2">
<view class="add-repair-label">报事报修</view>
<view class="add-repair-type-list">
<view v-for="(item, idx) in repairTypes" :key="idx"
:class="['add-repair-type-btn', selectedType === item ? 'selected' : '']"
@click="selectType(item)">{{ item }}</view>
</view>
<view class="add-repair-label2">其他服务</view>
<view class="add-repair-type-list">
<view v-for="(item, idx) in otherTypes" :key="idx" class="add-repair-type-btn">{{ item }}</view>
</view>
</view>
<!-- 问题详情 -->
<view class="add-repair-section">
<view class="add-repair-label">问题详情 <text class="add-repair-optional">(非必填)</text></view>
<textarea class="add-repair-detail-textarea" placeholder="如果以上报事不能解决您的问题,可以在这里填写说明" />
</view>
<!-- 上传照片 -->
<view class="add-repair-section">
<view class="add-repair-label">上传照片 <text class="add-repair-optional">(非必填最多三张)</text></view>
<view class="add-repair-upload-list">
<!-- 修改部分添加点击事件和图片预览 -->
<view
class="add-repair-upload-box"
@click="handleImageUpload"
>
<view class="add-repair-upload-plus">+</view>
<view class="add-repair-upload-text">上传图片</view>
</view>
<!-- 显示已选图片 -->
<view v-for="(image, index) in selectedImages" :key="index" class="add-repair-upload-box">
<image :src="image.path" class="add-repair-upload-image" @click.stop="previewImage(image.path)" />
</view>
</view>
</view>
<!-- 提交按钮 -->
<button class="add-repair-submit-btn" @click="goRepaired">提交</button>
</view>
</view>
</template>
<script>
// 导入MediaSelector和MediaType
import MediaSelector, { MediaType } from '@/utils/mediaSelector';
export default {
data() {
return {
repairTypes: ['安装挂件', '维修下水', '维修水阀', '疏通管道', '维修线路', '更换灯泡', '维修门窗', '其他维修'],
otherTypes: ['园区安保', '通水通电', '维修线路'],
selectedType: '更换灯泡',
selectedImages: [] // 存储已选图片
}
},
methods: {
selectType(item) {
this.selectedType = item;
},
// 新增:处理图片上传
async handleImageUpload() {
try {
// 调用MediaSelector选择图片最多选择3张
const images = await MediaSelector.choose({
type: MediaType.IMAGE,
count: 3 - this.selectedImages.length // 根据剩余数量选择
});
// 将选择的图片添加到selectedImages数组
this.selectedImages = [...this.selectedImages, ...images];
} catch (error) {
}
},
// 预览图片
previewImage(path) {
MediaSelector.preview(path, MediaType.IMAGE);
},
goRepaired(){
uni.navigateTo({ url: '/pages/mine/myRepair/repaired' });
}
}
}
</script>
<style scoped>
.add-repair-container {
height: 100vh;
background: #f7f7f7;
display: flex;
flex-direction: column;
}
.add-repair-back {
position: absolute;
left: 37rpx;
width: 15rpx;
height: 33rpx;
}
.add-repair-title {
font-size: 36rpx;
color: #000;
font-weight: 500;
}
.add-repair-scroll-content {
flex: 1;
overflow-y: auto;
padding-bottom: 40rpx;
}
.add-repair-section {
background: #fff;
border-radius: 12rpx;
margin: 22rpx 30rpx 0 30rpx;
padding: 24rpx 46rpx 24rpx 44rpx;
}
.add-repair-section2 {
background: #fff;
border-radius: 12rpx;
margin: 22rpx 30rpx 0 30rpx;
padding: 24rpx 26rpx 24rpx 26rpx;
}
.add-repair-address-btn {
width: 100%;
height: 48rpx;
background: #fff;
border: 2rpx solid #2186FF;
border-radius: 8rpx;
margin-bottom: 30rpx;
padding-left: 18rpx;
display: flex;
align-items: center;
justify-content: center;
}
.add-repair-address-text {
font-size: 24rpx;
color: #007CFF;
margin-right: 15rpx;
}
.add-repair-address-img {
width: 11rpx;
height: 21rpx;
}
.add-repair-detail {
display: flex;
flex-direction: row;
align-items: center;
}
.add-repair-detail1 {
font-size: 26rpx;
font-weight: bold;
color: #000;
}
.add-repair-detail2 {
font-size: 24rpx;
color: #676767;
margin-left: 39rpx;
}
.add-repair-label {
font-size: 32rpx;
color: #000000;
font-weight: 500;
margin-bottom: 41rpx;
}
.add-repair-label2 {
font-size: 32rpx;
color: #000000;
font-weight: 500;
margin-bottom: 25rpx;
margin-top: 25rpx;
}
.add-repair-type-list {
display: flex;
flex-wrap: wrap;
gap: 28rpx 21rpx;
}
.add-repair-type-btn {
flex: 0 0 calc((100% - 42rpx) / 3);
box-sizing: border-box;
height: 73rpx;
background: #F2F7FF;
color: #2186FF;
font-size: 26rpx;
border-radius: 8rpx;
display: flex;
align-items: center;
justify-content: center;
border: none;
}
.add-repair-type-btn.selected {
background: #2186FF;
color: #fff;
border: 2rpx solid #2186FF;
}
.add-repair-type-btn.dashed {
border: 2rpx dashed #BDBDBD;
background: #fff;
color: #2186FF;
}
.add-repair-optional {
color: #888;
font-size: 24rpx;
font-weight: 400;
}
.add-repair-detail-textarea {
width: 75vw;
min-height: 120rpx;
background: #F7F8FA;
border-radius: 10rpx;
font-size: 28rpx;
color: #888;
padding: 18rpx;
margin-top: 18rpx;
}
.add-repair-upload-list {
display: flex;
gap: 18rpx;
margin-top: 18rpx;
}
.add-repair-upload-box {
width: 140rpx;
height: 140rpx;
border: 2rpx dashed #BDBDBD;
border-radius: 8rpx;
background: #fafbfc;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.add-repair-upload-plus {
font-size: 60rpx;
color: #BDBDBD;
margin-bottom: 8rpx;
}
.add-repair-upload-text {
font-size: 24rpx;
color: #888;
}
.add-repair-submit-btn {
width: 90vw;
height: 80rpx;
background: linear-gradient(90deg, #2186FF 0%, #4FC3F7 100%);
color: #fff;
font-size: 32rpx;
border: none;
border-radius: 40rpx;
margin: 48rpx auto 0 auto;
display: block;
font-weight: bold;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.18);
}
/* 新增图片预览样式 */
.add-repair-upload-image {
width: 100%;
height: 100%;
border-radius: 8rpx;
object-fit: cover;
}
</style>

View File

@@ -0,0 +1,451 @@
<template>
<view class="repair-container">
<!-- 可滚动内容区 -->
<scroll-view scroll-y class="repair-scroll-content" @scroll="handleScroll">
<!-- 空状态 -->
<view v-if="records.length === 0" class="repair-empty-box">
<view class="repair-empty-img-box">
<image src="/static/ic_my_repair_01.png" class="repair-empty-img" />
</view>
<view class="repair-add-btn-box">
<image src="/static/ic_my_repair_02.png" class="repair-add-btn" @click="addRepair" />
</view>
</view>
<!-- 有数据时 -->
<view v-else class="repair-list-box">
<view v-for="(item, idx) in records" :key="idx" class="repair-card" @click="showDetail(item)">
<view class="repair-row">
<view class="repair-no">工单号{{ item.no }}</view>
<view class="repair-status" :class="item.statusClass">{{ item.statusText }}</view>
</view>
<image class="repair-line-image" src="/static/ic_my_repair_03.png"/>
<view class="repair-info">建立时间{{ item.time }}</view>
<view class="repair-info">报事内容{{ item.content }}</view>
<view class="repair-info">报事位置{{ item.addr }}</view>
<view v-if="item.statusText === '已结束'" class="repair-eval-btn eval-btn-right">服务评价</view>
</view>
<!-- 悬浮新增按钮 -->
<image src="/static/ic_my_repair_02.png" :class="['repair-add-btn-fixed', { 'hide': isAddBtnHidden }]" @click="addRepair" />
</view>
</scroll-view>
<!-- 详情弹窗 -->
<view v-if="showDetailDialog" class="repair-detail-mask" @click.self="closeDetail">
<view class="repair-detail-dialog">
<view class="repair-detail-title">报事详情
<image src="/static/ic_close_01.png" class="repair-detail-close" @click.stop="closeDetail" />
</view>
<view class="repair-detail-progress-box">
<view class="repair-detail-progress">
<view v-for="(step, idx) in progressSteps" :key="idx" class="repair-detail-step">
<view :class="['repair-detail-dot', detailStep >= idx ? 'active' : '', (detailStep === idx && detailStatus !== '已结束') ? 'current' : '']"></view>
<view v-if="idx < progressSteps.length - 1" :class="['repair-detail-line', detailStep > idx ? 'active' : '']"></view>
</view>
</view>
<view class="repair-detail-progress-labels">
<view v-for="(step, idx) in progressSteps" :key="idx" class="repair-detail-label">{{ step }}</view>
</view>
</view>
<view class="repair-detail-info">建立时间{{ detailItem.time }}</view>
<view class="repair-detail-info">报事内容{{ detailItem.content }}</view>
<view class="repair-detail-info">报事位置{{ detailItem.addr }}</view>
<button v-if="detailItem.statusText === '已结束'" class="repair-detail-btn" @click="goTEvaluate">评价服务</button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
// 空数组可切换空状态
records: [{
no: '20250628147856687',
time: '2025-07-02 15:24:36',
content: '室内无气,未欠费',
addr: '68栋1单元8-9',
statusText: '待处理',
statusClass: 'pending'
},
{
no: '20250628147856687',
time: '2025-07-02 15:24:36',
content: '室内无气,未欠费',
addr: '68栋1单元8-9',
statusText: '已结束',
statusClass: 'done'
},
{
no: '20250628147856687',
time: '2025-07-02 15:24:36',
content: '室内无气,未欠费',
addr: '68栋1单元8-9',
statusText: '处理中',
statusClass: 'doing'
},
{
no: '20250628147856687',
time: '2025-07-02 15:24:36',
content: '室内无气,未欠费',
addr: '68栋1单元8-9',
statusText: '已结束',
statusClass: 'done'
},
{
no: '20250628147856687',
time: '2025-07-02 15:24:36',
content: '室内无气,未欠费',
addr: '68栋1单元8-9',
statusText: '已结束',
statusClass: 'done'
}
],
showDetailDialog: false,
detailItem: {},
detailStep: 0,
detailStatus: '',
progressSteps: ['创建报事', '待处理', '处理中', '已结束'],
lastScrollTop: 0,
isAddBtnHidden: false
}
},
methods: {
goBack() {
uni.navigateBack();
},
addRepair() {
uni.navigateTo({
url: '/pages/sys/user/myRepair/addRepair'
});
},
showDetail(item) {
this.detailItem = item;
// 进度映射
if(item.statusText === '待处理') { this.detailStep = 1; this.detailStatus = '待处理'; }
else if(item.statusText === '处理中') { this.detailStep = 2; this.detailStatus = '处理中'; }
else if(item.statusText === '已结束') { this.detailStep = 3; this.detailStatus = '已结束'; }
else { this.detailStep = 0; this.detailStatus = '创建报事'; }
this.showDetailDialog = true;
},
closeDetail() {
this.showDetailDialog = false;
},
goTEvaluate(){
uni.navigateTo({
url: '/pages/sys/user/myRepair/repairEvaluate'
});
},
handleScroll(e) {
const scrollTop = e.detail.scrollTop;
// 为了避免过于频繁的触发,可以设置一个阈值
if (Math.abs(scrollTop - this.lastScrollTop) < 20) {
return;
}
if (scrollTop > this.lastScrollTop && scrollTop > 50) {
// 向下滚动,隐藏按钮
this.isAddBtnHidden = true;
} else {
// 向上滚动,显示按钮
this.isAddBtnHidden = false;
}
this.lastScrollTop = scrollTop;
}
}
}
</script>
<style scoped>
.repair-container {
height: 100vh;
background: #f7f7f7;
display: flex;
flex-direction: column;
}
.repair-navbar {
width: 100%;
height: 120rpx;
padding-top: 40rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
background: #fff;
flex-shrink: 0; /* 防止被压缩 */
}
.repair-back {
position: absolute;
left: 37rpx;
width: 15rpx;
height: 33rpx;
}
.repair-title {
font-size: 36rpx;
color: #000;
font-weight: 500;
}
.repair-scroll-content {
flex: 1;
overflow-y: auto;
padding-bottom: 40rpx;
}
.repair-empty-box {
margin: 48rpx 24rpx 0 24rpx;
position: relative;
}
.repair-empty-img-box {
border: 2rpx dashed #BDBDBD;
border-radius: 8rpx;
background: #fafbfc;
width: 100%;
min-height: 220rpx;
display: flex;
align-items: center;
justify-content: center;
}
.repair-empty-img {
width: 90%;
max-width: 400rpx;
height: 180rpx;
object-fit: contain;
margin: 24rpx auto;
}
.repair-add-btn-box {
position: absolute;
right: 24rpx;
bottom: -40rpx;
}
.repair-add-btn {
width: 80rpx;
height: 80rpx;
border: 2rpx dashed #BDBDBD;
border-radius: 50%;
background: #fff;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
}
.repair-list-box {
margin: 32rpx 0 0 0;
padding: 0 24rpx;
position: relative;
}
.repair-card {
background: #fff;
border-radius: 12rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
padding-top: 25rpx;
padding-bottom: 32rpx;
}
.repair-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12rpx;
margin-top: 25rpx;
margin-left: 19rpx;
margin-right: 50rpx;
}
.repair-no {
font-size: 24rpx;
color: #0B0B0B;
font-weight: 500;
}
.repair-status {
font-size: 24rpx;
font-weight: 500;
}
.repair-line-image{
margin: left 29rpx ;
margin-right: 39rpx;
height: 2rpx;
margin-bottom: 29rpx;
}
.repair-status.pending {
color: #FF9800;
}
.repair-status.doing {
color: #05C58C;
}
.repair-status.done {
color: #BDBDBD;
}
.repair-info {
font-size: 24rpx;
color: #888;
margin-bottom: 30rpx;
margin-left: 47rpx;
}
.repair-eval-btn {
margin-top: -20rpx;
margin-bottom: 0;
margin-left: auto;
margin-right: 44rpx;
display: flex;
justify-content: flex-end;
align-items: center;
padding: 0 18rpx;
height: 44rpx;
line-height: 44rpx;
border-radius: 8rpx;
border: 2rpx solid #2186FF;
color: #2186FF;
font-size: 24rpx;
background: #fff;
font-weight: 500;
width: fit-content;
}
.repair-add-btn-fixed {
position: fixed;
right: 40rpx;
bottom: 80rpx;
width: 100rpx;
height: 100rpx;
z-index: 100;
transition: transform 0.3s ease;
transform: translateX(0);
}
.repair-add-btn-fixed.hide {
transform: translateX(200%);
}
.repair-detail-mask {
position: fixed;
left: 0; top: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.45);
z-index: 999;
display: flex;
align-items: flex-end;
justify-content: center;
}
.repair-detail-dialog {
width: 100vw;
background: #fff;
border-radius: 18rpx 18rpx 0 0;
box-shadow: 0 -2rpx 24rpx rgba(0,0,0,0.10);
padding: 52rpx 56rpx 69rpx 56rpx;
margin-bottom: 0;
}
.repair-detail-title {
font-size: 36rpx;
color: #000;
text-align: center;
position: relative;
margin-bottom: 48rpx;
}
.repair-detail-close {
width: 32rpx;
height: 32rpx;
position: absolute;
right: 0;
top: 0;
}
.repair-detail-progress-box {
height: 107rpx;
margin-bottom: 41rpx;
background: #F7F7F7;
border-radius: 10rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: stretch;
}
.repair-detail-progress {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 5rpx;
margin-left: 70rpx;
width: 100%;
}
.repair-detail-step {
display: flex;
align-items: center;
flex: 1;
position: relative;
}
.repair-detail-dot {
width: 22rpx;
height: 22rpx;
border-radius: 50%;
background: #BDBDBD;
border: 2rpx solid #BDBDBD;
position: relative;
z-index: 2;
}
.repair-detail-dot.active {
background: #2186FF;
border-color: #2186FF;
}
.repair-detail-dot.current {
background: #EF8D00;
border-color: #EF8D00;
}
.repair-detail-line {
flex: 1;
height: 4rpx;
background: #BDBDBD;
margin: 0 2rpx;
z-index: 1;
}
.repair-detail-line.active {
background: #2186FF;
}
.repair-detail-progress-labels {
display: flex;
justify-content: space-between;
margin-left: 0;
width: 100%;
}
.repair-detail-label {
font-size: 22rpx;
color: #888;
text-align: center;
flex: 1;
position: relative;
top: 8rpx;
}
.repair-detail-info {
font-size: 26rpx;
color: #222;
margin-bottom: 30rpx;
}
.repair-detail-btn {
width:445rpx;
height: 73rpx;
background: linear-gradient(90deg, #005DE9 0%, #4B9BFF 100%);
color: #fff;
font-size: 32rpx;
border: none;
border-radius: 30rpx;
margin-top: 32rpx;
font-weight: 600;
margin-top: 100rpx;
}
</style>
/* 让服务评价按钮靠右 */
.eval-btn-right {
margin-left: auto;
display: block;
width: fit-content;
}

View File

@@ -0,0 +1,172 @@
<template>
<view class="evaluate-container">
<!-- 评分项 -->
<view class="evaluate-list">
<view class="evaluate-row">
<text class="evaluate-label">服务评价</text>
<view class="evaluate-stars">
<image v-for="i in 5" :key="i" :src="score >= i ? '/static/ic_evaluate_select.png' : '/static/ic_evaluate_disselect.png'" class="evaluate-star" @click="score = i" />
</view>
</view>
<view class="evaluate-desc">- 具体说说看 -</view>
<view class="evaluate-row">
<text class="evaluate-label">专业性</text>
<view class="evaluate-stars">
<image v-for="i in 5" :key="i" :src="proScore >= i ? '/static/ic_evaluate_select.png' : '/static/ic_evaluate_disselect.png'" class="evaluate-star" @click="proScore = i" />
</view>
</view>
<view class="evaluate-row">
<text class="evaluate-label">服务态度</text>
<view class="evaluate-stars">
<image v-for="i in 5" :key="i" :src="attitudeScore >= i ? '/static/ic_evaluate_select.png' : '/static/ic_evaluate_disselect.png'" class="evaluate-star" @click="attitudeScore = i" />
</view>
</view>
<view class="evaluate-row">
<text class="evaluate-label">时效性</text>
<view class="evaluate-stars">
<image v-for="i in 5" :key="i" :src="timelyScore >= i ? '/static/ic_evaluate_select.png' : '/static/ic_evaluate_disselect.png'" class="evaluate-star" @click="timelyScore = i" />
</view>
</view>
</view>
<!-- 标签选择 -->
<view class="evaluate-tag-title">- 选择或自定义任一标签 -</view>
<view class="evaluate-tags">
<view v-for="(tag, idx) in tags" :key="idx" :class="['evaluate-tag', selectedTags.includes(tag) ? 'selected' : '']" @click="toggleTag(tag)">{{ tag }}</view>
</view>
<!-- 输入框 -->
<textarea class="evaluate-textarea" placeholder="说点什么吧..." v-model="comment" />
<!-- 提交按钮 -->
<button class="evaluate-btn">提交</button>
</view>
</template>
<script>
export default {
data() {
return {
score: 4,
proScore: 5,
attitudeScore: 4,
timelyScore: 4,
tags: ['专业性强', '态度谦和', '声音甜美', '速度很快'],
selectedTags: ['专业性强', '态度谦和'],
comment: ''
}
},
methods: {
toggleTag(tag) {
if (this.selectedTags.includes(tag)) {
this.selectedTags = this.selectedTags.filter(t => t !== tag)
} else {
this.selectedTags.push(tag)
}
}
}
}
</script>
<style scoped>
.evaluate-container {
min-height: 100vh;
background: #fff;
padding-bottom: 40rpx;
/* 防止在iOS等设备上滑动到顶部或底部时出现回弹效果 */
overscroll-behavior-y: contain;
}
.evaluate-back {
position: absolute;
left: 37rpx;
width: 15rpx;
height: 33rpx;
}
.evaluate-title {
font-size: 30rpx;
color: #000;
}
.evaluate-list {
margin: 0 53rpx 0 40rpx;
}
.evaluate-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 38rpx;
}
.evaluate-label {
font-size: 30rpx;
color: #222;
width: 160rpx;
text-align: left;
}
.evaluate-stars {
display: flex;
align-items: center;
}
.evaluate-star {
width: 36rpx;
height: 34rpx;
margin-right: 36rpx;
}
.evaluate-desc {
color: #BDBDBD;
font-size: 24rpx;
text-align: center;
margin-top: 67rpx;
margin-bottom: 57rpx;
}
.evaluate-tag-title {
color: #BDBDBD;
font-size: 24rpx;
text-align: center;
margin: 60rpx 0 0 0;
}
.evaluate-tags {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
margin: 52rpx 25rpx 0 25rpx;
}
.evaluate-tag {
flex: 0 0 calc((100% - 60rpx) / 4);
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
height: 64rpx;
border-radius: 32rpx;
font-size: 28rpx;
color: #666666;
background: #F2F2F2;
}
.evaluate-tag.selected {
background: #0090FF;
color: #fff;
}
.evaluate-textarea {
width: 85vw;
min-height: 120rpx;
background: #F5F6F7;
border-radius: 10rpx;
font-size: 26rpx;
color: #B6C2CE;
padding: 23rpx 27rpx 23rpx 27rpx;
margin: 49rpx auto 0 auto;
display: block;
}
.evaluate-btn {
width: 80vw;
height: 88rpx;
background: #0090FF;
color: #fff;
font-size: 36rpx;
border: none;
border-radius: 44rpx;
margin: 137rpx auto 0 auto;
display: block;
font-weight: bold;
box-shadow: 0 8rpx 24rpx rgba(0,0,0,0.18);
}
</style>

View File

@@ -0,0 +1,107 @@
<template>
<view class="repaired-container">
<!-- 顶部栏 -->
<view class="repaired-navbar">
<image src="/static/ic_back.png" class="repaired-back" @click="goBack" />
<text class="repaired-progress" @click="goProgress">查看进度</text>
</view>
<!-- 成功图标 -->
<view class="repaired-icon-box">
<image src="/static/ic_repaired_01.png" class="repaired-icon" />
</view>
<!-- 提交成功文字 -->
<view class="repaired-title">提交成功</view>
<view class="repaired-desc">提交成功后我们将及时为您服务</view>
<!-- 返回首页按钮 -->
<button class="repaired-btn" @click="goHome">返回首页</button>
</view>
</template>
<script>
export default {
methods: {
goBack() {
uni.navigateBack();
},
goProgress() {
// 跳转到进度页面
uni.showToast({ title: '查看进度', icon: 'none' });
},
goHome() {
uni.switchTab({ url: '/pages/home/home' });
}
}
}
</script>
<style scoped>
.repaired-container {
min-height: 100vh;
background: #fff;
display: flex;
flex-direction: column;
align-items: center;
}
.repaired-navbar {
width: 100%;
height: 100rpx;
display: flex;
align-items: center;
justify-content: flex-start;
position: relative;
margin-top: 40rpx;
}
.repaired-back {
width: 18rpx;
height: 32rpx;
margin-left: 24rpx;
}
.repaired-progress {
position: absolute;
right: 36rpx;
top: 50%;
transform: translateY(-50%);
font-size: 24rpx;
color: #333;
}
.repaired-icon-box {
width: 180rpx;
height: 180rpx;
margin: 113rpx auto 0 auto;
display: flex;
align-items: center;
justify-content: center;
}
.repaired-icon {
width: 150rpx;
height: 150rpx;
}
.repaired-title {
margin-top: 96rpx;
font-size: 38rpx;
color: #333;
font-weight: 500;
text-align: center;
}
.repaired-desc {
margin-top: 39rpx;
font-size: 28rpx;
color: rgba(51,51,51,0.5);
text-align: center;
}
.repaired-btn {
width: 524rpx;
height: 92rpx;
background: linear-gradient(90deg, #00C7FF 0%, #0096FF 100%);
color: #fff;
font-size: 28rpx;
border: none;
border-radius: 22rpx;
margin: 246rpx auto 0 auto;
box-shadow: 0 15rpx 24rpx 0 rgba(0,199,255,0.18), 0 8rpx 24rpx 0 rgba(0,150,255,0.18);
display: flex;
align-items: center;
justify-content: center;
}
</style>

View File

@@ -0,0 +1,359 @@
<template>
<view class="cv-container">
<!-- 可滚动内容区 -->
<view class="cv-scroll-content">
<!-- 访客信息 -->
<view class="info">
<view class="cv-section-title">访客信息</view>
<view class="cv-avatar-upload" @click="chooseAvatar">
<view v-if="!form.avatar" class="cv-avatar-placeholder">上传正脸照</view>
<image v-else :src="form.avatar" class="cv-avatar-img" />
</view>
<input class="cv-input-name" placeholder="请输入姓名" v-model="form.name" />
<input class="cv-input-phone" placeholder="请输入来访人电话" v-model="form.phone" />
<input class="cv-input" placeholder="请输入来访人身份证/军官证" v-model="form.idCard" />
</view>
<!-- 选择来访目的 -->
<view class="cv-section">
<view class="cv-section-title">选择来访目的</view>
<view class="cv-purpose-list">
<view v-for="(item, idx) in purposes" :key="idx"
:class="['cv-purpose-btn', {active: form.purpose === item}]" @click="form.purpose = item">{{ item }}
</view>
</view>
<view class="cv-room-select" @click="chooseRoom">
<text>{{ form.room || '请选择访问楼栋及房间号' }}</text>
<text class="cv-arrow">&gt;</text>
</view>
</view>
<!-- 来访时间 -->
<view class="cv-section">
<view class="cv-section-title">来访时间</view>
<view class="cv-time-list">
<view v-for="(item, idx) in times" :key="idx" :class="['cv-time-btn', {active: form.time === item}]"
@click="form.time = item">{{ item }}</view>
</view>
</view>
<!-- 是否预约车位 -->
<view class="cv-section">
<view class="cv-parking-row">
<view class="cv-section-title">是否预约车位</view>
<view class="cv-radio-label" @click="form.parking = true">
<view :class="['cv-radio-custom', {checked: form.parking === true}]" />
<text></text>
</view>
<view class="cv-radio-label" @click="form.parking = false">
<view :class="['cv-radio-custom', {checked: form.parking === false}]" />
<text></text>
</view>
<text class="cv-parking-count">
<text class="cv-parking-num">50</text><text class="cv-parking-total">/100</text>
</text>
</view>
<view class="cv-room-select" @click="chooseCarNumber">
<text>{{ form.carNumber || '请选择车牌号' }}</text>
<text class="cv-arrow">&gt;</text>
</view>
</view>
<!-- 提交按钮 -->
<button class="cv-submit-btn">提交</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
form: {
name: '',
phone: '',
idCard: '',
avatar: '',
purpose: '',
room: '',
time: '',
parking: false,
carNumber: ''
},
purposes: ['商务合作', '园区参观', '面试签到', '装修放行', '家政服务', '送货上门'],
times: ['今天(2025-07-04)', '明天(2025-07-04)']
}
},
methods: {
chooseAvatar() {
// 这里可集成uni.chooseImage
},
chooseRoom() {
// 这里可弹出选择房间号
},
chooseCarNumber() {
// 这里可弹出选择车牌号
}
}
}
</script>
<style scoped>
.cv-container {
background: #fff;
height: 100vh;
display: flex;
flex-direction: column;
}
.cv-back {
position: absolute;
left: 37rpx;
width: 15rpx;
height: 33rpx;
}
.cv-title {
font-size: 36rpx;
color: #000;
}
.cv-scroll-content {
flex: 1;
overflow-y: auto;
padding-bottom: 120rpx;
}
.cv-section {
margin: 36rpx 56rpx 0 56rpx;
}
.info {
position: relative;
margin: 36rpx 56rpx 0 56rpx;
}
.cv-section-title {
font-size: 32rpx;
color: #000;
font-weight: 600;
}
.cv-inputs {
flex: 1;
display: flex;
flex-direction: column;
gap: 18rpx;
}
.cv-input-name {
width: 233rpx;
height: 73rpx;
background: #F7F7F7;
border-radius: 10rpx;
font-size: 24rpx;
color: #222;
padding: 0 24rpx;
margin-bottom: 29rpx;
margin-top: 23rpx;
border: none;
outline: none;
}
.cv-input-phone {
width: 363rpx;
height: 73rpx;
background: #F7F7F7;
border-radius: 10rpx;
font-size: 24rpx;
color: #222;
padding: 0 24rpx;
margin-bottom: 29rpx;
border: none;
outline: none;
}
.cv-input {
width: 435rpx;
height: 73rpx;
background: #F7F7F7;
border-radius: 10rpx;
font-size: 24rpx;
color: #222;
padding: 0 24rpx;
margin-bottom: 29rpx;
border: none;
outline: none;
}
.cv-avatar-upload {
position: absolute;
width: 163rpx;
height: 183rpx;
top: 50rpx;
right: 0rpx;
background: #F7F8FA;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
margin-left: 24rpx;
}
.cv-avatar-placeholder {
color: #bbb;
font-size: 24rpx;
text-align: center;
}
.cv-avatar-img {
width: 120rpx;
height: 120rpx;
border-radius: 12rpx;
}
.cv-purpose-list {
display: flex;
flex-wrap: wrap;
margin-top: 36rpx;
/* 不设置gap的列间距水平方向用margin控制 */
}
.cv-purpose-btn {
width: calc((100% - 58rpx) / 3);
margin-right: 29rpx;
margin-bottom: 29rpx;
box-sizing: border-box;
padding: 18rpx 0;
text-align: center;
background: #EBF5FF;
border-radius: 10rpx;
font-size: 24rpx;
color: #0B0B0B;
}
.cv-purpose-btn:nth-child(3n) {
margin-right: 0;
}
.cv-purpose-btn.active {
background: #007CFF;
color: #fff;
}
.cv-room-select {
width: 435rpx;
height: 73rpx;
background: #F7F7F7;
border-radius: 10rpx;
font-size: 24rpx;
color: #808080;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24rpx;
margin-top: 12rpx;
}
.cv-arrow {
font-size: 32rpx;
color: #bbb;
}
.cv-time-list {
display: flex;
flex-direction: column;
gap: 23rpx;
margin-top: 32rpx;
}
.cv-time-btn {
width: 302rpx;
height: 73rpx;
text-align: center;
background: #EBF5FF;
border-radius: 12rpx;
font-size: 24rpx;
color: #0B0B0B;
display: flex;
/* 新增 */
align-items: center;
/* 垂直居中 */
justify-content: center;
}
.cv-time-btn.active {
background: #007CFF;
color: #fff;
}
.cv-parking-row {
display: flex;
align-items: center;
gap: 24rpx;
margin-bottom: 18rpx;
}
.cv-radio-label {
font-size: 24rpx;
color: #0B0B0B;
display: flex;
align-items: center;
margin-left: 30rpx;
cursor: pointer;
}
.cv-radio-label2 {
font-size: 24rpx;
color: #0B0B0B;
display: flex;
align-items: center;
margin-left: 30rpx;
cursor: pointer;
}
.cv-radio-custom {
width: 22rpx;
height: 22rpx;
border-radius: 50%;
border: 1rpx solid #007CFF;
background: #fff;
margin-right: 12rpx;
box-sizing: border-box;
transition: background 0.2s, border 0.2s;
}
.cv-radio-custom.checked {
background: #007CFF;
border: 1rpx solid #007CFF;
}
.cv-parking-count {
margin-left: 24rpx;
font-size: 24rpx;
background: #EBF5FF;
border-radius: 8rpx;
padding: 0 18rpx;
height: 54rpx;
display: flex;
align-items: center;
}
.cv-parking-num {
color: #007CFF;
}
.cv-parking-total {
color: #0B0B0B;
}
.cv-submit-btn {
width: 90vw;
height: 90rpx;
background: #0090FF;
color: #fff;
font-size: 36rpx;
border: none;
border-radius: 45rpx;
margin: 80rpx auto 0 auto;
display: block;
font-weight: bold;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.18);
}
</style>

View File

@@ -0,0 +1,460 @@
<template>
<view class="visitor-container">
<!-- tab栏 -->
<view class="visitor-tabs">
<view v-for="(tab, idx) in tabs" :key="idx" :class="['visitor-tab', {active: idx === activeTab}]"
@click="changeTab(idx)">
{{ tab }}
<view v-if="idx === activeTab" class="tab-underline"></view>
</view>
</view>
<!-- 列表区 -->
<view class="visitor-list">
<view v-for="(item, idx) in list" :key="idx" class="visitor-card" @click="showVisitorDetail(item)">
<view class="card-row">
<view class="card-type">{{ item.type }}</view>
<view class="card-status" :class="item.statusClass">{{ item.status }}</view>
</view>
<view class="card-info">{{ item.name }}
<text class="card-phone">{{ item.phone }}</text>
<text class="card-divider"></text>
<text class="card-address">{{ item.address }}</text>
</view>
<view class="card-time">{{ item.time }}</view>
</view>
</view>
<!-- 底部按钮 -->
<button class="visitor-btn-fixed" @click="goCreateVisitor">访客邀约</button>
<view v-if="showDetail" class="visitor-detail-mask" @click.self="closeVisitorDetail">
<view class="visitor-detail-dialog">
<view class="visitor-detail-title-row">
<text class="visitor-detail-title">访客详情</text>
<image src="/static/ic_close_01.png" class="visitor-detail-close" @click="closeVisitorDetail" />
</view>
<view class="visitor-detail-info">
<view>{{ detailData.name }} {{ detailData.phone }}</view>
<view>{{ detailData.idCard || '5021119740208****' }}</view>
<view>车牌号{{ detailData.carNumber || '渝A·H1455' }}</view>
<view>来访时间{{ detailData.time }}</view>
<view>访问地址{{ detailData.address || '服务中心1栋8楼6871' }}</view>
<image :src="detailData.statusClass === 'orange' ? '/static/ic_my_visitor_01.png' : '/static/ic_my_visitor_02.png'" class="visitor-detail-status-img" />
</view>
<button class="visitor-detail-btn">修改</button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
tabs: ['我的预约', '我的邀约', '全部记录'],
activeTab: 1,
tabData: [
[],
[],
[]
], // 每个tab的数据
tabLoaded: [false, false, false], // 每个tab是否已加载
loading: false,
showDetail: false,
detailData: {},
}
},
computed: {
list() {
return this.tabData[this.activeTab];
}
},
created() {
this.loadTabData(this.activeTab); // 初始化加载当前tab数据
},
methods: {
goBack() {
uni.navigateBack();
},
goCreateVisitor() {
uni.navigateTo({
url: '/pages/sys/user/myVisitor/creatVisitor'
});
},
async changeTab(idx) {
this.activeTab = idx;
if (!this.tabLoaded[idx]) {
await this.loadTabData(idx);
}
},
async loadTabData(idx) {
this.loading = true;
// 模拟接口请求不同tab返回不同mock数据
let data = [];
if (idx === 0) {
data = [{
type: '商务合作',
status: '待处理',
statusClass: 'orange',
name: '张小晓',
phone: '18725468789',
address: '8栋2楼309',
time: '2025-07-06 15:28:16'
},
{
type: '送货上门',
status: '已到访',
statusClass: 'green',
name: '张小晓',
phone: '18725468789',
address: '8栋2楼309',
time: '2025-07-06 15:28:16'
},
{
type: '送货上门',
status: '未到访',
statusClass: 'green',
name: '张小晓',
phone: '18725468789',
address: '8栋2楼309',
time: '2025-07-06 15:28:16'
},
{ type: '商务合作', status: '待处理', statusClass: 'orange', name: '刘小备', phone: '18725468789', address: '8栋2楼309', time: '2025-07-06 15:28:16' },
{ type: '送货上门', status: '已到访', statusClass: 'green', name: '关小羽', phone: '18725468789', address: '8栋2楼309', time: '2025-07-06 15:28:16' },
{ type: '送货上门', status: '未到访', statusClass: 'green', name: '张小飞', phone: '18725468789', address: '8栋2楼309', time: '2025-07-06 15:28:16' },
{ type: '商务合作', status: '待处理', statusClass: 'orange', name: '曹小操', phone: '18725468789', address: '8栋2楼309', time: '2025-07-06 15:28:16' },
{ type: '送货上门', status: '已到访', statusClass: 'green', name: '孙小权', phone: '18725468789', address: '8栋2楼309', time: '2025-07-06 15:28:16' },
{ type: '送货上门', status: '未到访', statusClass: 'green', name: '周小瑜', phone: '18725468789', address: '8栋2楼309', time: '2025-07-06 15:28:16' }
];
} else if (idx === 1) {
data = [{
type: '商务合作',
status: '待处理',
statusClass: 'orange',
name: '李明',
phone: '13812345678',
address: '5栋1楼101',
time: '2025-07-07 10:00:00'
},
{
type: '送货上门',
status: '已到访',
statusClass: 'green',
name: '王五',
phone: '13987654321',
address: '6栋3楼202',
time: '2025-07-07 11:00:00'
},
{ type: '商务合作', status: '待处理', statusClass: 'orange', name: '赵六', phone: '13812345678', address: '5栋1楼101', time: '2025-07-07 10:00:00' },
{ type: '送货上门', status: '已到访', statusClass: 'green', name: '钱七', phone: '13987654321', address: '6栋3楼202', time: '2025-07-07 11:00:00' },
{ type: '商务合作', status: '待处理', statusClass: 'orange', name: '孙八', phone: '13812345678', address: '5栋1楼101', time: '2025-07-07 10:00:00' },
{ type: '送货上门', status: '已到访', statusClass: 'green', name: '周九', phone: '13987654321', address: '6栋3楼202', time: '2025-07-07 11:00:00' }
];
} else {
data = [{
type: '商务合作',
status: '已结束',
statusClass: 'gray',
name: '张小晓',
phone: '18725468789',
address: '8栋2楼309',
time: '2025-07-06 15:28:16'
},
{
type: '送货上门',
status: '已结束',
statusClass: 'gray',
name: '李明',
phone: '13812345678',
address: '5栋1楼101',
time: '2025-07-07 10:00:00'
},
{
type: '送货上门',
status: '已结束',
statusClass: 'gray',
name: '王五',
phone: '13987654321',
address: '6栋3楼202',
time: '2025-07-07 11:00:00'
},
{ type: '商务合作', status: '已结束', statusClass: 'gray', name: '张小晓', phone: '18725468789', address: '8栋2楼309', time: '2025-07-06 15:28:16' },
{ type: '送货上门', status: '已结束', statusClass: 'gray', name: '李明', phone: '13812345678', address: '5栋1楼101', time: '2025-07-07 10:00:00' },
{ type: '送货上门', status: '已结束', statusClass: 'gray', name: '王五', phone: '13987654321', address: '6栋3楼202', time: '2025-07-07 11:00:00' },
{ type: '商务合作', status: '已结束', statusClass: 'gray', name: '张小晓', phone: '18725468789', address: '8栋2楼309', time: '2025-07-06 15:28:16' },
{ type: '送货上门', status: '已结束', statusClass: 'gray', name: '李明', phone: '13812345678', address: '5栋1楼101', time: '2025-07-07 10:00:00' },
{ type: '送货上门', status: '已结束', statusClass: 'gray', name: '王五', phone: '13987654321', address: '6栋3楼202', time: '2025-07-07 11:00:00' }
];
}
// 模拟网络延迟
await new Promise(res => setTimeout(res, 300));
this.$set(this.tabData, idx, data);
this.$set(this.tabLoaded, idx, true);
this.loading = false;
},
showVisitorDetail(item) {
this.detailData = item;
this.showDetail = true;
},
closeVisitorDetail() {
this.showDetail = false;
}
}
}
</script>
<style scoped>
.visitor-container {
height: 100vh;
background: #f7f7f7;
display: flex;
flex-direction: column;
}
.visitor-navbar {
width: 100%;
height: 120rpx;
padding-top: 40rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
background: #fff;
flex-shrink: 0; /* 防止被压缩 */
}
.visitor-back {
position: absolute;
left: 37rpx;
width: 15rpx;
height: 33rpx;
}
.visitor-title {
font-size: 36rpx;
color: #000;
}
.visitor-tabs {
display: flex;
align-items: center;
justify-content: space-around;
background: #fff;
height: 80rpx;
border-bottom: 1px solid #f0f0f0;
flex-shrink: 0; /* 防止被压缩 */
}
.visitor-tab {
flex: 1;
text-align: center;
font-size: 30rpx;
color: #888;
position: relative;
font-weight: 500;
padding: 0 0 10rpx 0;
/* tab点击事件 */
cursor: pointer;
}
.visitor-tab.active {
color: #2186FF;
font-weight: bold;
}
.tab-underline {
width: 60rpx;
height: 6rpx;
background: #2186FF;
border-radius: 3rpx;
margin: 0 auto;
margin-top: 8rpx;
}
.visitor-list {
margin: 25rpx 0 0 0;
padding: 0 35rpx;
flex: 1; /* 占据所有剩余空间 */
overflow-y: auto; /* 内容超出时,开启垂直滚动 */
padding-bottom: 200rpx; /* 为底部按钮留出空间 */
}
.visitor-card {
background: #fff;
border-radius: 16rpx;
margin-bottom: 24rpx;
padding: 20rpx 40rpx 70rpx 12rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
}
.card-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 28rpx;
}
.card-type {
font-weight: 600;
font-size: 26rpx;
padding: 10rpx 45rpx 10rpx 12rpx;
border-radius: 15rpx;
color: #fff;
background: linear-gradient(90deg, #007CFF 0%, #FFFFFF 100%);
display: inline-block;
}
.card-status {
font-size: 28rpx;
}
.card-status.orange {
color: #F3831F;
}
.card-status.green {
color: #07C78E;
}
.card-status.gray {
color: #8F8F8F;
}
.card-info {
font-size: 26rpx;
color: #333;
margin-bottom: 8rpx;
display: flex;
align-items: center;
}
.card-phone {
margin: 0 8rpx 0 8rpx;
}
.card-divider {
width: 1rpx;
height: 40rpx;
background: #DCDCDC;
display: inline-block;
margin: 0 12rpx;
vertical-align: middle;
content: '';
}
.card-address {
margin-left: 0;
}
.card-time {
font-size: 28rpx;
color: #626262;
}
.visitor-btn {
width: 90vw;
height: 80rpx;
background: #0090FF;
color: #fff;
font-size: 32rpx;
border: none;
border-radius: 40rpx;
margin: 60rpx auto 0 auto;
display: block;
font-weight: bold;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.18);
}
.visitor-btn-fixed {
position: fixed;
left: 0;
right: 0;
bottom: 100rpx;
margin: 0 auto;
width: 90vw;
height: 80rpx;
background: #0090FF;
color: #fff;
font-size: 32rpx;
border: none;
border-radius: 40rpx;
display: block;
font-weight: bold;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.18);
z-index: 99;
}
.visitor-detail-mask {
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
background: rgba(0,0,0,0.5);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
.visitor-detail-dialog {
width: 66vw;
background: #fff;
border-radius: 36rpx;
box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.12);
padding: 48rpx 36rpx 36rpx 36rpx;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
.visitor-detail-title-row {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 52rpx;
}
.visitor-detail-title {
font-size: 36rpx;
color: #000;
}
.visitor-detail-close {
position: absolute;
right: 0;
top: 0;
width: 22rpx;
height: 22rpx;
}
.visitor-detail-info {
width: 100%;
font-size: 28rpx;
color: #222;
margin-bottom: 80rpx;
display: flex;
flex-direction: column;
gap: 28rpx;
position: relative;
}
.visitor-detail-status-img {
position: absolute;
right: 0;
bottom: -40rpx;
width: 160rpx;
height: 160rpx;
z-index: 1;
pointer-events: none;
}
.visitor-detail-btn {
width: 320rpx;
height: 80rpx;
background: linear-gradient(90deg, #0090FF 0%, #2E9FFF 100%);
color: #fff;
font-size: 32rpx;
border: none;
border-radius: 40rpx;
margin-bottom: 30rpx;
display: block;
font-weight: bold;
box-shadow: 0 8rpx 24rpx rgba(0,0,0,0.18);
}
</style>

View File

@@ -0,0 +1,343 @@
<template>
<view class="vi-container">
<!-- 顶部 -->
<view class="vi-navbar">
<image src="/static/ic_back.png" class="vi-back" @click="goBack" />
<text class="vi-title">预约申请</text>
</view>
<!-- 访客信息 -->
<view class="vi-section info">
<view class="vi-section-title">访客信息</view>
<view class="vi-avatar-box">
<view class="vi-avatar-placeholder">人脸照片</view>
</view>
<view class="vi-input-name">张桂花</view>
<view class="vi-input-phone">13254789579</view>
<view class="vi-input">5021119740214****</view>
</view>
<!-- 选择来访目的 -->
<view class="vi-section">
<view class="vi-section-title">选择来访目的</view>
<view class="vi-purpose-list">
<view class="vi-purpose-btn-active">园区参观</view>
<view class="vi-purpose-btn">综合服务中心1栋8023</view>
</view>
</view>
<!-- 来访时间 -->
<view class="vi-section">
<view class="vi-section-title">来访时间</view>
<view class="vi-time-btn">今天(2025-07-04)</view>
</view>
<!-- 是否预约车位 -->
<view class="vi-section">
<view class="vi-parking-row">
<view class="vi-section-title2">是否预约车位</view>
<view class="vi-checkbox-label">
<view class="vi-checkbox checked"></view>
<text></text>
</view>
<view class="vi-checkbox-label">
<view class="vi-checkbox"></view>
<text></text>
</view>
<text class="vi-parking-count">
<text class="vi-parking-num">50</text><text class="vi-parking-total">/100</text>
</text>
</view>
<view class="vi-car-input">渝A-V1254</view>
</view>
<!-- 底部按钮 -->
<button class="vi-submit-btn">同意</button>
</view>
</template>
<script>
export default {
methods: {
goBack() {
uni.navigateBack();
}
}
}
</script>
<style scoped>
.vi-container {
background: #fff;
min-height: 100vh;
padding-bottom: 120rpx;
}
.vi-navbar {
width: 100%;
height: 120rpx;
padding-top: 40rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
background: #fff;
}
.vi-back {
position: absolute;
left: 37rpx;
width: 15rpx;
height: 33rpx;
}
.vi-title {
font-size: 36rpx;
color: #222;
font-weight: bold;
}
.vi-section {
margin: 36rpx 56rpx 0 56rpx;
}
.vi-section-title {
font-size: 32rpx;
color: #000;
font-weight: 600;
margin-bottom: 23rpx;
}
.vi-section-title2 {
font-size: 32rpx;
color: #000;
font-weight: 600;
}
.vi-info-row {
display: flex;
align-items: flex-start;
justify-content: space-between;
}
.vi-inputs {
flex: 1;
display: flex;
flex-direction: column;
gap: 18rpx;
}
.vi-input-name,
.vi-input-phone,
.vi-input {
width: 233rpx;
height: 73rpx;
background: #F7F7F7;
border-radius: 10rpx;
font-size: 24rpx;
color: #222;
padding: 0 24rpx;
margin-bottom: 29rpx;
display: flex;
align-items: center;
}
.vi-avatar-box {
width: 120rpx;
height: 120rpx;
background: #F7F8FA;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
margin-left: 24rpx;
}
.vi-avatar-placeholder {
color: #bbb;
font-size: 24rpx;
text-align: center;
}
.vi-purpose-list {
display: flex;
flex-direction: row;
gap: 29rpx;
margin-top: 36rpx;
}
.vi-purpose-btn {
width: 100%;
box-sizing: border-box;
padding: 18rpx 0;
text-align: center;
background: #EBF5FF;
border-radius: 10rpx;
font-size: 24rpx;
color: #0B0B0B;
}
.vi-purpose-btn-active {
width: 193rpx;
border-radius: 10rpx;
font-size: 24rpx;
box-sizing: border-box;
padding: 18rpx 0;
text-align: center;
background: #007CFF;
color: #fff;
}
.vi-time-btn {
width: 302rpx;
height: 73rpx;
box-sizing: border-box;
text-align: center;
background: #EBF5FF;
border-radius: 10rpx;
font-size: 28rpx;
color: #222;
display: flex;
align-items: center;
justify-content: center;
}
.vi-parking-row {
display: flex;
align-items: center;
margin-top: 12rpx;
}
.vi-checkbox-label {
display: flex;
align-items: center;
font-size: 24rpx;
color: #0B0B0B;
margin-left: 30rpx;
cursor: pointer;
}
.vi-checkbox {
width: 22rpx;
height: 22rpx;
border-radius: 50%;
border: 1rpx solid #007CFF;
background: #fff;
margin-right: 12rpx;
box-sizing: border-box;
}
.vi-checkbox.checked {
background: #007CFF;
border: 1rpx solid #007CFF;
}
.vi-parking-count {
margin-left: 24rpx;
font-size: 24rpx;
background: #EBF5FF;
border-radius: 8rpx;
padding: 0 18rpx;
height: 54rpx;
display: flex;
align-items: center;
}
.vi-parking-num {
color: #007CFF;
}
.vi-parking-total {
color: #0B0B0B;
}
.vi-car-input {
width: 435rpx;
height: 73rpx;
background: #F7F7F7;
border-radius: 10rpx;
font-size: 24rpx;
color: #222;
padding: 0 24rpx;
margin-top: 29rpx;
display: flex;
align-items: center;
}
.vi-submit-btn {
width: 90vw;
height: 90rpx;
background: linear-gradient(90deg, #0090FF 0%, #2E9FFF 100%);
color: #fff;
font-size: 36rpx;
border: none;
border-radius: 45rpx;
margin: 80rpx auto 0 auto;
display: block;
font-weight: bold;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.18);
}
.info {
position: relative;
margin: 36rpx 56rpx 0 56rpx;
}
.vi-avatar-box {
position: absolute;
width: 163rpx;
height: 183rpx;
top: 50rpx;
right: 0rpx;
background: #F7F8FA;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
margin-left: 24rpx;
}
.vi-avatar-placeholder {
color: #bbb;
font-size: 24rpx;
text-align: center;
}
.vi-input-name {
width: 233rpx;
height: 73rpx;
background: #F7F7F7;
border-radius: 10rpx;
font-size: 24rpx;
color: #222;
padding: 0 24rpx;
margin-bottom: 29rpx;
margin-top: 23rpx;
display: flex;
align-items: center;
}
.vi-input-phone {
width: 363rpx;
height: 73rpx;
background: #F7F7F7;
border-radius: 10rpx;
font-size: 24rpx;
color: #222;
padding: 0 24rpx;
margin-bottom: 29rpx;
display: flex;
align-items: center;
}
.vi-input {
width: 435rpx;
height: 73rpx;
background: #F7F7F7;
border-radius: 10rpx;
font-size: 24rpx;
color: #222;
padding: 0 24rpx;
margin-bottom: 29rpx;
display: flex;
align-items: center;
}
</style>

View File

@@ -69,7 +69,7 @@ page {
background-color: #f5f5f5;
}
/deep/ .u-cell-title {
::v-deep .u-cell-title {
padding: 25rpx 30rpx;
font-size: 30rpx;
}

View File

@@ -0,0 +1,87 @@
<template>
<view class="detail-container">
<!-- 可滚动内容区 -->
<view class="scroll-content">
<!-- 问题标题 -->
<view class="question-title">
包月停车临时停车的办理流程及收费标准
<view class="title-underline"></view>
</view>
<!-- 内容区 -->
<view class="question-content">
<view class="question-desc">
您好:本项目只有8组团为包月停车办理流程为:业主携带身份证至物业客户中心前台办理租户带租赁合同和身份证到前台办理即可;5,6,7,9,10,11组团无包月停车无需办理包月停车手续;临时停车无需办理5,6组团为私家车位无需收费8组团包月停车的收费标准为:500/8组团临时停车的收费标准为:小区内9,10,11组团临时停车的收费标准为:二轮车:每小时1元12小时内5元/24小时内10元/;小型车:每小时3元12小时内10元/24小时内20元/;大型车:每小时4元12小时内15元/24小时内25元/
</view>
<image src="/static/ic_q_d_01.png" class="question-img" mode="widthFix" />
</view>
</view>
</view>
</template>
<script>
export default {
methods: {
}
}
</script>
<style scoped>
.detail-container {
height: 100vh;
background: #fff;
display: flex;
flex-direction: column;
}
.detail-back {
position: absolute;
left: 37rpx;
width: 15rpx;
height: 33rpx;
}
.detail-title {
font-size: 36rpx;
color: #000;
}
.scroll-content {
flex: 1;
overflow-y: auto;
padding-bottom: 40rpx;
}
.question-title {
font-size: 36rpx;
font-weight: bold;
color: #000000;
margin: 40rpx 32rpx 0 64rpx;
position: relative;
line-height: 1.4;
}
.title-underline {
width: 120rpx;
height: 6rpx;
background: #3B8BFF;
border-radius: 3rpx;
margin-top: 8rpx;
}
.question-content {
margin: 34rpx 24rpx 0 24rpx;
background: #EDF6FF;
border-radius: 12rpx;
border: 2rpx dashed #bfc8d6;
padding: 32rpx 24rpx 24rpx 24rpx;
display: flex;
flex-direction: column;
align-items: flex-start;
}
.question-desc {
font-size: 28rpx;
color: #4A4A4A;
line-height: 1.8;
margin-bottom: 24rpx;
}
.question-img {
width: 100%;
border-radius: 8rpx;
}
</style>

View File

@@ -0,0 +1,283 @@
<template>
<view class="service-container">
<!-- 固定顶部区域 -->
<view class="fixed-header">
<!-- 搜索框 -->
<view class="service-search">
<image src="/static/ic_search_gray.png" class="search-icon" />
<input class="search-input" placeholder="请输入关键词 例 停车" />
</view>
<!-- 公告banner -->
<view class="service-banner">
<image src="/static/ic_s_c_01.png" class="banner-img" mode="aspectFill" />
</view>
</view>
<!-- 可滚动内容区 -->
<view class="scroll-content">
<!-- 常见问题 -->
<view class="faq-title">常见问题</view>
<view class="faq-list">
<view class="faq-item" v-for="(item, idx) in faqList" :key="idx" @click="goDetail(idx)">
<text class="faq-text">{{ item }}</text>
</view>
</view>
<view class="faq-end">- 到底了 -</view>
<!-- 底部按钮 -->
<button class="service-btn" @click="showDialog = true">电话咨询</button>
</view>
<!-- 弹窗 -->
<view v-if="showDialog" class="dialog-mask" @click="showDialog = false">
<view class="dialog-box">
<image src="/static/ic_s_c_02.png" class="dialog-bg" mode="widthFix" />
<view class="dialog-content">
<view class="dialog-phone">023950888</view>
<button class="dialog-call-btn" @click.stop="callPhone">拨打电话</button>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
faqList: [
'如何开取物业费发票,需要携带哪些资料?需要多...',
'提交报修后一般多久会处理?',
'生活服务优惠券怎么领取',
'如何进行认证',
'工程服务收费标准',
'哪些时段可以进行施工装修',
'包月停车、临时停车的办理流程及收费标准',
'开锁的联系方式',
'物业服务电话/投诉电话是多少',
'电费查询及收费标准'
],
showDialog: false
}
},
methods: {
callPhone() {
this.showDialog = false;
uni.makePhoneCall({
phoneNumber: '023950888'
});
},
goDetail(idx) {
uni.navigateTo({ url: '/pages/sys/user/serviceCenter/questionDetail' });
}
}
}
</script>
<style scoped>
.service-container {
height: 100vh;
background: #fff;
display: flex;
flex-direction: column;
}
.fixed-header {
flex-shrink: 0;
}
.scroll-content {
flex: 1;
overflow-y: auto;
padding-bottom: 120rpx;
}
.service-navbar {
width: 100%;
height: 120rpx;
padding-top: 40rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
background: #fff;
}
.service-back {
position: absolute;
left: 37rpx;
width: 15rpx;
height: 33rpx;
}
.service-title {
font-size: 36rpx;
color: #222;
font-weight: bold;
}
.service-search {
width: 85vw;
background: rgba(225, 225, 225, 0.29);
border-radius: 16rpx;
display: flex;
align-items: center;
padding: 0 24rpx;
height: 64rpx;
margin: 0 auto;
}
.search-icon {
width: 32rpx;
height: 32rpx;
margin-right: 12rpx;
}
.search-input {
border: none;
background: transparent;
font-size: 28rpx;
flex: 1;
color: #757575;
}
.service-banner {
margin: 32rpx 32rpx 0 32rpx;
position: relative;
overflow: hidden;
}
.banner-img {
width: 100%;
height: 180rpx;
}
.banner-btn {
position: absolute;
right: 24rpx;
bottom: 18rpx;
background: #fff;
color: #0090FF;
font-size: 22rpx;
border-radius: 24rpx;
padding: 6rpx 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 144, 255, 0.08);
}
.faq-title {
font-size: 32rpx;
font-weight: bold;
color: #222;
margin: 40rpx 0 18rpx 32rpx;
}
.faq-list {
background: #fff;
margin: 0 0 0 0;
}
.faq-item {
padding: 0 32rpx;
height: 64rpx;
display: flex;
align-items: center;
border-bottom: 1px solid #f7f7f7;
}
.faq-item:last-child {
border-bottom: none;
}
.faq-text {
font-size: 28rpx;
color: #222;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.faq-end {
text-align: center;
color: #bbb;
font-size: 26rpx;
margin: 32rpx 0 0 0;
}
.service-btn {
width: 90vw;
height: 80rpx;
background: linear-gradient(90deg, #2e6cf6 0%, #0090FF 100%);
color: #fff;
font-size: 32rpx;
border: none;
border-radius: 40rpx;
margin: 60rpx auto 0 auto;
display: block;
font-weight: bold;
box-shadow: 0 8rpx 24rpx rgba(0, 144, 255, 0.12);
}
.dialog-mask {
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.5);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
.dialog-box {
width: 476rpx;
height: 552rpx;
overflow: hidden;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
.dialog-bg {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 0;
border-radius: 28rpx;
}
.dialog-content {
position: relative;
z-index: 1;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
padding-bottom: 48rpx;
}
.dialog-phone {
margin-top: 180rpx;
width: 100%;
text-align: center;
color: #fff;
font-size: 38rpx;
font-weight: bold;
letter-spacing: 2rpx;
}
.dialog-call-btn {
width: 90%;
height: 80rpx;
background: linear-gradient(90deg, #FFD600 0%, #FFB800 100%);
color: #222;
font-size: 32rpx;
border: none;
border-radius: 20rpx;
margin: 40rpx auto 0 auto;
font-weight: bold;
box-shadow: 0 8rpx 24rpx rgba(255, 184, 0, 0.12);
}
</style>

View File

@@ -72,7 +72,7 @@ page {
background-color: #f5f5f5;
}
/deep/ .u-cell-title {
::v-deep .u-cell-title {
padding: 25rpx 30rpx;
font-size: 30rpx;
}

View File

@@ -0,0 +1,443 @@
<template>
<view class="oa-container">
<!-- 顶部导航栏 -->
<view class="oa-navbar">
<image src="/static/ic_back.png" class="oa-back" @click="goBack" />
<text class="oa-title">申批中心</text>
</view>
<!-- 搜索框 -->
<view class="oa-search">
<view class="search-box">
<image src="/static/ic_search_gray.png" class="search-icon" />
<text class="search-placeholder">申批名称内容发起人编号</text>
</view>
</view>
<!-- tab栏 -->
<view class="oa-tabs">
<view v-for="(tab, idx) in tabs" :key="idx" :class="['oa-tab', {active: idx === activeTab}]"
@click="changeTab(idx)">
{{ tab }}
<view v-if="idx === activeTab" class="tab-underline"></view>
</view>
</view>
<!-- 列表区 -->
<view class="oa-list">
<view v-for="(item, idx) in list" :key="idx" class="oa-card" :class="{'special-leave': item.status === '已通过'}" @click="goToDetail(item)">
<view class="card-row">
<view class="card-type">{{ item.type }}</view>
<view class="card-status-tag" :class="item.statusClass">{{ item.status }}</view>
</view>
<view class="card-info">
<view class="card-leave-type">请假类型<text class="card-leave-type">{{ item.leaveType }}</text></view>
<view class="card-leave-type">
<text>开始时间{{ item.startTime }}</text>
</view>
<view class="card-leave-type">
<text>结束时间{{ item.endTime }}</text>
</view>
</view>
<view class="card-footer">
<view class="card-user">
<view class="user-avatar" :style="{backgroundColor: item.avatarColor}">{{ item.avatarText }}</view>
<text class="user-name">{{ item.userName }}</text>
</view>
<text class="card-datetime">{{ item.dateTime }}</text>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
tabs: ['待办', '已办', '已发起'],
activeTab: 2, // 默认选中已发起
tabData: [
[], // 待办
[], // 已办
[] // 已发起
],
tabLoaded: [false, false, false], // 每个tab是否已加载
loading: false
}
},
computed: {
list() {
return this.tabData[this.activeTab];
}
},
created() {
this.loadTabData(this.activeTab); // 初始化加载当前tab数据
},
methods: {
goBack() {
uni.navigateBack();
},
// 跳转到详情页
goToDetail(item) {
uni.navigateTo({
url: './oaDetail'
});
},
async changeTab(idx) {
this.activeTab = idx;
if (!this.tabLoaded[idx]) {
await this.loadTabData(idx);
}
},
async loadTabData(idx) {
this.loading = true;
// 模拟接口请求不同tab返回不同mock数据
let data = [];
if (idx === 0) { // 待办
data = [
{
type: '请假',
leaveType: '病假',
status: '待办',
statusClass: 'orange',
startTime: '2025-07-13',
endTime: '2025-07-15',
userName: '余永乐',
avatarText: '余',
avatarColor: '#4B7BF5',
dateTime: '07-12 18:28:22'
}
];
} else if (idx === 1) { // 已办
data = [
{
type: '请假',
leaveType: '病假',
status: '已通过',
statusClass: 'green',
startTime: '2025-07-13',
endTime: '2025-07-15',
userName: '余永乐',
avatarText: '余',
avatarColor: '#4B7BF5',
dateTime: '07-12 18:28:22'
}
];
} else { // 已发起
data = [
{
type: '请假',
leaveType: '病假',
status: '已通过',
statusClass: 'green',
startTime: '2025-07-13',
endTime: '2025-07-15',
userName: '余永乐',
avatarText: '余',
avatarColor: '#4B7BF5',
dateTime: '07-12 18:28:22'
},
{
type: '请假',
leaveType: '病假',
status: '待审核',
statusClass: 'orange',
startTime: '2025-07-13',
endTime: '2025-07-15',
userName: '余永乐',
avatarText: '余',
avatarColor: '#4B7BF5',
dateTime: '07-12 18:28:22'
},
{
type: '请假',
leaveType: '病假',
status: '待审核',
statusClass: 'orange',
startTime: '2025-07-13',
endTime: '2025-07-15',
userName: '余永乐',
avatarText: '余',
avatarColor: '#4B7BF5',
dateTime: '07-12 18:28:22'
},
{
type: '请假',
leaveType: '病假',
status: '待审核',
statusClass: 'orange',
startTime: '2025-07-13',
endTime: '2025-07-15',
userName: '余永乐',
avatarText: '余',
avatarColor: '#4B7BF5',
dateTime: '07-12 18:28:22'
},
{
type: '请假',
leaveType: '病假',
status: '待审核',
statusClass: 'orange',
startTime: '2025-07-13',
endTime: '2025-07-15',
userName: '余永乐',
avatarText: '余',
avatarColor: '#4B7BF5',
dateTime: '07-12 18:28:22'
},
{
type: '请假',
leaveType: '病假',
status: '待审核',
statusClass: 'orange',
startTime: '2025-07-13',
endTime: '2025-07-15',
userName: '余永乐',
avatarText: '余',
avatarColor: '#4B7BF5',
dateTime: '07-12 18:28:22'
},
{
type: '请假',
leaveType: '病假',
status: '待审核',
statusClass: 'orange',
startTime: '2025-07-13',
endTime: '2025-07-15',
userName: '余永乐',
avatarText: '余',
avatarColor: '#4B7BF5',
dateTime: '07-12 18:28:22'
},
{
type: '请假',
leaveType: '病假',
status: '待审核',
statusClass: 'orange',
startTime: '2025-07-13',
endTime: '2025-07-15',
userName: '余永乐',
avatarText: '余',
avatarColor: '#4B7BF5',
dateTime: '07-12 18:28:22'
}
];
}
// 模拟网络延迟
await new Promise(res => setTimeout(res, 300));
this.$set(this.tabData, idx, data);
this.$set(this.tabLoaded, idx, true);
this.loading = false;
}
}
}
</script>
<style scoped>
.oa-container {
height: 100vh;
background: #f7f7f7;
display: flex;
flex-direction: column;
}
.oa-navbar {
width: 100%;
height: 120rpx;
padding-top: 40rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
background: #fff;
flex-shrink: 0;
}
.oa-back {
position: absolute;
left: 37rpx;
width: 15rpx;
height: 33rpx;
}
.oa-title {
font-size: 36rpx;
color: #000;
}
.oa-search {
background: #fff;
padding: 20rpx 30rpx;
}
.search-box {
height: 70rpx;
background: #F7F7F7;
border-radius: 35rpx;
display: flex;
align-items: center;
justify-content: flex-start;
padding: 0 30rpx;
}
.search-icon {
width: 30rpx;
height: 30rpx;
margin-right: 10rpx;
}
.search-placeholder {
color: #9A9A9A;
font-size: 28rpx;
}
.oa-tabs {
display: flex;
align-items: center;
justify-content: space-around;
background: #fff;
height: 80rpx;
border-bottom: 1px solid #f0f0f0;
flex-shrink: 0;
}
.oa-tab {
flex: 1;
text-align: center;
font-size: 32rpx;
color: #737373;
position: relative;
padding: 0 0 10rpx 0;
cursor: pointer;
}
.oa-tab.active {
color: #007CFF;
font-weight: bold;
}
.tab-underline {
width: 60rpx;
height: 6rpx;
background: #2186FF;
border-radius: 3rpx;
margin: 0 auto;
margin-top: 8rpx;
}
.oa-list {
margin: 25rpx 0 0 0;
padding: 0 35rpx;
flex: 1;
overflow-y: auto;
padding-bottom: 50rpx;
}
.oa-card {
background: #fff;
border-radius: 10rpx;
margin-bottom: 26rpx;
padding: 20rpx 20rpx 20rpx 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.card-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 46rpx;
}
.card-type {
width: 126rpx;
height: 46rpx;
font-weight: 600;
font-size: 32rpx;
padding-left: 10rpx;
padding-top: 7rpx;
border-radius: 15rpx;
color: #fff;
background: linear-gradient(90deg, #007CFF 0%, #FFFFFF 100%);
}
.card-status-tag {
font-size: 28rpx;
}
.card-status-tag.orange {
color: #F27A0F;
}
.card-status-tag.green {
color: #0AC88F;
}
.card-status-tag.gray {
color: #8F8F8F;
background-color: rgba(143, 143, 143, 0.1);
border: 1px solid #8F8F8F;
}
.card-info {
font-size: 26rpx;
color: #333;
margin-bottom: 42rpx;
}
.card-leave-type {
color: #626262;
font-size: 28;
margin-bottom: 24rpx;
}
.card-footer {
display: flex;
align-items: center;
justify-content: space-between;
}
.card-user {
display: flex;
align-items: center;
}
.user-avatar {
width: 54rpx;
height: 54rpx;
border-radius: 27rpx;
background-color: #688CFF;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
margin-right: 18rpx;
}
.user-name {
font-size: 24rpx;
color: #626262;
}
.card-datetime {
font-size: 24rpx;
color: #626262;
}
.special-leave {
position: relative;
overflow: hidden;
}
.special-leave::after {
content: '特批假';
position: absolute;
top: 10rpx;
right: -50rpx;
background-color: #F3831F;
color: #fff;
padding: 5rpx 60rpx;
transform: rotate(45deg);
font-size: 22rpx;
font-weight: bold;
}
</style>

View File

@@ -0,0 +1,493 @@
<template>
<view class="detail-container">
<!-- 顶部导航栏 -->
<view class="detail-navbar">
<image src="/static/ic_back_white.webp" class="detail-back" @click="goBack" />
<view class="detail-right">
<image src="/static/ic_share_w.png" class="detail-scan" />
</view>
</view>
<!-- 卡片容器 -->
<view class="card-wrapper">
<!-- 请假信息卡片 -->
<view class="detail-card">
<!-- 请假状态 -->
<view class="leave-header">
<view class="leave-type">请假</view>
<view class="leave-status">已通过</view>
</view>
<!-- 申请人信息 -->
<view class="applicant-info">
<view class="applicant-avatar" style="background-color: #4B7BF5;"></view>
<view class="applicant-detail">
<text class="applicant-name">余永乐</text>
<text class="applicant-dept">数据开发及研发部</text>
</view>
</view>
<!-- 提交时间 -->
<view class="submit-time">提交时间7月16日 18:36:44</view>
</view>
</view>
<!-- 其他卡片保持原结构 -->
<view class="detail-card">
<view class="detail-title">申批详情</view>
<view class="detail-item-vertical">
<text class="detail-label-vertical">假期类型</text>
<text class="detail-value-vertical">病假</text>
</view>
<view class="detail-item-vertical">
<text class="detail-label-vertical">开始时间</text>
<text class="detail-value-vertical">2025-07-15</text>
</view>
<view class="detail-item-vertical">
<text class="detail-label-vertical">结束时间</text>
<text class="detail-value-vertical">2025-07-17</text>
</view>
<view class="detail-item-vertical">
<text class="detail-label-vertical">时长</text>
<text class="detail-value-vertical">2</text>
</view>
<view class="detail-item-vertical">
<text class="detail-label-vertical">请假事由</text>
<text class="detail-value-vertical">由于腿儿过大需要到医院进行检查</text>
</view>
<view class="detail-item-vertical">
<text class="detail-label-vertical">附件</text>
<view class="attachment">
<view class="attachment-preview">
<image src="/static/ic_attachment_preview.png" class="attachment-img" />
</view>
<view class="attachment-info">
<text class="attachment-name">174568963.jpg</text>
<text class="attachment-size">289.14 KB</text>
</view>
</view>
</view>
</view>
<!-- 审批记录卡片保持原结构 -->
<view class="detail-card">
<view class="detail-title">申批记录</view>
<view class="approval-item">
<view class="approval-status">
<view class="status-dot selected"></view>
<view class="status-line" v-if="true"></view>
</view>
<view class="approval-content">
<view class="approval-type">提交</view>
<view class="approver-info">
<view class="approver-avatar" style="background-color: #4B7BF5;"></view>
<text class="approver-name">余永乐</text>
<text class="approval-result">已提交</text>
</view>
<text class="approval-time">07-12 18:28:22</text>
</view>
</view>
<view class="approval-item">
<view class="approval-status">
<view class="status-dot selected"></view>
<view class="status-line" v-if="true"></view>
</view>
<view class="approval-content">
<view class="approval-type">审批</view>
<view class="approver-info">
<view class="approver-avatar" style="background-color: #F3831F;"></view>
<text class="approver-name">张桂花</text>
<text class="approval-result">已同意</text>
</view>
<text class="approval-time">07-12 18:28:22</text>
</view>
</view>
<view class="approval-item">
<view class="approval-status">
<view class="status-dot selected"></view>
<view class="status-line" v-if="false"></view>
</view>
<view class="approval-content">
<view class="approval-type">审批</view>
<view class="approver-info">
<view class="approver-avatar" style="background-color: #F3831F;"></view>
<text class="approver-name">张桂花</text>
<text class="approval-result">已批准</text>
</view>
<text class="approval-time">07-12 18:28:22</text>
</view>
</view>
<view class="approval-item">
<view class="approval-status">
<view class="status-dot"></view>
</view>
<view class="approval-content">
<view class="approval-type">结束</view>
<text class="approval-time">07-12 18:28:22</text>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* OA申批详情页面
* @author lyc
*/
export default {
data() {
return {
// 详情数据
detailData: {
type: '请假',
status: '已通过',
leaveType: '病假',
applicant: {
name: '余永乐',
dept: '数据开发及研发部',
avatarText: '余',
avatarColor: '#4B7BF5'
},
submitTime: '7月16日 18:36:44',
startTime: '2025-07-15',
endTime: '2025-07-17',
duration: '2天',
reason: '由于腿儿过大,需要到医院进行检查',
attachment: {
name: '174568963.jpg',
size: '289.14 KB'
},
approvalRecords: [
{
type: '提交',
approver: {
name: '余永乐',
avatarText: '余',
avatarColor: '#4B7BF5'
},
result: '已提交',
time: '07-12 18:28:22'
},
{
type: '审批',
approver: {
name: '张桂花',
avatarText: '张',
avatarColor: '#F3831F'
},
result: '已同意',
time: '07-12 18:28:22'
},
{
type: '审批',
approver: {
name: '张桂花',
avatarText: '张',
avatarColor: '#F3831F'
},
result: '已批准',
time: '07-12 18:28:22'
},
{
type: '结束',
time: '07-12 18:28:22'
}
]
}
}
},
methods: {
// 返回上一页
goBack() {
uni.navigateBack();
}
}
}
</script>
<style scoped>
.detail-container {
background-color: #f7f7f7;
min-height: 100vh;
padding-bottom: 30rpx;
}
/* 导航栏样式 */
.detail-navbar {
width: 100%;
height: 372rpx;
margin-bottom: -180rpx; /* 减少负边距避免过度覆盖 */
display: flex;
justify-content: space-between;
position: relative;
background: linear-gradient(180deg, #0A60ED 0%, #FFFFFF 100%);
}
.detail-back {
margin-left: 37rpx;
width: 15rpx;
height: 33rpx;
}
.detail-right {
margin-right: 37rpx;
}
.detail-scan {
width: 36rpx;
height: 35rpx;
}
/* 卡片通用样式 */
.detail-card {
z-index: 20; /* 提高层级确保显示在导航栏之上 */
background-color: #fff;
margin: 20rpx 30rpx;
border-radius: 16rpx;
padding: 30rpx;
border: 1px dashed #ddd;
}
/* 请假信息卡片 */
.leave-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
}
.leave-type {
font-weight: 600;
font-size: 26rpx;
padding: 10rpx 20rpx;
border-radius: 8rpx;
color: #2186FF;
background: rgba(33, 134, 255, 0.1);
border: 1px solid #2186FF;
display: inline-block;
}
.leave-status {
font-size: 26rpx;
padding: 6rpx 20rpx;
border-radius: 8rpx;
color: #07C78E;
background-color: rgba(7, 199, 142, 0.1);
border: 1px solid #07C78E;
display: inline-block;
}
.applicant-info {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.applicant-avatar {
width: 60rpx;
height: 60rpx;
border-radius: 30rpx;
background-color: #4B7BF5;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
margin-right: 20rpx;
}
.applicant-detail {
display: flex;
flex-direction: column;
}
.applicant-name {
font-size: 28rpx;
color: #333;
font-weight: 500;
margin-bottom: 6rpx;
}
.applicant-dept {
font-size: 24rpx;
color: #666;
}
.submit-time {
font-size: 24rpx;
color: #999;
}
/* 申批详情 */
.detail-title {
font-size: 30rpx;
color: #333;
font-weight: bold;
margin-bottom: 30rpx;
}
.detail-item {
display: flex;
margin-bottom: 20rpx;
}
.detail-label {
width: 150rpx;
font-size: 26rpx;
color: #666;
}
.detail-value {
flex: 1;
font-size: 26rpx;
color: #333;
}
/* 垂直布局的申批详情 */
.detail-item-vertical {
display: flex;
flex-direction: column;
margin-bottom: 20rpx;
}
.detail-label-vertical {
font-size: 26rpx;
color: #666;
margin-bottom: 10rpx;
}
.detail-value-vertical {
font-size: 26rpx;
color: #333;
margin-bottom: 10rpx;
}
/* 附件样式 */
.attachment {
display: flex;
align-items: center;
}
.attachment-preview {
width: 80rpx;
height: 80rpx;
background-color: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
border-radius: 8rpx;
overflow: hidden;
}
.attachment-img {
width: 80rpx;
height: 80rpx;
}
.attachment-info {
display: flex;
flex-direction: column;
}
.attachment-name {
font-size: 26rpx;
color: #333;
margin-bottom: 6rpx;
}
.attachment-size {
font-size: 22rpx;
color: #999;
}
/* 审批记录 */
.approval-item {
display: flex;
margin-bottom: 30rpx;
}
.approval-status {
width: 30rpx;
display: flex;
flex-direction: column;
align-items: center;
margin-right: 20rpx;
}
.status-dot {
width: 20rpx;
height: 20rpx;
border-radius: 10rpx;
border: 1px solid #ddd;
background-color: #fff;
margin-bottom: 10rpx;
}
.status-dot.selected {
background-color: #2186FF;
border-color: #2186FF;
}
.status-line {
width: 2rpx;
flex: 1;
background-color: #ddd;
}
.approval-content {
flex: 1;
}
.approval-type {
font-size: 26rpx;
color: #333;
margin-bottom: 10rpx;
}
.approver-info {
display: flex;
align-items: center;
margin-bottom: 10rpx;
}
.approver-avatar {
width: 40rpx;
height: 40rpx;
border-radius: 20rpx;
background-color: #4B7BF5;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 22rpx;
margin-right: 10rpx;
}
.approver-name {
font-size: 26rpx;
color: #333;
margin-right: 20rpx;
}
.approval-result {
font-size: 24rpx;
color: #07C78E;
}
.approval-time {
font-size: 24rpx;
color: #999;
}
</style>

View File

@@ -0,0 +1,257 @@
<template>
<view class="workbench-container">
<view class="workbench-header">
<view class="workbench-topbg"></view>
<!-- 顶部蓝色背景和搜索栏 -->
<view class="workbench-search-row">
<view class="search-bar">
<image class="search-icon" src="/static/ic_search_gray.png"/>
<input class="search-input" placeholder="搜索" />
</view>
<text class="search-btn">搜索</text>
</view>
<!-- banner 区域 -->
<view class="workbench-banner">
<image class="banner-img" src="/static/workbench_banner.png" mode="aspectFill" />
</view>
</view>
<!-- 常用应用九宫格 -->
<view class="workbench-grid">
<view class="grid-item" v-for="(item, idx) in commonApps" :key="idx">
<image :src="item.icon" class="grid-icon" />
<text class="grid-text">{{ item.text }}</text>
</view>
</view>
<!-- 全部应用 -->
<view >
<view class="all-apps-title">全部应用</view>
<view class="all-apps-tabs">
<text v-for="(tab, idx) in tabs" :key="idx" :class="['tab', {active: idx === activeTab}]"
@click="activeTab = idx">{{ tab }}</text>
</view>
<view class="line"></view>
<view class="workbench-grid">
<view class="grid-item" v-for="(item, idx) in allApps[activeTab]" :key="idx">
<image :src="item.icon" class="grid-icon" />
<text class="grid-text">{{ item.text }}</text>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'Workbench',
data() {
return {
commonApps: [
{ icon: 'https://picsum.photos/80/80?random=3', text: '审批' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '假勤' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '工单' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '停车' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '保洁' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '邀约' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '会议' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '添加常用' }
],
tabs: ['最近使用', 'OA 管理', '敏捷开发', '协同办公'],
activeTab: 0,
allApps: [
// 最近使用
[
{ icon: 'https://picsum.photos/80/80?random=3', text: '文档' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '表格' },
{ icon: 'https://picsum.photos/80/80?random=3', text: 'PPT' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '笔记' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '录音' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '云打印' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '智能门禁' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '易企秀H5' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '多维表格' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '问卷网' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '集客云' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '收票宝' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '图表' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '消息' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '沟通' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '' }
],
// OA 管理
[
{ icon: 'https://picsum.photos/80/80?random=3', text: '文档' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '表格' },
{ icon: 'https://picsum.photos/80/80?random=3', text: 'PPT' }
],
// 敏捷开发
[
{ icon: 'https://picsum.photos/80/80?random=3', text: '集客云' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '问卷网' }
],
// 协同办公
[
{ icon: 'https://picsum.photos/80/80?random=3', text: '笔记' },
{ icon: 'https://picsum.photos/80/80?random=3', text: '沟通' }
]
]
}
}
}
</script>
<style scoped>
.workbench-container {
min-height: 100vh;
background: #fff;
padding-bottom: 120rpx;
display: flex;
flex-direction: column;
}
.workbench-header {
position: relative;
height: 463rpx;
width: 100vw;
margin-bottom: -40rpx;
flex-shrink: 0;
}
.workbench-topbg {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 463rpx;
background-image: linear-gradient(to bottom, #075EED, #FFFFFF);
z-index: 0;
}
.workbench-search-row {
position: relative;
z-index: 3;
display: flex;
align-items: center;
color: #fff;
padding: 80rpx 44rpx 0 34rpx;
}
.search-bar {
flex: 1;
display: flex;
align-items: center;
background: #fff;
border-radius: 30rpx;
padding: 0 20rpx;
height: 56rpx;
}
.search-icon {
width: 27rpx;
height: 27rpx;
margin-right: 8rpx;
}
.search-input {
border: none;
font-size: 26rpx;
flex: 1;
color: #000;
}
.search-btn {
color: #fff;
font-size: 30rpx;
margin-left: 10rpx;
}
.workbench-banner {
width: 92vw;
height: 140rpx;
margin: 0 auto 20rpx auto;
border-radius: 20rpx;
overflow: hidden;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
}
.banner-img {
width: 100%;
height: 140rpx;
border-radius: 20rpx;
}
.workbench-grid {
display: flex;
flex-wrap: wrap;
background: #fff;
border-radius: 20rpx;
padding-left: 30rpx;
padding-right: 30rpx;
}
.grid-item {
width: 25%;
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 32rpx;
}
.grid-icon {
width: 98rpx;
height: 98rpx;
margin-bottom: 20rpx;
}
.grid-text {
font-size: 24rpx;
color: #1D1D1D;
font-weight: 500;
}
.all-apps-title {
font-size: 32rpx;
font-weight: bold;
color: #333333;
margin-bottom: 33rpx;
margin-left: 35rpx;
}
.all-apps-tabs {
display: flex;
align-items: center;
padding-left: 54rpx;
padding-right: 54rpx;
}
.tab {
font-size: 24rpx;
color: #999;
margin-right: 32rpx;
padding-bottom: 6rpx;
border-bottom: 4rpx solid transparent;
}
.tab.active {
color: #2e6cf6;
border-bottom: 4rpx solid #2e6cf6;
}
.all-apps-grid {
display: flex;
flex-wrap: wrap;
background: #fff;
border-radius: 20rpx;
padding: 0 54rpx 0 54rpx;
}
.line{
background: #DCDCDC;
height: 2rpx;
margin-bottom: 40rpx;
}
</style>

BIN
static/ic_add_repair_01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

BIN
static/ic_arrow_gray.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

BIN
static/ic_back.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

BIN
static/ic_back_white.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

BIN
static/ic_close_01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
static/ic_i_c_01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
static/ic_i_c_02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
static/ic_i_c_03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
static/ic_i_c_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 KiB

BIN
static/ic_login_agree.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
static/ic_login_code.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
static/ic_login_dis.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
static/ic_login_phone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 B

BIN
static/ic_login_topbg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 KiB

BIN
static/ic_main_home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
static/ic_main_mine.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
static/ic_main_work.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

BIN
static/ic_mine_check.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1004 B

BIN
static/ic_mine_head.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
static/ic_mine_info.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 848 B

BIN
static/ic_mine_notice.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

BIN
static/ic_mine_pay.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 B

BIN
static/ic_mine_pwd.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

BIN
static/ic_mine_repair.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
static/ic_mine_setting.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
static/ic_mine_setting2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
static/ic_mine_topbg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

BIN
static/ic_mine_version.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
static/ic_mine_visitor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
static/ic_msg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
static/ic_msg_01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

BIN
static/ic_msg_empty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

BIN
static/ic_my_payment_01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 947 KiB

BIN
static/ic_my_payment_02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
static/ic_my_repair_01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 KiB

BIN
static/ic_my_repair_02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
static/ic_my_repair_03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

BIN
static/ic_my_visitor_01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
static/ic_my_visitor_02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
static/ic_repaired_01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
static/ic_s_c_01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

BIN
static/ic_s_c_02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 KiB

BIN
static/ic_scan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 B

BIN
static/ic_search.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 962 B

BIN
static/ic_search_gray.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
static/ic_share_w.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
static/ic_tq.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
static/ic_work_01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 682 KiB

BIN
static/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1,144 @@
# 媒体选择器工具类 (MediaSelector)
这是一个用于在uni-app项目中选择本地图片和视频的工具类提供了统一的接口和灵活的配置选项。
## 功能特点
- 支持选择图片、视频或让用户选择图片/视频
- 支持设置选择数量
- 支持设置是否压缩
- 支持图片裁剪
- 支持设置视频最大时长
- 支持设置使用的摄像头(前置/后置)
- 支持设置允许的文件扩展名
- 提供媒体预览功能
## 安装和使用
### 1. 导入工具类
```javascript
import MediaSelector, { MediaType } from '@/utils/mediaSelector';
```
### 2. 选择图片
```javascript
// 选择最多9张图片
const images = await MediaSelector.choose({
type: MediaType.IMAGE,
count: 9,
compressed: true,
crop: false
});
console.log('选择的图片:', images);
```
### 3. 选择视频
```javascript
// 选择最多1个视频
const videos = await MediaSelector.choose({
type: MediaType.VIDEO,
count: 1,
compressed: true,
videoMaxDuration: 60 // 最长60秒
});
console.log('选择的视频:', videos);
```
### 4. 选择图片或视频(用户可选择)
```javascript
// 让用户选择图片或视频
const media = await MediaSelector.choose({
type: MediaType.BOTH,
count: 5
});
console.log('选择的媒体:', media);
```
### 5. 预览媒体
```javascript
// 预览媒体(自动判断类型)
if (media.length > 0) {
MediaSelector.preview(media[0].path, media[0].type);
}
```
## API 文档
### MediaType 枚举
```javascript
export const MediaType = {
IMAGE: 'image', // 图片
VIDEO: 'video', // 视频
BOTH: 'both' // 图片和视频
};
```
### MediaSelector.choose(options)
选择媒体文件(图片或视频)。
#### 参数
| 参数名 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| options.type | string | 'image' | 媒体类型,可选值:'image'、'video'、'both' |
| options.count | number | 9 | 最大选择数量 |
| options.imageExtensions | Array<string> | ['png', 'jpg', 'jpeg', 'gif', 'webp'] | 图片扩展名 |
| options.videoExtensions | Array<string> | ['mp4', 'mov', '3gp', 'avi', 'rmvb', 'rm', 'flv', 'mkv'] | 视频扩展名 |
| options.compressed | boolean | true | 是否压缩所选文件 |
| options.crop | boolean | false | 是否裁剪(仅对图片有效) |
| options.videoMaxDuration | number | 60 | 拍摄视频最长拍摄时间,单位秒 |
| options.camera | string | 'back' | 使用的摄像头,可选值:'back'、'front' |
#### 返回值
返回一个Promise解析为选择的媒体文件数组。每个媒体文件对象包含以下属性
- **图片对象属性**
- path: 文件路径
- size: 文件大小(字节)
- name: 文件名
- type: 媒体类型('image'
- extension: 文件扩展名
- createTime: 创建时间戳
- **视频对象属性**
- path: 文件路径
- size: 文件大小(字节)
- duration: 视频时长(秒)
- width: 视频宽度
- height: 视频高度
- name: 文件名
- type: 媒体类型('video'
- extension: 文件扩展名
- createTime: 创建时间戳
### MediaSelector.preview(path, type)
预览媒体文件。
#### 参数
| 参数名 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| path | string | - | 文件路径 |
| type | string | 自动判断 | 媒体类型,可选值:'image'、'video',默认根据文件扩展名自动判断 |
## 注意事项
1. 由于uni.chooseVideo一次只能选择一个视频当设置count>1时工具类会引导用户多次选择。
2. 视频预览功能需要一个视频播放页面,默认路径为`/pages/common/video-player`,如果您的项目中没有此页面,请自行创建或修改预览方法。
3. 在某些平台上,部分功能可能受到限制,请根据实际情况调整使用方式。
## 示例代码
完整的使用示例请参考 `mediaSelector.example.js` 文件。

View File

@@ -0,0 +1,136 @@
/**
* 媒体选择器使用示例
*/
// 导入媒体选择器工具类
import MediaSelector, { MediaType } from './mediaSelector';
/**
* 选择图片示例
*/
export async function chooseImagesExample() {
try {
// 选择最多9张图片
const images = await MediaSelector.choose({
type: MediaType.IMAGE,
count: 9,
compressed: true,
crop: false
});
console.log('选择的图片:', images);
return images;
} catch (error) {
console.error('选择图片失败:', error);
uni.showToast({
title: '选择图片失败',
icon: 'none'
});
return [];
}
}
/**
* 选择视频示例
*/
export async function chooseVideosExample() {
try {
// 选择最多1个视频
const videos = await MediaSelector.choose({
type: MediaType.VIDEO,
count: 1,
compressed: true,
videoMaxDuration: 60 // 最长60秒
});
console.log('选择的视频:', videos);
return videos;
} catch (error) {
console.error('选择视频失败:', error);
uni.showToast({
title: '选择视频失败',
icon: 'none'
});
return [];
}
}
/**
* 选择图片或视频示例(用户可选择)
*/
export async function chooseBothExample() {
try {
// 让用户选择图片或视频
const media = await MediaSelector.choose({
type: MediaType.BOTH,
count: 5
});
console.log('选择的媒体:', media);
return media;
} catch (error) {
console.error('选择媒体失败:', error);
uni.showToast({
title: '选择媒体失败',
icon: 'none'
});
return [];
}
}
/**
* 预览媒体示例
* @param {Object} mediaItem 媒体项
*/
export function previewMediaExample(mediaItem) {
if (!mediaItem || !mediaItem.path) {
uni.showToast({
title: '无效的媒体文件',
icon: 'none'
});
return;
}
// 预览媒体(自动判断类型)
MediaSelector.preview(mediaItem.path, mediaItem.type);
}
/**
* 在页面中使用的完整示例
*/
export default {
data() {
return {
mediaList: [] // 存储选择的媒体列表
};
},
methods: {
// 选择图片
async chooseImages() {
const images = await chooseImagesExample();
this.mediaList = [...this.mediaList, ...images];
},
// 选择视频
async chooseVideos() {
const videos = await chooseVideosExample();
this.mediaList = [...this.mediaList, ...videos];
},
// 选择图片或视频
async chooseBoth() {
const media = await chooseBothExample();
this.mediaList = [...this.mediaList, ...media];
},
// 预览媒体
previewMedia(item) {
previewMediaExample(item);
},
// 删除媒体
deleteMedia(index) {
this.mediaList.splice(index, 1);
}
}
};

328
utils/mediaSelector.js Normal file
View File

@@ -0,0 +1,328 @@
/**
* 媒体选择器工具类
* 用于选择本地图片和视频
* 支持设置选择类型(图片、视频或两者)和选择数量
*/
// 媒体类型枚举
export const MediaType = {
IMAGE: 'image',
VIDEO: 'video',
BOTH: 'both'
};
/**
* 媒体选择器类
*/
export default class MediaSelector {
/**
* 选择媒体文件(图片或视频)
* @param {Object} options 选择配置项
* @param {string} options.type 媒体类型,可选值:'image'、'video'、'both',默认为'image'
* @param {number} options.count 最大选择数量默认为9
* @param {Array<string>} options.imageExtensions 图片扩展名,默认为['png', 'jpg', 'jpeg', 'gif', 'webp']
* @param {Array<string>} options.videoExtensions 视频扩展名,默认为['mp4', 'mov', '3gp', 'avi', 'rmvb', 'rm', 'flv', 'mkv']
* @param {boolean} options.compressed 是否压缩所选文件默认为true
* @param {boolean} options.crop 是否裁剪仅对图片有效默认为false
* @param {number} options.videoMaxDuration 拍摄视频最长拍摄时间单位秒默认为60
* @param {string} options.camera 使用的摄像头,可选值:'back'、'front',默认为'back'
* @returns {Promise<Array>} 返回选择的媒体文件数组
*/
static async choose(options = {}) {
const {
type = MediaType.IMAGE,
count = 9,
imageExtensions = ['png', 'jpg', 'jpeg', 'gif', 'webp'],
videoExtensions = ['mp4', 'mov', '3gp', 'avi', 'rmvb', 'rm', 'flv', 'mkv'],
compressed = true,
crop = false,
videoMaxDuration = 60,
camera = 'back'
} = options;
// 根据类型选择不同的媒体
if (type === MediaType.IMAGE) {
return this.chooseImages({ count, extensions: imageExtensions, compressed, crop, camera });
} else if (type === MediaType.VIDEO) {
return this.chooseVideos({ count, extensions: videoExtensions, compressed, maxDuration: videoMaxDuration, camera });
} else if (type === MediaType.BOTH) {
// 如果是两者都选,则先让用户选择类型
return new Promise((resolve, reject) => {
uni.showActionSheet({
itemList: ['选择图片', '选择视频'],
success: async (res) => {
try {
if (res.tapIndex === 0) {
// 选择图片
const images = await this.chooseImages({ count, extensions: imageExtensions, compressed, crop, camera });
resolve(images);
} else {
// 选择视频
const videos = await this.chooseVideos({ count, extensions: videoExtensions, compressed, maxDuration: videoMaxDuration, camera });
resolve(videos);
}
} catch (error) {
reject(error);
}
},
fail: (err) => {
reject(err);
}
});
});
} else {
throw new Error('不支持的媒体类型');
}
}
/**
* 选择图片
* @param {Object} options 选择图片的配置项
* @returns {Promise<Array>} 返回选择的图片数组
*/
static chooseImages(options) {
const { count = 9, extensions = ['png', 'jpg', 'jpeg', 'gif', 'webp'], compressed = true, crop = false, camera = 'back' } = options;
return new Promise((resolve, reject) => {
uni.chooseImage({
count,
sizeType: compressed ? ['compressed'] : ['original'],
sourceType: ['album', 'camera'],
extension: extensions,
crop: crop ? {
quality: 100,
width: 300,
height: 300,
resize: true
} : false,
camera,
success: (res) => {
// 处理返回的图片数据,统一格式
const images = res.tempFiles.map(file => ({
path: file.path,
size: file.size,
name: this.getFileName(file.path),
type: 'image',
extension: this.getFileExtension(file.path),
createTime: new Date().getTime()
}));
resolve(images);
},
fail: (err) => {
// 用户取消选择不报错
if (err.errMsg.indexOf('cancel') !== -1) {
resolve([]);
} else {
reject(err);
}
}
});
});
}
/**
* 选择视频
* @param {Object} options 选择视频的配置项
* @returns {Promise<Array>} 返回选择的视频数组
*/
static chooseVideos(options) {
const { count = 1, extensions = ['mp4', 'mov', '3gp', 'avi', 'rmvb', 'rm', 'flv', 'mkv'], compressed = true, maxDuration = 60, camera = 'back' } = options;
// 由于uni.chooseVideo一次只能选择一个视频如果count>1需要多次选择
if (count <= 1) {
return this.chooseSingleVideo({ extensions, compressed, maxDuration, camera });
} else {
// 提示用户需要多次选择
return new Promise((resolve, reject) => {
uni.showModal({
title: '提示',
content: `您需要选择${count}个视频,将分${count}次选择`,
confirmText: '开始选择',
cancelText: '取消',
success: async (res) => {
if (res.confirm) {
try {
const videos = [];
for (let i = 0; i < count; i++) {
// 显示当前选择进度
uni.showLoading({
title: `正在选择第${i + 1}/${count}个视频`,
mask: true
});
// 选择单个视频
const videoResult = await this.chooseSingleVideo({ extensions, compressed, maxDuration, camera });
// 隐藏加载提示
uni.hideLoading();
// 如果用户取消了选择,则结束循环
if (videoResult.length === 0) {
break;
}
// 添加到结果数组
videos.push(...videoResult);
// 如果还没选完,询问是否继续
if (i < count - 1) {
const continueRes = await new Promise((resolveDialog) => {
uni.showModal({
title: '提示',
content: `已选择${videos.length}个视频,是否继续选择?`,
confirmText: '继续',
cancelText: '完成',
success: (modalRes) => {
resolveDialog(modalRes.confirm);
}
});
});
// 如果用户选择不继续,则结束循环
if (!continueRes) {
break;
}
}
}
resolve(videos);
} catch (error) {
uni.hideLoading();
reject(error);
}
} else {
resolve([]);
}
},
fail: (err) => {
reject(err);
}
});
});
}
}
/**
* 选择单个视频
* @param {Object} options 选择视频的配置项
* @returns {Promise<Array>} 返回选择的视频数组
*/
static chooseSingleVideo(options) {
const { extensions = ['mp4', 'mov', '3gp', 'avi', 'rmvb', 'rm', 'flv', 'mkv'], compressed = true, maxDuration = 60, camera = 'back' } = options;
return new Promise((resolve, reject) => {
uni.chooseVideo({
sourceType: ['album', 'camera'],
compressed,
maxDuration,
camera,
extension: extensions,
success: (res) => {
// 处理返回的视频数据,统一格式
const video = {
path: res.tempFilePath,
size: res.size,
duration: res.duration,
width: res.width,
height: res.height,
name: this.getFileName(res.tempFilePath),
type: 'video',
extension: this.getFileExtension(res.tempFilePath),
createTime: new Date().getTime()
};
resolve([video]);
},
fail: (err) => {
// 用户取消选择不报错
if (err.errMsg.indexOf('cancel') !== -1) {
resolve([]);
} else {
reject(err);
}
}
});
});
}
/**
* 获取文件名
* @param {string} path 文件路径
* @returns {string} 文件名
*/
static getFileName(path) {
if (!path) return '';
return path.substring(path.lastIndexOf('/') + 1);
}
/**
* 获取文件扩展名
* @param {string} path 文件路径
* @returns {string} 文件扩展名
*/
static getFileExtension(path) {
if (!path) return '';
return path.substring(path.lastIndexOf('.') + 1).toLowerCase();
}
/**
* 预览媒体文件
* @param {string} path 文件路径
* @param {string} type 媒体类型,可选值:'image'、'video',默认根据文件扩展名自动判断
*/
static preview(path, type) {
if (!path) return;
// 如果未指定类型,则根据文件扩展名判断
if (!type) {
const extension = this.getFileExtension(path);
if (['png', 'jpg', 'jpeg', 'gif', 'webp'].includes(extension)) {
type = MediaType.IMAGE;
} else if (['mp4', 'mov', '3gp', 'avi', 'rmvb', 'rm', 'flv', 'mkv'].includes(extension)) {
type = MediaType.VIDEO;
} else {
uni.showToast({
title: '不支持的文件类型',
icon: 'none'
});
return;
}
}
// 根据类型预览
if (type === MediaType.IMAGE) {
uni.previewImage({
urls: [path],
current: path
});
} else if (type === MediaType.VIDEO) {
// 视频预览
uni.navigateTo({
url: `/pages/common/video-player?url=${encodeURIComponent(path)}`
});
}
}
}
/**
* 使用示例:
*
* // 导入
* import MediaSelector, { MediaType } from '@/utils/mediaSelector';
*
* // 选择图片
* const images = await MediaSelector.choose({ type: MediaType.IMAGE, count: 9 });
* console.log('选择的图片:', images);
*
* // 选择视频
* const videos = await MediaSelector.choose({ type: MediaType.VIDEO, count: 1 });
* console.log('选择的视频:', videos);
*
* // 选择图片或视频(用户可选择)
* const media = await MediaSelector.choose({ type: MediaType.BOTH, count: 5 });
* console.log('选择的媒体:', media);
*
* // 预览媒体
* if (media.length > 0) {
* MediaSelector.preview(media[0].path, media[0].type);
* }
*/

View File

@@ -8,7 +8,7 @@ module.exports = {
disableHostCheck : true,
proxy : {
"/js" : {
target : "http://192.168.0.104:8080",
target : "http://tc.cqsznc.com:7080",
// target : "https://demo.aidex.vip",
changeOrigin : true,
secure : false