This commit is contained in:
Leo_Ding 2025-08-06 18:12:54 +08:00
commit a411e94890
5 changed files with 1821 additions and 1642 deletions

View File

@ -96,7 +96,7 @@
<text class="card-desc">维修方便便捷</text> <text class="card-desc">维修方便便捷</text>
<view class="card-bg"></view> <view class="card-bg"></view>
</view> </view>
<image class="card-icon" src="/static/imgs/index/weixiu.png"></image> <image class="card-icon" src="/static/imgs/index/weixiu.png" style="width: 80rpx;height: 80rpx;bottom: 32rpx;"></image>
</view> </view>
<!-- <view class="service-card large card-3" @click="goDetail('neighborList')" hover-class="card-hover"> <!-- <view class="service-card large card-3" @click="goDetail('neighborList')" hover-class="card-hover">
@ -522,13 +522,13 @@
&.card-2 { &.card-2 {
.card-bg { .card-bg {
background: #ff7e7e; background: #5b9cf8;
top: -30rpx; top: -50rpx;
right: -30rpx; right: -30rpx;
} }
.card-icon { .card-icon {
filter: drop-shadow(0 4rpx 8rpx rgba(255, 126, 126, 0.3)); filter: drop-shadow(0 4rpx 8rpx rgba(91, 156, 248, 0.3));
} }
} }

View File

