This commit is contained in:
qiuyuan 2025-07-22 14:11:42 +08:00
parent 2fb1c184cf
commit 59551ae26a
5 changed files with 1627 additions and 1189 deletions

View File

@ -92,8 +92,10 @@
const defaultValue=this.generateMiniProgramUUID() const defaultValue=this.generateMiniProgramUUID()
wx.setStorageSync('userId', defaultValue) wx.setStorageSync('userId', defaultValue)
this.userId=defaultValue this.userId=defaultValue
} }else{
this.userId=value this.userId=value
}
} catch (e) { } catch (e) {
console.log(e) console.log(e)
const defaultValue=this.generateMiniProgramUUID() const defaultValue=this.generateMiniProgramUUID()

View File

@ -52,19 +52,19 @@
</view> </view>
<view class="weather-footer"> <view class="weather-footer">
<view class="footer-item"> <view class="footer-item">
<text class="footer-icon">🌬</text> <text class="footer-icon"></text>
<text class="footer-text">{{weather.wind}}</text> <text class="footer-text">{{weather.wind}}</text>
</view> </view>
<view class="footer-item"> <view class="footer-item">
<text class="footer-icon">💧湿度</text> <text class="footer-icon">湿度</text>
<text class="footer-text">{{weather.humidity}}%</text> <text class="footer-text">{{weather.humidity}}%</text>
</view> </view>
<view class="footer-item"> <view class="footer-item">
<text class="footer-icon">🌫空气质量</text> <text class="footer-icon">空气质量</text>
<text class="footer-text">{{weather.quality}}</text> <text class="footer-text">{{weather.quality}}</text>
</view> </view>
<view class="footer-item"> <view class="footer-item">
<text class="footer-icon">紫外线</text> <text class="footer-icon">紫外线</text>
<text class="footer-text">{{weather.uv_index}}</text> <text class="footer-text">{{weather.uv_index}}</text>
</view> </view>
</view> </view>
@ -389,7 +389,8 @@
flex: 1; flex: 1;
.footer-icon { .footer-icon {
font-size: 24rpx; font-size: 28rpx;
font-weight:500;
margin-bottom: 6rpx; margin-bottom: 6rpx;
} }

View File

@ -10,6 +10,7 @@
<view class="edit-icon" @click="handleEditClick"> <view class="edit-icon" @click="handleEditClick">
<u-icon name="edit-pen" color="#fff" size="24"></u-icon> <u-icon name="edit-pen" color="#fff" size="24"></u-icon>
</view> </view>
<u-loading-icon v-if="uploading" mode="circle" color="#2979ff" size="28"></u-loading-icon>
</view> </view>
</view> </view>
<view class="name-section" @click="handleEditClick"> <view class="name-section" @click="handleEditClick">
@ -35,61 +36,50 @@
</view> </view>
<!-- 修改用户信息弹窗 --> <!-- 修改用户信息弹窗 -->
<u-modal :show="showEditModal" <u-modal :show="showEditModal" :title="isLogin ? '修改用户信息' : '微信授权登录'" :show-confirm-button="isLogin"
:title="isLogin ? '修改用户信息' : '微信授权登录'" :show-cancel-button="isLogin" @confirm="handleSubmit" @cancel="handleCancel" confirm-color="#2979ff"
:show-confirm-button="isLogin"
:show-cancel-button="isLogin"
@confirm="handleSubmit"
@cancel="handleCancel"
confirm-color="#2979ff"
cancel-color="#606266"> cancel-color="#606266">
<view class="modal-content"> <view class="modal-content">
<!-- 未登录时显示授权按钮 --> <!-- 未登录时显示授权按钮 -->
<view v-if="!isLogin" class="auth-section"> <view v-if="!isLogin" class="auth-section">
<button v-if="!showPhoneButton" <view class="auth-title">请先登录以使用完整功能</view>
class="auth-button" <button v-if="!showPhoneButton" class="auth-button" open-type="getUserInfo"
open-type="getUserInfo"
@getuserinfo="onGetUserInfo"> @getuserinfo="onGetUserInfo">
<image class="wechat-icon" src="/static/imgs/wechat.png"></image>
微信一键登录 微信一键登录
</button> </button>
<button v-if="showPhoneButton" <button v-if="showPhoneButton" type="default" open-type="getPhoneNumber"
type="default" @getphonenumber="getPhoneNumber" class="auth-button phone-button">
open-type="getPhoneNumber" <image class="phone-icon" src="/static/imgs/phone.png"></image>
@getphonenumber="getPhoneNumber"
class="auth-button phone-button">
授权获取手机号 授权获取手机号
</button> </button>
<view class="auth-tip">登录即表示同意用户协议隐私政策</view>
</view> </view>
<!-- 已登录时显示编辑表单 --> <!-- 已登录时显示编辑表单 -->
<view v-else> <view v-else class="edit-section">
<view class="form-item"> <view class="form-item">
<text class="form-label">头像</text> <text class="form-label">头像</text>
<u-upload <u-upload :file-list="avatarList" :max-count="1" @afterRead="handleAvatarUpload"
:file-list="avatarList" @delete="handleAvatarDelete" width="160" height="160" preview-full-image>
:max-count="1" <view slot="delete" class="custom-delete">
@afterRead="handleAvatarUpload" <u-icon name="close" color="#fff" size="24"></u-icon>
@delete="handleAvatarDelete" </view>
width="160"
height="160"
preview-full-image>
</u-upload> </u-upload>
<view class="upload-tip">点击上传建议尺寸1:1</view>
</view> </view>
<view class="form-item"> <view class="form-item">
<text class="form-label">姓名</text> <text class="form-label">姓名</text>
<u-input v-model="formData.name" placeholder="请输入姓名" border="bottom"></u-input> <u-input v-model="formData.name" placeholder="请输入姓名" border="bottom" clearable></u-input>
</view> </view>
<view class="form-item"> <view class="form-item">
<text class="form-label">简介</text> <text class="form-label">简介</text>
<u-input <u-input v-model="formData.bio" type="textarea" placeholder="请输入简介" :maxlength="200"
v-model="formData.bio" height="120" border="bottom">
type="textarea"
placeholder="请输入简介"
:maxlength="200"
height="320">
</u-input> </u-input>
<view class="word-count">{{formData.bio.length}}/200</view>
</view> </view>
</view> </view>
</view> </view>
@ -101,8 +91,14 @@
<script> <script>
import Footer from '@/components/footer_common.vue'; import Footer from '@/components/footer_common.vue';
import { get, post } from '@/utils/request'; import {
import { IMAGE_BASE_URL, BASE_URL } from '@/utils/config'; get,
post
} from '@/utils/request';
import {
IMAGE_BASE_URL,
BASE_URL
} from '@/utils/config';
export default { export default {
components: { components: {
@ -124,8 +120,9 @@ export default {
bio: "", bio: "",
phone: "" phone: ""
}, },
choseList: [ uploading: false,
{ displayAvatar: '/static/imgs/index/nav.png',
choseList: [{
key: 1, key: 1,
url: "/static/imgs/service/service_list.png", url: "/static/imgs/service/service_list.png",
name: '全部工单', name: '全部工单',
@ -198,13 +195,18 @@ export default {
phone: data.phone || '' phone: data.phone || ''
}; };
//
this.displayAvatar = this.userInfo.avatarUrl || '/static/imgs/index/nav.png';
this.formData = { this.formData = {
name: this.userInfo.nickName, name: this.userInfo.nickName,
bio: this.userInfo.bio bio: this.userInfo.bio
}; };
if (this.userInfo.avatarUrl) { if (this.userInfo.avatarUrl) {
this.avatarList = [{ url: this.userInfo.avatarUrl }]; this.avatarList = [{
url: this.userInfo.avatarUrl
}];
} }
}, },
@ -216,7 +218,10 @@ export default {
avatarUrl: '/static/imgs/index/nav.png', avatarUrl: '/static/imgs/index/nav.png',
bio: '这家伙很懒,什么都没有写~' bio: '这家伙很懒,什么都没有写~'
}; };
this.formData = { name: '', bio: '' }; this.formData = {
name: '',
bio: ''
};
this.avatarList = []; this.avatarList = [];
uni.removeStorageSync('token'); uni.removeStorageSync('token');
uni.removeStorageSync('userInfo'); uni.removeStorageSync('userInfo');
@ -236,7 +241,9 @@ export default {
}; };
if (this.userInfo.avatarUrl) { if (this.userInfo.avatarUrl) {
this.avatarList = [{ url: this.userInfo.avatarUrl }]; this.avatarList = [{
url: this.userInfo.avatarUrl
}];
} else { } else {
this.avatarList = []; this.avatarList = [];
} }
@ -261,7 +268,9 @@ export default {
// code // code
wxLogin() { wxLogin() {
uni.showLoading({ title: '登录中...' }); uni.showLoading({
title: '登录中...'
});
uni.login({ uni.login({
provider: 'weixin', provider: 'weixin',
@ -282,7 +291,9 @@ export default {
// //
async sendLoginRequest(code) { async sendLoginRequest(code) {
try { try {
const res = await post('/api/v1/apps/login/weixins', { code: code }); const res = await post('/api/v1/apps/login/weixins', {
code: code
});
if (res.success) { if (res.success) {
uni.setStorageSync('token', res.data.access_token); uni.setStorageSync('token', res.data.access_token);
@ -319,7 +330,9 @@ export default {
return; return;
} }
uni.showLoading({ title: '获取手机号中...' }); uni.showLoading({
title: '获取手机号中...'
});
try { try {
const res = await post('/api/v1/app_auth/weixin/bind_phone', { const res = await post('/api/v1/app_auth/weixin/bind_phone', {
@ -352,7 +365,9 @@ export default {
this.isLogin = true; this.isLogin = true;
this.showEditModal = false; this.showEditModal = false;
this.showPhoneButton = false; this.showPhoneButton = false;
uni.showToast({ title: '登录成功' }); uni.showToast({
title: '登录成功'
});
} else { } else {
throw new Error(res.message || '获取用户信息失败'); throw new Error(res.message || '获取用户信息失败');
} }
@ -372,12 +387,14 @@ export default {
return; return;
} }
const { file } = event; const {
file
} = event;
if (!file || !file.url) return; if (!file || !file.url) return;
try { this.uploading = true;
uni.showLoading({ title: '上传中...' });
try {
// //
const fileInfo = await new Promise((resolve, reject) => { const fileInfo = await new Promise((resolve, reject) => {
uni.getFileInfo({ uni.getFileInfo({
@ -391,17 +408,23 @@ export default {
throw new Error('图片大小不能超过2MB'); throw new Error('图片大小不能超过2MB');
} }
//
const avatarUrl = await this.uploadAvatar(file.url); const avatarUrl = await this.uploadAvatar(file.url);
this.avatarList = [{ url: avatarUrl }];
this.userInfo.avatarUrl = avatarUrl;
uni.hideLoading(); //
await this.updateUserAvatar(avatarUrl);
uni.showToast({
title: '头像上传成功'
});
} catch (error) { } catch (error) {
uni.hideLoading(); console.error('头像上传失败:', error);
uni.showToast({ uni.showToast({
title: '头像上传失败: ' + error.message, title: '头像上传失败: ' + error.message,
icon: 'none' icon: 'none'
}); });
} finally {
this.uploading = false;
} }
}, },
@ -434,9 +457,35 @@ export default {
}); });
}, },
//
async updateUserAvatar(avatarUrl) {
try {
// 1.
await post('/api/v1/app_auth/bind', {
avatar: avatarUrl,
introduce: this.formData.bio,
name: this.formData.name
});
// 2.
const res = await get('/api/v1/app_auth/mine');
if (res && res.success) {
// 3.
uni.setStorageSync('userInfo', res.data);
this.updateUserInfo(res.data);
} else {
throw new Error(res.message || '获取用户信息失败');
}
} catch (err) {
console.error('更新头像失败:', err);
throw err;
}
},
// //
handleAvatarDelete() { handleAvatarDelete() {
this.avatarList = []; this.avatarList = [];
this.displayAvatar = '/static/imgs/index/nav.png';
this.userInfo.avatarUrl = ''; this.userInfo.avatarUrl = '';
}, },
@ -444,7 +493,10 @@ export default {
async handleSubmit() { async handleSubmit() {
if (!this.isLogin) return; if (!this.isLogin) return;
const { name, bio } = this.formData; const {
name,
bio
} = this.formData;
const avatarUrl = this.avatarList.length > 0 ? this.avatarList[0].url : ""; const avatarUrl = this.avatarList.length > 0 ? this.avatarList[0].url : "";
if (!name) { if (!name) {
@ -455,7 +507,9 @@ export default {
return; return;
} }
uni.showLoading({ title: '提交中...' }); uni.showLoading({
title: '提交中...'
});
try { try {
// 1. // 1.
@ -473,7 +527,9 @@ export default {
this.updateUserInfo(res.data); this.updateUserInfo(res.data);
uni.hideLoading(); uni.hideLoading();
uni.showToast({ title: '修改成功' }); uni.showToast({
title: '修改成功'
});
this.showEditModal = false; this.showEditModal = false;
} else { } else {
throw new Error(res.message || '获取用户信息失败'); throw new Error(res.message || '获取用户信息失败');
@ -575,11 +631,24 @@ export default {
justify-content: center; justify-content: center;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1); box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
} }
.u-loading-icon {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
}
} }
} }
.name-section { .name-section {
// display: flex;
width: 100%; width: 100%;
align-items: center; align-items: center;
text-align: center; text-align: center;
@ -589,13 +658,12 @@ export default {
font-size: 36rpx; font-size: 36rpx;
font-weight: 600; font-weight: 600;
color: #333; color: #333;
// margin-right: 12rpx;
} }
.phone { .phone {
color: #999; color: #999;
font-size: 24rpx; font-size: 24rpx;
} }
} }
.description-card { .description-card {
@ -664,6 +732,12 @@ export default {
padding: 0 30rpx; padding: 0 30rpx;
width: 90%; width: 90%;
margin: 0 auto; margin: 0 auto;
max-height: 70vh;
overflow-y: auto;
/* 编辑表单区域 */
.edit-section {
padding: 20rpx 0;
.form-item { .form-item {
margin-bottom: 40rpx; margin-bottom: 40rpx;
@ -673,28 +747,70 @@ export default {
font-size: 28rpx; font-size: 28rpx;
color: #606266; color: #606266;
margin-bottom: 16rpx; margin-bottom: 16rpx;
font-weight: 500;
}
.upload-tip {
font-size: 24rpx;
color: #999;
margin-top: 10rpx;
text-align: center;
}
.word-count {
text-align: right;
font-size: 24rpx;
color: #999;
margin-top: 10rpx;
} }
} }
/* 自定义删除按钮样式 */
.custom-delete {
position: absolute;
top: -10rpx;
right: -10rpx;
width: 40rpx;
height: 40rpx;
background: #f56c6c;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
}
}
/* 授权登录区域 */
.auth-section { .auth-section {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding: 40rpx 0; padding: 40rpx 0;
.auth-title {
font-size: 32rpx;
color: #333;
margin-bottom: 40rpx;
text-align: center;
font-weight: 500;
}
.auth-button { .auth-button {
width: 100%; width: 100%;
height: 80rpx; height: 90rpx;
line-height: 80rpx; line-height: 90rpx;
background: #07C160; background: #07C160;
color: #fff; color: #fff;
font-size: 26rpx; font-size: 30rpx;
border-radius: 40rpx; border-radius: 45rpx;
margin-bottom: 20rpx; margin-bottom: 30rpx;
text-align: center;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
position: relative;
box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.3);
&::after { &::after {
border: none; border: none;
@ -702,12 +818,25 @@ export default {
&.phone-button { &.phone-button {
background: #2979ff; background: #2979ff;
margin-top: 20rpx; box-shadow: 0 4rpx 12rpx rgba(41, 121, 255, 0.3);
} }
&:active { &:active {
opacity: 0.8; opacity: 0.9;
} }
.wechat-icon, .phone-icon {
width: 40rpx;
height: 40rpx;
margin-right: 15rpx;
}
}
.auth-tip {
font-size: 24rpx;
color: #999;
margin-top: 20rpx;
text-align: center;
} }
} }
} }

View File

@ -1,44 +1,69 @@
<template> <template>
<view class="work-order-detail"> <view class="work-order-detail">
<!-- 头部用户信息区域 -->
<view class="header">
<image class="avatar" :src="detailObj.customerPortrait" mode="aspectFill"></image>
<text class="username">{{detailObj.customerName}}</text>
</view>
<!-- 图片区域 - 编辑状态下可修改 --> <!-- 图片区域 - 编辑状态下可修改 -->
<view class="image-area"> <view class="image-area card">
<image class="work-order-img" :src="`${IMAGE_BASE_URL}`+detailObj.images[0]" mode="widthFix" v-if="!isEditing"></image> <view class="img-container">
<view v-else> <image
<image class="work-order-img" :src="workOrderImg" mode="widthFix"></image> class="work-order-img"
<view class="upload-btn" @click="uploadImage"> :src="!isEditing ? `${IMAGE_BASE_URL}${detailObj.images[0] || ''}` : workOrderImg"
<u-icon name="camera" size="40" color="#fff"></u-icon> mode="widthFix"
:lazy-load="true"
></image>
<view class="upload-btn" @click="uploadImage" v-if="isEditing">
<u-icon name="camera" size="28" color="#fff"></u-icon>
<text class="upload-text">更换图片</text> <text class="upload-text">更换图片</text>
</view> </view>
</view> </view>
</view> </view>
<!-- 标题与日期 --> <!-- 标题与日期 -->
<view class="title-area"> <view class="title-area card">
<text class="title" >{{detailObj.label}}</text> <view class="title-wrap">
<text class="date">{{formatTime(detailObj.createdAt,"YYYY-MM-DD")}}</text> <text class="title" :class="{empty: !detailObj.label}">{{detailObj.label || '未设置标题'}}</text>
</view>
<view class="date-wrap">
<text class="date">{{formatTime(detailObj.createdAt,"YYYY-MM-DD") || '未知时间'}}</text>
</view>
</view> </view>
<!-- 工单详情内容 - 编辑状态下可修改 --> <!-- 工单详情内容 - 编辑状态下可修改 -->
<view class="content"> <view class="content card">
<view class="label"> <view class="label">
<view class="label-name">
<!-- <u-icon name="map-marker" size="24" color="#409EFF"></u-icon> -->
<text>工单地点</text> <text>工单地点</text>
<text v-if="!isEditing" style="font-weight: normal;">{{detailObj.address}}</text> </view>
<input class="edit-input" v-model="editAddress" v-else placeholder="请输入地点" /> <text v-if="!isEditing" style="font-weight: normal;" :class="{empty: !detailObj.address}">{{detailObj.address || '未填写'}}</text>
<input class="edit-input" v-model="editAddress" v-else placeholder="请输入工单地点" />
</view> </view>
<view class="label"> <view class="label">
<view class="label-name">
<!-- <u-icon name="user" size="24" color="#409EFF"></u-icon> -->
<text>联系人</text>
</view>
<text v-if="!isEditing" style="font-weight: normal;" :class="{empty: !detailObj.concatName}">{{detailObj.concatName || '未填写'}}</text>
<input class="edit-input" v-model="editContactName" v-else placeholder="请输入联系人" />
</view>
<view class="label">
<view class="label-name">
<!-- <u-icon name="phone" size="24" color="#409EFF"></u-icon> -->
<text>联系电话</text>
</view>
<text v-if="!isEditing" style="font-weight: normal;" :class="{empty: !detailObj.customerPhone}">{{detailObj.customerPhone || '未填写'}}</text>
<input class="edit-input" v-model="editContactPhone" v-else placeholder="请输入联系电话" type="number" />
</view>
<view class="label label-content">
<view class="label-name">
<!-- <u-icon name="file-text" size="24" color="#409EFF"></u-icon> -->
<text>工单内容</text> <text>工单内容</text>
<text v-if="!isEditing" style="font-weight: normal;">{{detailObj.title}}</text> </view>
<text v-if="!isEditing" style="font-weight: normal; white-space: pre-line;" :class="{empty: !detailObj.title}">{{detailObj.title || '未填写'}}</text>
<textarea <textarea
class="edit-textarea" class="edit-textarea"
v-model="editTitle" v-model="editTitle"
v-else v-else
placeholder="请输入内容" placeholder="请输入工单详细内容"
auto-height auto-height
/> />
</view> </view>
@ -49,15 +74,17 @@
<u-button <u-button
@click="handleModify" @click="handleModify"
:type="isEditing ? 'success' : 'primary'" :type="isEditing ? 'success' : 'primary'"
class="action-btn" class="action-btn primary-btn"
:loading="isLoading" :loading="isLoading"
shape="circle"
> >
{{isEditing ? '保存修改' : '修改'}} {{isEditing ? '保存修改' : '修改'}}
</u-button> </u-button>
<u-button <u-button
@click="handleWithdraw" @click="handleWithdraw"
:type="isEditing ? 'default' : 'primary'" :type="isEditing ? 'default' : 'warning'"
class="action-btn" class="action-btn"
shape="circle"
> >
{{isEditing ? '取消编辑' : '撤回'}} {{isEditing ? '取消编辑' : '撤回'}}
</u-button> </u-button>
@ -83,6 +110,8 @@
editLabel: '', editLabel: '',
editAddress: '', editAddress: '',
editTitle: '', editTitle: '',
editContactName: '', //
editContactPhone: '', //
detailObj: {}, detailObj: {},
tempImagePath: '' // tempImagePath: '' //
}; };
@ -91,6 +120,9 @@
let obj = uni.getStorageSync("Detail"); let obj = uni.getStorageSync("Detail");
this.detailObj = {...obj}; this.detailObj = {...obj};
this.workOrderImg = `${IMAGE_BASE_URL}${this.detailObj.images[0]}`; this.workOrderImg = `${IMAGE_BASE_URL}${this.detailObj.images[0]}`;
//
this.editContactName = this.detailObj.concatName || '';
this.editContactPhone = this.detailObj.customerPhone || '';
}, },
methods: { methods: {
// //
@ -145,6 +177,8 @@
this.editLabel = this.detailObj.label; this.editLabel = this.detailObj.label;
this.editAddress = this.detailObj.address; this.editAddress = this.detailObj.address;
this.editTitle = this.detailObj.title; this.editTitle = this.detailObj.title;
this.editContactName = this.detailObj.concatName;
this.editContactPhone = this.detailObj.customerPhone;
this.isEditing = true; this.isEditing = true;
} else { } else {
// //
@ -164,7 +198,9 @@
address: this.editAddress, address: this.editAddress,
title: this.editTitle, title: this.editTitle,
images: images, images: images,
status:1 status: 1,
concatName: this.editContactName, //
customerPhone: this.editContactPhone //
}; };
const res = await put(`/api/v1/app_auth/work-order/${this.detailObj.id}`, requestData); const res = await put(`/api/v1/app_auth/work-order/${this.detailObj.id}`, requestData);
@ -203,6 +239,14 @@
// / // /
async handleWithdraw() { async handleWithdraw() {
if (this.isEditing) {
//
this.isEditing = false;
this.tempImagePath = '';
return;
}
//
const res = await put(`/api/v1/app_auth/work-order/${this.detailObj.id}`, {status:98}); const res = await put(`/api/v1/app_auth/work-order/${this.detailObj.id}`, {status:98});
if (res && res.success) { if (res && res.success) {
@ -222,146 +266,335 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
/* 原有样式保持不变 */ /* 基础变量优化 */
// $primary-color: #409EFF; //
$primary-color: #007AFF; $success-color: #67C23A;
$success-color: #4cd964; $warning-color: #F56C6C;
$warning-color: #FF9500; $text-color: #303133;
$text-color: #333; $subtext-color: #606266;
$subtext-color: #999; $light-text: #909399;
$border-radius: 10rpx; $border-color: #E4E7ED;
$padding-base: 15rpx; $bg-color: #F5F7FA;
$font-base: 32rpx; $card-bg: #FFFFFF;
$border-radius: 12rpx;
$shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
$transition: all 0.2s ease;
// mixin /* 工具类 */
@mixin flex-center { @mixin flex-center {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.work-order-detail { @mixin card {
padding: $padding-base; background: $card-bg;
width: 90%; border-radius: $border-radius;
margin: 0 auto; box-shadow: $shadow;
border-radius: 10rpx; padding: 30rpx;
box-shadow: 0rpx 2rpx 10rpx rgba(0, 0, 0, 0.25); margin-bottom: 24rpx;
transition: $transition;
.header { &:last-child {
margin-bottom: 0;
}
}
/* 页面容器 */
.work-order-detail {
background-color: $bg-color;
min-height: 100vh;
padding: 0 30rpx 40rpx;
}
/* 导航栏 */
.navbar {
@include flex-center; @include flex-center;
margin-bottom: $padding-base; height: 100rpx;
width: 100%;
padding: 0 10rpx;
background: $card-bg;
border-bottom: 1px solid $border-color;
position: sticky;
top: 0;
z-index: 10;
margin-bottom: 24rpx;
.nav-title {
font-size: 36rpx;
color: $text-color;
font-weight: 500;
flex: 1;
text-align: center;
margin-left: -36rpx; //
}
}
/* 头部用户信息 */
.header {
@include card;
display: flex;
align-items: center;
padding: 24rpx 30rpx;
.avatar { .avatar {
width: 80rpx; width: 100rpx;
height: 80rpx; height: 100rpx;
border-radius: 50%; border-radius: 50%;
margin-right: 10rpx; background-color: $bg-color;
border: 2rpx solid #F0F2F5;
}
.user-info {
margin-left: 24rpx;
flex: 1;
}
.user-name-wrap {
@include flex-center;
margin-bottom: 8rpx;
} }
.username { .username {
font-size: $font-base; font-size: 34rpx;
font-weight: bold;
color: $text-color; color: $text-color;
} font-weight: 500;
} }
.image-area { .status-tag {
position: relative; margin-left: 12rpx;
margin-bottom: $padding-base; height: 40rpx;
line-height: 40rpx;
} }
.work-order-img { .contact-info {
width: 100%;
border-radius: $border-radius;
}
.upload-btn {
position: absolute;
bottom: 20rpx;
right: 20rpx;
background: rgba(0, 0, 0, 0.6);
padding: 8rpx 20rpx;
border-radius: 30rpx;
@include flex-center; @include flex-center;
font-size: 28rpx;
.upload-text {
color: #fff;
font-size: 24rpx;
margin-left: 8rpx;
}
} }
.title-area { .info-icon {
@include flex-center; margin-right: 6rpx;
justify-content: space-between;
margin-bottom: $padding-base;
border-bottom: 2rpx solid $subtext-color;
height: 70rpx;
line-height: 70rpx;
.title {
font-size: $font-base + 2rpx;
font-weight: bold;
color: $text-color;
max-width: 70%;
} }
.title-input { .user-phone {
font-size: $font-base + 2rpx;
font-weight: bold;
color: $text-color;
width: 70%;
padding: 10rpx;
background: #f8f8f8;
border-radius: 8rpx;
}
.date {
font-size: $font-base - 4rpx;
color: $subtext-color; color: $subtext-color;
} }
} }
/* 图片区域优化 */
.image-area {
@include card;
padding: 24rpx;
.img-container {
position: relative;
width: 100%;
border-radius: 8rpx;
overflow: hidden;
background-color: $bg-color;
}
.work-order-img {
width: 100%;
border-radius: 8rpx;
transition: $transition;
//
&:empty {
height: 300rpx;
background-color: $bg-color;
display: flex;
align-items: center;
justify-content: center;
color: $light-text;
}
}
.upload-btn {
position: absolute;
right: 16rpx;
bottom: 16rpx;
background: rgba(0, 0, 0, 0.5);
padding: 8rpx 16rpx;
border-radius: 24rpx;
@include flex-center;
transition: $transition;
cursor: pointer;
&:hover {
background: rgba(0, 0, 0, 0.7);
}
.upload-text {
color: #fff;
font-size: 26rpx;
margin-left: 6rpx;
}
}
}
/* 标题区域优化 */
.title-area {
@include card;
display: flex;
flex-direction: column;
gap: 16rpx;
.title-wrap {
@include flex-center;
width: 100%;
}
.title-icon {
margin-right: 12rpx;
flex-shrink: 0;
}
.title {
font-size: 34rpx;
color: $text-color;
font-weight: 500;
line-height: 1.5;
}
.date-wrap {
@include flex-center;
align-self: flex-end;
padding-top: 4rpx;
}
.date-icon {
margin-right: 6rpx;
}
.date {
font-size: 26rpx;
color: $light-text;
}
}
/* 内容区域优化 */
.content { .content {
margin-bottom: $padding-base; @include card;
line-height: 50rpx;
font-size: 24rpx;
.label { .label {
text { display: flex;
font-weight: bold; width: 100%;
} margin-bottom: 28rpx;
margin-bottom: 20rpx; align-items: flex-start;
padding-bottom: 28rpx;
border-bottom:1rpx dashed $border-color;
&:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
} }
.edit-input { .label-name {
width: 95%; @include flex-center;
padding: 10rpx; min-width: 160rpx;
background: #f8f8f8; font-weight: 500;
color: $subtext-color;
font-size: 28rpx;
flex-shrink: 0;
}
.label-name u-icon {
margin-right: 8rpx;
}
& > text {
font-size: 28rpx;
color: $text-color;
line-height: 1.6;
flex: 1;
}
}
.label-content {
align-items: flex-start;
& > text {
white-space: pre-line;
word-break: break-all;
}
}
.edit-input, .edit-textarea {
flex: 1;
padding: 12rpx 16rpx;
background: $bg-color;
border-radius: 8rpx; border-radius: 8rpx;
margin-top: 5rpx; border: 1px solid $border-color;
font-size: 28rpx;
color: $text-color;
line-height: 1.5;
transition: $transition;
&:focus {
border-color: $primary-color;
background: #fff;
outline: none;
box-shadow: 0 0 0 2rpx rgba(64, 158, 255, 0.2);
}
} }
.edit-textarea { .edit-textarea {
width: 95%; min-height: 180rpx;
padding: 10rpx; resize: none;
background: #f8f8f8; }
border-radius: 8rpx;
margin-top: 5rpx; .empty {
min-height: 150rpx; color: $light-text;
font-style: italic;
}
}
/* 按钮区域优化 */
.btn-group {
display: flex;
gap: 24rpx;
padding: 30rpx 10rpx 20rpx;
background-color: transparent;
.action-btn {
flex: 1;
height: 90rpx;
line-height: 90rpx;
font-size: 30rpx;
font-weight: 500;
border-radius: 45rpx;
transition: $transition;
}
.primary-btn {
background-color: $primary-color;
&:active {
background-color: #3A8EE6;
}
}
}
/* 适配小屏幕 */
@media (max-width: 375px) {
$font-base: 28rpx;
.header {
padding: 18rpx;
.avatar {
width: 80rpx;
height: 80rpx;
} }
} }
.btn-group { .btn-group {
@include flex-center; padding: 20rpx 5rpx;
justify-content: space-around;
margin-top: 30rpx;
gap:30rpx;
.action-btn { .action-btn {
width: 30%; //
height: 80rpx; height: 80rpx;
border-radius: 40rpx; line-height: 80rpx;
font-size: $font-base - 4rpx; font-size: 28rpx;
} }
} }
} }

View File

@ -1,16 +1,35 @@
<template> <template>
<view class="container"> <view class="container">
<view class="conent"> <view class="content">
<view class="form-item"> <view class="form-item">
<view class="label"> <view class="label">
<u-icon name="order" color="#3B8CFF" size="38"></u-icon> <u-icon name="order" color="#3B8CFF" size="38"></u-icon>
工单内容 工单内容
</view> </view>
<textarea v-model="workOrderContent" placeholder="请输入..." style="width: 100%;" /> <textarea v-model="workOrderContent" placeholder="请输入工单详细内容..." class="textarea" />
<view class="voice-icon"> <view class="voice-icon">
<u-icon name="mic" color="#409EFF" size="36"></u-icon> <u-icon name="mic" color="#409EFF" size="36"></u-icon>
</view> </view>
</view> </view>
<!-- 联系人信息 -->
<view class="form-item contact-info">
<view class="contact-row">
<view class="label">
<u-icon name="account" color="#3B8CFF" size="38"></u-icon>
联系人
</view>
<input v-model="concatName" placeholder="请输入联系人姓名" class="contact-input" />
</view>
<view class="contact-row">
<view class="label">
<u-icon name="phone" color="#3B8CFF" size="38"></u-icon>
电话
</view>
<input v-model="customerPhone" placeholder="请输入联系人电话" class="contact-input" type="number" />
</view>
</view>
<!-- 上传照片/视频 --> <!-- 上传照片/视频 -->
<view class="form-item"> <view class="form-item">
<view class="label"> <view class="label">
@ -19,17 +38,18 @@
</view> </view>
<view class="upload-area"> <view class="upload-area">
<u-upload :fileList="fileList" @afterRead="handleAfterRead" @delete="handleDelete" :maxCount="8" <u-upload :fileList="fileList" @afterRead="handleAfterRead" @delete="handleDelete" :maxCount="8"
width="200" height="200" accept="image/*,video/*" :previewFullImage="true"> width="160" height="160" accept="image/*,video/*" :previewFullImage="true">
</u-upload> </u-upload>
</view> </view>
<text class="note">照片可识别工单内容支持分批上传但最大数量为8</text> <text class="note">照片可识别工单内容支持分批上传但最大数量为8</text>
</view> </view>
<!-- 工单分类 --> <!-- 工单分类 -->
<view class="form-item" style="height: 60rpx;line-height: 50rpx;"> <view class="form-item">
<view class="label" @click="showSelect = true"> <view class="label" @click="showSelect = true">
<u-icon name="list" color="#3B8CFF" size="38"></u-icon>
工单类型 工单类型
<view @click="showPicker = true" class="select-box"> <view class="select-box">
{{ workOrderCategory || '请选择' }} {{ workOrderCategory || '请选择' }}
<u-icon name="arrow-down" color="#999" size="30"></u-icon> <u-icon name="arrow-down" color="#999" size="30"></u-icon>
</view> </view>
@ -40,17 +60,17 @@
</view> </view>
<!-- 工单地点 --> <!-- 工单地点 -->
<view class="form-item" style="margin-bottom: 30rpx;height: 200rpx;"> <view class="form-item">
<view class="label"> <view class="label">
<u-icon name="map" color="#3B8CFF" size="38"></u-icon> <u-icon name="map" color="#3B8CFF" size="38"></u-icon>
工作地点 工作地点
</view> </view>
<textarea v-model="workOrderLocation" placeholder="请输入..." style="width: 100%;" /> <textarea v-model="workOrderLocation" placeholder="请输入详细工作地点..." class="textarea" />
</view> </view>
<!-- 提交按钮 --> <!-- 提交按钮 -->
<view class="submit-button"> <view class="submit-button">
<u-button type="primary" @click="handleSubmit">提交</u-button> <u-button type="primary" @click="handleSubmit" shape="circle">提交工单</u-button>
</view> </view>
</view> </view>
<Footer></Footer> <Footer></Footer>
@ -74,6 +94,8 @@
data() { data() {
return { return {
workOrderContent: '', workOrderContent: '',
concatName: '', //
customerPhone: '', //
fileList: [], fileList: [],
workOrderCategory: '', workOrderCategory: '',
workOrderCategoryID: '', workOrderCategoryID: '',
@ -81,8 +103,8 @@
categoryList: [], categoryList: [],
workOrderLocation: '', workOrderLocation: '',
expectedTime: '', expectedTime: '',
uploadedImages: [], // URL uploadedImages: [],
uploadedVideos: [], // URL uploadedVideos: [],
videoFormats: ['mp4', 'mov', 'avi', 'wmv', 'mpeg', '3gp', 'flv', 'mkv'] videoFormats: ['mp4', 'mov', 'avi', 'wmv', 'mpeg', '3gp', 'flv', 'mkv']
}; };
}, },
@ -103,7 +125,6 @@
} = event; } = event;
let files = [].concat(file); let files = [].concat(file);
//
if (this.fileList.length + files.length > 8) { if (this.fileList.length + files.length > 8) {
uni.showToast({ uni.showToast({
title: '最多只能上传8个文件', title: '最多只能上传8个文件',
@ -113,11 +134,9 @@
} }
files.forEach(item => { files.forEach(item => {
//
const extension = this.getFileExtension(item.url); const extension = this.getFileExtension(item.url);
const isVideo = this.videoFormats.includes(extension.toLowerCase()); const isVideo = this.videoFormats.includes(extension.toLowerCase());
//
this.fileList.push({ this.fileList.push({
...item, ...item,
status: 'uploading', status: 'uploading',
@ -125,12 +144,10 @@
isVideo: isVideo isVideo: isVideo
}); });
//
this.uploadFile(item, isVideo); this.uploadFile(item, isVideo);
}); });
}, },
//
getFileExtension(filename) { getFileExtension(filename) {
return filename.split('.').pop().split('?')[0]; return filename.split('.').pop().split('?')[0];
}, },
@ -141,7 +158,6 @@
} = event; } = event;
const file = this.fileList[index]; const file = this.fileList[index];
//
if (file.isVideo) { if (file.isVideo) {
const videoIndex = this.uploadedVideos.indexOf(file.url); const videoIndex = this.uploadedVideos.indexOf(file.url);
if (videoIndex !== -1) this.uploadedVideos.splice(videoIndex, 1); if (videoIndex !== -1) this.uploadedVideos.splice(videoIndex, 1);
@ -158,19 +174,17 @@
const fullUrl = await this.uploadAvatar(file.url); const fullUrl = await this.uploadAvatar(file.url);
const relativePath = fullUrl.replace(BASE_URL, ''); const relativePath = fullUrl.replace(BASE_URL, '');
//
const fileIndex = this.fileList.findIndex(item => item.url === file.url); const fileIndex = this.fileList.findIndex(item => item.url === file.url);
if (fileIndex !== -1) { if (fileIndex !== -1) {
this.fileList[fileIndex] = { this.fileList[fileIndex] = {
...this.fileList[fileIndex], ...this.fileList[fileIndex],
status: 'success', status: 'success',
message: '上传成功', message: '上传成功',
url: fullUrl, // URL url: fullUrl,
relativeUrl: relativePath, // relativeUrl: relativePath,
isVideo: isVideo isVideo: isVideo
}; };
//
if (isVideo) { if (isVideo) {
this.uploadedVideos.push(relativePath); this.uploadedVideos.push(relativePath);
} else { } else {
@ -230,6 +244,22 @@
return; return;
} }
if (!this.concatName) {
uni.showToast({
title: '请填写联系人姓名',
icon: 'none'
});
return;
}
if (!this.customerPhone) {
uni.showToast({
title: '请填写联系电话',
icon: 'none'
});
return;
}
if (!this.workOrderCategory) { if (!this.workOrderCategory) {
uni.showToast({ uni.showToast({
title: '请选择工单类型', title: '请选择工单类型',
@ -248,10 +278,12 @@
const submitData = { const submitData = {
title: this.workOrderContent, title: this.workOrderContent,
concatName: this.concatName, //
customerPhone: this.customerPhone, //
orderTypeId: this.workOrderCategoryID, orderTypeId: this.workOrderCategoryID,
address: this.workOrderLocation, address: this.workOrderLocation,
images: this.uploadedImages, // images: this.uploadedImages,
videos: this.uploadedVideos, // · videos: this.uploadedVideos,
}; };
console.log('提交数据:', submitData); console.log('提交数据:', submitData);
@ -267,19 +299,12 @@
icon: 'success', icon: 'success',
duration: 2000 duration: 2000
}); });
this.workOrderContent = null; this.resetForm();
this.workOrderCategoryID = null;
this.workOrderCategory = null;
this.workOrderLocation = null;
this.fileList = [];
this.uploadedImages = [];
this.uploadedVideos = [];
setTimeout(()=>{ setTimeout(()=>{
uni.navigateTo({ uni.navigateTo({
url: `/pages/myTickets/index`, url: `/pages/myTickets/index`,
}); });
},1000) },1000)
} else { } else {
uni.showToast({ uni.showToast({
title: res?.message || '提交失败', title: res?.message || '提交失败',
@ -288,19 +313,28 @@
} }
}, },
// "" resetForm() {
this.workOrderContent = '';
this.concatName = '';
this.customerPhone = '';
this.workOrderCategoryID = '';
this.workOrderCategory = '';
this.workOrderLocation = '';
this.fileList = [];
this.uploadedImages = [];
this.uploadedVideos = [];
},
handleConfirm(e) { handleConfirm(e) {
this.workOrderCategory = e.value[0].label; this.workOrderCategory = e.value[0].label;
this.workOrderCategoryID = e.value[0].id; this.workOrderCategoryID = e.value[0].id;
this.showSelect = false; this.showSelect = false;
}, },
// ""
handleCancel() { handleCancel() {
this.showSelect = false; this.showSelect = false;
}, },
//
async getOrderList() { async getOrderList() {
try { try {
const res = await get('/api/v1/apps/work-order-type'); const res = await get('/api/v1/apps/work-order-type');
@ -324,65 +358,104 @@
<style lang="scss" scoped> <style lang="scss" scoped>
.container { .container {
width: 100%; width: 100%;
height: 130vh; min-height: 100vh;
opacity: 1; background: #f5f7fa;
background: rgba(210, 227, 255, 1); padding-bottom: 120rpx;
position: relative;
.conent { .content {
width: 100%; padding: 20rpx;
position: absolute;
margin-top: 20rpx;
.form-item { .form-item {
margin-bottom: 20px;
height: 400rpx;
width: 95%;
margin: 0 auto;
margin-bottom: 20rpx;
background: #fff; background: #fff;
padding: 20rpx; border-radius: 16rpx;
display: flex; padding: 24rpx;
flex-direction: column; margin-bottom: 24rpx;
position: relative; box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
.label { .label {
font-size: 32rpx; font-size: 32rpx;
color: #333; color: #333;
margin-bottom: 30px; font-weight: 500;
display: flex; margin-bottom: 24rpx;
white-space: nowrap;
}
.select-box {
display: flex; display: flex;
align-items: center; align-items: center;
margin-left: 30rpx;
color: #999; .u-icon {
margin-right: 12rpx;
}
}
.textarea {
width: 95%;
height: 180rpx;
padding: 16rpx;
background: #f8f8f8;
border-radius: 8rpx;
font-size: 28rpx;
} }
.voice-icon { .voice-icon {
position: absolute; position: absolute;
right: 30rpx; right: 40rpx;
bottom: 20rpx; bottom: 40rpx;
}
.select-box {
margin-left: auto;
color: #666;
display: flex;
align-items: center;
font-size: 28rpx;
.u-icon {
margin-left: 8rpx;
}
} }
.upload-area { .upload-area {
display: flex; margin-top: 16rpx;
justify-content: center;
align-items: center;
height: 100px;
} }
.note { .note {
font-size: 20rpx; font-size: 24rpx;
color: #999; color: #999;
margin-top: 60rpx; margin-top: 24rpx;
display: block;
}
}
.contact-info {
.contact-row {
display: flex;
align-items: center;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
.label {
margin-bottom: 0;
width: 160rpx;
flex-shrink: 0;
}
.contact-input {
flex: 1;
padding: 16rpx;
background: #f8f8f8;
border-radius: 8rpx;
font-size: 28rpx;
}
}
} }
.submit-button { .submit-button {
text-align: center; margin-top: 40rpx;
margin-top: 20px;
.u-button {
height: 88rpx;
font-size: 32rpx;
} }
} }
} }