This commit is contained in:
ChuShiZ 2024-09-22 15:07:01 +08:00
commit 1a4a33174a
369 changed files with 43486 additions and 0 deletions

16
.hbuilderx/launch.json Normal file
View File

@ -0,0 +1,16 @@
{ // launch.json configurations app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
// launchtypelocalremote, localremote
"version": "0.0",
"configurations": [{
"default" :
{
"launchtype" : "local"
},
"mp-weixin" :
{
"launchtype" : "local"
},
"type" : "uniCloud"
}
]
}

5
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/

23
App.vue Normal file
View File

@ -0,0 +1,23 @@
<script>
export default {
onLaunch: function() {
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
</script>
<style>
/*每个页面公共css */
page,
uni-page-body,
html,
body {
height: 100%;
}
</style>

72
api/login.js Normal file
View File

@ -0,0 +1,72 @@
import request from '@/utils/request'
// 登录方法
export function login(username, password, code, uuid) {
const data = {
username,
password,
code,
uuid
}
return request({
'url': '/rescue/login',
headers: {
isToken: false
},
'method': 'post',
'data': data
})
}
// 获取用户详细信息
export function getInfo() {
return request({
'url': '/rescue/getInfo',
'method': 'get'
})
}
// 退出方法
export function logout() {
return request({
'url': '/rescue/logout',
'method': 'post'
})
}
// 获取验证码
export function getCodeImg() {
return request({
'url': '/rescue/captchaImage',
headers: {
isToken: false,
},
method: 'get',
timeout: 20000
})
}
// 获取验证码
export function loginApp(data) {
return request({
'url': '/rescue/loginApp',
headers: {
isToken: false
},
method: 'post',
timeout: 20000,
data
})
}
// 使用租户域名(标识),获得租户编号
export function getTenantIdByWebsite(website) {
return request({
url: '/system/tenant/getListByWebsite',
method: 'get',
params: {
website
}
})
}

41
api/system/user.js Normal file
View File

@ -0,0 +1,41 @@
import upload from '@/utils/upload'
import request from '@/utils/request'
// 用户密码重置
export function updateUserPwd(oldPassword, newPassword) {
const data = {
oldPassword,
newPassword
}
return request({
url: '/system/user/profile/updatePwd',
method: 'put',
params: data
})
}
// 查询用户个人信息
export function getUserProfile() {
return request({
url: '/system/user/profile',
method: 'get'
})
}
// 修改用户个人信息
export function updateUserProfile(data) {
return request({
url: '/system/user/profile',
method: 'put',
data: data
})
}
// 用户头像上传
export function uploadAvatar(data) {
return upload({
url: '/system/user/profile/avatar',
name: data.name,
filePath: data.filePath
})
}

View File

@ -0,0 +1,112 @@
<template>
<!-- header -->
<view :style="{ backgroundColor, paddingTop: homeHeaderPaddingTop + 'px', height: homeHeaderMenuHeight + 'px' }"
class="navigationBar" :class="{leftTitle: leftTitle}">
<template v-if="leftTitle">
<view class="navigationBarTitle" :style="{ color: titleColor }">
{{ title ? title : '' }}
</view>
</template>
<template v-else>
<view class="navigationBarBack" @click="back" >
<slot name="back">
<uni-icons size="24" type="left"
:color="titleColor"></uni-icons>
</slot>
</view>
<view class="navigationBarTitle" :style="{ color: titleColor }">
{{ title }}
</view>
</template>
<view class="navigationBarBackExtra">
<slot name="extra">
</slot?>
</view>
</view>
</template>
<script>
/* 计算标题位置 */
import {
getWXStatusHeight
} from "@/utils/utils";
export default {
props: {
backgroundColor: {
type: String,
default: '#317DFA'
},
title: String,
titleColor: {
type: String,
default: '#fff'
},
leftTitle: {
type: Boolean,
default: false
}
},
mounted() {
// #ifdef MP
const {
barHeight,
barTop,
menuButtonLeft
} = getWXStatusHeight()
console.log('barHeight, barTop, menuButtonLeft: ', barHeight, barTop, menuButtonLeft);
this.homeHeaderPaddingTop = barTop || 0
this.homeHeaderMenuHeight = barHeight
this.homeHeaderMenuLeft = menuButtonLeft - 6
// #endif
},
data() {
return {
// #ifdef MP
homeHeaderPaddingTop: 0,
homeHeaderMenuHeight: 0,
homeHeaderMenuLeft: 0,
// #endif
// #ifdef APP || H5
homeHeaderPaddingTop: 20,
homeHeaderMenuHeight: 50,
homeHeaderMenuLeft: 0
// #endif
}
},
methods: {
back() {
uni.navigateBack()
}
}
}
</script>
<style lang="scss" scoped>
.navigationBar {
position: relative;
display: flex;
align-items: center;
justify-content: center;
&.leftTitle {
justify-content: start;
padding-left: 28rpx;
}
.navigationBarBack {
position: absolute;
left: 20rpx;
}
.navigationBarBackExtra {
position: absolute;
right: 20rpx;
}
.navigationBarTitle {
font-size: 36rpx;
font-weight: bold;
}
}
</style>

View File

@ -0,0 +1,138 @@
<template>
<view>
<view style="position: relative" v-if="isHide">
<view class="dt-content" :style="'-webkit-line-clamp:'+line">
<text class="content">
<slot>{{ dt ? dt : '' }}</slot>
</text>
</view>
<view class="button-show" @tap="isHide = false" v-if="enableButton&&lines>line">
<text style="color: blue">{{ expandText }}</text>
</view>
</view>
<view v-else>
<view>
<text class="content">
<slot>{{ dt ? dt : '' }}</slot>
</text>
</view>
<view class="fold-hint" v-if="foldHint">
<view @tap="isHide = true">
{{ foldHint }}
</view>
</view>
</view>
<view>
<text class="placeholder">
{{ placeholder }}
</text>
</view>
</view>
</template>
<script>
export default {
data() {
return {
//
isHide: true,
//
textHeight: 0,
//
lineHeight: 1,
//
placeholder: '占位'
};
},
props: {
//
line: {
type: [Number, String],
default: 1
},
//
dt: {
type: [String],
default: ''
},
enableButton: {
type: Boolean,
default: true
},
//
expandText: {
type: String,
default: "展开"
},
//
foldHint: {
type: String,
default: "收起"
}
},
watch:{
dt(){
let that = this
setTimeout(() => {
let query = uni.createSelectorQuery().in(that);
// html
query.select('.content').boundingClientRect(data => {
that.textHeight = data.height
}).exec();
}, 100)
}
},
mounted() {
if (this.enableButton) {
let query = uni.createSelectorQuery().in(this);
// html
query.select('.content').boundingClientRect(data => {
this.textHeight = data.height
}).exec();
//
query.select('.placeholder').boundingClientRect(data => {
this.lineHeight = data.height
}).exec();
}
// 使
this.placeholder = ''
},
computed: {
//
lines() {
if (!this.enableButton) {
return this.line
}
return Math.floor(this.textHeight > 0 && this.lineHeight > 0 ? this.textHeight / this.lineHeight : 0)
}
}
}
</script>
<style scoped>
.dt-content {
overflow: hidden;
text-overflow: clip;
display: -webkit-box;
-webkit-box-orient: vertical;
}
.button-show {
width: 70rpx;
position: absolute;
right: 0;
bottom: 0;
z-index: 0;
text-align: right;
background-image: linear-gradient(-180deg, rgba(233, 236, 239, 0) 50%, #FFF 80%);
padding-top: 2rem;
}
.fold-hint {
color: blue;
text-align: right
}
</style>

View File

@ -0,0 +1,172 @@
<template>
<view class="orderInfo">
<view class="header">
<text class="orderTitle">{{ orderInfo.title }}</text>
<text class="orderStatus" :class="['status_' + orderInfo.status]">
{{ getOrderStatusTxt() }}
</text>
</view>
<view class="detail">
<view class="line">
<image class="line-icon" src="../../static/icons/order-icon1.png" mode="aspectFit"></image>
<text class="line-text">{{orderInfo.address}}</text>
</view>
<view class="line">
<image class="line-icon" src="../../static/icons/order-icon2.png" mode="aspectFit"></image>
<text class="line-text">{{orderInfo.phone}}</text>
</view>
<view class="line">
<image class="line-icon" src="../../static/icons/order-icon3.png" mode="aspectFit"></image>
<text class="line-text">{{orderInfo.busiTypeStr}}</text>
</view>
</view>
<view v-if="!hideFooter" class="footer">
<view class="btn" :class="{phone: isDetail}">
<image v-if="isDetail" class="btnIcon" src="../../static/icons/order-icon7.png" mode="aspectFit"></image>
<text>拨打电话</text>
</view>
<view class="btn" :class="{address: isDetail}">
<image v-if="isDetail" class="btnIcon" src="../../static/icons/order-icon6.png" mode="aspectFit"></image>
<text>地址导航</text>
</view>
<view v-if="!isDetail" class="btn primary" @click="showOrderDetail">查看订单</view>
</view>
</view>
</template>
<script>
export default {
name: "reservationOrder",
props: {
orderInfo: {
type: Object,
default: () => {
return {}
}
},
isDetail: {
type: Boolean,
default: false
},
hideFooter: {
type: Boolean,
default: false
}
},
data() {
return {
};
},
methods: {
getOrderStatusTxt() {
switch (this.orderInfo.status) {
case '1':
return '等待维修'
default:
return ''
}
},
showOrderDetail() {
uni.navigateTo({
url: '/pages/orderDetail/orderDetail'
})
}
}
}
</script>
<style lang="less">
.orderInfo {
background: #FFFFFF;
border-radius: 12rpx 12rpx 12rpx 12rpx;
box-shadow: 2rpx 4rpx 8rpx #efefef;
padding: 0 30rpx;
.header {
display: flex;
align-items: center;
border-bottom: 1rpx solid #EEEEEE;
padding: 30rpx 0;
.orderTitle {
flex: 1;
width: 0;
font-weight: bold;
font-size: 32rpx;
color: #333333;
}
.orderStatus {
font-weight: 500;
font-size: 24rpx;
&.status_1 {
color: #0174F6;
}
}
}
.detail {
margin-bottom: 30rpx;
border-bottom: 1rpx solid #EEEEEE;;
.line {
margin: 30rpx 0;
display: flex;
align-items: center;
column-gap: 10rpx;
font-weight: 500;
font-size: 24rpx;
color: #999999;
}
.line-icon {
width: 28rpx;
height: 28rpx;
}
}
.footer {
display: flex;
align-items: center;
column-gap: 20rpx;
.btn {
margin-bottom: 30rpx;
flex: 1;
width: 0;
height: 60rpx;
border-radius: 30rpx 30rpx 30rpx 30rpx;
border: 1rpx solid #0174F6;
font-weight: 500;
font-size: 28rpx;
color: #0174F6;
display: flex;
align-items: center;
justify-content: center;
column-gap: 8rpx;
&.primary {
background: #0174F6;
color: #fff;
color: #FFFFFF;
}
&.address {
background: #0174F6;
color: #fff;
border: none;
}
&.phone {
background: #E8A321;
color: #fff;
border: none;
}
.btnIcon {
width: 32rpx;
height: 32rpx;
}
}
}
}
</style>

View File

@ -0,0 +1,172 @@
<template>
<view class="bottoms">
<view class="box" :class="{active: aindex == 1}" @click="getgogo(1)" v-if="identity == 'user' ">
<view class="imgs">
<image mode="aspectFit" src="@/static/icons/tabbar/home.png" v-show="aindex != 1"></image>
<image mode="aspectFit" src="@/static/icons/tabbar/home-checked.png" v-show="aindex == 1"></image>
</view>
<view class="">首页</view>
</view>
<view class="box" :class="{active: aindex == 2}" @click="getgogo(2)">
<view class="imgs">
<image mode="aspectFit" src="@/static/icons/tabbar/order.png" v-show="aindex != 2"></image>
<image mode="aspectFit" src="@/static/icons/tabbar/order-checked.png" v-show="aindex == 2"></image>
</view>
<view class="">订单</view>
</view>
<view class="box" :class="{active: aindex == 3}" @click="getgogo(3)">
<view class="imgs">
<image mode="aspectFit" src="@/static/icons/tabbar/my.png" v-show="aindex != 3"></image>
<image mode="aspectFit" src="@/static/icons/tabbar/my-checked.png" v-show="aindex == 3"></image>
</view>
<view class="">我的</view>
</view>
</view>
</template>
<script>
import request from '../../utils/request';
import {
getToken
} from '@/utils/auth'
export default {
data() {
return {
aindex: 1,
msgNum: null,
identity: 'user',
nowPageInterval: null,
arr: [{
text: '首页'
},
{
text: '消息'
},
{
text: '运力'
}
]
}
},
props: {
msg: String|Number
},
onLoad() {
console.log('tabbag', uni.getStorageSync('identity'));
},
onUnload() {
if (this.nowPageInterval) {
clearTimeout(this.nowPageInterval);
}
},
mounted() {
// #ifdef APP || H5
this.identity = uni.getStorageSync('identity');
// #endif
this.aindex = this.msg
this.getbottom()
},
methods: {
async getbottom() {
if (!getToken()) {
return
}
let res = await request({
url: '/announcement/announcement/getOwnNoRead',
method: 'get',
})
console.log('jitiao', res);
if (res.code == 200) {
this.msgNum = res.data
}
},
getgogo(index) {
if (index == 1) {
this.aindex = index
uni.reLaunch({
url: '/pages/home/home'
})
}
if (index == 2) {
this.aindex = index
uni.reLaunch({
url: '/pages/orderList/orderList'
})
}
if (index == 3) {
this.aindex = index
uni.reLaunch({
url: '/pages/my/my'
})
}
}
}
}
</script>
<style scoped lang="scss">
.bottoms {
width: 100%;
// position: fixed;
// bottom: 0px;
background: #fff;
display: flex;
justify-content: space-between;
font-size: 14px;
font-weight: 400;
color: #FFFFFF;
padding: 12rpx 40rpx;
box-sizing: border-box;
box-shadow: 4rpx 2rpx 12rpx 0 #c3c3c3;
position: relative;
z-index: 99;
}
.box {
flex: 1;
width: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 12px;
color: #929292;
&.active {
color: #327DFB;
}
}
.imgs {
margin: 0 auto;
width: 50rpx;
height: 50rpx;
image {
width: 100%;
height: 100%;
}
}
.hongdian {
width: 12px;
height: 12px;
background-color: crimson;
border-radius: 50%;
overflow: hidden;
position: absolute;
top: -5px;
right: -8px;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 2px;
}
</style>

30
config.js Normal file
View File

@ -0,0 +1,30 @@
// 应用全局配置
module.exports = {
//baseUrl: 'https://www.nuoyunr.com/admin-api',
baseUrl: 'http://192.168.1.4:48080/admin-api',
imagesUrl: 'http://shequ.0315e.com/static/images/pages/',
baseImageUrl: 'https://www.nuoyunr.com/minio',
//wsUrl: 'wss://www.nuoyunr.com/admin-api',
wsUrl: 'ws://192.168.1.4:48080/admin-api',
// 应用信息
appInfo: {
// 应用名称
name: "道路救援",
// 应用版本
version: "2.6",
// 应用logo
logo: "/static/logo.png'",
// 官方网站
site_url: "http://ruoyi.vip",
// 政策协议
agreements: [{
title: "隐私政策",
url: "https://ruoyi.vip/protocol.html"
},
{
title: "用户服务协议",
url: "https://ruoyi.vip/protocol.html"
}
]
}
}

20
index.html Normal file
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>

22
main.js Normal file
View File

@ -0,0 +1,22 @@
import App from './App'
// #ifndef VUE3
import Vue from 'vue'
import './uni.promisify.adaptor'
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
// #endif
// #ifdef VUE3
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
return {
app
}
}
// #endif

73
manifest.json Normal file
View File

@ -0,0 +1,73 @@
{
"name" : "qixiu",
"appid" : "__UNI__FA456F7",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
/* 5+App */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/* */
"modules" : {},
/* */
"distribute" : {
/* android */
"android" : {
"permissions" : [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios */
"ios" : {},
/* SDK */
"sdkConfigs" : {}
}
},
/* */
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "",
"setting" : {
"urlCheck" : false,
"es6" : true
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "2"
}

9
package.json Normal file
View File

@ -0,0 +1,9 @@
{
"dependencies": {
"uview-ui": "^2.0.36"
},
"devDependencies": {
"sass": "^1.69.5",
"sass-loader": "^10.4.1"
}
}

151
pages.json Normal file
View File

@ -0,0 +1,151 @@
{
"pages": [ //pageshttps://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "uni-app"
}
},
{
"path" : "pages/home/home",
"style" :
{
"navigationBarTitleText" : ""
}
},
{
"path" : "pages/orderList/orderList",
"style" :
{
"navigationBarTitleText" : ""
}
},
{
"path" : "pages/my/my",
"style" :
{
"navigationBarTitleText" : ""
}
},
{
"path" : "pages/myReservation/myReservation",
"style" :
{
"navigationBarTitleText" : ""
}
},
{
"path" : "pages/myCar/myCar",
"style" :
{
"navigationBarTitleText" : ""
}
},
{
"path" : "pages/shopDetail/shopDetail",
"style" :
{
"navigationBarTitleText" : ""
}
},
{
"path" : "pages/orderDetail/orderDetail",
"style" :
{
"navigationBarTitleText" : ""
}
},
{
"path" : "pages/my/myInfo",
"style" :
{
"navigationBarTitleText" : ""
}
},
{
"path" : "pages/myCar/carDetail",
"style" :
{
"navigationBarTitleText" : ""
}
},
{
"path" : "pages/my/myEquity",
"style" :
{
"navigationBarTitleText" : ""
}
},
{
"path" : "pages/my/register",
"style" :
{
"navigationBarTitleText" : ""
}
},
{
"path" : "pages/myReservation/addReservation",
"style" :
{
"navigationBarTitleText" : ""
}
},
{
"path" : "pages/my/message",
"style" :
{
"navigationBarTitleText" : ""
}
},
{
"path" : "pages/my/evaluate",
"style" :
{
"navigationBarTitleText" : ""
}
},
{
"path" : "pages/my/cardRoll",
"style" :
{
"navigationBarTitleText" : ""
}
},
{
"path" : "pages/myReservation/reservationSuccess",
"style" :
{
"navigationBarTitleText" : ""
}
},
{
"path" : "pages/orderDetail/evaluate",
"style" :
{
"navigationBarTitleText" : ""
}
},
{
"path" : "pages/guideList/guideList",
"style" :
{
"navigationBarTitleText" : ""
}
},
{
"path" : "pages/guideList/guideDetail",
"style" :
{
"navigationBarTitleText" : ""
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8",
"navigationStyle": "custom"
},
"uniIdRouter": {}
}

View File

@ -0,0 +1,47 @@
<template>
<view class="container">
<VNavigationBar background-color="rgba(0,0,0,0)" title-color="rgba(0,0,0,0)"></VNavigationBar>
<view class="body">
<view class="title">如何在小程序上下单车辆维修</view>
<view class="content"></view>
</view>
</view>
</template>
<script>
import VNavigationBar from '@/components/VNavigationBar.vue'
export default {
components: {
VNavigationBar
},
data() {
return {
};
}
}
</script>
<style lang="less" scoped>
.container {
height: 100%;
display: flex;
flex-direction: column;
background-color: #f1f1f1;
.body {
background-color: #fff;
flex: 1;
height: 0;
margin: 32rpx;
padding: 30rpx;
display: flex;
flex-direction: column;
row-gap: 20rpx;
.title {
text-align: center;
}
}
}
</style>

View File

@ -0,0 +1,75 @@
<template>
<view class="container">
<v-navigation-bar title="操作指南" background-color="#fff" title-color="#333"></v-navigation-bar>
<view class="body">
<view @click="gotoDetail(item)" v-for="(item,index) in data" :key="index" class="guideItem">
<text class="guide_content">如何在小程序上下单车辆维修</text>
<image class="guideIcon" src="../../static/icons/homeInfoMore.png" mode="aspectFit"></image>
</view>
</view>
</view>
</template>
<script>
import VNavigationBar from '@/components/VNavigationBar.vue'
export default {
components: {
VNavigationBar
},
data() {
return {
data: [{}, {}]
};
},
methods: {
gotoDetail() {
uni.navigateTo({
url: '/pages/guideList/guideDetail'
})
}
}
}
</script>
<style lang="less" scoped>
.container {
background-color: #F3F5F7;
height: 100%;
display: flex;
flex-direction: column;
.body {
flex: 1;
height: 0;
padding: 0 0 20rpx;
display: flex;
flex-direction: column;
.guideItem {
margin: 20rpx 32rpx 0;
background: #FFFFFF;
border-radius: 12rpx 12rpx 12rpx 12rpx;
padding: 40rpx 30rpx;
display: flex;
align-items: center;
column-gap: 10rpx;
font-size: 32rpx;
color: #333333;
.guide_content {
flex: 1;
width: 0;
}
.guideIcon {
width: 24rpx;
height: 24rpx;
}
}
}
}
</style>

295
pages/home/home.vue Normal file
View File

@ -0,0 +1,295 @@
<template>
<view class="container">
<VNavigationBar leftTitle="true" backgroundColor="transparent" title="车辆检测维修"></VNavigationBar>
<view class="body">
<view class="body-top-banner">
<image class="banner" src="" mode="aspectFit" />
</view>
<view class="menus">
<view @click="gotoPage(menu)" v-for="(menu, index) in menus" :key="index" class="menu-item">
<image class="menu-icon" :src="menu.icon" mode="aspectFit"></image>
<text class="menu-title">{{menu.title}}</text>
</view>
</view>
<view class="info">
<image class="info-icon" src="@/static/icons/homeInfo.png" mode="aspectFit"></image>
<view class="infoList">
<view v-for="(item, index) in infoList" :key="item.id" class="infoItem">
<view class="infoIndex"></view>
<view class="infoText">{{item.title}}</view>
</view>
</view>
<image class="more-icon" src="../../static/icons/homeInfoMore.png" mode="aspectFit"></image>
</view>
<view class="repairShop">
<view class="title">附近修理厂</view>
<view class="shopList">
<view v-for="(item, index) in shopList" :key="index" class="shopItem" @click="gotoShopDetail(item)">
<image class="shopImg" :src="item.image" mode="aspectFill"></image>
<view class="shopInfo">
<view class="shopTitle">{{item.title}}</view>
<view class="shopDetail">
<view class="shopAddress">{{item.address}}</view>
<view class="line"></view>
<view class="shopDistance">{{item.distance}}</view>
</view>
</view>
</view>
</view>
</view>
</view>
<tabBarVue msg="1"></tabBarVue>
</view>
</template>
<script>
import tabBarVue from '@/components/tabBar/tabBar.vue'
import VNavigationBar from '@/components/VNavigationBar.vue'
export default {
components: {
tabBarVue,
VNavigationBar
},
data() {
return {
menus: [{
title: '预约记录',
icon: require('@/static/icons/homeMenuIcon/menu1.png'),
path: '/pages/myReservation/myReservation'
},
{
title: '我的车辆',
icon: require('@/static/icons/homeMenuIcon/menu2.png'),
path: '/pages/myCar/myCar'
},
{
title: '客服中心',
icon: require('@/static/icons/homeMenuIcon/menu3.png'),
path: ''
},
{
title: '操作指南',
icon: require('@/static/icons/homeMenuIcon/menu4.png'),
path: '/pages/guideList/guideList'
},
],
infoList: [{
id: 1,
title: '暖心!深夜汽车高速路上爆胎 彝良交警化身“修理工”帮忙换胎'
},
{
id: 2,
title: '车辆机件“焕新上岗”,武汉公交修旧创效出实招'
},
],
shopList: [{
id: 1,
title: '顺捷汽车维修搭电救援补胎中心',
address: '济南市历下区福瑞达历下护理院东南门旁',
distance: '1.9km',
image: ''
},
{
id: 2,
title: '顺捷汽车维修搭电救援补胎中心',
address: '济南市历下区福瑞达历下护理院东南门旁',
distance: '1.9km',
image: ''
},
{
id: 3,
title: '顺捷汽车维修搭电救援补胎中心',
address: '济南市历下区福瑞达历下护理院东南门旁',
distance: '1.9km',
image: ''
},
]
}
},
methods: {
gotoShopDetail() {
uni.navigateTo({
url: '/pages/shopDetail/shopDetail'
})
},
gotoPage(menu) {
uni.navigateTo({
url: menu.path
})
}
}
}
</script>
<style scoped lang="less">
.container {
height: 100%;
background-color: #fff;
background: linear-gradient(180deg, #0174F6 0%, rgba(1, 116, 246, 0) 100%);
background-size: 100% 600rpx;
background-repeat: no-repeat;
color: #333333;
display: flex;
flex-direction: column;
.body {
flex: 1;
height: 0;
overflow: auto;
padding-bottom: 30rpx;
}
.body-top-banner {
text-align: center;
}
.banner {
margin: 24rpx 0 0;
width: 686rpx;
height: 290rpx;
border-radius: 12rpx 12rpx 12rpx 12rpx;
background-color: #0174F6;
}
.menus {
margin: 30rpx auto 0;
box-sizing: border-box;
width: 686rpx;
padding: 40rpx 38rpx;
background: #FFFFFF;
box-shadow: 0rpx 0rpx 16rpx 0rpx rgba(10, 54, 104, 0.1);
border-radius: 12rpx 12rpx 12rpx 12rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.menu-item {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
row-gap: 22rpx;
font-size: 24rpx;
.menu-icon {
width: 48rpx;
height: 48rpx;
}
}
.info {
width: 686rpx;
margin: 38rpx auto 0;
display: flex;
align-items: center;
column-gap: 20rpx;
.info-icon {
width: 80rpx;
height: 80rpx;
}
.infoList {
flex: 1;
width: 0;
.infoItem {
display: flex;
align-items: center;
justify-content: space-between;
column-gap: 10rpx;
}
.infoIndex {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background-color: #333;
}
.infoText {
flex: 1;
width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 24rpx;
}
}
.more-icon {
width: 24rpx;
height: 24rpx;
}
}
.repairShop {
width: 686rpx;
margin: 38rpx auto 0;
.title {
font-weight: bold;
font-size: 32rpx;
color: #333333;
}
.shopList {
.shopItem {
padding: 30rpx 0;
border-bottom: 1rpx solid #DDDDDD;
display: flex;
align-items: center;
column-gap: 20rpx;
}
.shopImg {
width: 160rpx;
height: 100rpx;
border-radius: 8rpx 8rpx 8rpx 8rpx;
background-color: #999;
}
.shopInfo {
flex: 1;
width: 0;
}
.shopTitle {
font-weight: 500;
font-size: 28rpx;
color: #333333;
margin-bottom: 20rpx;
}
.shopDetail {
display: flex;
align-items: center;
font-size: 24rpx;
color: #666666;
column-gap: 10rpx;
}
.shopAddress {
flex: 1;
width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.line {
width: 2rpx;
height: 24rpx;
background-color: #858BA0;
}
.shopDistance {
flex-shrink: 0;
}
}
}
}
</style>

27
pages/index/index.vue Normal file
View File

@ -0,0 +1,27 @@
<template>
<view class="content">
</view>
</template>
<script>
export default {
data() {
return {
}
},
onLoad() {
uni.reLaunch({
url: '/pages/home/home'
})
},
methods: {
}
}
</script>
<style>
</style>

24
pages/my/cardRoll.vue Normal file
View File

@ -0,0 +1,24 @@
<template>
<view>
</view>
</template>
<script>
import VNavigationBar from '@/components/VNavigationBar.vue'
export default {
components: {
VNavigationBar,
},
data() {
return {
};
}
}
</script>
<style lang="less">
</style>

107
pages/my/evaluate.vue Normal file
View File

@ -0,0 +1,107 @@
<template>
<view class="container">
<v-navigation-bar background-color="#fff" title-color="#333" title="我的评价"></v-navigation-bar>
<view class="body">
<view v-for="(item, index) in evaluateList" :key="index" class="item">
<view class="date">07-06</view>
<view class="message">
这家修理厂的喷漆工艺非常不错特别均匀师傅也很细心如果您需要做喷漆或者维修服务不要错过这里
</view>
<view class="rate">
<!-- 设置尺寸大小 -->
<uni-rate allow-half :value="3.5"/>
</view>
<view class="shopInfo">
<image class="shopImg" src="" mode="aspectFill"></image>
<view class="shopInfo_content">
<view class="shopName">顺捷汽车维修搭电救援补胎中心</view>
<view class="shopAddress">济南市历下区福瑞达历下护理院东南门旁</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import VNavigationBar from '@/components/VNavigationBar.vue'
export default {
components: {
VNavigationBar,
},
data() {
return {
evaluateList: [{}, {}, {}]
};
}
}
</script>
<style lang="less" scoped>
.container {
height: 100%;
background-color: #F3F5F7;
display: flex;
flex-direction: column;
.body {
padding: 20rpx 0;
flex: 1;
height: 0;
overflow: auto;
display: flex;
flex-direction: column;
row-gap: 10rpx;
}
.item {
background-color: #fff;
padding: 30rpx;
display: flex;
flex-direction: column;
row-gap: 20rpx;
}
.date {
font-weight: bold;
font-size: 36rpx;
color: #333333;
}
.shopInfo {
display: flex;
align-items: stretch;
column-gap: 20rpx;
.shopImg {
width: 160rpx;
height: 100rpx;
border-radius: 8rpx 8rpx 8rpx 8rpx;
background-color: #efefef;
}
.shopInfo_content {
flex: 1;
width: 0;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 6rpx 0;
}
.shopName {
font-size: 28rpx;
color: #333333;
}
.shopAddress {
font-size: 24rpx;
color: #666666;
}
}
}
</style>

78
pages/my/message.vue Normal file
View File

@ -0,0 +1,78 @@
<template>
<view class="container">
<VNavigationBar background-color="#fff" title-color="#333" title="消息中心"></VNavigationBar>
<view class="body">
<view class="messageList">
<view v-for="(item, index) in messageList" :key="index" class="messageItem">
<image class="messageIcon" src="../../static/icons/message-icon1.png" mode="aspectFit"></image>
<view class="messageContent">
<view class="messageTitle">系统通知</view>
<view class="messageContent_content">节日快乐在这美好的时刻送上我最真挚的祝福愿你的每一天都充满阳光和欢笑每一步都走向成功和辉煌</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import VNavigationBar from '@/components/VNavigationBar.vue'
export default {
components: {
VNavigationBar,
},
data() {
return {
messageList: [{}, {}]
};
}
}
</script>
<style lang="less" scoped>
.container {
height: 100%;
display: flex;
flex-direction: column;
.body {
flex: 1;
height: 0;
overflow: auto;
}
.messageList {
padding: 0 32rpx;
}
.messageItem {
padding: 30rpx 0;
display: flex;
align-items: center;
column-gap: 20rpx;
border-bottom: 1rpx solid #EEEEEE;
.messageIcon {
width: 80rpx;
height: 80rpx;
}
.messageContent {
flex: 1;
width: 0;
}
.messageTitle {
font-weight: bold;
font-size: 32rpx;
color: #333333;
}
.messageContent_content {
font-weight: 500;
font-size: 28rpx;
color: #858BA0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
</style>

285
pages/my/my.vue Normal file
View File

@ -0,0 +1,285 @@
<template>
<view class="container">
<view class="body">
<VNavigationBar titleColor="rgba(0,0,0,0.9)" leftTitle="true" backgroundColor="transparent" title=" ">
</VNavigationBar>
<view class="body-top">
<image class="userBmg" src="../../static/images/image1.png" mode="widthFix"></image>
<view class="userInfo">
<image class="avatar" src="../../static/images/avatar.png" mode="aspectFit"></image>
<text class="userName">用户昵称</text>
</view>
</view>
<view class="level">
<image class="levelIcon" src="" mode="aspectFit"></image>
<view class="levelInfo">
<text class="levelNum">Lv.2</text>
<text class="levelQy">当前可享受4项权益</text>
</view>
<view class="showQyBtn" @click="showEquity">
查看权益
<image class="showQyBtnIcon" src="../../static/icons/icon1.png" mode="aspectFit"></image>
</view>
</view>
<view class="menu1">
<view @click="goToPage('/pages/myCar/myCar')" style="background: linear-gradient( 90deg, #DEE5FC 0%, #CAD6FA 100%);"
class="menu1-item menu-myCar">
<image class="menu1-item-icon" src="../../static/icons/myCar.png" mode="aspectFit"></image>
<text style="flex: 1;">我的车辆</text>
<image class="menu1-item-btnIcon" src="../../static/icons/icon2.png" mode=""></image>
<image class="bmg" src="../../static/images/image2.png" mode="aspectFit"></image>
</view>
<view @click="goToPage('/pages/myReservation/myReservation')" style="background: linear-gradient( 90deg, #CFF3ED 0%, #9FE9DD 100%);"
class="menu1-item menu-myOrder">
<image class="menu1-item-icon" src="../../static/icons/myOrder.png" mode="aspectFit"></image>
<text style="flex: 1;">我的预约</text>
<image class="menu1-item-btnIcon" src="../../static/icons/icon3.png" mode=""></image>
<image class="bmg" src="../../static/images/image2.png" mode="aspectFit"></image>
</view>
</view>
<view class="menuCard">
<view @click="goToPage(item.path)" v-for="(item, index) in menuCard1" :key="item.title" class="menu-item">
<image class="menu-item-icon" :src="item.icon" mode="aspectFit"></image>
<text class="menu-item-title">{{ item.title }}</text>
<text class="messageNum" v-if="item.title === '消息中心'">
{{ '12' }}
</text>
<image class="menu-item-more" src="../../static/icons/homeInfoMore.png" mode="widthFix"></image>
</view>
</view>
<view class="menuCard">
<view @click="goToPage(item.path)" v-for="(item, index) in menuCard2" :key="item.title" class="menu-item">
<image class="menu-item-icon" :src="item.icon" mode="aspectFit"></image>
<text class="menu-item-title">{{ item.title }}</text>
<image class="menu-item-more" src="../../static/icons/homeInfoMore.png" mode="widthFix"></image>
</view>
</view>
</view>
<tabBarVue msg="3"></tabBarVue>
</view>
</template>
<script>
import VNavigationBar from '@/components/VNavigationBar.vue'
import tabBarVue from '@/components/tabBar/tabBar.vue'
export default {
components: {
tabBarVue,
VNavigationBar
},
data() {
return {
menuCard1: [
{ title: '我的资料', icon: require('@/static/icons/my-menu-icon1.png'), path: '/pages/my/myInfo' },
{ title: '消息中心', icon: require('@/static/icons/my-menu-icon2.png'), path: '/pages/my/message' },
{ title: '我的评价', icon: require('@/static/icons/my-menu-icon3.png'), path: '/pages/my/evaluate' },
],
menuCard2: [
{ title: '客服中心', icon: require('@/static/icons/my-menu-icon4.png') },
{ title: '操作指南', icon: require('@/static/icons/my-menu-icon5.png'), path: "/pages/guideList/guideList" },
]
}
},
methods: {
goToPage(path) {
uni.navigateTo({
url: path
})
},
showEquity() {
uni.navigateTo({
url: '/pages/my/myEquity'
})
}
}
}
</script>
<style scoped lang="less">
.container {
height: 100%;
background: #fff;
background: linear-gradient(180deg, rgba(1, 116, 246, 0.3) 0%, rgba(1, 116, 246, 0) 100%);
background-size: 100% 600rpx;
background-repeat: no-repeat;
display: flex;
flex-direction: column;
color: #333333;
.body {
flex: 1;
height: 0;
padding-bottom: 30rpx;
overflow: auto;
}
.body-top {
position: relative;
.userBmg {
width: 750rpx;
}
.userInfo {
display: flex;
flex-direction: column;
align-items: center;
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
.avatar {
width: 140rpx;
height: 140rpx;
margin-bottom: 30rpx;
}
.userName {
font-weight: bold;
font-size: 36rpx;
color: #333333;
}
}
}
.level {
box-sizing: border-box;
width: 686rpx;
margin: 30rpx auto 0;
background: linear-gradient(90deg, #0174F6 0%, #01BBF6 100%);
border-radius: 12rpx 12rpx 12rpx 12rpx;
display: flex;
align-items: center;
padding: 24rpx 30rpx;
column-gap: 20rpx;
.levelIcon {
width: 68rpx;
height: 68rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.7);
}
.levelInfo {
flex: 1;
width: 0;
display: flex;
flex-direction: column;
row-gap: 8rpx;
.levelNum {
font-weight: bold;
font-size: 28rpx;
color: #FFFFFF;
}
.levelQy {
font-weight: 500;
font-size: 24rpx;
color: rgba(255, 255, 255, 0.7);
}
}
.showQyBtn {
width: 156rpx;
height: 48rpx;
background: rgba(255, 255, 255, 0.7);
border-radius: 24rpx 24rpx 24rpx 24rpx;
display: flex;
justify-content: center;
align-items: center;
column-gap: 2rpx;
font-weight: 500;
font-size: 24rpx;
color: #0174F6;
.showQyBtnIcon {
width: 20rpx;
height: 20rpx;
}
}
}
.menu1 {
box-sizing: border-box;
width: 686rpx;
margin: 30rpx auto 0;
display: flex;
align-items: center;
column-gap: 22rpx;
.menu1-item {
flex: 1;
width: 0;
padding: 38rpx 28rpx;
border-radius: 12rpx 12rpx 12rpx 12rpx;
position: relative;
display: flex;
align-items: center;
column-gap: 18rpx;
.bmg {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
}
}
.menu1-item-icon {
width: 56rpx;
height: 56rpx;
}
.menu1-item-btnIcon {
width: 32rpx;
height: 32rpx;
}
}
.menuCard {
width: 686rpx;
margin: 30rpx auto 0;
background: #FFFFFF;
box-shadow: 0rpx 0rpx 16rpx 0rpx rgba(10,54,104,0.1);
border-radius: 12rpx 12rpx 12rpx 12rpx;
.menu-item {
padding: 40rpx 30rpx;
display: flex;
align-items: center;
column-gap: 20rpx;
}
.menu-item-icon {
width: 44rpx;
height: 44rpx;
}
.menu-item-title {
flex: 1;
width: 0;
font-weight: 500;
font-size: 28rpx;
color: #333333;
}
.menu-item-more {
width: 14rpx;
}
.messageNum {
width: 36rpx;
height: 36rpx;
background: #D54941;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 20rpx;
color: #FFFFFF;
}
}
}
</style>

261
pages/my/myEquity.vue Normal file
View File

@ -0,0 +1,261 @@
<template>
<view class="container">
<VNavigationBar title="权益信息" background-color="rgba(0,0,0,0)" title-color="#333"></VNavigationBar>
<view class="body">
<view class="card1">
<image class="card1Bg" src="../../static/images/image3.png" mode="aspectFit"></image>
<view class="topLeft">
当前等级
</view>
<view class="levelNum">
Lv.2
</view>
</view>
<view class="equityTitle">
<image class="leftIcon" src="../../static/icons/my-icon1.png" mode="aspectFit"></image>
<view class="">尊享<text class="colorTitle">4</text>权益</view>
<image class="rightIcon" src="../../static/icons/my-icon1.png" mode="aspectFit"></image>
</view>
<view class="card2">
<view v-for="(item, index) in equityList" :key="index" class="cardItem">
<view class="dian"></view>
<text>{{ item.title }}</text>
</view>
</view>
<view class="equityTitle">
<image class="leftIcon" src="../../static/icons/my-icon1.png" mode="aspectFit"></image>
<view class=""><text class="colorTitle">等级</text>介绍</view>
<image class="rightIcon" src="../../static/icons/my-icon1.png" mode="aspectFit"></image>
</view>
<view class="card3">
<view class="card3Header card3Tr">
<view class="card3Td">
等级标识
</view>
<view class="card3Td">
权益信息
</view>
</view>
<view v-for="(item, index) in levelEquityList" :key="index" class="card3Tr">
<view class="card3Td">
<image class="levelIcon" src="../../static/icons/my-icon2.png" mode="aspectFit"></image>
<text>LV.{{item.level}}</text>
</view>
<view class="card3Td">
{{ item.desc }}
</view>
</view>
</view>
</view>
<view></view>
</view>
</template>
<script>
import VNavigationBar from '@/components/VNavigationBar.vue'
export default {
components: {
VNavigationBar,
},
data() {
return {
equityList: [{
title: '进店送'
},
{
title: '赠送原车'
},
{
title: '赠送198'
},
{
title: '赠送全车360'
},
],
levelEquityList: [{
level: '1',
desc: '进店送精美礼品一份\n赠送原车配套空气滤芯一个\n赠送198空调雾化杀菌除味一次\n赠送全车360安全检测'
},
{
level: '2',
desc: '进店送精美礼品一份\n赠送原车配套空气滤芯一个\n赠送198空调雾化杀菌除味一次\n赠送全车360安全检测'
},
{
level: '3',
desc: '进店送精美礼品一份\n赠送原车配套空气滤芯一个\n赠送198空调雾化杀菌除味一次\n赠送全车360安全检测'
},
{
level: '4',
desc: '进店送精美礼品一份\n赠送原车配套空气滤芯一个\n赠送198空调雾化杀菌除味一次\n赠送全车360安全检测'
},
{
level: '5',
desc: '进店送精美礼品一份\n赠送原车配套空气滤芯一个\n赠送198空调雾化杀菌除味一次\n赠送全车360安全检测'
},
]
};
}
}
</script>
<style lang="less" scoped>
.container {
height: 100%;
display: flex;
flex-direction: column;
background: linear-gradient(180deg, #FFE8C2 0%, rgba(255, 255, 255, 0) 100%);
background-size: 100% 750rpx;
background-repeat: no-repeat;
.body {
flex: 1;
height: 0;
padding-bottom: 30rpx;
overflow: auto;
.card1 {
margin: 40rpx auto;
position: relative;
width: 686rpx;
// background: linear-gradient( 135deg, #FDF6EB 0%, #E6CB94 100%);
// border-radius: 24rpx 24rpx 24rpx 24rpx;
// border: 2rpx solid;
// border-image: linear-gradient(180deg, rgba(255, 255, 255, 1), rgba(223, 189, 126, 1)) 2 2;
.card1Bg {
width: 100%;
height: 200rpx;
}
.topLeft {
width: 172rpx;
height: 68rpx;
background: rgba(0, 0, 0, 0.1);
border-radius: 24rpx 0rpx 24rpx 0rpx;
position: absolute;
left: 0;
top: 0;
display: flex;
align-items: center;
justify-content: center;
}
.levelNum {
position: absolute;
left: 30rpx;
bottom: 30rpx;
font-weight: bold;
font-size: 64rpx;
color: #62440C;
}
}
.card2 {
box-sizing: border-box;
width: 686rpx;
padding: 30rpx;
margin: 40rpx auto;
background: #FFF5D0;
border-radius: 16rpx 16rpx 16rpx 16rpx;
font-size: 24rpx;
color: #62440C;
display: flex;
flex-direction: column;
row-gap: 24rpx;
.cardItem {
display: flex;
align-items: center;
column-gap: 16rpx;
}
.dian {
width: 12rpx;
height: 12rpx;
background-color: #62440C;
border-radius: 50%;
}
}
.card3 {
box-sizing: border-box;
width: 686rpx;
margin: 40rpx auto;
border-radius: 16rpx 16rpx 16rpx 16rpx;
border: 2rpx solid #FFF5D0;
font-size: 28rpx;
.card3Tr {
background-color: #fff;
box-sizing: border-box;
display: flex;
align-items: stretch;
border-bottom: 1rpx solid #DDDDDD;
&.card3Header {
color: #62440C;
background: #FFF5D0;
}
&:last-child {
border: none;
}
.card3Td {
box-sizing: border-box;
padding: 30rpx;
flex-shrink: 0;
white-space: break-spaces;
&:first-child {
width: 200rpx;
border-right: 1rpx solid #DDDDDD;
display: flex;
align-items: center;
justify-content: center;
}
}
}
.levelIcon {
width: 60rpx;
height: 60rpx;
margin-right: 8rpx;
}
}
}
.equityTitle {
display: flex;
align-items: center;
justify-content: center;
column-gap: 10rpx;
font-weight: bold;
font-size: 32rpx;
color: #333333;
.leftIcon,
.rightIcon {
width: 28rpx;
height: 28rpx;
}
.rightIcon {
transform: scaleX(-1);
}
.colorTitle {
color: #E8A321;
}
}
}
</style>

84
pages/my/myInfo.vue Normal file
View File

@ -0,0 +1,84 @@
<template>
<view class="container">
<VNavigationBarVue titleColor="#333" backgroundColor="#fff" title="我的资料"></VNavigationBarVue>
<view class="body">
<view class="formItem">
<text class="formLabel">头像</text>
<image class="avatar" src="../../static/images/avatar.png" mode="aspectFit"></image>
</view>
<view class="formItem">
<text class="formLabel">昵称</text>
<text class="formValue">用户昵称</text>
<image class="formBtn" src="../../static/icons/homeInfoMore.png" mode="aspectFit"></image>
</view>
<view class="formItem">
<text class="formLabel">手机号</text>
<text class="formValue">157****9706</text>
<image class="formBtn" src="../../static/icons/homeInfoMore.png" mode="aspectFit"></image>
</view>
</view>
</view>
</template>
<script>
import VNavigationBarVue from '../../components/VNavigationBar.vue';
export default {
components: {
VNavigationBarVue
},
data() {
return {
};
}
}
</script>
<style lang="less" scoped>
.container {
height: 100%;
background-color: #fff;
.body {
}
.formItem {
box-sizing: border-box;
width: 686rpx;
margin: 0 auto;
padding: 40rpx;
display: flex;
align-items: center;
justify-content: space-between;
column-gap: 20rpx;
border-bottom: 1rpx solid #DDDDDD;
}
.formLabel {
font-size: 32rpx;
color: #333333;
}
.formValue {
flex: 1;
width: 0;
text-align: right;
font-size: 32rpx;
color: #999999;
}
.formBtn {
width: 24rpx;
height: 24rpx;
}
.avatar {
width: 108rpx;
height: 108rpx;
}
}
</style>

189
pages/my/register.vue Normal file
View File

@ -0,0 +1,189 @@
<template>
<view class="container">
<VNavigationBar title-color="#333" background-color="#fff" title="信息填写"></VNavigationBar>
<view class="body">
<view class="formItem">
<view class="formLabel">姓名</view>
<view class="formContainer">
<input placeholder="请填写你的真实姓名" type="text" />
</view>
</view>
<view class="formItem">
<view class="formLabel">性别</view>
<view class="formContainer">
<radio-group class="radioGroup">
<label class="radio">
<radio activeBackgroundColor="#009EDA" value="r1" checked="true" />
</label>
<label class="radio">
<radio activeBackgroundColor="#009EDA" value="r2" />
</label>
</radio-group>
</view>
</view>
<view class="formItem">
<view class="formLabel">省份</view>
<view class="formContainer">
<picker :range="addressRange" mode="multiSelector" @columnchange="addressColumnChangeFun">
<view class="formPicker">
<input class="formPickerInput" type="text" disabled="true" placeholder="请选择所在省份/城市/区" />
<image class="formPickerBtn" src="../../static/icons/homeInfoMore.png" mode="aspectFit">
</image>
</view>
</picker>
</view>
</view>
<view class="formItem">
<view class="formLabel">详细地址</view>
<view class="formContainer">
<picker :range="detailAddress" mode="multiSelector" @columnchange="detailColumnChangeFun">
<view class="formPicker">
<input class="formPickerInput" type="text" disabled="true" placeholder="请选择所在街道/小区" />
<image class="formPickerBtn" src="../../static/icons/homeInfoMore.png" mode="aspectFit">
</image>
</view>
</picker>
</view>
</view>
<view class="formItem">
<view class="formLabel">上传图片</view>
<view class="formContainer">
<view class="">
<uni-file-picker :image-styles="{width: 80, height: 80}" v-model="imageValue"
fileMediatype="image" limit="1" mode="grid" @select="select" @progress="progress"
@success="success" @fail="fail">
<image style="width: 160rpx;height: 160rpx;" src="../../static/icons/addImageIcon.png"
mode="aspectFit"></image>
</uni-file-picker>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import VNavigationBar from '@/components/VNavigationBar.vue'
export default {
components: {
VNavigationBar
},
data() {
return {
detailAddress: [
['a', 'b'],
['b', 'a']
],
addressRange: [
['山东'],
['济南'],
['历下区']
]
};
},
methods: {
detailColumnChangeFun({
detail
}) {
if (detail.column === 0) {
//
this.$set(this.detailAddress, 1, ['m', 'n'])
}
},
addressColumnChangeFun({
detail
}) {
if (detail.column === 0) {
//
this.$set(this.detailAddress, 1, ['济宁'])
//
this.$set(this.detailAddress, 2, ['任城'])
} else if (detail.column === 1) {
//
this.$set(this.detailAddress, 2, ['任城'])
}
},
//
select(e) {
console.log('选择文件:', e)
},
//
progress(e) {
console.log('上传进度:', e)
},
//
success(e) {
console.log('上传成功')
},
//
fail(e) {
console.log('上传失败:', e)
}
}
}
</script>
<style lang="less" scoped>
.container {
height: 100%;
display: flex;
flex-direction: column;
.body {
flex: 1;
height: 0;
overflow: auto;
padding: 20rpx 0;
}
.formItem {
padding: 40rpx 0;
margin: 0 32rpx;
border-bottom: 1rpx solid #EEEEEE;
}
.formLabel {
font-weight: 500;
font-size: 28rpx;
color: #333333;
padding-bottom: 20rpx;
}
.radioGroup {
display: flex;
align-items: center;
column-gap: 100rpx;
}
.radio {
display: flex;
align-items: center;
column-gap: 20rpx;
}
/* #ifdef MP-WEIXIN */
radio {
filter: hue-rotate(90deg);
}
/* #endif */
.formPicker {
display: flex;
align-items: center;
column-gap: 20rpx;
}
.formPickerInput {
flex: 1;
width: 0;
}
.formPickerBtn {
width: 28rpx;
height: 28rpx;
}
}
</style>

186
pages/myCar/carDetail.vue Normal file
View File

@ -0,0 +1,186 @@
<template>
<view class="container">
<VNavigationBar title="车辆详情" background-color="#fff" title-color="#333"></VNavigationBar>
<view class="body">
<view class="card">
<view class="formItem">
<text class="formLabel">车辆照片</text>
<image class="carImg" src="" mode="aspectFill"></image>
</view>
<view class="formItem">
<text class="formLabel">车牌号</text>
<text class="formValue">鲁A 781NB</text>
</view>
<view class="formItem">
<text class="formLabel">车辆持有人</text>
<text class="formValue">魏书豪</text>
</view>
<view class="formItem">
<text class="formLabel">持有人电话</text>
<text class="formValue">15726576890</text>
</view>
</view>
<view class="card">
<view class="formItem1">
<view class="labelVal">
<text class="formLabel">车辆年检时间</text>
<text class="formValue">2024</text>
</view>
<image class="formImg" src="" mode="aspectFill"></image>
</view>
<view class="formItem1">
<view class="labelVal">
<text class="formLabel">车辆保险时间</text>
<text class="formValue">2024</text>
</view>
<image class="formImg" src="" mode="aspectFill"></image>
</view>
</view>
</view>
<view class="footer">
<view class="btnItem delete">
<uni-icons type="trash" color="#F92C2C"></uni-icons>
删除
</view>
<view class="line"></view>
<view class="btnItem edit">
<uni-icons type="compose" color="#0174F6"></uni-icons>
编辑
</view>
</view>
</view>
</template>
<script>
import VNavigationBar from '@/components/VNavigationBar.vue'
export default {
components: {
VNavigationBar,
},
data() {
return {
}
},
methods: {
}
}
</script>
<style lang="less" scoped>
.container {
box-sizing: border-box;
height: 100%;
background-color: #F3F5F7;
display: flex;
flex-direction: column;
.body {
flex: 1;
height: 0;
overflow: auto;
.card {
margin: 20rpx 0;
padding: 0 32rpx;
background-color: #fff;
.formItem {
box-sizing: border-box;
width: 686rpx;
margin: 0 auto;
padding: 40rpx;
display: flex;
align-items: center;
justify-content: space-between;
column-gap: 20rpx;
border-bottom: 1rpx solid #DDDDDD;
&:last-child {
border: none;
}
}
.labelVal {
display: flex;
align-items: center;
justify-content: space-between;
column-gap: 20rpx;
}
.formItem1 {
box-sizing: border-box;
width: 686rpx;
margin: 0 auto;
padding: 40rpx;
border-bottom: 1rpx solid #DDDDDD;
&:last-child {
border: none;
}
}
.formLabel {
font-size: 32rpx;
color: #333333;
}
.formValue {
flex: 1;
width: 0;
text-align: right;
font-size: 32rpx;
color: #999999;
}
.carImg {
width: 240rpx;
height: 150rpx;
border-radius: 8rpx 8rpx 8rpx 8rpx;
background-color: #efefef;
}
.formImg {
margin-top: 30rpx;
width: 240rpx;
height: 150rpx;
border-radius: 12rpx 12rpx 12rpx 12rpx;
background-color: #efefef;
}
}
}
.footer {
background: #FFFFFF;
display: flex;
align-items: center;
.line {
width: 2rpx;
background-color: #DDDDDD;
}
.btnItem {
flex: 1;
width: 0;
padding: 34rpx 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
&.delete {
color: #F92C2C;
}
&.edit {
color: #0174F6;
}
}
}
}
</style>

118
pages/myCar/myCar.vue Normal file
View File

@ -0,0 +1,118 @@
<template>
<view class="container">
<VNavigationBar titleColor="rgba(0,0,0,0.9)" backgroundColor="#fff" title="我的车辆">
</VNavigationBar>
<view class="body">
<scroll-view style="height: 100%;" scroll-y="true">
<view class="carList">
<view v-for="(item, index) in carList" :key="index" class="carItem" @click="gotoDetail(item)">
<image class="carImage" src="" mode="aspectFit"></image>
<view class="carInfo">
<view class="carNum">{{ item.carNum }}</view>
<view class="name">车辆持有人{{ item.name }}</view>
<view class="phone">持有人电话{{ item.phone }}</view>
</view>
</view>
</view>
</scroll-view>
</view>
<view class="addCarBtn">
<uni-icons color="#0174F6" type="plusempty"></uni-icons>
添加车辆
</view>
</view>
</template>
<script>
import VNavigationBar from '@/components/VNavigationBar.vue'
export default {
components: {
VNavigationBar
},
data() {
return {
carList: [{
carNum: '鲁A 781NB',
name: '魏书豪',
phone: '15726786903',
image: ''
}]
}
},
methods: {
gotoDetail() {
uni.navigateTo({
url: '/pages/myCar/carDetail'
})
}
}
}
</script>
<style scoped lang="less">
.container {
height: 100%;
display: flex;
flex-direction: column;
padding-bottom: env(safe-area-inset-bottom);
.body {
flex: 1;
height: 0;
background-color: #F3F5F7;
}
.carList {
display: flex;
flex-direction: column;
}
.carItem {
box-sizing: border-box;
width: 686rpx;
margin: 20rpx auto;
display: flex;
align-items: center;
padding: 30rpx;
background: #FFFFFF;
border-radius: 12rpx 12rpx 12rpx 12rpx;
column-gap: 20rpx;
}
.carImage {
background-color: #eee;
width: 240rpx;
height: 150rpx;
border-radius: 8rpx 8rpx 8rpx 8rpx;
}
.carNum {
font-weight: bold;
font-size: 36rpx;
color: #333333;
margin-bottom: 20rpx;
}
.name,
.phone {
font-weight: 500;
font-size: 28rpx;
color: #858BA0;
}
.addCarBtn {
padding: 34rpx 0;
background: #FFFFFF;
border-radius: 0rpx 0rpx 0rpx 0rpx;
display: flex;
align-items: center;
justify-content: center;
column-gap: 12rpx;
font-weight: 500;
font-size: 32rpx;
color: #0174F6;
}
}
</style>

View File

@ -0,0 +1,435 @@
<template>
<view class="container">
<VNavigationBar background-color="rgba(0,0,0,0)" title-color="#333" title="提交预约"></VNavigationBar>
<view class="body">
<view class="carInfo">
<view class="cardInfoHeader">
<view class="carInfoLeftTop">
<image class="carInfoLeftTopIcon" src="../../static/icons/order-icon10.png" mode="widthFix">
</image>
车辆信息
</view>
<view class="carInfoRightTop">
<uni-icons type="compose" color="#0174F6"></uni-icons>
</view>
</view>
<view class="cardInfoBody">
<view class="cardInfoBody_content">
<view class="carForm">
<view class="carForm_num">鲁A 781NB</view>
<view class="carForm_carName">车辆持有人位数好</view>
<view class="carForm_carPhone">持有人电话13111111111</view>
</view>
<image class="carImg" src="" mode="aspectFill"></image>
</view>
<view class="cardInfoBody_footer">
<view class="nj">
年检时间<text class="date">2024</text>
<image class="cardInfoBody_footerIcon" src="../../static/icons/icon2.png" mode="aspectFit">
</image>
</view>
<view class="bx">
保险时间<text class="date">2024</text>
<image class="cardInfoBody_footerIcon" src="../../static/icons/icon2.png" mode="aspectFit">
</image>
</view>
</view>
</view>
</view>
<view class="baseInfo">
<view class="formItem">
<view class="formItem_content">
<view class="label">姓名</view>
<input class="formItemInput" placeholder="请输入姓名" type="text" />
</view>
</view>
<view class="formItem">
<view class="formItem_content">
<view class="label">联系电话</view>
<input class="formItemInput" placeholder="请输入姓名" type="text" />
</view>
</view>
<view class="formItem">
<picker mode="selector" :range="['汽车维修']" @change="">
<view class="formItem_content">
<view class="label">预约项目</view>
<input disabled class="formItemInput" placeholder="请选择预约项目" type="text" />
<image class="formItemBtn" src="../../static/icons/homeInfoMore.png" mode="aspectFit">
</image>
</view>
</picker>
</view>
</view>
<view class="dateCard">
<view class="cardTitle">预约时间</view>
<view class="datePicker">
<view v-for="(date, index) in dateList" :key="date.date" class="dateItem"
:class="{active: chooseDate === date.date, disabled: date.disabled}"
@click="chooseDateFun(date)">
<text>{{date.date}}</text>
<text>{{date.title}}</text>
<image v-if="chooseDate === date.date" class="activeIcon" src="../../static/icons/order-icon11.png" mode="aspectFit"></image>
</view>
</view>
<view class="timerPicker">
<view v-for="(time, index) in timeList" :key="index" class="timeItem"
:class="{active: chooseTime === time.time, disabled: time.disabled}"
@click="chooseTimeFun(time)">
<text>{{time.time}}</text>
<image v-if="chooseTime === time.time" class="activeIcon" src="../../static/icons/order-icon11.png" mode="aspectFit"></image>
</view>
</view>
</view>
</view>
<view class="footer">
<view class="footerBtn" @click="submit">提交预约</view>
</view>
</view>
</template>
<script>
import VNavigationBar from '@/components/VNavigationBar.vue'
export default {
components: {
VNavigationBar,
},
data() {
return {
dateList: [{
date: '06-05',
title: '周四',
disabled: true
}, {
date: '06-06',
title: '周四',
disabled: false
}, {
date: '06-07',
title: '周四',
disabled: false
}, {
date: '06-08',
title: '周四',
disabled: false
}, {
date: '06-09',
title: '周四',
disabled: false
}, ],
chooseDate: '06-06',
chooseTime: '13:00',
timeList: [{
time: '11:00',
disabled: false
},
{
time: '13:00',
disabled: false
},
{
time: '15:00',
disabled: false
},
{
time: '16:30',
disabled: false
},
{
time: '18:00',
disabled: false
},
{
time: '15:00',
disabled: false
},
]
};
},
methods: {
chooseTimeFun(time) {
if (time.disabled) {
return
}
this.chooseTime = time.time
},
chooseDateFun(date) {
if (date.disabled) {
return
}
this.chooseDate = date.date
},
submit() {
uni.redirectTo({
url: '/pages/myReservation/reservationSuccess'
})
}
}
}
</script>
<style lang="less" scoped>
.container {
height: 100%;
display: flex;
flex-direction: column;
background: linear-gradient(180deg, #C1DEFF 0%, rgba(193, 222, 255, 0) 100%);
background-size: 100% 500rpx;
background-repeat: no-repeat;
}
.body {
flex: 1;
height: 0;
overflow: auto;
background: linear-gradient(180deg, rgba(193, 222, 255, 0) 0%, #F3F5F7 50%, #F3F5F7 100%);
.carInfo {
margin: 20rpx 32rpx 30rpx;
background: #022B9A;
box-shadow: 0rpx 8rpx 16rpx 0rpx rgba(10, 54, 104, 0.1);
border-radius: 16rpx 16rpx 16rpx 16rpx;
overflow: hidden;
.cardInfoHeader {
display: flex;
align-items: stretch;
}
.carInfoLeftTop {
display: flex;
align-items: center;
column-gap: 10rpx;
padding: 20rpx;
color: #fff;
.carInfoLeftTopIcon {
width: 36rpx;
}
}
.carInfoRightTop {
flex: 1;
width: 0;
background-color: #fff;
text-align: right;
padding-right: 20rpx;
display: flex;
align-items: center;
justify-content: flex-end;
border: 10rpx;
clip-path: polygon(20rpx 0, 100% 0, 100% 0, 100% 100%, 100% 100%, 0 100%, 10rpx 10rpx);
}
.cardInfoBody {
padding: 30rpx;
background-color: #fff;
border-radius: 16rpx 0rpx 0 0;
}
.cardInfoBody_content {
display: flex;
align-items: stretch;
column-gap: 32rpx;
padding-bottom: 20rpx;
border-bottom: 1rpx dashed #858BA0;
.carForm {
padding: 10rpx 0;
display: flex;
flex-direction: column;
justify-content: space-between;
font-weight: 500;
font-size: 28rpx;
color: #858BA0;
flex: 1;
width: 0;
}
.carForm_num {
font-weight: bold;
font-size: 36rpx;
color: #333333;
}
.carImg {
width: 240rpx;
height: 150rpx;
border-radius: 8rpx 8rpx 8rpx 8rpx;
background-color: #efefef;
}
}
.cardInfoBody_footer {
display: flex;
align-items: center;
font-weight: 500;
font-size: 28rpx;
color: #333333;
padding-top: 20rpx;
.nj {
border-right: 1rpx solid #ddd;
margin-right: 20rpx;
}
.nj,
.bx {
flex: 1;
width: 0;
display: flex;
align-items: center;
column-gap: 10rpx;
}
.date {
color: #0174F6;
}
.cardInfoBody_footerIcon {
width: 28rpx;
height: 28rpx;
}
}
}
.baseInfo {
margin: 20rpx 32rpx 30rpx;
background: #FFFFFF;
border-radius: 12rpx 12rpx 12rpx 12rpx;
padding: 0 30rpx;
font-weight: 500;
font-size: 28rpx;
color: #333333;
.formItem {
padding: 40rpx 0;
border-bottom: 1rpx solid #EEEEEE;
&:last-child {
border: none;
}
}
.formItem_content {
display: flex;
align-items: center;
column-gap: 10rpx;
}
.formItemInput {
flex: 1;
width: 0;
text-align: right;
}
.formItemBtn {
width: 24rpx;
height: 24rpx;
}
}
.dateCard {
margin: 20rpx 32rpx 30rpx;
padding: 22rpx 30rpx;
background: #FFFFFF;
border-radius: 16rpx 16rpx 16rpx 16rpx;
.cardTitle {
font-weight: bold;
font-size: 28rpx;
color: #333333;
}
.datePicker {
display: flex;
column-gap: 20rpx;
overflow: auto;
padding: 30rpx 0;
border-bottom: 1rpx solid #EEEEEE;
.dateItem {
padding: 20rpx 40rpx;
border-radius: 8rpx 8rpx 8rpx 8rpx;
border: 1rpx solid rgba(0, 0, 0, 0.1);
font-size: 24rpx;
color: #666666;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
}
.timerPicker {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20rpx;
padding-top: 30rpx;
.timeItem {
border-radius: 8rpx 8rpx 8rpx 8rpx;
border: 1rpx solid rgba(0, 0, 0, 0.1);
padding: 30rpx 0;
text-align: center;
}
}
.timeItem,
.dateItem {
position: relative;
overflow: hidden;
&.active {
border-radius: 8rpx 8rpx 8rpx 8rpx;
border: 2rpx solid #0174F6;
color: #0174F6;
}
&.disabled {
border-radius: 8rpx 8rpx 8rpx 8rpx;
border: 1rpx solid rgba(0,0,0,0.1);
color: rgba(51,51,51,0.4);
}
.activeIcon {
position: absolute;
right: 0;
top: 0;
width: 36rpx;
height: 24rpx;
}
}
}
}
.footer {
background: #FFFFFF;
padding: 12rpx 0;
padding-bottom: calc(12rpx + env(safe-area-inset-bottom));
.footerBtn {
width: 80%;
margin: 0 auto;
padding: 22rpx 0;
background: #0174F6;
border-radius: 38rpx 38rpx 38rpx 38rpx;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 32rpx;
color: #FFFFFF;
}
}
</style>

View File

@ -0,0 +1,78 @@
<template>
<view class="container">
<VNavigationBar titleColor="rgba(0,0,0,0.9)" backgroundColor="transparent" title="我的预约">
</VNavigationBar>
<view class="body">
<scroll-view style="height: 100%;" scroll-y="true">
<view class="orderList">
<reservationOrderVue v-for="(item, index) in orderList" :key="index" :orderInfo="item">
</reservationOrderVue>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
import VNavigationBar from '@/components/VNavigationBar.vue'
import tabBarVue from '@/components/tabBar/tabBar.vue'
import reservationOrderVue from '../../components/reservationOrder/reservationOrder.vue'
export default {
components: {
tabBarVue,
VNavigationBar,
reservationOrderVue
},
data() {
return {
orderList: [{
title: '顺捷汽车维修搭电救援补胎中心',
status: '1',
address: '济南市历下区福瑞达历下护理院东南门旁',
phone: '15726506879',
busiTypeStr: '汽车维修'
},
{
title: '顺捷汽车维修搭电救援补胎中心',
status: '1',
address: '济南市历下区福瑞达历下护理院东南门旁',
phone: '15726506879',
busiTypeStr: '汽车维修'
}
]
}
},
methods: {
}
}
</script>
<style scoped lang="less">
.container {
height: 100%;
background: #F3F5F7;
background: linear-gradient(180deg, #C1DEFF 0%, rgba(#F3F5F7, 0) 100%);
background-size: 100% 600rpx;
background-repeat: no-repeat;
display: flex;
flex-direction: column;
color: #333333;
.body {
flex: 1;
height: 0;
padding: 16rpx 0 30rpx;
overflow: auto;
}
.orderList {
width: 686rpx;
margin: 0 auto;
display: flex;
flex-direction: column;
row-gap: 20rpx;
}
}
</style>

View File

@ -0,0 +1,164 @@
<template>
<view class="container">
<v-navigation-bar background-color="rgba(0,0,0,0)" title-color="#333" title="预约成功"></v-navigation-bar>
<view class="body">
<view class="card">
<view class="success">
<image class="successIcon" src="../../static/icons/success.png" mode="aspectFit"></image>
<text>预约成功</text>
</view>
<view class="orderInfo">
<view class="shopName">顺捷汽车维修搭电救援补胎中心</view>
<view class="baseInfo">
<image class="baseInfoIcon" src="../../static/icons/order-icon1.png" mode=""></image>
济南市历下区福瑞达历下护理院东南门旁
</view>
<view class="baseInfo">
<image class="baseInfoIcon" src="../../static/icons/order-icon2.png" mode=""></image>
15726506879
</view>
<view class="baseInfo">
<image class="baseInfoIcon" src="../../static/icons/order-icon3.png" mode=""></image>
汽车维修
</view>
</view>
</view>
<view class="footer">
<view class="showOrder" @click="showOrder">查看订单</view>
<view class="back" @click="back">完成</view>
</view>
</view>
</view>
</template>
<script>
import VNavigationBar from '@/components/VNavigationBar.vue'
export default {
components: {
VNavigationBar,
},
data() {
return {
};
},
methods: {
back() {
uni.navigateBack({
delta: 1
})
},
showOrder() {
uni.redirectTo({
url: '/pages/myReservation/myReservation'
})
}
}
}
</script>
<style lang="less" scoped>
.container {
height: 100%;
background: linear-gradient(180deg, #C1DEFF 0%, rgba(193, 222, 255, 0) 100%);
background-size: 100% 500rpx;
background-repeat: no-repeat;
display: flex;
flex-direction: column;
.body {
flex: 1;
height: 0;
overflow: auto;
position: relative;
z-index: 1;
background: linear-gradient(180deg, rgba(193, 222, 255, 0) 0%, #f1f1f1 100%);
}
.card {
margin: 40rpx 32rpx;
padding: 60rpx 30rpx;
background: #FFFFFF;
border-radius: 12rpx 12rpx 12rpx 12rpx;
}
.success {
font-weight: bold;
font-size: 32rpx;
color: #333333;
padding-bottom: 60rpx;
display: flex;
flex-direction: column;
align-items: center;
row-gap: 30rpx;
border-bottom: 1rpx solid #DDDDDD;
}
.successIcon {
width: 120rpx;
height: 120rpx;
}
.orderInfo {
padding: 40rpx 0 60rpx;
}
.shopName {
font-weight: bold;
font-size: 32rpx;
color: #333333;
}
.baseInfo {
font-weight: 500;
font-size: 24rpx;
color: #999999;
margin-top: 30rpx;
display: flex;
align-items: center;
column-gap: 12rpx;
.baseInfoIcon {
width: 28rpx;
height: 28rpx;
}
}
.footer {
display: flex;
flex-direction: column;
align-items: center;
row-gap: 40rpx;
.showOrder, .back {
width: 510rpx;
height: 76rpx;
border-radius: 38rpx 38rpx 38rpx 38rpx;
display: flex;
align-items: center;
justify-content: center;
}
.showOrder {
background: #0174F6;
font-weight: bold;
font-size: 32rpx;
color: #FFFFFF;
}
.back {
border-radius: 38rpx 38rpx 38rpx 38rpx;
border: 2rpx solid #0174F6;
font-weight: bold;
font-size: 32rpx;
color: #0174F6;
}
}
}
</style>

View File

@ -0,0 +1,116 @@
<template>
<view class="container">
<v-navigation-bar background-color="#fff" title-color="#333" title="服务评价"></v-navigation-bar>
<view class="body">
<view style="padding: 0 32rpx;">
<view class="shopInfo">
<image class="shopImg" src="" mode="aspectFill"></image>
<view class="shopInfo_content">
<view class="shopName">顺捷汽车维修搭电救援补胎中心</view>
<view class="shopAdress">济南市历下区福瑞达历下护理院东南门旁</view>
</view>
</view>
<view class="rate">
<text>服务评价</text>
<uni-rate allow-half="true" value=""></uni-rate>
</view>
<textarea class="message" value="" placeholder="可在此输入当前维修厂的服务评价" />
<view class="submit" @click="submit">提交评价</view>
</view>
</view>
</view>
</template>
<script>
import VNavigationBar from '@/components/VNavigationBar.vue'
export default {
components: {
VNavigationBar
},
data() {
return {
};
},
methods: {
submit() {
uni.navigateBack()
}
}
}
</script>
<style lang="less" scoped>
.container {
height: 100%;
background-color: #fff;
display: flex;
flex-direction: column;
.body {
flex: 1;
height: 0;
overflow: auto;
}
.shopInfo {
display: flex;
align-items: stretch;
column-gap: 20rpx;
margin: 30rpx 0;
.shopImg {
width: 160rpx;
height: 100rpx;
border-radius: 8rpx 8rpx 8rpx 8rpx;
background-color: #efefef;
}
.shopInfo_content {
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 6rpx 0;
}
.shopName {
font-weight: 500;
font-size: 28rpx;
color: #333333;
}
.shopAdress {
font-weight: 500;
font-size: 24rpx;
color: #666666;
}
}
.rate {
padding: 30rpx 0;
border-bottom: 1rpx solid #DDDDDD;
display: flex;
align-items: center;
column-gap: 28rpx;
}
.message {
padding: 30rpx 0;
height: 400rpx;
}
.submit {
width: 510rpx;
height: 76rpx;
margin: 0 auto;
background: #0174F6;
border-radius: 38rpx 38rpx 38rpx 38rpx;
font-size: 32rpx;
color: #FFFFFF;
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

View File

@ -0,0 +1,324 @@
<template>
<view class="container">
<view class="containerBody">
<VNavigationBar title="订单详情" background-color="rgba(0,0,0,0)" title-color="#333"></VNavigationBar>
<view class="body">
<view class="orderStatus card">
<template v-if="orderInfo.status === '1'">
<image class="statusIcon" src="@/static/icons/order-icon8.png" mode="aspectFit"></image>
<text>维修中</text>
</template>
<template v-else-if="orderInfo.status === '0'">
<image class="statusIcon" src="@/static/icons/order-icon9.png" mode="aspectFit"></image>
<text>等待维修</text>
</template>
</view>
<view class="reservationOrder card">
<reservationOrder :hideFooter="orderInfo.status === '0'" :order-info="orderInfo" :isDetail="true">
</reservationOrder>
</view>
<view v-if="orderInfo.status !== '0'" class="progress card">
<view v-for="(item, index) in processList" :key="index" class="processItem">
<view class="row1">
<view v-if="item.status !== '3'" class="processIndex">{{ index + 1 }}</view>
<view v-if="item.status === '3'" class="processIndex end">
<uni-icons type="checkmarkempty" color="#fff"></uni-icons>
</view>
<text class="processTitle">{{ item.title }}</text>
</view>
<view class="row2">
<view class="lineBox">
<view v-if="index < processList.length - 1" class="line"></view>
</view>
<view class="row2_body">
<view class="desc">{{item.desc}}</view>
<view class="imageList">
<image class="imageItem" v-for="(img, imgIndex) in item.imageList" :key="imgIndex"
:src="img" mode="aspectFill"></image>
</view>
</view>
</view>
</view>
</view>
<view class="reservationInfo card">
<view class="row">
<text class="col1">姓名</text>
<text class="col2">{{'魏书豪'}}</text>
</view>
<view class="row">
<text class="col1">联系电话</text>
<text class="col2">{{'15728586970'}}</text>
</view>
<view class="row">
<text class="col1">预约项目</text>
<text class="col2">{{'车辆维修'}}</text>
</view>
<view class="row">
<text class="col1">预约时间</text>
<text class="col2">{{'2024-06-05 13:00'}}</text>
</view>
</view>
</view>
<view class="footer" v-if="['0', '2'].includes(orderInfo.status)">
<view v-if="orderInfo.status === '2'" class="footerBtn" @click="gotoEvaluate">服务评价</view>
<template v-else-if="orderInfo.status === '0'">
<view class="footerBtn phone">
<image class="footerBtnIcon" src="../../static/icons/order-icon7.png" mode="aspectFit"></image>
拨打电话
</view>
<view class="footerBtn address">
<image class="footerBtnIcon" src="../../static/icons/order-icon6.png" mode="aspectFit"></image>地址导航
</view>
</template>
</view>
</view>
</view>
</template>
<script>
import VNavigationBar from '@/components/VNavigationBar.vue'
import reservationOrder from '@/components/reservationOrder/reservationOrder.vue'
export default {
components: {
VNavigationBar,
reservationOrder
},
data() {
return {
orderInfo: {
title: '顺捷汽车维修搭电救援补胎中心',
address: '济南市历下区福瑞达历下护理院东南门旁',
phone: '15726506879',
busiTypeStr: '15726506879',
status: '1'
},
processList: [{
title: '接收车辆',
desc: '车辆已到维修厂,工作人员正准备开始维修',
imageList: ['', '', ''],
status: '1'
},
{
title: '开始维修轮胎',
desc: '工作人员开始对车辆轮胎进行维修',
imageList: [],
status: '2'
},
{
title: '车辆维修完成',
desc: '车辆维修完成,请选择合适时间到店提车',
imageList: [],
status: '3'
}
]
};
},
methods: {
gotoEvaluate() {
uni.navigateTo({
url: '/pages/orderDetail/evaluate'
})
}
}
}
</script>
<style lang="less" scoped>
.container {
height: 100%;
background-color: #F3F5F7;
.containerBody {
height: 100%;
display: flex;
flex-direction: column;
background: linear-gradient(180deg, #C1DEFF 0%, rgba(193, 222, 255, 0) 100%);
background-size: 100% 500rpx;
background-repeat: no-repeat;
}
.body {
flex: 1;
height: 0;
overflow: auto;
}
.card {
box-sizing: border-box;
width: 686rpx;
margin: 30rpx auto;
}
.orderStatus {
font-weight: bold;
font-size: 36rpx;
color: #0174F6;
display: flex;
align-items: center;
column-gap: 20rpx;
.statusIcon {
width: 48rpx;
height: 48rpx;
}
}
.reservationOrder {
box-sizing: border-box;
width: 686rpx;
margin: 20rpx auto;
}
.progress {
padding: 30rpx;
background: #FFFFFF;
border-radius: 12rpx 12rpx 12rpx 12rpx;
display: flex;
flex-direction: column;
row-gap: 20rpx;
.processItem {
.row1 {
display: flex;
align-items: center;
column-gap: 10rpx;
}
.processIndex {
width: 44rpx;
height: 44rpx;
background: #E1EFFF;
border-radius: 22rpx 22rpx 22rpx 22rpx;
font-size: 28rpx;
text-align: center;
color: #0174F6;
line-height: 44rpx;
&.end {
background: #0174F6;
}
}
.processTitle {
font-size: 28rpx;
color: #333333;
line-height: 44rpx;
}
.row2 {
display: flex;
align-items: stretch;
column-gap: 10rpx;
.lineBox {
width: 44rpx;
position: relative;
padding-top: 10rpx;
}
.line {
position: absolute;
left: 50%;
transform: translateX(-50%);
width: 2rpx;
height: 100%;
background-color: #0174F6;
}
.row2_body {
padding-bottom: 20rpx;
.desc {
font-size: 24rpx;
color: #858BA0;
margin-bottom: 16rpx;
}
.imageList {
display: flex;
row-gap: 20rpx;
column-gap: 20rpx;
}
.imageItem {
width: 120rpx;
height: 120rpx;
background-color: #efefef;
}
}
}
}
}
.reservationInfo {
padding: 30rpx;
background: #FFFFFF;
border-radius: 12rpx 12rpx 12rpx 12rpx;
display: flex;
flex-direction: column;
row-gap: 40rpx;
.row {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 28rpx;
.col1 {
color: #333333;
}
.col2 {
color: #858BA0;
}
}
}
.footer {
background: #FFFFFF;
border-radius: 0rpx 0rpx 0rpx 0rpx;
padding: 12rpx 32rpx;
display: flex;
align-items: center;
column-gap: 22rpx;
.footerBtn {
flex: 1;
width: 0;
height: 76rpx;
margin: 0 auto;
background: #0174F6;
border-radius: 38rpx 38rpx 38rpx 38rpx;
font-size: 32rpx;
color: #FFFFFF;
display: flex;
align-items: center;
justify-content: center;
column-gap: 8rpx;
line-height: 1.5;
&.phone {
background: #E8A321;
}
&.address {
}
.footerBtnIcon {
width: 32rpx;
height: 32rpx;
}
}
}
}
</style>

View File

@ -0,0 +1,251 @@
<template>
<view class="container">
<VNavigationBar titleColor="rgba(0,0,0,0.9)" leftTitle="true" backgroundColor="transparent" title="我的订单">
</VNavigationBar>
<view class="body">
<view class="tabList">
<view @click="changeTabFun(item.id)" v-for="(item, index) in tabList" :key="index" class="tabItem"
:class="{actived: item.id === activeKey}">
{{ item.title }}
<view v-if="activeKey === item.id" class="activeLine"></view>
</view>
</view>
<view class="orderList">
<view v-for="(item, index) in orderList" :key="index" class="orderItem">
<view class="line1">
<view class="orderNo">
订单编号{{item.orderNo}}
</view>
<text class="orderStatus" :class="['status_' + item.status]">
{{ getStatus(item.status) }}
</text>
</view>
<view class="orderInfo">
<image class="orderInfoIcon" src="" mode="aspectFit"></image>
<text class="orderInfoText">{{ item.busiTypeStr }}</text>
</view>
<view class="orderInfo">
<image class="orderInfoIcon" src="" mode="aspectFit"></image>
<text class="orderInfoText">{{ item.address }}</text>
</view>
<view class="line2">
<view>
共计
<text class="orderAmountUnit"></text>
<text class="orderAmount">781</text>
</view>
<text>{{ item.date }}</text>
</view>
<view class="line3">
<view @click="gotoDetail(item)" class="showOrder" v-if="item.status === '1'">查看订单</view>
<view @click="gotoEvaluate(item)" class="evaluate" v-if="item.status === '2'">评价订单</view>
</view>
</view>
</view>
</view>
<tabBarVue msg="2"></tabBarVue>
</view>
</template>
<script>
import VNavigationBar from '@/components/VNavigationBar.vue'
import tabBarVue from '@/components/tabBar/tabBar.vue'
export default {
components: {
tabBarVue,
VNavigationBar
},
data() {
return {
activeKey: 0,
tabList: [{
id: 0,
title: '全部订单'
},
{
id: 1,
title: '维修中'
},
{
id: 2,
title: '待评价'
},
],
orderList: [{
orderNo: '20198104817050157810',
status: '1',
busiTypeStr: '维修轮胎',
address: '顺捷汽车维修搭电救援补胎中心',
amount: '781',
date: '2024-07-02 12:00'
},
{
orderNo: '20198104817050157810',
status: '2',
busiTypeStr: '维修轮胎',
address: '顺捷汽车维修搭电救援补胎中心',
amount: '781',
date: '2024-07-02 12:00'
}
]
}
},
methods: {
changeTabFun(id) {
this.activeKey = id
},
getStatus(status) {
switch (status) {
case '1':
return '维修中'
case '2':
return '待评价'
default:
break;
}
},
gotoDetail() {
uni.navigateTo({
url: '/pages/orderDetail/orderDetail'
})
},
gotoEvaluate() {
uni.navigateTo({
url: '/pages/orderDetail/evaluate'
})
}
}
}
</script>
<style scoped lang="less">
.container {
height: 100%;
background: #F3F5F7;
display: flex;
flex-direction: column;
color: #333333;
.body {
flex: 1;
height: 0;
padding: 24rpx 32rpx;
}
.tabList {
background: #FFFFFF;
border-radius: 12rpx 12rpx 12rpx 12rpx;
display: flex;
align-items: center;
padding: 0 40rpx;
.tabItem {
padding: 30rpx;
flex: 1;
text-align: center;
position: relative;
font-size: 24rpx;
&.actived {
color: #0174F6;
}
.activeLine {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 96rpx;
height: 6rpx;
background: #0174F6;
border-radius: 4rpx 4rpx 0rpx 0rpx;
}
}
}
.orderList {
padding: 30rpx 0;
display: flex;
flex-direction: column;
row-gap: 20rpx;
.orderItem {
padding: 30rpx;
background: #FFFFFF;
border-radius: 12rpx 12rpx 12rpx 12rpx;
}
.line1 {
margin-bottom: 30rpx;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 24rpx;
.orderNo {
flex: 1;
width: 0;
font-weight: 500;
color: #858BA0;
}
.status_1 {
color: #0174F6;
}
.status_2 {
color: #999999;
}
}
.orderInfo {
margin-bottom: 20rpx;
display: flex;
align-items: center;
column-gap: 10rpx;
.orderInfoIcon {
width: 28rpx;
height: 28rpx;
background: #ddd;
}
.orderInfoText {
font-weight: bold;
font-size: 28rpx;
color: #333333;
}
}
.line2 {
margin: 30rpx 0;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 12px;
color: #858BA0;
.orderAmountUnit {
color: #F92C2C;
}
.orderAmount {
color: #F92C2C;
font-weight: bold;
font-size: 40rpx;
}
}
.line3 {
display: flex;
align-items: center;
justify-content: flex-end;
.showOrder, .evaluate {
width: 172rpx;
height: 60rpx;
border-radius: 30rpx 30rpx 30rpx 30rpx;
border: 2rpx solid #0174F6;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: #0174F6;
}
.evaluate {
border: 1rpx solid #EEEEEE;
color: #333333;
}
}
}
}
</style>

View File

@ -0,0 +1,324 @@
<template>
<view class="container">
<view class="body">
<view class="body-top">
<view style="z-index: 2;position: relative;">
<VNavigationBar titleColor="#fff" backgroundColor="rgba(0,0,0,0)">
<template v-slot:back>
<image style="width: 56rpx;height: 56rpx;" src="../../static/icons/backIcon.png"
mode="aspectFit"></image>
</template>
</VNavigationBar>
</view>
<image class="shopImg" src="" mode="aspectFill"></image>
</view>
<view class="shopBody">
<view class="shopDetail">
<view class="shopTitle">顺捷汽车维修搭电救援补胎中心</view>
<view class="rate">
<view class="rateNumBox">
<text>4.5</text>
<image style="width: 28rpx;height: 28rpx;" src="../../static/icons/rateIcon.png"
mode="aspectFit"></image>
</view>
<text>强烈推荐</text>
</view>
<view class="shopDetailText">
<mote-lines-divide :line="3" expandText="全部" foldHint="收起">
<text class="shopDetailTextLabel">厂家介绍</text>
<text class="shopDetailTextValue">
正安汽车维修服务有限公司成立于1993年10月25日属东莞市成立最早规模最大的民营汽修企业之一现在莞城区及桥头镇开设有二家连锁经营分厂及直属汽车销售部主要从事汽车销售售后
</text>
</mote-lines-divide>
</view>
<view class="shopDetailFooter">
<view class="shopAddress">
<image style="width: 32rpx;height: 32rpx;" src="../../static/icons/order-icon1.png"
mode="aspectFit"></image>
<text>济南市历下区福瑞达历下护理院东南门旁</text>
</view>
<view class="shopPhone">
<image style="width: 32rpx;height: 32rpx;" src="../../static/icons/order-icon2.png"
mode="aspectFit"></image>
<text>电话</text>
</view>
</view>
</view>
<view class="busiDetail">
<view class="busiDetailTitle">顺捷汽车维修搭电救援补胎中心</view>
<view class="busiList">
<view v-for="(item, index) in busiList" :key="index" class="busiItem">
<image class="busiTypeImg" :src="item.image" mode="aspectFill"></image>
<view class="busiItemInfo">
<view class="busiItemTitle">{{ item.title }}</view>
<view class="busiItemDesc">{{ item.desc }}</view>
</view>
<view class="busiItemBtn">查看</view>
</view>
</view>
</view>
</view>
</view>
<view class="footer">
<view class="btn" @click="gotoReservation">开始预约</view>
</view>
</view>
</template>
<script>
import VNavigationBar from '@/components/VNavigationBar.vue'
export default {
components: {
VNavigationBar
},
data() {
return {
busiList: [{
title: '钣金喷漆维修',
desc: '钣金喷漆维修是一个汽车修理的技术手段,此方面汽车钣金等于汽车钣金修理,指汽车发生碰撞后要对车身进行修复,也即除对车身进行防腐和装饰的喷涂工作外其余的所有工作。如汽车车身损伤的分析,汽车车身的测量,汽车车身钣金的整形,拉伸矫正,去应力焊接,以及汽车车身附件装配,调整等工作。',
image: ''
}, {
title: '钣金喷漆维修',
desc: '钣金喷漆维修是一个汽车修理的技术手段,此方面汽车钣金等于汽车钣金修理,指汽车发生碰撞后要对车身进行修复,也即除对车身进行防腐和装饰的喷涂工作外其余的所有工作。如汽车车身损伤的分析,汽车车身的测量,汽车车身钣金的整形,拉伸矫正,去应力焊接,以及汽车车身附件装配,调整等工作。',
image: ''
}, {
title: '钣金喷漆维修',
desc: '钣金喷漆维修是一个汽车修理的技术手段,此方面汽车钣金等于汽车钣金修理,指汽车发生碰撞后要对车身进行修复,也即除对车身进行防腐和装饰的喷涂工作外其余的所有工作。如汽车车身损伤的分析,汽车车身的测量,汽车车身钣金的整形,拉伸矫正,去应力焊接,以及汽车车身附件装配,调整等工作。',
image: ''
}, {
title: '钣金喷漆维修',
desc: '钣金喷漆维修是一个汽车修理的技术手段,此方面汽车钣金等于汽车钣金修理,指汽车发生碰撞后要对车身进行修复,也即除对车身进行防腐和装饰的喷涂工作外其余的所有工作。如汽车车身损伤的分析,汽车车身的测量,汽车车身钣金的整形,拉伸矫正,去应力焊接,以及汽车车身附件装配,调整等工作。',
image: ''
}, ]
};
},
methods: {
//
gotoReservation() {
uni.navigateTo({
url: '/pages/myReservation/addReservation'
})
}
}
}
</script>
<style lang="less" scoped>
.container {
height: 100%;
display: flex;
flex-direction: column;
background-color: #fff;
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
.body {
flex: 1;
height: 0;
overflow: auto;
}
.body-top {
position: relative;
width: 750rpx;
height: 468rpx;
.shopImg {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
background-color: #eee;
}
.back {}
}
.shopBody {
position: relative;
top: -50rpx;
overflow: auto;
padding-bottom: 20rpx;
}
.shopDetail {
position: relative;
z-index: 2;
padding: 30rpx 32rpx;
background: #FFFFFF;
box-shadow: 0rpx 8rpx 16rpx 0rpx rgba(10, 54, 104, 0.1);
border-radius: 32rpx 32rpx 0rpx 0rpx;
.shopTitle {
font-weight: bold;
font-size: 36rpx;
color: #333333;
}
.rate {
margin: 30rpx 0;
box-sizing: border-box;
width: 282rpx;
height: 56rpx;
background: #FFF1DB;
border-radius: 30rpx 30rpx 30rpx 30rpx;
padding-right: 30rpx;
display: flex;
align-items: center;
justify-content: space-between;
font-weight: 500;
font-size: 28rpx;
color: #E8A321;
.rateNumBox {
width: 120rpx;
height: 56rpx;
background: linear-gradient(180deg, #FFD187 0%, #FEB33A 100%);
border-radius: 30rpx 30rpx 30rpx 30rpx;
display: flex;
align-items: center;
justify-content: center;
column-gap: 4rpx;
font-size: 32rpx;
color: #FFFFFF;
line-height: 1.5;
}
}
.shopDetailText {
padding-bottom: 30rpx;
border-bottom: 1rpx solid #EEEEEE;
margin-bottom: 30rpx;
.shopDetailTextLabel {
font-weight: bold;
font-size: 28rpx;
color: #333333;
}
.shopDetailTextValue {
font-size: 28rpx;
color: #666666;
}
}
.shopDetailFooter {
display: flex;
align-items: center;
.shopAddress {
flex: 1;
width: 0;
display: flex;
align-items: center;
column-gap: 8rpx;
font-size: 28rpx;
color: #666666;
border-right: 1rpx solid #EEEEEE;
margin-right: 20rpx;
}
.shopPhone {
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
row-gap: 4rpx;
font-size: 24rpx;
color: #333333;
}
}
}
.busiDetail {
padding: 30rpx 32rpx;
background: #FFFFFF;
.busiDetailTitle {
font-weight: bold;
font-size: 36rpx;
color: #333333;
}
.busiList {
.busiItem {
padding: 30rpx 0;
display: flex;
align-items: center;
border-bottom: 1rpx solid #DDDDDD;
column-gap: 20rpx;
}
.busiTypeImg {
width: 112rpx;
height: 112rpx;
background-color: #eee;
}
.busiItemInfo {
flex: 1;
width: 0;
}
.busiItemTitle {
font-size: 28rpx;
color: #333333;
}
.busiItemDesc {
font-size: 24rpx;
color: #858BA0;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
word-break: break-all;
}
.busiItemBtn {
width: 100rpx;
height: 48rpx;
background: #0174F6;
border-radius: 24rpx 24rpx 24rpx 24rpx;
font-size: 28rpx;
color: #FFFFFF;
display: flex;
align-items: center;
justify-content: center;
}
}
}
.footer {
padding: 12rpx;
background: #FFFFFF;
box-shadow: 0rpx -8rpx 16rpx 0rpx rgba(10, 54, 104, 0.1);
border-radius: 0rpx 0rpx 0rpx 0rpx;
position: relative;
.btn {
width: 510rpx;
height: 76rpx;
margin: 0 auto;
background: #0174F6;
border-radius: 38rpx 38rpx 38rpx 38rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #FFFFFF;
}
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
static/icons/backIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
static/icons/homeInfo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
static/icons/icon1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

BIN
static/icons/icon2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 B

BIN
static/icons/icon3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
static/icons/my-icon1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 B

BIN
static/icons/my-icon2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
static/icons/myCar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
static/icons/myOrder.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
static/icons/rateIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

BIN
static/icons/success.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
static/icons/tabbar/car.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 B

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="25px" height="25px" viewBox="0 0 25 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>01运输中、物流-线性</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="首页修改" transform="translate(-175.000000, -860.000000)" fill="#000000" fill-rule="nonzero">
<g id="底部" transform="translate(0.000000, 853.000000)">
<g id="Tab-2" transform="translate(125.000000, 0.000000)">
<g id="01运输中、物流-线性" transform="translate(50.000000, 7.000000)">
<rect id="矩形" opacity="0" x="0" y="0" width="24.9756098" height="24.9756098"></rect>
<path d="M23.3560976,13.9707317 L20.4097561,9.54146341 C20.3317073,9.42439024 20.2146341,9.36585366 20.0780488,9.36585366 L17.1707317,9.36585366 L17.1707317,5.46341463 C17.1707317,5.03414634 16.8195122,4.68292683 16.3902439,4.68292683 L2.34146341,4.68292683 C1.91219512,4.68292683 1.56097561,5.03414634 1.56097561,5.46341463 L1.56097561,16.3902439 C1.56097561,16.8195122 1.91219512,17.1707317 2.34146341,17.1707317 L3.12195122,17.1707317 C3.12195122,18.8878049 4.52682927,20.2926829 6.24390244,20.2926829 C7.96097561,20.2926829 9.36585366,18.8878049 9.36585366,17.1707317 L15.6097561,17.1707317 C15.6097561,18.8878049 17.0146341,20.2926829 18.7317073,20.2926829 C20.4487805,20.2926829 21.8536585,18.8878049 21.8536585,17.1707317 L22.6341463,17.1707317 C23.0634146,17.1707317 23.4146341,16.8195122 23.4146341,16.3902439 L23.4146341,14.1853659 C23.4146341,14.1073171 23.395122,14.0292683 23.3560976,13.9707317 Z M6.24390244,18.7317073 C5.38536585,18.7317073 4.68292683,18.0292683 4.68292683,17.1707317 C4.68292683,16.3121951 5.38536585,15.6097561 6.24390244,15.6097561 C7.10243902,15.6097561 7.80487805,16.3121951 7.80487805,17.1707317 C7.80487805,18.0292683 7.10243902,18.7317073 6.24390244,18.7317073 Z M15.6097561,15.6097561 L8.93658537,15.6097561 C8.3902439,14.6731707 7.39512195,14.0487805 6.24390244,14.0487805 C5.09268293,14.0487805 4.07804878,14.6731707 3.55121951,15.6097561 L3.12195122,15.6097561 L3.12195122,6.24390244 L15.6097561,6.24390244 L15.6097561,15.6097561 Z M18.7317073,18.7317073 C17.8731707,18.7317073 17.1707317,18.0292683 17.1707317,17.1707317 C17.1707317,16.3121951 17.8731707,15.6097561 18.7317073,15.6097561 C19.5902439,15.6097561 20.2926829,16.3121951 20.2926829,17.1707317 C20.2926829,18.0292683 19.5902439,18.7317073 18.7317073,18.7317073 Z M21.8536585,15.6097561 L21.4243902,15.6097561 C20.8780488,14.6731707 19.8829268,14.0487805 18.7317073,14.0487805 C18.1658537,14.0487805 17.6390244,14.204878 17.1707317,14.4780488 L17.1707317,10.9268293 L19.4926829,10.9268293 C19.6292683,10.9268293 19.7463415,10.9853659 19.804878,11.102439 L21.7756098,13.9512195 C21.8146341,14.0097561 21.8536585,14.0878049 21.8536585,14.1658537 L21.8536585,15.6097561 Z" id="形状"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="25px" height="25px" viewBox="0 0 25 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>204首页</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="首页修改" transform="translate(-50.000000, -860.000000)" fill="#327DFB" fill-rule="nonzero">
<g id="底部" transform="translate(0.000000, 853.000000)">
<g id="204首页" transform="translate(50.000000, 7.000000)">
<rect id="矩形" opacity="0" x="0" y="0" width="25" height="25"></rect>
<path d="M22.4414063,10.234375 L12.96875,3.45703125 C12.6953125,3.26171875 12.3242188,3.26171875 12.0703125,3.45703125 L2.5390625,10.234375 C2.2265625,10.4492188 2.3828125,10.9375 2.7734375,10.9375 L4.6875,10.9375 L4.6875,21.484375 C4.6875,21.6992188 4.86328125,21.875 5.078125,21.875 L9.765625,21.875 C9.98046875,21.875 10.15625,21.6992188 10.15625,21.484375 L10.15625,15.625 L14.84375,15.625 L14.84375,21.484375 C14.84375,21.6992188 15.0195313,21.875 15.234375,21.875 L19.921875,21.875 C20.1367188,21.875 20.3125,21.6992188 20.3125,21.484375 L20.3125,10.9375 L22.2265625,10.9375 C22.5976563,10.9375 22.7539063,10.4492188 22.4414063,10.234375 Z" id="路径"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
static/icons/tabbar/my.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="25px" height="25px" viewBox="0 0 25 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>个人</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="首页修改" transform="translate(-300.000000, -860.000000)" fill="#444444" fill-rule="nonzero">
<g id="底部" transform="translate(0.000000, 853.000000)">
<g id="Tab-3" transform="translate(250.000000, 0.000000)">
<g id="个人" transform="translate(50.000000, 7.000000)">
<rect id="矩形" opacity="0" x="0" y="0" width="24.9756098" height="24.9756098"></rect>
<path d="M12.4878049,5.46341463 C11.1940683,5.46341463 10.1463415,6.51239024 10.1463415,7.80487805 L10.1463415,8.8451122 C10.1463415,10.1382667 11.1946503,11.1865756 12.4878049,11.1865756 C13.7809594,11.1865756 14.8292683,10.1382667 14.8292683,8.8451122 L14.8292683,7.80487805 C14.8292683,6.51114146 13.7802927,5.46341463 12.4878049,5.46341463 Z M15.1976585,11.6536195 C18.4090225,12.8004939 20.5526302,15.8424372 20.5524293,19.2524488 C20.5524293,20.2577171 19.7369756,21.0731707 18.7317073,21.0731707 L6.24390244,21.0731707 C5.23834547,21.0731707 4.42318049,20.2580057 4.42318049,19.2524488 C4.42297956,15.8424372 6.56658728,12.8004939 9.77795122,11.6536195 C9.01465278,10.9188471 8.58396957,9.90459938 8.58536585,8.8451122 L8.58536585,7.80487805 C8.58536585,5.64962049 10.3325473,3.90243902 12.4878049,3.90243902 C14.6430624,3.90243902 16.3902439,5.64962049 16.3902439,7.80487805 L16.3902439,8.8451122 C16.3902439,9.94778537 15.9331902,10.9430634 15.1976585,11.6536195 L15.1976585,11.6536195 Z M12.4878049,12.7475512 C10.7627136,12.7475512 9.1082958,13.4329265 7.88858916,14.6528673 C6.66888251,15.8728082 5.98382486,17.5273575 5.98415598,19.2524488 C5.98415598,19.3960585 6.10029268,19.5121951 6.24390244,19.5121951 L18.7317073,19.5121951 C18.8751613,19.5121951 18.9914538,19.3959027 18.9914538,19.2524488 C18.9917849,17.5273575 18.3067272,15.8728082 17.0870206,14.6528673 C15.867314,13.4329265 14.2128962,12.7475512 12.4878049,12.7475512 Z" id="形状"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
static/images/avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
static/images/image1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

BIN
static/images/image2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
static/images/image3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

BIN
static/images/profile.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

8
store/getters.js Normal file
View File

@ -0,0 +1,8 @@
const getters = {
token: state => state.user.token,
avatar: state => state.user.avatar,
name: state => state.user.name,
roles: state => state.user.roles,
permissions: state => state.user.permissions
}
export default getters

15
store/index.js Normal file
View File

@ -0,0 +1,15 @@
import Vue from 'vue'
import Vuex from 'vuex'
import user from '@/store/modules/user'
import getters from './getters'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
user
},
getters
})
export default store

98
store/modules/user.js Normal file
View File

@ -0,0 +1,98 @@
import config from '@/config'
import storage from '@/utils/storage'
import constant from '@/utils/constant'
import { login, logout, getInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
const baseUrl = config.baseUrl
const user = {
state: {
token: getToken(),
name: storage.get(constant.name),
avatar: storage.get(constant.avatar),
roles: storage.get(constant.roles),
permissions: storage.get(constant.permissions)
},
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_NAME: (state, name) => {
state.name = name
storage.set(constant.name, name)
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
storage.set(constant.avatar, avatar)
},
SET_ROLES: (state, roles) => {
state.roles = roles
storage.set(constant.roles, roles)
},
SET_PERMISSIONS: (state, permissions) => {
state.permissions = permissions
storage.set(constant.permissions, permissions)
}
},
actions: {
// 登录
Login({ commit }, userInfo) {
const username = userInfo.username.trim()
const password = userInfo.password
const code = userInfo.code
const uuid = userInfo.uuid
return new Promise((resolve, reject) => {
login(username, password, code, uuid).then(res => {
setToken(res.token)
commit('SET_TOKEN', res.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
// 获取用户信息
GetInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo().then(res => {
const user = res.user
const avatar = (user == null || user.avatar == "" || user.avatar == null) ? require("@/static/images/profile.jpg") : baseUrl + user.avatar
const username = (user == null || user.userName == "" || user.userName == null) ? "" : user.userName
if (res.roles && res.roles.length > 0) {
commit('SET_ROLES', res.roles)
commit('SET_PERMISSIONS', res.permissions)
} else {
commit('SET_ROLES', ['ROLE_DEFAULT'])
}
commit('SET_NAME', username)
commit('SET_AVATAR', avatar)
resolve(res)
}).catch(error => {
reject(error)
})
})
},
// 退出系统
LogOut({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
commit('SET_PERMISSIONS', [])
removeToken()
storage.clean()
resolve()
}).catch(error => {
reject(error)
})
})
}
}
}
export default user

10
uni.promisify.adaptor.js Normal file
View File

@ -0,0 +1,10 @@
uni.addInterceptor({
returnValue (res) {
if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) {
return res;
}
return new Promise((resolve, reject) => {
res.then((res) => res[0] ? reject(res[0]) : resolve(res[1]));
});
},
});

76
uni.scss Normal file
View File

@ -0,0 +1,76 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场https://ext.dcloud.net.cn上很多三方插件均使用了这些样式变量
* 如果你是插件开发者建议你使用scss预处理并在插件代码中直接使用这些变量无需 import 这个文件方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者插件使用者你可以通过修改这些变量来定制自己的插件主题实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理你也可以直接在你的 scss 代码中使用如下变量同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
/* 文字基本颜色 */
$uni-text-color:#333;//基本色
$uni-text-color-inverse:#fff;//反色
$uni-text-color-grey:#999;//辅助灰色如加载更多的提示信息
$uni-text-color-placeholder: #808080;
$uni-text-color-disable:#c0c0c0;
/* 背景颜色 */
$uni-bg-color:#ffffff;
$uni-bg-color-grey:#f8f8f8;
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
/* 边框颜色 */
$uni-border-color:#c8c7cc;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size-sm:12px;
$uni-font-size-base:14px;
$uni-font-size-lg:16px;
/* 图片尺寸 */
$uni-img-size-sm:20px;
$uni-img-size-base:26px;
$uni-img-size-lg:40px;
/* Border Radius */
$uni-border-radius-sm: 2px;
$uni-border-radius-base: 3px;
$uni-border-radius-lg: 6px;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 5px;
$uni-spacing-row-base: 10px;
$uni-spacing-row-lg: 15px;
/* 垂直间距 */
$uni-spacing-col-sm: 4px;
$uni-spacing-col-base: 8px;
$uni-spacing-col-lg: 12px;
/* 透明度 */
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
/* 文章场景相关 */
$uni-color-title: #2C405A; // 文章标题颜色
$uni-font-size-title:20px;
$uni-color-subtitle: #555555; // 二级标题颜色
$uni-font-size-subtitle:26px;
$uni-color-paragraph: #3F536E; // 文章段落颜色
$uni-font-size-paragraph:15px;

View File

@ -0,0 +1,81 @@
## 1.0.112024-07-19
- 修复 vue3 使用value报错的bug
## 1.0.102024-07-09
- 优化 vue3兼容性
## 1.0.92024-07-09
- 修复 value 属性不兼容vue3的bug
## 1.0.82024-03-20
- 补充 删除文件时返回文件下标
## 1.0.72024-02-21
- 新增 微信小程序选择视频时改用chooseMedia,并返回视频缩略图
## 1.0.62024-01-06
- 新增 微信小程序不再调用chooseImage,而是调用chooseMedia
## 1.0.52024-01-03
- 新增 上传文件至云存储携带本地文件名称
## 1.0.42023-03-29
- 修复 手动上传删除一个文件后不能再上传的bug
## 1.0.32022-12-19
- 新增 sourceType 属性, 可以自定义图片和视频选择的来源
## 1.0.22022-07-04
- 修复 在uni-forms下样式不生效的bug
## 1.0.12021-11-23
- 修复 参数为对象的情况下url在某些情况显示错误的bug
## 1.0.02021-11-19
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-file-picker](https://uniapp.dcloud.io/component/uniui/uni-file-picker)
## 0.2.162021-11-08
- 修复 传入空对象 显示错误的Bug
## 0.2.152021-08-30
- 修复 return-type="object" 时且存在v-model时无法删除文件的Bug
## 0.2.142021-08-23
- 新增 参数中返回 fileID 字段
## 0.2.132021-08-23
- 修复 腾讯云传入fileID 不能回显的bug
- 修复 选择图片后,不能放大的问题
## 0.2.122021-08-17
- 修复 由于 0.2.11 版本引起的不能回显图片的Bug
## 0.2.112021-08-16
- 新增 clearFiles(index) 方法,可以手动删除指定文件
- 修复 v-model 值设为 null 报错的Bug
## 0.2.102021-08-13
- 修复 return-type="object" 时无法删除文件的Bug
## 0.2.92021-08-03
- 修复 auto-upload 属性失效的Bug
## 0.2.82021-07-31
- 修复 fileExtname属性不指定值报错的Bug
## 0.2.72021-07-31
- 修复 在某种场景下图片不回显的Bug
## 0.2.62021-07-30
- 修复 return-type为object下返回值不正确的Bug
## 0.2.52021-07-30
- 修复(重要) H5 平台下如果和uni-forms组件一同使用导致页面卡死的问题
## 0.2.32021-07-28
- 优化 调整示例代码
## 0.2.22021-07-27
- 修复 vue3 下赋值错误的Bug
- 优化 h5平台下上传文件导致页面卡死的问题
## 0.2.02021-07-13
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 0.1.12021-07-02
- 修复 sourceType 缺少默认值导致 ios 无法选择文件
## 0.1.02021-06-30
- 优化 解耦与uniCloud的强绑定关系 如不绑定服务空间默认autoUpload为false且不可更改
## 0.0.112021-06-30
- 修复 由 0.0.10 版本引发的 returnType 属性失效的问题
## 0.0.102021-06-29
- 优化 文件上传后进度条消失时机
## 0.0.92021-06-29
- 修复 在uni-forms 中,删除文件 获取的值不对的Bug
## 0.0.82021-06-15
- 修复 删除文件时无法触发 v-model 的Bug
## 0.0.72021-05-12
- 新增 组件示例地址
## 0.0.62021-04-09
- 修复 选择的文件非 file-extname 字段指定的扩展名报错的Bug
## 0.0.52021-04-09
- 优化 更新组件示例
## 0.0.42021-04-09
- 优化 file-extname 字段支持字符串写法,多个扩展名需要用逗号分隔
## 0.0.32021-02-05
- 调整为uni_modules目录规范
- 修复 微信小程序不指定 fileExtname 属性选择失败的Bug

View File

@ -0,0 +1,287 @@
'use strict';
const ERR_MSG_OK = 'chooseAndUploadFile:ok';
const ERR_MSG_FAIL = 'chooseAndUploadFile:fail';
function chooseImage(opts) {
const {
count,
sizeType = ['original', 'compressed'],
sourceType,
extension
} = opts
return new Promise((resolve, reject) => {
// 微信由于旧接口不再维护针对微信小程序平台改用chooseMedia接口
// #ifdef MP-WEIXIN
uni.chooseMedia({
count,
sizeType,
sourceType,
mediaType: ['image'],
extension,
success(res) {
res.tempFiles.forEach(item => {
item.path = item.tempFilePath;
})
resolve(normalizeChooseAndUploadFileRes(res, 'image'));
},
fail(res) {
reject({
errMsg: res.errMsg.replace('chooseImage:fail', ERR_MSG_FAIL),
});
},
})
// #endif
// #ifndef MP-WEIXIN
uni.chooseImage({
count,
sizeType,
sourceType,
extension,
success(res) {
resolve(normalizeChooseAndUploadFileRes(res, 'image'));
},
fail(res) {
reject({
errMsg: res.errMsg.replace('chooseImage:fail', ERR_MSG_FAIL),
});
},
});
// #endif
});
}
function chooseVideo(opts) {
const {
count,
camera,
compressed,
maxDuration,
sourceType,
extension
} = opts;
return new Promise((resolve, reject) => {
// 微信由于旧接口不再维护针对微信小程序平台改用chooseMedia接口
// #ifdef MP-WEIXIN
uni.chooseMedia({
count,
compressed,
maxDuration,
sourceType,
extension,
mediaType: ['video'],
success(res) {
const {
tempFiles,
} = res;
resolve(normalizeChooseAndUploadFileRes({
errMsg: 'chooseVideo:ok',
tempFiles: tempFiles.map(item => {
return {
name: item.name || '',
path: item.tempFilePath,
thumbTempFilePath: item.thumbTempFilePath,
size:item.size,
type: (res.tempFile && res.tempFile.type) || '',
width:item.width,
height:item.height,
duration:item.duration,
fileType: 'video',
cloudPath: '',
}
}),
}, 'video'));
},
fail(res) {
reject({
errMsg: res.errMsg.replace('chooseVideo:fail', ERR_MSG_FAIL),
});
},
})
// #endif
// #ifndef MP-WEIXIN
uni.chooseVideo({
camera,
compressed,
maxDuration,
sourceType,
extension,
success(res) {
const {
tempFilePath,
duration,
size,
height,
width
} = res;
resolve(normalizeChooseAndUploadFileRes({
errMsg: 'chooseVideo:ok',
tempFilePaths: [tempFilePath],
tempFiles: [{
name: (res.tempFile && res.tempFile.name) || '',
path: tempFilePath,
size,
type: (res.tempFile && res.tempFile.type) || '',
width,
height,
duration,
fileType: 'video',
cloudPath: '',
}, ],
}, 'video'));
},
fail(res) {
reject({
errMsg: res.errMsg.replace('chooseVideo:fail', ERR_MSG_FAIL),
});
},
});
// #endif
});
}
function chooseAll(opts) {
const {
count,
extension
} = opts;
return new Promise((resolve, reject) => {
let chooseFile = uni.chooseFile;
if (typeof wx !== 'undefined' &&
typeof wx.chooseMessageFile === 'function') {
chooseFile = wx.chooseMessageFile;
}
if (typeof chooseFile !== 'function') {
return reject({
errMsg: ERR_MSG_FAIL + ' 请指定 type 类型,该平台仅支持选择 image 或 video。',
});
}
chooseFile({
type: 'all',
count,
extension,
success(res) {
resolve(normalizeChooseAndUploadFileRes(res));
},
fail(res) {
reject({
errMsg: res.errMsg.replace('chooseFile:fail', ERR_MSG_FAIL),
});
},
});
});
}
function normalizeChooseAndUploadFileRes(res, fileType) {
res.tempFiles.forEach((item, index) => {
if (!item.name) {
item.name = item.path.substring(item.path.lastIndexOf('/') + 1);
}
if (fileType) {
item.fileType = fileType;
}
item.cloudPath =
Date.now() + '_' + index + item.name.substring(item.name.lastIndexOf('.'));
});
if (!res.tempFilePaths) {
res.tempFilePaths = res.tempFiles.map((file) => file.path);
}
return res;
}
function uploadCloudFiles(files, max = 5, onUploadProgress) {
files = JSON.parse(JSON.stringify(files))
const len = files.length
let count = 0
let self = this
return new Promise(resolve => {
while (count < max) {
next()
}
function next() {
let cur = count++
if (cur >= len) {
!files.find(item => !item.url && !item.errMsg) && resolve(files)
return
}
const fileItem = files[cur]
const index = self.files.findIndex(v => v.uuid === fileItem.uuid)
fileItem.url = ''
delete fileItem.errMsg
uniCloud
.uploadFile({
filePath: fileItem.path,
cloudPath: fileItem.cloudPath,
fileType: fileItem.fileType,
onUploadProgress: res => {
res.index = index
onUploadProgress && onUploadProgress(res)
}
})
.then(res => {
fileItem.url = res.fileID
fileItem.index = index
if (cur < len) {
next()
}
})
.catch(res => {
fileItem.errMsg = res.errMsg || res.message
fileItem.index = index
if (cur < len) {
next()
}
})
}
})
}
function uploadFiles(choosePromise, {
onChooseFile,
onUploadProgress
}) {
return choosePromise
.then((res) => {
if (onChooseFile) {
const customChooseRes = onChooseFile(res);
if (typeof customChooseRes !== 'undefined') {
return Promise.resolve(customChooseRes).then((chooseRes) => typeof chooseRes === 'undefined' ?
res : chooseRes);
}
}
return res;
})
.then((res) => {
if (res === false) {
return {
errMsg: ERR_MSG_OK,
tempFilePaths: [],
tempFiles: [],
};
}
return res
})
}
function chooseAndUploadFile(opts = {
type: 'all'
}) {
if (opts.type === 'image') {
return uploadFiles(chooseImage(opts), opts);
} else if (opts.type === 'video') {
return uploadFiles(chooseVideo(opts), opts);
}
return uploadFiles(chooseAll(opts), opts);
}
export {
chooseAndUploadFile,
uploadCloudFiles
};

View File

@ -0,0 +1,668 @@
<template>
<view class="uni-file-picker">
<view v-if="title" class="uni-file-picker__header">
<text class="file-title">{{ title }}</text>
<text class="file-count">{{ filesList.length }}/{{ limitLength }}</text>
</view>
<upload-image v-if="fileMediatype === 'image' && showType === 'grid'" :readonly="readonly"
:image-styles="imageStyles" :files-list="filesList" :limit="limitLength" :disablePreview="disablePreview"
:delIcon="delIcon" @uploadFiles="uploadFiles" @choose="choose" @delFile="delFile">
<slot>
<view class="is-add">
<view class="icon-add"></view>
<view class="icon-add rotate"></view>
</view>
</slot>
</upload-image>
<upload-file v-if="fileMediatype !== 'image' || showType !== 'grid'" :readonly="readonly"
:list-styles="listStyles" :files-list="filesList" :showType="showType" :delIcon="delIcon"
@uploadFiles="uploadFiles" @choose="choose" @delFile="delFile">
<slot><button type="primary" size="mini">选择文件</button></slot>
</upload-file>
</view>
</template>
<script>
import {
chooseAndUploadFile,
uploadCloudFiles
} from './choose-and-upload-file.js'
import {
get_file_ext,
get_extname,
get_files_and_is_max,
get_file_info,
get_file_data
} from './utils.js'
import uploadImage from './upload-image.vue'
import uploadFile from './upload-file.vue'
let fileInput = null
/**
* FilePicker 文件选择上传
* @description 文件选择上传组件可以选择图片视频等任意文件并上传到当前绑定的服务空间
* @tutorial https://ext.dcloud.net.cn/plugin?id=4079
* @property {Object|Array} value 组件数据通常用来回显 ,类型由return-type属性决定
* @property {Boolean} disabled = [true|false] 组件禁用
* @value true 禁用
* @value false 取消禁用
* @property {Boolean} readonly = [true|false] 组件只读不可选择不显示进度不显示删除按钮
* @value true 只读
* @value false 取消只读
* @property {String} return-type = [array|object] 限制 value 格式当为 object 组件只能单选且会覆盖
* @value array 规定 value 属性的类型为数组
* @value object 规定 value 属性的类型为对象
* @property {Boolean} disable-preview = [true|false] 禁用图片预览 mode:grid 时生效
* @value true 禁用图片预览
* @value false 取消禁用图片预览
* @property {Boolean} del-icon = [true|false] 是否显示删除按钮
* @value true 显示删除按钮
* @value false 不显示删除按钮
* @property {Boolean} auto-upload = [true|false] 是否自动上传值为true则只触发@select,可自行上传
* @value true 自动上传
* @value false 取消自动上传
* @property {Number|String} limit 最大选择个数 h5 会自动忽略多选的部分
* @property {String} title 组件标题右侧显示上传计数
* @property {String} mode = [list|grid] 选择文件后的文件列表样式
* @value list 列表显示
* @value grid 宫格显示
* @property {String} file-mediatype = [image|video|all] 选择文件类型
* @value image 只选择图片
* @value video 只选择视频
* @value all 选择所有文件
* @property {Array} file-extname 选择文件后缀根据 file-mediatype 属性而不同
* @property {Object} list-style mode:list 时的样式
* @property {Object} image-styles 选择文件后缀根据 file-mediatype 属性而不同
* @event {Function} select 选择文件后触发
* @event {Function} progress 文件上传时触发
* @event {Function} success 上传成功触发
* @event {Function} fail 上传失败触发
* @event {Function} delete 文件从列表移除时触发
*/
export default {
name: 'uniFilePicker',
components: {
uploadImage,
uploadFile
},
options: {
virtualHost: true
},
emits: ['select', 'success', 'fail', 'progress', 'delete', 'update:modelValue', 'input'],
props: {
modelValue: {
type: [Array, Object],
default () {
return []
}
},
value: {
type: [Array, Object],
default () {
return []
}
},
disabled: {
type: Boolean,
default: false
},
disablePreview: {
type: Boolean,
default: false
},
delIcon: {
type: Boolean,
default: true
},
//
autoUpload: {
type: Boolean,
default: true
},
// h5
limit: {
type: [Number, String],
default: 9
},
// grid | list | list-card
mode: {
type: String,
default: 'grid'
},
// image/video/all
fileMediatype: {
type: String,
default: 'image'
},
//
fileExtname: {
type: [Array, String],
default () {
return []
}
},
title: {
type: String,
default: ''
},
listStyles: {
type: Object,
default () {
return {
//
border: true,
// 线
dividline: true,
// 线
borderStyle: {}
}
}
},
imageStyles: {
type: Object,
default () {
return {
width: 'auto',
height: 'auto'
}
}
},
readonly: {
type: Boolean,
default: false
},
returnType: {
type: String,
default: 'array'
},
sizeType: {
type: Array,
default () {
return ['original', 'compressed']
}
},
sourceType: {
type: Array,
default () {
return ['album', 'camera']
}
},
provider: {
type: String,
default: '' // unicloud extStorage
}
},
data() {
return {
files: [],
localValue: []
}
},
watch: {
value: {
handler(newVal, oldVal) {
this.setValue(newVal, oldVal)
},
immediate: true
},
modelValue: {
handler(newVal, oldVal) {
this.setValue(newVal, oldVal)
},
immediate: true
},
},
computed: {
filesList() {
let files = []
this.files.forEach(v => {
files.push(v)
})
return files
},
showType() {
if (this.fileMediatype === 'image') {
return this.mode
}
return 'list'
},
limitLength() {
if (this.returnType === 'object') {
return 1
}
if (!this.limit) {
return 1
}
if (this.limit >= 9) {
return 9
}
return this.limit
}
},
created() {
// TODO
if (!(uniCloud.config && uniCloud.config.provider)) {
this.noSpace = true
uniCloud.chooseAndUploadFile = chooseAndUploadFile
}
this.form = this.getForm('uniForms')
this.formItem = this.getForm('uniFormsItem')
if (this.form && this.formItem) {
if (this.formItem.name) {
this.rename = this.formItem.name
this.form.inputChildrens.push(this)
}
}
},
methods: {
/**
* 公开用户使用清空文件
* @param {Object} index
*/
clearFiles(index) {
if (index !== 0 && !index) {
this.files = []
this.$nextTick(() => {
this.setEmit()
})
} else {
this.files.splice(index, 1)
}
this.$nextTick(() => {
this.setEmit()
})
},
/**
* 公开用户使用继续上传
*/
upload() {
let files = []
this.files.forEach((v, index) => {
if (v.status === 'ready' || v.status === 'error') {
files.push(Object.assign({}, v))
}
})
return this.uploadFiles(files)
},
async setValue(newVal, oldVal) {
const newData = async (v) => {
const reg = /cloud:\/\/([\w.]+\/?)\S*/
let url = ''
if(v.fileID){
url = v.fileID
}else{
url = v.url
}
if (reg.test(url)) {
v.fileID = url
v.url = await this.getTempFileURL(url)
}
if(v.url) v.path = v.url
return v
}
if (this.returnType === 'object') {
if (newVal) {
await newData(newVal)
} else {
newVal = {}
}
} else {
if (!newVal) newVal = []
for(let i =0 ;i < newVal.length ;i++){
let v = newVal[i]
await newData(v)
}
}
this.localValue = newVal
if (this.form && this.formItem &&!this.is_reset) {
this.is_reset = false
this.formItem.setValue(this.localValue)
}
let filesData = Object.keys(newVal).length > 0 ? newVal : [];
this.files = [].concat(filesData)
},
/**
* 选择文件
*/
choose() {
if (this.disabled) return
if (this.files.length >= Number(this.limitLength) && this.showType !== 'grid' && this.returnType ===
'array') {
uni.showToast({
title: `您最多选择 ${this.limitLength} 个文件`,
icon: 'none'
})
return
}
this.chooseFiles()
},
/**
* 选择文件并上传
*/
chooseFiles() {
const _extname = get_extname(this.fileExtname)
//
uniCloud
.chooseAndUploadFile({
type: this.fileMediatype,
compressed: false,
sizeType: this.sizeType,
sourceType: this.sourceType,
// TODO video
extension: _extname.length > 0 ? _extname : undefined,
count: this.limitLength - this.files.length, //9
onChooseFile: this.chooseFileCallback,
onUploadProgress: progressEvent => {
this.setProgress(progressEvent, progressEvent.index)
}
})
.then(result => {
this.setSuccessAndError(result.tempFiles)
})
.catch(err => {
console.log('选择失败', err)
})
},
/**
* 选择文件回调
* @param {Object} res
*/
async chooseFileCallback(res) {
const _extname = get_extname(this.fileExtname)
const is_one = (Number(this.limitLength) === 1 &&
this.disablePreview &&
!this.disabled) ||
this.returnType === 'object'
//
if (is_one) {
this.files = []
}
let {
filePaths,
files
} = get_files_and_is_max(res, _extname)
if (!(_extname && _extname.length > 0)) {
filePaths = res.tempFilePaths
files = res.tempFiles
}
let currentData = []
for (let i = 0; i < files.length; i++) {
if (this.limitLength - this.files.length <= 0) break
files[i].uuid = Date.now()
let filedata = await get_file_data(files[i], this.fileMediatype)
filedata.progress = 0
filedata.status = 'ready'
this.files.push(filedata)
currentData.push({
...filedata,
file: files[i]
})
}
this.$emit('select', {
tempFiles: currentData,
tempFilePaths: filePaths
})
res.tempFiles = files
//
if (!this.autoUpload || this.noSpace) {
res.tempFiles = []
}
res.tempFiles.forEach((fileItem, index) => {
this.provider && (fileItem.provider = this.provider);
const fileNameSplit = fileItem.name.split('.')
const ext = fileNameSplit.pop()
const fileName = fileNameSplit.join('.').replace(/[\s\/\?<>\\:\*\|":]/g, '_')
fileItem.cloudPath = fileName + '_' + Date.now() + '_' + index + '.' + ext
})
},
/**
* 批传
* @param {Object} e
*/
uploadFiles(files) {
files = [].concat(files)
return uploadCloudFiles.call(this, files, 5, res => {
this.setProgress(res, res.index, true)
})
.then(result => {
this.setSuccessAndError(result)
return result;
})
.catch(err => {
console.log(err)
})
},
/**
* 成功或失败
*/
async setSuccessAndError(res, fn) {
let successData = []
let errorData = []
let tempFilePath = []
let errorTempFilePath = []
for (let i = 0; i < res.length; i++) {
const item = res[i]
const index = item.uuid ? this.files.findIndex(p => p.uuid === item.uuid) : item.index
if (index === -1 || !this.files) break
if (item.errMsg === 'request:fail') {
this.files[index].url = item.path
this.files[index].status = 'error'
this.files[index].errMsg = item.errMsg
// this.files[index].progress = -1
errorData.push(this.files[index])
errorTempFilePath.push(this.files[index].url)
} else {
this.files[index].errMsg = ''
this.files[index].fileID = item.url
const reg = /cloud:\/\/([\w.]+\/?)\S*/
if (reg.test(item.url)) {
this.files[index].url = await this.getTempFileURL(item.url)
}else{
this.files[index].url = item.url
}
this.files[index].status = 'success'
this.files[index].progress += 1
successData.push(this.files[index])
tempFilePath.push(this.files[index].fileID)
}
}
if (successData.length > 0) {
this.setEmit()
//
this.$emit('success', {
tempFiles: this.backObject(successData),
tempFilePaths: tempFilePath
})
}
if (errorData.length > 0) {
this.$emit('fail', {
tempFiles: this.backObject(errorData),
tempFilePaths: errorTempFilePath
})
}
},
/**
* 获取进度
* @param {Object} progressEvent
* @param {Object} index
* @param {Object} type
*/
setProgress(progressEvent, index, type) {
const fileLenth = this.files.length
const percentNum = (index / fileLenth) * 100
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
let idx = index
if (!type) {
idx = this.files.findIndex(p => p.uuid === progressEvent.tempFile.uuid)
}
if (idx === -1 || !this.files[idx]) return
// fix by mehaotian 100 -1
this.files[idx].progress = percentCompleted - 1
//
this.$emit('progress', {
index: idx,
progress: parseInt(percentCompleted),
tempFile: this.files[idx]
})
},
/**
* 删除文件
* @param {Object} index
*/
delFile(index) {
this.$emit('delete', {
index,
tempFile: this.files[index],
tempFilePath: this.files[index].url
})
this.files.splice(index, 1)
this.$nextTick(() => {
this.setEmit()
})
},
/**
* 获取文件名和后缀
* @param {Object} name
*/
getFileExt(name) {
const last_len = name.lastIndexOf('.')
const len = name.length
return {
name: name.substring(0, last_len),
ext: name.substring(last_len + 1, len)
}
},
/**
* 处理返回事件
*/
setEmit() {
let data = []
if (this.returnType === 'object') {
data = this.backObject(this.files)[0]
this.localValue = data?data:null
} else {
data = this.backObject(this.files)
if (!this.localValue) {
this.localValue = []
}
this.localValue = [...data]
}
// #ifdef VUE3
this.$emit('update:modelValue', this.localValue)
// #endif
// #ifndef VUE3
this.$emit('input', this.localValue)
// #endif
},
/**
* 处理返回参数
* @param {Object} files
*/
backObject(files) {
let newFilesData = []
files.forEach(v => {
newFilesData.push({
extname: v.extname,
fileType: v.fileType,
image: v.image,
name: v.name,
path: v.path,
size: v.size,
fileID:v.fileID,
url: v.url,
// bug, #694
uuid: v.uuid,
status: v.status,
cloudPath: v.cloudPath
})
})
return newFilesData
},
async getTempFileURL(fileList) {
fileList = {
fileList: [].concat(fileList)
}
const urls = await uniCloud.getTempFileURL(fileList)
return urls.fileList[0].tempFileURL || ''
},
/**
* 获取父元素实例
*/
getForm(name = 'uniForms') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false;
parentName = parent.$options.name;
}
return parent;
}
}
}
</script>
<style>
.uni-file-picker {
/* #ifndef APP-NVUE */
box-sizing: border-box;
overflow: hidden;
width: 100%;
/* #endif */
flex: 1;
}
.uni-file-picker__header {
padding-top: 5px;
padding-bottom: 10px;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: space-between;
}
.file-title {
font-size: 14px;
color: #333;
}
.file-count {
font-size: 14px;
color: #999;
}
.is-add {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
}
.icon-add {
width: 50px;
height: 5px;
background-color: #f1f1f1;
border-radius: 2px;
}
.rotate {
position: absolute;
transform: rotate(90deg);
}
</style>

View File

@ -0,0 +1,325 @@
<template>
<view class="uni-file-picker__files">
<view v-if="!readonly" class="files-button" @click="choose">
<slot></slot>
</view>
<!-- :class="{'is-text-box':showType === 'list'}" -->
<view v-if="list.length > 0" class="uni-file-picker__lists is-text-box" :style="borderStyle">
<!-- ,'is-list-card':showType === 'list-card' -->
<view class="uni-file-picker__lists-box" v-for="(item ,index) in list" :key="index" :class="{
'files-border':index !== 0 && styles.dividline}"
:style="index !== 0 && styles.dividline &&borderLineStyle">
<view class="uni-file-picker__item">
<!-- :class="{'is-text-image':showType === 'list'}" -->
<!-- <view class="files__image is-text-image">
<image class="header-image" :src="item.logo" mode="aspectFit"></image>
</view> -->
<view class="files__name">{{item.name}}</view>
<view v-if="delIcon&&!readonly" class="icon-del-box icon-files" @click="delFile(index)">
<view class="icon-del icon-files"></view>
<view class="icon-del rotate"></view>
</view>
</view>
<view v-if="(item.progress && item.progress !== 100) ||item.progress===0 " class="file-picker__progress">
<progress class="file-picker__progress-item" :percent="item.progress === -1?0:item.progress" stroke-width="4"
:backgroundColor="item.errMsg?'#ff5a5f':'#EBEBEB'" />
</view>
<view v-if="item.status === 'error'" class="file-picker__mask" @click.stop="uploadFiles(item,index)">
点击重试
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: "uploadFile",
emits:['uploadFiles','choose','delFile'],
props: {
filesList: {
type: Array,
default () {
return []
}
},
delIcon: {
type: Boolean,
default: true
},
limit: {
type: [Number, String],
default: 9
},
showType: {
type: String,
default: ''
},
listStyles: {
type: Object,
default () {
return {
//
border: true,
// 线
dividline: true,
// 线
borderStyle: {}
}
}
},
readonly:{
type:Boolean,
default:false
}
},
computed: {
list() {
let files = []
this.filesList.forEach(v => {
files.push(v)
})
return files
},
styles() {
let styles = {
border: true,
dividline: true,
'border-style': {}
}
return Object.assign(styles, this.listStyles)
},
borderStyle() {
let {
borderStyle,
border
} = this.styles
let obj = {}
if (!border) {
obj.border = 'none'
} else {
let width = (borderStyle && borderStyle.width) || 1
width = this.value2px(width)
let radius = (borderStyle && borderStyle.radius) || 5
radius = this.value2px(radius)
obj = {
'border-width': width,
'border-style': (borderStyle && borderStyle.style) || 'solid',
'border-color': (borderStyle && borderStyle.color) || '#eee',
'border-radius': radius
}
}
let classles = ''
for (let i in obj) {
classles += `${i}:${obj[i]};`
}
return classles
},
borderLineStyle() {
let obj = {}
let {
borderStyle
} = this.styles
if (borderStyle && borderStyle.color) {
obj['border-color'] = borderStyle.color
}
if (borderStyle && borderStyle.width) {
let width = borderStyle && borderStyle.width || 1
let style = borderStyle && borderStyle.style || 0
if (typeof width === 'number') {
width += 'px'
} else {
width = width.indexOf('px') ? width : width + 'px'
}
obj['border-width'] = width
if (typeof style === 'number') {
style += 'px'
} else {
style = style.indexOf('px') ? style : style + 'px'
}
obj['border-top-style'] = style
}
let classles = ''
for (let i in obj) {
classles += `${i}:${obj[i]};`
}
return classles
}
},
methods: {
uploadFiles(item, index) {
this.$emit("uploadFiles", {
item,
index
})
},
choose() {
this.$emit("choose")
},
delFile(index) {
this.$emit('delFile', index)
},
value2px(value) {
if (typeof value === 'number') {
value += 'px'
} else {
value = value.indexOf('px') !== -1 ? value : value + 'px'
}
return value
}
}
}
</script>
<style lang="scss">
.uni-file-picker__files {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: flex-start;
}
.files-button {
// border: 1px red solid;
}
.uni-file-picker__lists {
position: relative;
margin-top: 5px;
overflow: hidden;
}
.file-picker__mask {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
position: absolute;
right: 0;
top: 0;
bottom: 0;
left: 0;
color: #fff;
font-size: 14px;
background-color: rgba(0, 0, 0, 0.4);
}
.uni-file-picker__lists-box {
position: relative;
}
.uni-file-picker__item {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
padding: 8px 10px;
padding-right: 5px;
padding-left: 10px;
}
.files-border {
border-top: 1px #eee solid;
}
.files__name {
flex: 1;
font-size: 14px;
color: #666;
margin-right: 25px;
/* #ifndef APP-NVUE */
word-break: break-all;
word-wrap: break-word;
/* #endif */
}
.icon-files {
/* #ifndef APP-NVUE */
position: static;
background-color: initial;
/* #endif */
}
// .icon-files .icon-del {
// background-color: #333;
// width: 12px;
// height: 1px;
// }
.is-list-card {
border: 1px #eee solid;
margin-bottom: 5px;
border-radius: 5px;
box-shadow: 0 0 2px 0px rgba(0, 0, 0, 0.1);
padding: 5px;
}
.files__image {
width: 40px;
height: 40px;
margin-right: 10px;
}
.header-image {
width: 100%;
height: 100%;
}
.is-text-box {
border: 1px #eee solid;
border-radius: 5px;
}
.is-text-image {
width: 25px;
height: 25px;
margin-left: 5px;
}
.rotate {
position: absolute;
transform: rotate(90deg);
}
.icon-del-box {
/* #ifndef APP-NVUE */
display: flex;
margin: auto 0;
/* #endif */
align-items: center;
justify-content: center;
position: absolute;
top: 0px;
bottom: 0;
right: 5px;
height: 26px;
width: 26px;
// border-radius: 50%;
// background-color: rgba(0, 0, 0, 0.5);
z-index: 2;
transform: rotate(-45deg);
}
.icon-del {
width: 15px;
height: 1px;
background-color: #333;
// border-radius: 1px;
}
/* #ifdef H5 */
@media all and (min-width: 768px) {
.uni-file-picker__files {
max-width: 375px;
}
}
/* #endif */
</style>

View File

@ -0,0 +1,292 @@
<template>
<view class="uni-file-picker__container">
<view class="file-picker__box" v-for="(item,index) in filesList" :key="index" :style="boxStyle">
<view class="file-picker__box-content" :style="borderStyle">
<image class="file-image" :src="item.url" mode="aspectFill" @click.stop="prviewImage(item,index)"></image>
<view v-if="delIcon && !readonly" class="icon-del-box" @click.stop="delFile(index)">
<view class="icon-del"></view>
<view class="icon-del rotate"></view>
</view>
<view v-if="(item.progress && item.progress !== 100) ||item.progress===0 " class="file-picker__progress">
<progress class="file-picker__progress-item" :percent="item.progress === -1?0:item.progress" stroke-width="4"
:backgroundColor="item.errMsg?'#ff5a5f':'#EBEBEB'" />
</view>
<view v-if="item.errMsg" class="file-picker__mask" @click.stop="uploadFiles(item,index)">
点击重试
</view>
</view>
</view>
<view v-if="filesList.length < limit && !readonly" class="file-picker__box" :style="boxStyle">
<view class="file-picker__box-content is-add" :style="borderStyle" @click="choose">
<slot>
<view class="icon-add"></view>
<view class="icon-add rotate"></view>
</slot>
</view>
</view>
</view>
</template>
<script>
export default {
name: "uploadImage",
emits:['uploadFiles','choose','delFile'],
props: {
filesList: {
type: Array,
default () {
return []
}
},
disabled:{
type: Boolean,
default: false
},
disablePreview: {
type: Boolean,
default: false
},
limit: {
type: [Number, String],
default: 9
},
imageStyles: {
type: Object,
default () {
return {
width: 'auto',
height: 'auto',
border: {}
}
}
},
delIcon: {
type: Boolean,
default: true
},
readonly:{
type:Boolean,
default:false
}
},
computed: {
styles() {
let styles = {
width: 'auto',
height: 'auto',
border: {}
}
return Object.assign(styles, this.imageStyles)
},
boxStyle() {
const {
width = 'auto',
height = 'auto'
} = this.styles
let obj = {}
if (height === 'auto') {
if (width !== 'auto') {
obj.height = this.value2px(width)
obj['padding-top'] = 0
} else {
obj.height = 0
}
} else {
obj.height = this.value2px(height)
obj['padding-top'] = 0
}
if (width === 'auto') {
if (height !== 'auto') {
obj.width = this.value2px(height)
} else {
obj.width = '33.3%'
}
} else {
obj.width = this.value2px(width)
}
let classles = ''
for(let i in obj){
classles+= `${i}:${obj[i]};`
}
return classles
},
borderStyle() {
let {
border
} = this.styles
let obj = {}
const widthDefaultValue = 1
const radiusDefaultValue = 3
if (typeof border === 'boolean') {
obj.border = border ? '1px #eee solid' : 'none'
} else {
let width = (border && border.width) || widthDefaultValue
width = this.value2px(width)
let radius = (border && border.radius) || radiusDefaultValue
radius = this.value2px(radius)
obj = {
'border-width': width,
'border-style': (border && border.style) || 'solid',
'border-color': (border && border.color) || '#eee',
'border-radius': radius
}
}
let classles = ''
for(let i in obj){
classles+= `${i}:${obj[i]};`
}
return classles
}
},
methods: {
uploadFiles(item, index) {
this.$emit("uploadFiles", item)
},
choose() {
this.$emit("choose")
},
delFile(index) {
this.$emit('delFile', index)
},
prviewImage(img, index) {
let urls = []
if(Number(this.limit) === 1&&this.disablePreview&&!this.disabled){
this.$emit("choose")
}
if(this.disablePreview) return
this.filesList.forEach(i => {
urls.push(i.url)
})
uni.previewImage({
urls: urls,
current: index
});
},
value2px(value) {
if (typeof value === 'number') {
value += 'px'
} else {
if (value.indexOf('%') === -1) {
value = value.indexOf('px') !== -1 ? value : value + 'px'
}
}
return value
}
}
}
</script>
<style lang="scss">
.uni-file-picker__container {
/* #ifndef APP-NVUE */
display: flex;
box-sizing: border-box;
/* #endif */
flex-wrap: wrap;
margin: -5px;
}
.file-picker__box {
position: relative;
// flex: 0 0 33.3%;
width: 33.3%;
height: 0;
padding-top: 33.33%;
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
}
.file-picker__box-content {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: 5px;
border: 1px #eee solid;
border-radius: 5px;
overflow: hidden;
}
.file-picker__progress {
position: absolute;
bottom: 0;
left: 0;
right: 0;
/* border: 1px red solid; */
z-index: 2;
}
.file-picker__progress-item {
width: 100%;
}
.file-picker__mask {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
position: absolute;
right: 0;
top: 0;
bottom: 0;
left: 0;
color: #fff;
font-size: 12px;
background-color: rgba(0, 0, 0, 0.4);
}
.file-image {
width: 100%;
height: 100%;
}
.is-add {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
}
.icon-add {
width: 50px;
height: 5px;
background-color: #f1f1f1;
border-radius: 2px;
}
.rotate {
position: absolute;
transform: rotate(90deg);
}
.icon-del-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
position: absolute;
top: 3px;
right: 3px;
height: 26px;
width: 26px;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 2;
transform: rotate(-45deg);
}
.icon-del {
width: 15px;
height: 2px;
background-color: #fff;
border-radius: 2px;
}
</style>

View File

@ -0,0 +1,110 @@
/**
* 获取文件名和后缀
* @param {String} name
*/
export const get_file_ext = (name) => {
const last_len = name.lastIndexOf('.')
const len = name.length
return {
name: name.substring(0, last_len),
ext: name.substring(last_len + 1, len)
}
}
/**
* 获取扩展名
* @param {Array} fileExtname
*/
export const get_extname = (fileExtname) => {
if (!Array.isArray(fileExtname)) {
let extname = fileExtname.replace(/(\[|\])/g, '')
return extname.split(',')
} else {
return fileExtname
}
return []
}
/**
* 获取文件和检测是否可选
*/
export const get_files_and_is_max = (res, _extname) => {
let filePaths = []
let files = []
if(!_extname || _extname.length === 0){
return {
filePaths,
files
}
}
res.tempFiles.forEach(v => {
let fileFullName = get_file_ext(v.name)
const extname = fileFullName.ext.toLowerCase()
if (_extname.indexOf(extname) !== -1) {
files.push(v)
filePaths.push(v.path)
}
})
if (files.length !== res.tempFiles.length) {
uni.showToast({
title: `当前选择了${res.tempFiles.length}个文件 ${res.tempFiles.length - files.length} 个文件格式不正确`,
icon: 'none',
duration: 5000
})
}
return {
filePaths,
files
}
}
/**
* 获取图片信息
* @param {Object} filepath
*/
export const get_file_info = (filepath) => {
return new Promise((resolve, reject) => {
uni.getImageInfo({
src: filepath,
success(res) {
resolve(res)
},
fail(err) {
reject(err)
}
})
})
}
/**
* 获取封装数据
*/
export const get_file_data = async (files, type = 'image') => {
// 最终需要上传数据库的数据
let fileFullName = get_file_ext(files.name)
const extname = fileFullName.ext.toLowerCase()
let filedata = {
name: files.name,
uuid: files.uuid,
extname: extname || '',
cloudPath: files.cloudPath,
fileType: files.fileType,
thumbTempFilePath: files.thumbTempFilePath,
url: files.path || files.path,
size: files.size, //单位是字节
image: {},
path: files.path,
video: {}
}
if (type === 'image') {
const imageinfo = await get_file_info(files.path)
delete filedata.video
filedata.image.width = imageinfo.width
filedata.image.height = imageinfo.height
filedata.image.location = imageinfo.path
} else {
delete filedata.image
}
return filedata
}

View File

@ -0,0 +1,84 @@
{
"id": "uni-file-picker",
"displayName": "uni-file-picker 文件选择上传",
"version": "1.0.11",
"description": "文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间",
"keywords": [
"uni-ui",
"uniui",
"图片上传",
"文件上传"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": ["uni-scss"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y",
"alipay": "n"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

View File

@ -0,0 +1,11 @@
## FilePicker 文件选择上传
> **组件名uni-file-picker**
> 代码块: `uFilePicker`
文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-file-picker)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

View File

@ -0,0 +1,42 @@
## 2.0.102024-06-07
- 优化 uni-app x 中size 属性的类型
## 2.0.92024-01-12
fix: 修复图标大小默认值错误的问题
## 2.0.82023-12-14
- 修复 项目未使用 ts 情况下打包报错的bug
## 2.0.72023-12-14
- 修复 size 属性为 string 时不加单位导致尺寸异常的bug
## 2.0.62023-12-11
- 优化 兼容老版本icon类型如 top bottom 等
## 2.0.52023-12-11
- 优化 兼容老版本icon类型如 top bottom 等
## 2.0.42023-12-06
- 优化 uni-app x 下示例项目图标排序
## 2.0.32023-12-06
- 修复 nvue下引入组件报错的bug
## 2.0.22023-12-05
-优化 size 属性支持单位
## 2.0.12023-12-05
- 新增 uni-app x 支持定义图标
## 1.3.52022-01-24
- 优化 size 属性可以传入不带单位的字符串数值
## 1.3.42022-01-24
- 优化 size 支持其他单位
## 1.3.32022-01-17
- 修复 nvue 有些图标不显示的bug兼容老版本图标
## 1.3.22021-12-01
- 优化 示例可复制图标名称
## 1.3.12021-11-23
- 优化 兼容旧组件 type 值
## 1.3.02021-11-19
- 新增 更多图标
- 优化 自定义图标使用方式
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-icons](https://uniapp.dcloud.io/component/uniui/uni-icons)
## 1.1.72021-11-08
## 1.2.02021-07-30
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.1.52021-05-12
- 新增 组件示例地址
## 1.1.42021-02-05
- 调整为uni_modules目录规范

Some files were not shown because too many files have changed in this diff Show More