@ -6,7 +6,7 @@
<view class="avatar-section"> <view class="avatar-section">
<view class="avatar-wrapper"> <view class="avatar-wrapper">
<image class="avatar" :src="userInfo.avatarUrl || '/static/imgs/index/nav.png'" <image class="avatar" :src="userInfo.avatarUrl || '/static/imgs/index/nav.png'"
mode="aspectFill"></image> mode="aspectFill" @click="onAvatarClick"></image>
<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>
@ -23,7 +23,6 @@
</view> </view>
</view> </view>
</view> </view>
<!-- 功能按钮区域 --> <!-- 功能按钮区域 -->
<view class="function-card"> <view class="function-card">
<view class="button-item" v-for="(item, index) in choseList" :key="index" @click="goPage(item)"> <view class="button-item" v-for="(item, index) in choseList" :key="index" @click="goPage(item)">
@ -34,7 +33,6 @@
<u-icon name="arrow-right" color="#c8c9cc" size="28"></u-icon> <u-icon name="arrow-right" color="#c8c9cc" size="28"></u-icon>
</view> </view>
</view> </view>
<!-- 修改用户信息弹窗 --> <!-- 修改用户信息弹窗 -->
<u-modal :show="showEditModal" :title="isLogin ? '修改用户信息' : '微信授权登录'" :show-confirm-button="isLogin" <u-modal :show="showEditModal" :title="isLogin ? '修改用户信息' : '微信授权登录'" :show-confirm-button="isLogin"
:show-cancel-button="isLogin" @confirm="handleSubmit" @cancel="handleCancel" confirm-color="#2979ff" :show-cancel-button="isLogin" @confirm="handleSubmit" @cancel="handleCancel" confirm-color="#2979ff"
@ -55,25 +53,33 @@
</button> </button>
<view class="auth-tip">登录即表示同意用户协议隐私政策</view> <view class="auth-tip">登录即表示同意用户协议隐私政策</view>
</view> </view>
<!-- 已登录时显示编辑表单 --> <!-- 已登录时显示编辑表单 -->
<view v-else class="edit-section"> <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 :file-list="avatarList" :max-count="1" @afterRead="handleAvatarUpload" <view class="avatar-upload-container" @click="onAvatarClick">
@delete="handleAvatarDelete" width="160" height="160" preview-full-image> <view class="avatar-preview">
<view slot="delete" class="custom-delete"> <image
<u-icon name="close" color="#fff" size="24"></u-icon> v-if="avatarList.length > 0"
:src="avatarList[0].url"
mode="aspectFill"
class="avatar-img">
</image>
<view v-else class="avatar-placeholder">
<u-icon name="plus" color="#c0c4cc" size="28"></u-icon>
<text class="placeholder-text">点击上传</text>
</view> </view>
</u-upload> <view v-if="avatarList.length > 0" class="avatar-delete" @click.stop="handleAvatarDelete">
<view class="upload-tip">点击上传建议尺寸1:1</view> <u-icon name="close" color="#fff" size="26"></u-icon>
</view>
</view>
</view>
<view class="upload-tip">点击上传支持拍照或从相册选择</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" clearable></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 v-model="formData.bio" type="textarea" placeholder="请输入简介" :maxlength="200" <u-input v-model="formData.bio" type="textarea" placeholder="请输入简介" :maxlength="200"
@ -84,7 +90,6 @@
</view> </view>
</view> </view>
</u-modal> </u-modal>
<u-modal <u-modal
:show="showPhoneDialog" :show="showPhoneDialog"
title="联系社区" title="联系社区"
@ -98,7 +103,6 @@
<text class="phone-number">{{communityPhone}}</text> <text class="phone-number">{{communityPhone}}</text>
</view> </view>
</u-modal> </u-modal>
<Footer></Footer> <Footer></Footer>
</view> </view>
</template> </template>
@ -113,7 +117,6 @@
IMAGE_BASE_URL, IMAGE_BASE_URL,
BASE_URL BASE_URL
} from '@/utils/config'; } from '@/utils/config';
import { navigateTo } from '@/utils/router';
export default { export default {
components: { components: {
Footer Footer
@ -179,6 +182,44 @@ import { navigateTo } from '@/utils/router';
this.initData(); this.initData();
}, },
methods: { methods: {
//
onAvatarClick() {
if (!this.isLogin) {
this.showEditModal = true;
return;
}
//
if (this.uploading) return;
//
uni.chooseImage({
count: 1, //
sizeType: ['original', 'compressed'], //
sourceType: ['album', 'camera'], //
success: (res) => {
//
const tempFilePaths = res.tempFilePaths;
if (tempFilePaths && tempFilePaths.length > 0) {
// filehandleAvatarUpload
const file = {
url: tempFilePaths[0]
};
//
this.handleAvatarUpload({ file });
}
},
fail: (err) => {
console.error('选择图片失败', err);
uni.showToast({
title: '选择图片失败',
icon: 'none'
});
}
});
},
// ...
handleCloseModal() { handleCloseModal() {
this.showPhoneDialog = false; this.showPhoneDialog = false;
}, },
@ -203,34 +244,28 @@ import { navigateTo } from '@/utils/router';
this.clearUserData(); this.clearUserData();
} }
}, },
// //
updateUserInfo(data) { updateUserInfo(data) {
// IMAGE_BASE_URL // IMAGE_BASE_URL
const avatarUrl = data.avatar ? `${IMAGE_BASE_URL}${data.avatar}` : '/static/imgs/index/nav.png'; const avatarUrl = data.avatar ? `${IMAGE_BASE_URL}${data.avatar}` : '/static/imgs/index/nav.png';
this.userInfo = { this.userInfo = {
nickName: data.name || data.nickName || '未命名用户', nickName: data.name || data.nickName || '未命名用户',
avatarUrl: avatarUrl, avatarUrl: avatarUrl,
bio: data.introduce || data.bio || '这家伙很懒,什么都没有写~', bio: data.introduce || data.bio || '这家伙很懒,什么都没有写~',
phone: data.phone || '' phone: data.phone || ''
}; };
// //
this.displayAvatar = this.userInfo.avatarUrl; this.displayAvatar = this.userInfo.avatarUrl;
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 = [{ this.avatarList = [{
url: this.userInfo.avatarUrl url: this.userInfo.avatarUrl
}]; }];
} }
}, },
// //
clearUserData() { clearUserData() {
this.isLogin = false; this.isLogin = false;
@ -247,20 +282,17 @@ import { navigateTo } from '@/utils/router';
uni.removeStorageSync('token'); uni.removeStorageSync('token');
uni.removeStorageSync('userInfo'); uni.removeStorageSync('userInfo');
}, },
// //
handleEditClick() { handleEditClick() {
if (!this.isLogin) { if (!this.isLogin) {
this.showEditModal = true; this.showEditModal = true;
return; return;
} }
// //
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 = [{ this.avatarList = [{
url: this.userInfo.avatarUrl url: this.userInfo.avatarUrl
@ -268,10 +300,8 @@ import { navigateTo } from '@/utils/router';
} else { } else {
this.avatarList = []; this.avatarList = [];
} }
this.showEditModal = true; this.showEditModal = true;
}, },
// //
onGetUserInfo(e) { onGetUserInfo(e) {
if (e.detail.userInfo) { if (e.detail.userInfo) {
@ -286,13 +316,12 @@ import { navigateTo } from '@/utils/router';
this.showEditModal = false; this.showEditModal = false;
} }
}, },
// code // code
wxLogin() { wxLogin() {
uni.showLoading({ uni.showLoading({
title: '登录中...' title: '登录中...'
}); });
this.showEditModal = false;
uni.login({ uni.login({
provider: 'weixin', provider: 'weixin',
success: (loginRes) => { success: (loginRes) => {
@ -308,17 +337,14 @@ import { navigateTo } from '@/utils/router';
} }
}); });
}, },
// //
async sendLoginRequest(code) { async sendLoginRequest(code) {
try { try {
const res = await post('/api/v1/apps/login/weixins', { const res = await post('/api/v1/apps/login/weixins', {
code: code code: code
}); });
if (res.success) { if (res.success) {
uni.setStorageSync('token', res.data.access_token); uni.setStorageSync('token', res.data.access_token);
if (!res.data.phone) { if (!res.data.phone) {
this.showPhoneButton = true; this.showPhoneButton = true;
uni.showToast({ uni.showToast({
@ -340,7 +366,6 @@ import { navigateTo } from '@/utils/router';
console.error('登录接口错误:', err); console.error('登录接口错误:', err);
} }
}, },
// //
async getPhoneNumber(e) { async getPhoneNumber(e) {
if (e.detail.errMsg !== "getPhoneNumber:ok") { if (e.detail.errMsg !== "getPhoneNumber:ok") {
@ -350,16 +375,13 @@ import { navigateTo } from '@/utils/router';
}); });
return; return;
} }
uni.showLoading({ uni.showLoading({
title: '获取手机号中...' 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', {
code: e.detail.code code: e.detail.code
}); });
if (res.success) { if (res.success) {
await this.loginComplete(res.data); await this.loginComplete(res.data);
} else { } else {
@ -374,7 +396,6 @@ import { navigateTo } from '@/utils/router';
console.error('绑定手机号失败:', err); console.error('绑定手机号失败:', err);
} }
}, },
// //
async loginComplete(data) { async loginComplete(data) {
try { try {
@ -385,7 +406,6 @@ import { navigateTo } from '@/utils/router';
uni.setStorageSync('userInfo', res.data); uni.setStorageSync('userInfo', res.data);
this.updateUserInfo(res.data); this.updateUserInfo(res.data);
this.isLogin = true; this.isLogin = true;
// this.showEditModal = false;
this.showPhoneButton = false; this.showPhoneButton = false;
uni.showToast({ uni.showToast({
title: '登录成功' title: '登录成功'
@ -401,21 +421,17 @@ import { navigateTo } from '@/utils/router';
console.error('登录完成处理失败:', err); console.error('登录完成处理失败:', err);
} }
}, },
// //
async handleAvatarUpload(event) { async handleAvatarUpload(event) {
if (!this.isLogin) { if (!this.isLogin) {
this.showEditModal = true; this.showEditModal = true;
return; return;
} }
const { const {
file file
} = event; } = event;
if (!file || !file.url) return; if (!file || !file.url) return;
this.uploading = true; this.uploading = true;
try { try {
// //
const fileInfo = await new Promise((resolve, reject) => { const fileInfo = await new Promise((resolve, reject) => {
@ -425,17 +441,13 @@ import { navigateTo } from '@/utils/router';
fail: reject fail: reject
}); });
}); });
if (fileInfo.size > 3 * 1024 * 1024) { if (fileInfo.size > 3 * 1024 * 1024) {
throw new Error('图片大小不能超过2MB'); throw new Error('图片大小不能超过3MB');
} }
// //
const avatarUrl = await this.uploadAvatar(file.url); const avatarUrl = await this.uploadAvatar(file.url);
// //
await this.updateUserAvatar(avatarUrl); await this.updateUserAvatar(avatarUrl);
uni.showToast({ uni.showToast({
title: '头像上传成功' title: '头像上传成功'
}); });
@ -449,7 +461,6 @@ import { navigateTo } from '@/utils/router';
this.uploading = false; this.uploading = false;
} }
}, },
// //
uploadAvatar(filePath) { uploadAvatar(filePath) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -478,7 +489,6 @@ import { navigateTo } from '@/utils/router';
}); });
}); });
}, },
// //
async updateUserAvatar(avatarUrl) { async updateUserAvatar(avatarUrl) {
try { try {
@ -488,7 +498,6 @@ import { navigateTo } from '@/utils/router';
introduce: this.formData.bio, introduce: this.formData.bio,
name: this.formData.name name: this.formData.name
}); });
// 2. // 2.
const res = await get('/api/v1/app_auth/mine'); const res = await get('/api/v1/app_auth/mine');
if (res && res.success) { if (res && res.success) {
@ -503,29 +512,38 @@ import { navigateTo } from '@/utils/router';
throw err; throw err;
} }
}, },
// -
//
handleAvatarDelete() { handleAvatarDelete() {
uni.showModal({
title: '提示',
content: '确定要删除当前头像吗?',
confirmText: '删除',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
this.avatarList = []; this.avatarList = [];
this.displayAvatar = '/static/imgs/index/nav.png'; this.displayAvatar = '/static/imgs/index/nav.png';
this.userInfo.avatarUrl = ''; this.userInfo.avatarUrl = '';
uni.showToast({
title: '已删除',
icon: 'none'
});
}
}
});
}, },
// //
async handleSubmit() { async handleSubmit() {
if (!this.isLogin) return; if (!this.isLogin) return;
const { const {
name, name,
bio bio
} = this.formData; } = this.formData;
console.log("====", this.avatarList)
let avatarUrl = this.avatarList.length > 0 ? this.avatarList[0].url : "";
let avatarUrl = this.avatarList.length > 0 ? this.avatarList[0].url : "";
if (avatarUrl && avatarUrl.includes(IMAGE_BASE_URL)) { if (avatarUrl && avatarUrl.includes(IMAGE_BASE_URL)) {
avatarUrl = avatarUrl.replace(IMAGE_BASE_URL, ''); avatarUrl = avatarUrl.replace(IMAGE_BASE_URL, '');
} }
if (!name) { if (!name) {
uni.showToast({ uni.showToast({
title: '请输入姓名', title: '请输入姓名',
@ -533,11 +551,9 @@ import { navigateTo } from '@/utils/router';
}); });
return; return;
} }
uni.showLoading({ uni.showLoading({
title: '提交中...' title: '提交中...'
}); });
try { try {
// 1. // 1.
await post('/api/v1/app_auth/bind', { await post('/api/v1/app_auth/bind', {
@ -545,7 +561,6 @@ import { navigateTo } from '@/utils/router';
introduce: bio, introduce: bio,
name: name name: name
}); });
// 2. // 2.
const res = await get('/api/v1/app_auth/mine'); const res = await get('/api/v1/app_auth/mine');
if (res && res.success) { if (res && res.success) {
@ -553,12 +568,10 @@ import { navigateTo } from '@/utils/router';
uni.setStorageSync('userInfo', res.data); uni.setStorageSync('userInfo', res.data);
this.updateUserInfo(res.data); this.updateUserInfo(res.data);
this.showEditModal = false; this.showEditModal = false;
uni.hideLoading(); uni.hideLoading();
uni.showToast({ uni.showToast({
title: '修改成功' title: '修改成功'
}); });
} else { } else {
throw new Error(res.message || '获取用户信息失败'); throw new Error(res.message || '获取用户信息失败');
} }
@ -571,13 +584,10 @@ import { navigateTo } from '@/utils/router';
console.error('修改失败:', err); console.error('修改失败:', err);
} }
}, },
// //
handleCancel() { handleCancel() {
this.showEditModal = false; this.showEditModal = false;
}, },
//
// //
goPage(item) { goPage(item) {
if (item.key === 6) { // if (item.key === 6) { //
@ -588,19 +598,16 @@ import { navigateTo } from '@/utils/router';
this.handleEditClick(); // this.handleEditClick(); //
return; return;
} }
if (!this.checkLogin()) {
// if (!this.checkLogin()) { this.showEditModal = true;
// this.showEditModal = true; return;
// return; }
// }
if (item.pageUrl) { if (item.pageUrl) {
navigateTo({ uni.navigateTo({
url: `/pages/${item.pageUrl}/index` url: `/pages/${item.pageUrl}/index`
},false); });
} }
}, },
// //
handleContactCommunity() { handleContactCommunity() {
if (!this.checkLogin()) { if (!this.checkLogin()) {
@ -620,7 +627,6 @@ import { navigateTo } from '@/utils/router';
} }
}); });
}, },
// //
callCommunityPhone() { callCommunityPhone() {
uni.makePhoneCall({ uni.makePhoneCall({
@ -638,7 +644,6 @@ import { navigateTo } from '@/utils/router';
}); });
this.showPhoneDialog = false; this.showPhoneDialog = false;
}, },
// //
checkLogin() { checkLogin() {
const token = uni.getStorageSync('token'); const token = uni.getStorageSync('token');
@ -663,29 +668,24 @@ import { navigateTo } from '@/utils/router';
box-sizing: border-box; box-sizing: border-box;
padding-bottom: 120rpx; padding-bottom: 120rpx;
} }
.user-info-card { .user-info-card {
background: #fff; background: #fff;
border-radius: 16rpx; border-radius: 16rpx;
padding: 40rpx 30rpx; padding: 40rpx 30rpx;
margin-bottom: 24rpx; margin-bottom: 24rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04); box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04);
.user-info { .user-info {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
} }
} }
.avatar-section { .avatar-section {
margin-bottom: 20rpx; margin-bottom: 20rpx;
.avatar-wrapper { .avatar-wrapper {
position: relative; position: relative;
width: 140rpx; width: 140rpx;
height: 140rpx; height: 140rpx;
.avatar { .avatar {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -693,7 +693,6 @@ import { navigateTo } from '@/utils/router';
border: 4rpx solid #fff; border: 4rpx solid #fff;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1); box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
} }
.edit-icon { .edit-icon {
position: absolute; position: absolute;
right: 0; right: 0;
@ -707,7 +706,6 @@ import { navigateTo } from '@/utils/router';
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 { .u-loading-icon {
position: absolute; position: absolute;
top: 0; top: 0;
@ -723,79 +721,66 @@ import { navigateTo } from '@/utils/router';
} }
} }
} }
.name-section { .name-section {
width: 100%; width: 100%;
align-items: center; align-items: center;
text-align: center; text-align: center;
margin-bottom: 30rpx; margin-bottom: 30rpx;
.name { .name {
font-size: 36rpx; font-size: 36rpx;
font-weight: 600; font-weight: 600;
color: #333; color: #333;
} }
.phone { .phone {
color: #999; color: #999;
font-size: 24rpx; font-size: 24rpx;
} }
} }
.description-card { .description-card {
background: #f8f9fa; background: #f8f9fa;
border-radius: 12rpx; border-radius: 12rpx;
padding: 24rpx; padding: 24rpx;
width: 100%; width: 100%;
text-align: center; text-align: center;
.title { .title {
font-size: 28rpx; font-size: 28rpx;
color: #666; color: #666;
margin-bottom: 12rpx; margin-bottom: 12rpx;
display: block; display: block;
} }
.content { .content {
font-size: 26rpx; font-size: 26rpx;
color: #999; color: #999;
line-height: 1.5; line-height: 1.5;
} }
} }
.function-card { .function-card {
background: #fff; background: #fff;
border-radius: 16rpx; border-radius: 16rpx;
padding: 0 30rpx; padding: 0 30rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04); box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04);
.button-item { .button-item {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 28rpx 0; padding: 28rpx 0;
border-bottom: 1rpx solid #f2f3f5; border-bottom: 1rpx solid #f2f3f5;
&:active { &:active {
background-color: #f8f8f8; background-color: #f8f8f8;
} }
&:last-child { &:last-child {
border-bottom: none; border-bottom: none;
} }
.button-content { .button-content {
display: flex; display: flex;
align-items: center; align-items: center;
flex: 1; flex: 1;
} }
.icon { .icon {
width: 48rpx; width: 48rpx;
height: 48rpx; height: 48rpx;
margin-right: 24rpx; margin-right: 24rpx;
} }
.label { .label {
font-size: 30rpx; font-size: 30rpx;
color: #333; color: #333;
@ -803,21 +788,17 @@ import { navigateTo } from '@/utils/router';
} }
} }
} }
.modal-content { .modal-content {
padding: 0 30rpx; padding: 0 30rpx;
width: 90%; width: 90%;
margin: 0 auto; margin: 0 auto;
max-height: 70vh; max-height: 70vh;
overflow-y: auto; overflow-y: auto;
/* 编辑表单区域 */ /* 编辑表单区域 */
.edit-section { .edit-section {
padding: 20rpx 0; padding: 20rpx 0;
.form-item { .form-item {
margin-bottom: 40rpx; margin-bottom: 40rpx;
.form-label { .form-label {
display: block; display: block;
font-size: 28rpx; font-size: 28rpx;
@ -825,14 +806,12 @@ import { navigateTo } from '@/utils/router';
margin-bottom: 16rpx; margin-bottom: 16rpx;
font-weight: 500; font-weight: 500;
} }
.upload-tip { .upload-tip {
font-size: 24rpx; font-size: 24rpx;
color: #999; color: #999;
margin-top: 10rpx; margin-top: 10rpx;
text-align: center; text-align: center;
} }
.word-count { .word-count {
text-align: right; text-align: right;
font-size: 24rpx; font-size: 24rpx;
@ -840,31 +819,13 @@ import { navigateTo } from '@/utils/router';
margin-top: 10rpx; 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 { .auth-title {
font-size: 32rpx; font-size: 32rpx;
color: #333; color: #333;
@ -872,7 +833,6 @@ import { navigateTo } from '@/utils/router';
text-align: center; text-align: center;
font-weight: 500; font-weight: 500;
} }
.auth-button { .auth-button {
width: 100%; width: 100%;
height: 90rpx; height: 90rpx;
@ -887,20 +847,16 @@ import { navigateTo } from '@/utils/router';
justify-content: center; justify-content: center;
position: relative; position: relative;
box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.3); box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.3);
&::after { &::after {
border: none; border: none;
} }
&.phone-button { &.phone-button {
background: #2979ff; background: #2979ff;
box-shadow: 0 4rpx 12rpx rgba(41, 121, 255, 0.3); box-shadow: 0 4rpx 12rpx rgba(41, 121, 255, 0.3);
} }
&:active { &:active {
opacity: 0.9; opacity: 0.9;
} }
.wechat-icon, .wechat-icon,
.phone-icon { .phone-icon {
width: 40rpx; width: 40rpx;
@ -908,7 +864,6 @@ import { navigateTo } from '@/utils/router';
margin-right: 15rpx; margin-right: 15rpx;
} }
} }
.auth-tip { .auth-tip {
font-size: 24rpx; font-size: 24rpx;
color: #999; color: #999;
@ -916,12 +871,10 @@ import { navigateTo } from '@/utils/router';
text-align: center; text-align: center;
} }
} }
.phone-dialog-content { .phone-dialog-content {
padding: 40rpx; padding: 40rpx;
text-align: center; text-align: center;
font-size: 32rpx; font-size: 32rpx;
.phone-number { .phone-number {
font-weight: bold; font-weight: bold;
color: #2979ff; color: #2979ff;
@ -929,4 +882,61 @@ import { navigateTo } from '@/utils/router';
} }
} }
} }
//
.avatar-upload-container {
width: 160rpx;
height: 160rpx;
margin: 0 auto;
position: relative;
border-radius: 8rpx;
overflow: hidden;
}
//
.avatar-preview {
width: 100%;
height: 100%;
position: relative;
border-radius: 8rpx;
overflow: hidden;
background-color: #f5f7fa;
display: flex;
align-items: center;
justify-content: center;
.avatar-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #c0c4cc;
.placeholder-text {
font-size: 24rpx;
margin-top: 10rpx;
}
}
.avatar-delete {
position: absolute;
top: -10rpx;
right: -10rpx;
width: 40rpx;
height: 40rpx;
background-color: #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);
}
}
</style> </style>

View File

@ -42,7 +42,7 @@
<view class="date">{{ formatTime(order.createdAt,"YYYY-MM-DD") }}</view> <view class="date">{{ formatTime(order.createdAt,"YYYY-MM-DD") }}</view>
</view> </view>
<view class="order-description">{{ order.title }}</view> <view class="order-description">{{ order.title }}</view>
<button @click="handleAction(order)" class="order-action" v-if="order.status == 1 || order.status == 2 || order.status == 99">撤回</button> <button @click.stop="handleAction(order, $event)" class="order-action" v-if="order.status == 1 || order.status == 2 || order.status == 99">撤回</button>
</view> </view>
</view> </view>
<!-- 空状态提示 --> <!-- 空状态提示 -->
@ -73,7 +73,7 @@
<script> <script>
// import Header from '@/components/header_common.vue'; // import Header from '@/components/header_common.vue';
import Footer from '@/components/footer_common.vue'; import Footer from '@/components/footer_common.vue';
import { get, post } from '@/utils/request'; import { get, post,put } from '@/utils/request';
import { IMAGE_BASE_URL,BASE_URL } from '@/utils/config'; import { IMAGE_BASE_URL,BASE_URL } from '@/utils/config';
import { formatTime, formatRelativeTime } from '@/utils/timeFormat'; import { formatTime, formatRelativeTime } from '@/utils/timeFormat';
export default { export default {
@ -116,7 +116,7 @@
2: '进行中', 2: '进行中',
3: '已完成', 3: '已完成',
99:'驳回', 99:'驳回',
98:'已删除' 98:'已撤回'
}; };
return statusMap[status] || '未知状态'; return statusMap[status] || '未知状态';
}, },
@ -153,20 +153,39 @@
uni.navigateTo({ url: '/pages/serviceTickets/index' }); uni.navigateTo({ url: '/pages/serviceTickets/index' });
}, },
// //
handleAction(order) { handleAction(order, e) {
if (order.actionText === '撤回') { //
e.stopPropagation();
if (order.status == 1 || order.status == 2 || order.status == 99) {
uni.showModal({ uni.showModal({
title: '提示', title: '提示',
content: '确定要撤回这个工单吗?', content: '确定要撤回这个工单吗?',
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
// //
this.withdrawOrder(order.id);
}
}
});
}
},
async withdrawOrder(orderId) {
try {
const res = await put(`/api/v1/app_auth/work-order/${orderId}`, {status:98});
if (res?.success) {
uni.showToast({ uni.showToast({
title: '工单已撤回', title: '工单已撤回',
icon: 'success' icon: 'success'
}); });
//
this.getWorkOrderList();
} }
} } catch (err) {
console.error('撤回工单失败:', err);
uni.showToast({
title: '撤回失败',
icon: 'none'
}); });
} }
}, },

View File

@ -1,22 +1,5 @@
<template> <template>
<view class="work-order-detail"> <view class="work-order-detail">
<!-- 图片区域 - 编辑状态下可修改 -->
<view class="image-area card">
<view class="img-container">
<image
class="work-order-img"
:src="!isEditing ? `${IMAGE_BASE_URL}${detailObj.images[0] || ''}` : workOrderImg"
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>
</view>
</view>
</view>
<!-- 标题与日期 --> <!-- 标题与日期 -->
<view class="title-area card"> <view class="title-area card">
<view class="title-wrap"> <view class="title-wrap">
@ -31,7 +14,6 @@
<view class="content card"> <view class="content card">
<view class="label"> <view class="label">
<view class="label-name"> <view class="label-name">
<!-- <u-icon name="map-marker" size="24" color="#409EFF"></u-icon> -->
<text>工单地点</text> <text>工单地点</text>
</view> </view>
<text v-if="!isEditing" style="font-weight: normal;" :class="{empty: !detailObj.address}">{{detailObj.address || '未填写'}}</text> <text v-if="!isEditing" style="font-weight: normal;" :class="{empty: !detailObj.address}">{{detailObj.address || '未填写'}}</text>
@ -39,7 +21,6 @@
</view> </view>
<view class="label"> <view class="label">
<view class="label-name"> <view class="label-name">
<!-- <u-icon name="user" size="24" color="#409EFF"></u-icon> -->
<text>联系人</text> <text>联系人</text>
</view> </view>
<text v-if="!isEditing" style="font-weight: normal;" :class="{empty: !detailObj.concatName}">{{detailObj.concatName || '未填写'}}</text> <text v-if="!isEditing" style="font-weight: normal;" :class="{empty: !detailObj.concatName}">{{detailObj.concatName || '未填写'}}</text>
@ -47,7 +28,6 @@
</view> </view>
<view class="label"> <view class="label">
<view class="label-name"> <view class="label-name">
<!-- <u-icon name="phone" size="24" color="#409EFF"></u-icon> -->
<text>联系电话</text> <text>联系电话</text>
</view> </view>
<text v-if="!isEditing" style="font-weight: normal;" :class="{empty: !detailObj.customerPhone}">{{detailObj.customerPhone || '未填写'}}</text> <text v-if="!isEditing" style="font-weight: normal;" :class="{empty: !detailObj.customerPhone}">{{detailObj.customerPhone || '未填写'}}</text>
@ -55,7 +35,6 @@
</view> </view>
<view class="label label-content"> <view class="label label-content">
<view class="label-name"> <view class="label-name">
<!-- <u-icon name="file-text" size="24" color="#409EFF"></u-icon> -->
<text>工单内容</text> <text>工单内容</text>
</view> </view>
<text v-if="!isEditing" style="font-weight: normal; white-space: pre-line;" :class="{empty: !detailObj.title}">{{detailObj.title || '未填写'}}</text> <text v-if="!isEditing" style="font-weight: normal; white-space: pre-line;" :class="{empty: !detailObj.title}">{{detailObj.title || '未填写'}}</text>
@ -68,6 +47,65 @@
/> />
</view> </view>
</view> </view>
<!-- 图片区域 - 支持多张展示和编辑 -->
<view class="image-area card">
<view class="img-container">
<!-- 非编辑状态显示服务器图片 -->
<scroll-view
class="image-scroll"
scroll-x="true"
v-if="!isEditing && detailObj.images && detailObj.images.length > 0"
>
<view class="image-list">
<image
class="work-order-img"
v-for="(img, index) in detailObj.images"
:key="index"
:src="`${IMAGE_BASE_URL}${img}`"
mode="aspectFill"
lazy-load="true"
@click="previewImage(index)"
></image>
</view>
</scroll-view>
<!-- 编辑状态显示本地选择的图片 -->
<scroll-view
class="image-scroll"
scroll-x="true"
v-if="isEditing && localImages.length > 0"
>
<view class="image-list">
<view class="image-item" v-for="(img, index) in localImages" :key="index">
<image
class="work-order-img"
:src="img"
mode="aspectFill"
lazy-load="true"
></image>
<view class="delete-btn" @click="removeImage(index)">
<u-icon name="close" size="20" color="#fff"></u-icon>
</view>
</view>
</view>
</scroll-view>
<!-- 没有图片时的占位 -->
<view class="empty-placeholder" v-if="(!detailObj.images || detailObj.images.length === 0) && !isEditing">
<u-icon name="photo" size="48" color="#c0c4cc"></u-icon>
<text class="empty-text">暂无图片</text>
</view>
<!-- 编辑状态的上传按钮 -->
<view class="upload-btn-container" v-if="isEditing">
<view class="upload-btn" @click="uploadImage">
<u-icon name="camera" size="28" color="#fff"></u-icon>
<text class="upload-text">{{ localImages.length > 0 ? '添加更多' : '上传图片' }}</text>
</view>
<text class="upload-tip" v-if="localImages.length > 0">最多可上传9张</text>
</view>
</view>
</view>
<!-- 操作按钮 --> <!-- 操作按钮 -->
<view class="btn-group"> <view class="btn-group">
@ -105,44 +143,85 @@
formatTime, formatTime,
isEditing: false, // isEditing: false, //
isLoading: false, // isLoading: false, //
workOrderImg: '', // localImages: [], //
// //
editLabel: '', editLabel: '',
editAddress: '', editAddress: '',
editTitle: '', editTitle: '',
editContactName: '', // editContactName: '',
editContactPhone: '', // editContactPhone: '',
detailObj: {}, detailObj: {},
tempImagePath: '' // tempImagePaths: [] //
}; };
}, },
mounted() { mounted() {
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]}`; //
if (this.detailObj.images && this.detailObj.images.length > 0) {
this.localImages = this.detailObj.images.map(img => `${IMAGE_BASE_URL}${img}`);
}
// //
this.editContactName = this.detailObj.concatName || ''; this.editContactName = this.detailObj.concatName || '';
this.editContactPhone = this.detailObj.customerPhone || ''; this.editContactPhone = this.detailObj.customerPhone || '';
}, },
methods: { methods: {
// //
uploadImage() { previewImage(index) {
uni.chooseImage({ uni.previewImage({
count: 1, current: index,
success: async (res) => { urls: this.detailObj.images.map(img => `${IMAGE_BASE_URL}${img}`)
const tempFilePaths = res.tempFilePaths[0];
this.tempImagePath = tempFilePaths;
this.workOrderImg = tempFilePaths;
uni.showToast({
title: '图片已更换',
icon: 'success'
}); });
},
//
uploadImage() {
const maxCount = 9 - this.localImages.length;
if (maxCount <= 0) {
uni.showToast({
title: '最多只能上传9张图片',
icon: 'none'
});
return;
}
uni.chooseImage({
count: maxCount,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
this.tempImagePaths = [...this.tempImagePaths, ...res.tempFilePaths];
this.localImages = [...this.localImages, ...res.tempFilePaths];
} }
}); });
}, },
//
removeImage(index) {
this.localImages.splice(index, 1);
// tempImagePaths
if (index >= this.localImages.length - this.tempImagePaths.length) {
this.tempImagePaths.splice(index - (this.localImages.length - this.tempImagePaths.length), 1);
}
},
// //
async uploadImageToServer(filePath) { async uploadImagesToServer() {
const uploadedPaths = [];
for (const filePath of this.tempImagePaths) {
try {
const path = await this.uploadSingleImage(filePath);
uploadedPaths.push(path);
} catch (error) {
console.error('图片上传失败:', error);
throw error;
}
}
return uploadedPaths;
},
//
uploadSingleImage(filePath) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
uni.uploadFile({ uni.uploadFile({
url: `${BASE_URL}/api/v1/upload`, url: `${BASE_URL}/api/v1/upload`,
@ -155,7 +234,7 @@
try { try {
const res = JSON.parse(uploadRes.data); const res = JSON.parse(uploadRes.data);
if (res && res.success) { if (res && res.success) {
resolve(res.data); // resolve(res.data);
} else { } else {
reject(new Error(res.message || '上传失败')); reject(new Error(res.message || '上传失败'));
} }
@ -188,9 +267,16 @@
let images = this.detailObj.images; let images = this.detailObj.images;
// //
if (this.tempImagePath) { if (this.tempImagePaths.length > 0) {
const uploadedPath = await this.uploadImageToServer(this.tempImagePath); const uploadedPaths = await this.uploadImagesToServer();
images = [uploadedPath]; // //
const originalPaths = this.localImages
.filter(img => img.startsWith(IMAGE_BASE_URL))
.map(img => img.replace(IMAGE_BASE_URL, ''));
images = [...originalPaths, ...uploadedPaths];
} else if (this.localImages.length === 0) {
//
images = [];
} }
const requestData = { const requestData = {
@ -199,8 +285,8 @@
title: this.editTitle, title: this.editTitle,
images: images, images: images,
status: 1, status: 1,
concatName: this.editContactName, // concatName: this.editContactName,
customerPhone: this.editContactPhone // 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);
@ -218,7 +304,7 @@
}); });
this.isEditing = false; this.isEditing = false;
this.tempImagePath = ''; // this.tempImagePaths = []; //
} else { } else {
uni.showToast({ uni.showToast({
title: res.message || '修改失败', title: res.message || '修改失败',
@ -240,9 +326,14 @@
// / // /
async handleWithdraw() { async handleWithdraw() {
if (this.isEditing) { if (this.isEditing) {
// //
this.isEditing = false; this.isEditing = false;
this.tempImagePath = ''; this.tempImagePaths = [];
if (this.detailObj.images && this.detailObj.images.length > 0) {
this.localImages = this.detailObj.images.map(img => `${IMAGE_BASE_URL}${img}`);
} else {
this.localImages = [];
}
return; return;
} }
@ -267,7 +358,7 @@
<style lang="scss" scoped> <style lang="scss" scoped>
/* 基础变量优化 */ /* 基础变量优化 */
$primary-color: #409EFF; // $primary-color: #409EFF;
$success-color: #67C23A; $success-color: #67C23A;
$warning-color: #F56C6C; $warning-color: #F56C6C;
$text-color: #303133; $text-color: #303133;
@ -299,87 +390,6 @@
} }
} }
/* 页面容器 */
.work-order-detail {
background-color: $bg-color;
min-height: 100vh;
padding: 0 30rpx 40rpx;
}
/* 导航栏 */
.navbar {
@include flex-center;
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 {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
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 {
font-size: 34rpx;
color: $text-color;
font-weight: 500;
}
.status-tag {
margin-left: 12rpx;
height: 40rpx;
line-height: 40rpx;
}
.contact-info {
@include flex-center;
font-size: 28rpx;
}
.info-icon {
margin-right: 6rpx;
}
.user-phone {
color: $subtext-color;
}
}
/* 图片区域优化 */ /* 图片区域优化 */
.image-area { .image-area {
@include card; @include card;
@ -391,37 +401,76 @@
border-radius: 8rpx; border-radius: 8rpx;
overflow: hidden; overflow: hidden;
background-color: $bg-color; background-color: $bg-color;
min-height: 200rpx;
}
.image-scroll {
width: 100%;
white-space: nowrap;
}
.image-list {
display: inline-flex;
gap: 16rpx;
}
.image-item {
position: relative;
width: 200rpx;
height: 200rpx;
border-radius: 8rpx;
overflow: hidden;
} }
.work-order-img { .work-order-img {
width: 100%; width: 200rpx;
height: 200rpx;
border-radius: 8rpx; border-radius: 8rpx;
transition: $transition;
//
&:empty {
height: 300rpx;
background-color: $bg-color;
display: flex;
align-items: center;
justify-content: center;
color: $light-text;
} }
.delete-btn {
position: absolute;
right: 8rpx;
top: 8rpx;
width: 36rpx;
height: 36rpx;
background: rgba(0, 0, 0, 0.5);
border-radius: 50%;
@include flex-center;
justify-content: center;
}
.empty-placeholder {
width: 100%;
height: 300rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #f5f7fa;
border-radius: 8rpx;
.empty-text {
margin-top: 16rpx;
font-size: 28rpx;
color: #909399;
}
}
.upload-btn-container {
margin-top: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 10rpx;
} }
.upload-btn { .upload-btn {
position: absolute; background: $primary-color;
right: 16rpx; padding: 12rpx 24rpx;
bottom: 16rpx;
background: rgba(0, 0, 0, 0.5);
padding: 8rpx 16rpx;
border-radius: 24rpx; border-radius: 24rpx;
@include flex-center; @include flex-center;
transition: $transition; transition: $transition;
cursor: pointer;
&:hover {
background: rgba(0, 0, 0, 0.7);
}
.upload-text { .upload-text {
color: #fff; color: #fff;
@ -429,9 +478,20 @@
margin-left: 6rpx; margin-left: 6rpx;
} }
} }
.upload-tip {
font-size: 24rpx;
color: $light-text;
}
}
/* 其他样式保持不变 */
.work-order-detail {
background-color: $bg-color;
min-height: 100vh;
padding: 0 30rpx 40rpx;
} }
/* 标题区域优化 */
.title-area { .title-area {
@include card; @include card;
display: flex; display: flex;
@ -443,11 +503,6 @@
width: 100%; width: 100%;
} }
.title-icon {
margin-right: 12rpx;
flex-shrink: 0;
}
.title { .title {
font-size: 34rpx; font-size: 34rpx;
color: $text-color; color: $text-color;
@ -461,17 +516,12 @@
padding-top: 4rpx; padding-top: 4rpx;
} }
.date-icon {
margin-right: 6rpx;
}
.date { .date {
font-size: 26rpx; font-size: 26rpx;
color: $light-text; color: $light-text;
} }
} }
/* 内容区域优化 */
.content { .content {
@include card; @include card;
@ -498,10 +548,6 @@
flex-shrink: 0; flex-shrink: 0;
} }
.label-name u-icon {
margin-right: 8rpx;
}
& > text { & > text {
font-size: 28rpx; font-size: 28rpx;
color: $text-color; color: $text-color;
@ -549,7 +595,6 @@
} }
} }
/* 按钮区域优化 */
.btn-group { .btn-group {
display: flex; display: flex;
gap: 24rpx; gap: 24rpx;
@ -574,28 +619,4 @@
} }
} }
} }
/* 适配小屏幕 */
@media (max-width: 375px) {
$font-base: 28rpx;
.header {
padding: 18rpx;
.avatar {
width: 80rpx;
height: 80rpx;
}
}
.btn-group {
padding: 20rpx 5rpx;
.action-btn {
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
}
}
}
</style> </style>

View File

@ -11,11 +11,7 @@
<u-icon name="arrow-down" color="#999" size="30"></u-icon> <u-icon name="arrow-down" color="#999" size="30"></u-icon>
</view> </view>
</view> </view>
<u-picker <u-picker :show="showTypeSelect" :columns="areaList" keyName="title" @confirm="handleTypeConfirm"
:show="showTypeSelect"
:columns="areaList"
keyName="title"
@confirm="handleTypeConfirm"
@cancel="showTypeSelect = false"> @cancel="showTypeSelect = false">
</u-picker> </u-picker>
</view> </view>
@ -35,7 +31,6 @@
</u-picker> </u-picker>
</view> </view>
<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>
@ -65,21 +60,29 @@
</view> </view>
</view> </view>
<!-- 上传照片/视频 --> <!-- 上传照片/视频 - 完全重写的上传组件 -->
<view class="form-item"> <view class="form-item">
<view class="label"> <view class="label">
<u-icon name="photo" color="#3B8CFF" size="38"></u-icon> <u-icon name="photo" color="#3B8CFF" size="38"></u-icon>
上传照片/视频 上传照片/视频
</view> </view>
<view class="upload-area"> <view class="upload-area">
<u-upload :fileList="fileList" @afterRead="handleAfterRead" @delete="handleDelete" :maxCount="8" <view class="upload-list">
width="160" height="160" accept="image/*,video/*" :previewFullImage="true"> <view class="upload-item" v-for="(item, index) in fileList" :key="index">
</u-upload> <image v-if="!item.isVideo" :src="item.url" mode="aspectFill" @click="previewImage(index)"></image>
<video v-else :src="item.url" controls></video>
<view class="delete-btn" @click="handleDelete(index)">
<u-icon name="close" color="#fff" size="24"></u-icon>
</view>
</view>
<view class="upload-btn" @click="showUploadAction" v-if="fileList.length < 8">
<u-icon name="plus" size="40" color="#c0c4cc"></u-icon>
</view>
</view>
</view> </view>
<text class="note">照片可识别工单内容支持分批上传但最大数量为8</text> <text class="note">照片可识别工单内容支持分批上传但最大数量为8</text>
</view> </view>
<!-- 工单地点 --> <!-- 工单地点 -->
<view class="form-item"> <view class="form-item">
<view class="label"> <view class="label">
@ -95,42 +98,52 @@
</view> </view>
</view> </view>
<Footer></Footer> <Footer></Footer>
<!-- 上传操作菜单 -->
<u-action-sheet
:list="actionList"
v-model="showActionSheet"
@click="handleActionClick"
></u-action-sheet>
</view> </view>
</template> </template>
<script> <script>
import Footer from '@/components/footer_common.vue'; import Footer from '@/components/footer_common.vue';
import { import { get, post } from '@/utils/request';
get, import { IMAGE_BASE_URL, BASE_URL } from '@/utils/config';
post import uActionSheet from 'uview-ui/components/u-action-sheet/u-action-sheet';
} from '@/utils/request';
import { export default {
IMAGE_BASE_URL,
BASE_URL
} from '@/utils/config';
export default {
components: { components: {
Footer Footer,uActionSheet
}, },
data() { data() {
return { return {
workOrderContent: '', workOrderContent: '',
concatName: '', // concatName: '',
customerPhone: '', // customerPhone: '',
fileList: [], fileList: [],
workOrderCategory: '', workOrderCategory: '',
workOrderCategoryID: '', workOrderCategoryID: '',
workOrderArea:'', workOrderArea: '',
workOrderAreaID:'', workOrderAreaID: '',
showSelect: false, showSelect: false,
showTypeSelect:false, showTypeSelect: false,
categoryList: [], categoryList: [],
workOrderLocation: '', workOrderLocation: '',
expectedTime: '', expectedTime: '',
uploadedImages: [], uploadedImages: [],
uploadedVideos: [], uploadedVideos: [],
videoFormats: ['mp4', 'mov', 'avi', 'wmv', 'mpeg', '3gp', 'flv', 'mkv'], videoFormats: ['mp4', 'mov', 'avi', 'wmv', 'mpeg', '3gp', 'flv', 'mkv'],
areaList:[], areaList: [],
//
showActionSheet: false,
actionList: [
{ text: '拍照', value: 'camera' },
{ text: '从相册选择', value: 'album' }
]
}; };
}, },
mounted() { mounted() {
@ -138,129 +151,206 @@
this.getOrderTypeList(); this.getOrderTypeList();
}, },
methods: { methods: {
handleBack() { //
uni.navigateBack({ showUploadAction() {
delta: 1 uni.showActionSheet({
itemList: ['拍照', '从相册选择'],
success: (res) => {
if (res.tapIndex === 0) {
this.chooseMedia('camera');
} else {
this.chooseMedia('album');
}
}
}); });
}, },
// //
handleAfterRead(event) { handleActionClick(index) {
const { const action = this.actionList[index].value;
file if (action === 'camera') {
} = event; this.chooseMedia('camera');
let files = [].concat(file); } else {
this.chooseMedia('album');
}
},
if (this.fileList.length + files.length > 8) { //
uni.showToast({ async chooseMedia(sourceType) {
title: '最多只能上传8个文件', try {
icon: 'none' // 1.
const res = await new Promise((resolve, reject) => {
uni.chooseImage({
count: 8 - this.fileList.length,
sourceType: [sourceType === 'camera' ? 'camera' : 'album'],
success: resolve,
fail: reject
}); });
return; });
// 2.
const newFiles = res.tempFilePaths.map(url => ({
url,
isVideo: false,
status: 'uploading'
}));
this.fileList = [...this.fileList, ...newFiles];
// 3.
for (const file of newFiles) {
await this.uploadFile(file);
} }
files.forEach(item => { } catch (err) {
const extension = this.getFileExtension(item.url); console.error('选择图片失败:', err);
const isVideo = this.videoFormats.includes(extension.toLowerCase()); uni.showToast({ title: '选择图片失败', icon: 'none' });
}
},
_chooseMedia(sourceType) {
uni.chooseMedia({
count: 8 - this.fileList.length,
mediaType: ['image', 'video'],
sourceType: [sourceType],
success: async (res) => {
//
const newFiles = res.tempFiles.map(file => ({
url: file.tempFilePath,
isVideo: file.fileType === 'video',
status: 'uploading' //
}));
this.fileList = [...this.fileList, ...newFiles];
this.fileList.push({ //
...item, for (const file of newFiles) {
status: 'uploading', await this.uploadFile(file); //
message: '上传中...', }
isVideo: isVideo }
});
this.uploadFile(item, isVideo);
}); });
}, },
getFileExtension(filename) { //
return filename.split('.').pop().split('?')[0]; uploadFiles(files) {
}, files.forEach(file => {
this.uploadFile(file).then(res => {
handleDelete(event) { const index = this.fileList.findIndex(item => item.url === file.url);
const { if (index !== -1) {
index this.$set(this.fileList, index, {
} = event; ...file,
const file = this.fileList[index]; status: 'success',
serverUrl: res
});
if (file.isVideo) { if (file.isVideo) {
const videoIndex = this.uploadedVideos.indexOf(file.url); this.uploadedVideos.push(res.replace(BASE_URL, ''));
} else {
this.uploadedImages.push(res.replace(BASE_URL, ''));
}
}
}).catch(err => {
const index = this.fileList.findIndex(item => item.url === file.url);
if (index !== -1) {
this.$set(this.fileList, index, {
...file,
status: 'failed'
});
}
console.error('上传失败:', err);
});
});
},
//
async uploadFile(file) {
try {
const res = await new Promise((resolve, reject) => {
uni.uploadFile({
url: `${BASE_URL}/api/v1/upload`,
filePath: file.url,
name: 'file',
header: {
'Authorization': `Bearer ${uni.getStorageSync('token')}`,
'Content-Type': 'multipart/form-data'
},
success: (uploadRes) => {
try {
const data = JSON.parse(uploadRes.data);
if (data.success) {
resolve(data.data); // 使URL
} else {
reject(data.message || '上传失败');
}
} catch (e) {
reject('解析响应失败');
}
},
fail: (err) => reject(err)
});
});
//
const index = this.fileList.findIndex(f => f.url === file.url);
if (index !== -1) {
this.$set(this.fileList, index, {
...file,
status: 'success',
serverUrl: res // URL
});
//
if (file.isVideo) {
this.uploadedVideos.push(res);
} else {
this.uploadedImages.push(res);
}
}
} catch (err) {
console.error('上传失败:', err);
const index = this.fileList.findIndex(f => f.url === file.url);
if (index !== -1) {
this.$set(this.fileList, index, {
...file,
status: 'failed',
error: err.message || err
});
}
uni.showToast({
title: `上传失败: ${err.message || err}`,
icon: 'none'
});
}
},
//
previewImage(index) {
const images = this.fileList
.filter(item => !item.isVideo)
.map(item => item.url);
uni.previewImage({
current: index,
urls: images
});
},
//
handleDelete(index) {
const file = this.fileList[index];
if (file.serverUrl) {
if (file.isVideo) {
const videoIndex = this.uploadedVideos.indexOf(file.serverUrl.replace(BASE_URL, ''));
if (videoIndex !== -1) this.uploadedVideos.splice(videoIndex, 1); if (videoIndex !== -1) this.uploadedVideos.splice(videoIndex, 1);
} else { } else {
const imageIndex = this.uploadedImages.indexOf(file.url); const imageIndex = this.uploadedImages.indexOf(file.serverUrl.replace(BASE_URL, ''));
if (imageIndex !== -1) this.uploadedImages.splice(imageIndex, 1); if (imageIndex !== -1) this.uploadedImages.splice(imageIndex, 1);
} }
}
this.fileList.splice(index, 1); this.fileList.splice(index, 1);
}, },
async uploadFile(file, isVideo) { // ...
try {
const fullUrl = await this.uploadAvatar(file.url);
const relativePath = fullUrl.replace(BASE_URL, '');
const fileIndex = this.fileList.findIndex(item => item.url === file.url);
if (fileIndex !== -1) {
this.fileList[fileIndex] = {
...this.fileList[fileIndex],
status: 'success',
message: '上传成功',
url: fullUrl,
relativeUrl: relativePath,
isVideo: isVideo
};
if (isVideo) {
this.uploadedVideos.push(relativePath);
} else {
this.uploadedImages.push(relativePath);
}
}
} catch (error) {
const fileIndex = this.fileList.findIndex(item => item.url === file.url);
if (fileIndex !== -1) {
this.fileList[fileIndex] = {
...this.fileList[fileIndex],
status: 'failed',
message: '上传失败'
};
}
uni.showToast({
title: `${isVideo ? '视频' : '图片'}上传失败: ${error.message}`,
icon: 'none'
});
}
},
uploadAvatar(filePath) {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: `${BASE_URL}/api/v1/upload`,
filePath: filePath,
name: 'file',
header: {
'Authorization': `Bearer ${uni.getStorageSync('token')}`
},
success: (uploadRes) => {
try {
const res = JSON.parse(uploadRes.data);
if (res && res.success) {
resolve(`${IMAGE_BASE_URL}${res.data}`);
} else {
reject(new Error(res.message || '上传失败'));
}
} catch (e) {
reject(new Error('解析响应失败'));
}
},
fail: (err) => {
reject(new Error('上传失败: ' + JSON.stringify(err)));
}
});
});
},
async handleSubmit() { async handleSubmit() {
if (!this.workOrderContent) { if (!this.workOrderContent) {
uni.showToast({ uni.showToast({
@ -304,10 +394,10 @@
const submitData = { const submitData = {
title: this.workOrderContent, title: this.workOrderContent,
concatName: this.concatName, // concatName: this.concatName,
customerPhone: this.customerPhone, // customerPhone: this.customerPhone,
orderTypeId: this.workOrderCategoryID, orderTypeId: this.workOrderCategoryID,
orderAreaId:this.workOrderAreaID, orderAreaId: this.workOrderAreaID,
address: this.workOrderLocation, address: this.workOrderLocation,
images: this.uploadedImages, images: this.uploadedImages,
videos: this.uploadedVideos, videos: this.uploadedVideos,
@ -315,7 +405,6 @@
console.log('提交数据:', submitData); console.log('提交数据:', submitData);
// API
const res = await post('/api/v1/app_auth/work-order', submitData); const res = await post('/api/v1/app_auth/work-order', submitData);
uni.hideLoading(); uni.hideLoading();
@ -327,11 +416,11 @@
duration: 2000 duration: 2000
}); });
this.resetForm(); this.resetForm();
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 || '提交失败',
@ -387,8 +476,7 @@
} }
}, },
// async getOrderTypeList() {
async getOrderTypeList(){
try { try {
const res = await get('/api/v1/apps/work-order-area'); const res = await get('/api/v1/apps/work-order-area');
if (res?.success) { if (res?.success) {
@ -405,11 +493,11 @@
} }
} }
} }
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.container { .container {
width: 100%; width: 100%;
min-height: 100vh; min-height: 100vh;
background: #f5f7fa; background: #f5f7fa;
@ -467,6 +555,47 @@
.upload-area { .upload-area {
margin-top: 16rpx; margin-top: 16rpx;
.upload-list {
display: flex;
flex-wrap: wrap;
margin: -5rpx;
.upload-item, .upload-btn {
width: 160rpx;
height: 160rpx;
margin: 5rpx;
position: relative;
background: #f8f8f8;
border-radius: 8rpx;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
image, video {
width: 100%;
height: 100%;
}
.delete-btn {
position: absolute;
right: 0;
top: 0;
width: 40rpx;
height: 40rpx;
background: rgba(0, 0, 0, 0.5);
border-radius: 0 0 0 8rpx;
display: flex;
justify-content: center;
align-items: center;
}
}
.upload-btn {
border: 1rpx dashed #c0c4cc;
}
}
} }
.note { .note {
@ -512,5 +641,5 @@
} }
} }
} }
} }
</style> </style>