Browse Source

'开始联调0-一堆的小问题'

mian
hflllll 1 week ago
parent
commit
c448959c65
18 changed files with 1000 additions and 331 deletions
  1. +13
    -3
      api/modules/config.js
  2. +32
    -0
      api/modules/exhibit.js
  3. +1
    -2
      api/modules/login.js
  4. +4
    -29
      api/modules/user.js
  5. +15
    -4
      mixins/config.js
  6. +4
    -1
      mixins/list.js
  7. +1
    -1
      pages.json
  8. +3
    -3
      pages/components/SearchInput.vue
  9. +20
    -37
      pages/index/home.vue
  10. +20
    -6
      pages/index/user.vue
  11. +0
    -0
      static/默认图片.png
  12. +22
    -7
      stores/index.js
  13. +1
    -1
      subPages/home/RAArecord.vue
  14. +82
    -66
      subPages/home/maintainanceSubmit.vue
  15. +58
    -13
      subPages/login/login.vue
  16. +415
    -0
      subPages/login/otherDemo.vue
  17. +275
    -142
      subPages/login/userInfo.vue
  18. +34
    -16
      subPages/user/profile.vue

+ 13
- 3
api/modules/config.js View File

@ -9,8 +9,18 @@ export default {
})
},
async queryDepartmentList() {
return http({
url: '/config/queryDepartmentList',
method: 'GET'
})
},
// 获取分类的接口
async queryCategoryList(){
return http({
url: '/config/queryCategoryList',
method: 'GET'
})
}
}

+ 32
- 0
api/modules/exhibit.js View File

@ -0,0 +1,32 @@
import http from "@/api/http";
export default {
// 查看展品信息列表
async queryShowpieceList(data) {
return http({
url: '/showpiece/queryShowpieceList',
method: 'GET',
data
})
},
// 查看维修记录列表
async queryRepairList(data) {
return http({
url: '/showpiece/queryRepairList',
method: 'GET',
data
})
},
// 保养-新增保养记录
async addMaintenance(data) {
return http({
url: '/showpiece/addMaintenance',
method: 'POST',
data,
needToken: true
})
},
}

+ 1
- 2
api/modules/login.js View File

@ -18,8 +18,7 @@ export default {
// header: {
// 'Content-Type': 'application/x-www-form-urlencoded'
// },
showLoading: true,
noToken: true
showLoading: true
})
}
}

+ 4
- 29
api/modules/user.js View File

@ -2,39 +2,13 @@
import http from "@/api/http";
export default {
// 兑换记录- 确认取货
async finishOrder(data) {
return http ({
url: '/order/finishOrder',
method: 'POST',
data
})
},
// 兑换记录- 查看订单详情
async queryOrderById(data) {
return http({
url: '/order/queryOrderById',
method: 'GET',
data
})
},
// 兑换记录- 查看订单列表
async queryOrderList(data) {
return http({
url: '/order/queryOrderList',
method: 'GET',
data,
// showLoading: true
})
},
// 我的资料- 获取个人信息
async queryUser() {
return http({
url: '/userInfo/queryUser',
method: 'GET'
method: 'GET',
needToken: true
})
},
@ -43,7 +17,8 @@ export default {
return http({
url: '/userInfo/updateUser',
method: 'POST',
data
data,
needToken: true
})
},
}

+ 15
- 4
mixins/config.js View File

@ -1,7 +1,7 @@
export default {
data() {
return {
containerHeight: 0
}
},
methods: {
@ -9,7 +9,15 @@ export default {
mixinCustomShare() {
return {
}
}
},
// 计算容器高度
calculateContainerHeight() {
const systemInfo = uni.getSystemInfoSync();
const statusBarHeight = systemInfo.statusBarHeight || 0;
const navigationBarHeight = 44; // 默认导航栏高度
this.containerHeight = 2 * (systemInfo.windowHeight - statusBarHeight - navigationBarHeight);
console.log('最后的容器高度为', this.containerHeight/2 + 'px', '状态栏高度:', statusBarHeight + 'px', 'nav导航栏高度:', navigationBarHeight + 'px');
},
},
computed: {
// 获取全局配置的文本
@ -27,9 +35,9 @@ export default {
// 默认的全局分享参数
GShare() {
return {
title: this.configParamText('config_app_name'),
title: this.configParamText('app_name'),
desc: this.configParamText('share_desc'),
imageUrl: this.configParamImage('config_logo'),
imageUrl: this.configParamImage('app_logo'),
path: '/pages/index/index'
}
}
@ -45,5 +53,8 @@ export default {
...this.GShare,
...this.mixinCustomShare()
}
},
onLoad() {
// this.calculateContainerHeight();
}
}

+ 4
- 1
mixins/list.js View File

@ -4,7 +4,7 @@ export default {
return {
list: [],
pageNo : 1,
pageSize : 10,
pageSize : 8,
mixinListApi: '',
isLoading: false,
hasMore: true,
@ -53,6 +53,8 @@ export default {
},
// 获取列表
async getList(isRefresh = false) {
// console.log('本次请求的pageNo和pageSize', this.pageNo, this.pageSize)
if (!this.hasMore) {
return
}
@ -66,6 +68,7 @@ export default {
if (this.beforeUpdateDataFn) {
this.beforeUpdateDataFn(this.list)
}
const res = await apiMethod({
pageNo: this.pageNo,
pageSize: this.pageSize,


+ 1
- 1
pages.json View File

@ -35,7 +35,7 @@
{
"path": "login/userInfo",
"style": {
"navigationBarTitleText": ""
"navigationBarTitleText": "补充信息"
}
},
{


+ 3
- 3
pages/components/SearchInput.vue View File

@ -1,7 +1,7 @@
<template>
<view class="search-input-container">
<view class="search-box">
<view class="search-icon">
<view class="search-icon" @click="handleSearch">
<uv-icon name="search" size="20"></uv-icon>
</view>
<input
@ -11,10 +11,9 @@
v-model="inputValue"
@input="handleInput"
@confirm="handleSearch"
@keyup.enter="handleSearch"
/>
<text class="search-text" v-if="showSearchButton">{{ searchButtonText }}</text>
<text class="search-text" v-if="showSearchButton" @click="handleSearch">{{ searchButtonText }}</text>
</view>
</view>
</template>
@ -59,6 +58,7 @@ export default {
this.$emit('input', this.inputValue)
},
handleSearch() {
// console.log('', this.inputValue);
this.$emit('search', this.inputValue)
}
}


+ 20
- 37
pages/index/home.vue View File

@ -31,7 +31,7 @@
<!-- 展品列表 -->
<view class="exhibit-list">
<view class="exhibit-item" v-for="(item, index) in exhibitList" :key="index" @click="handleItemClick(item)">
<view class="exhibit-item" v-for="(item, index) in list" :key="index" @click="handleItemClick(item)">
<view class="item-header">
<text class="item-id">{{ item.id }}</text>
<img src="@/static/copy.png" alt="我是复制黏贴" class="item-icon" @click="copyId(item.id)">
@ -39,19 +39,16 @@
<view class="item-border" />
<view class="item-content">
<view class="content-row">
<text class="name">{{ item.name }}</text>
</view>
<view class="content-row">
<text class="label">展品编号</text>
<text class="value">{{ item.code }}</text>
<text class="name">{{ item.title }}</text>
</view>
<view class="content-row">
<text class="label">展品编号</text>
<text class="value">{{ item.code }}</text>
<text class="value">{{ item.id }}</text>
</view>
<view class="content-row">
<text class="label">展品位置</text>
<text class="value">{{ item.location }}</text>
<text class="value">{{ item.position }}</text>
</view>
<view class="content-row">
<text class="label">展品类型</text>
@ -94,18 +91,25 @@
</view>
</view>
</view>
<!-- 空状态 -->
<uv-empty v-if="!list.length" icon="/static/暂无搜索结果.png" />
</view>
</template>
<script>
import SearchInput from '@/pages/components/SearchInput.vue'
import ListMixin from '@/mixins/list'
export default {
mixins: [ListMixin],
components: {
SearchInput
},
data() {
return {
mixinListApi: 'exhibit.queryShowpieceList',
searchValue: '',
categoryShow: false,
selectedCategory: '',
@ -116,40 +120,19 @@ export default {
'互动设备',
'展示设备'
]
],
exhibitList: [
{
id: '3451356565',
name: '展品名称内容',
code: '56559685623452',
location: '2楼互动区液体力学',
type: '文字内容',
maintenanceDate: '2025-08-19'
},
{
id: '3451356566',
name: '展品名称内容',
code: '56559685623453',
location: '1楼展示区机械原理',
type: '互动设备',
maintenanceDate: '2025-09-15',
maintenanceProject: '3天后维保',
},
{
id: '3451356567',
name: '展品名称内容',
code: '56559685623454',
location: '3楼体验区声学实验',
type: '展示设备',
maintenanceDate: '2025-07-22'
}
]
}
},
methods: {
handleSearch() {
console.log('搜索:', this.searchValue)
// TODO:
console.log('搜索的数值为', this.searchValue);
this.initPage()
this.getList(true)
},
mixinSetParams() {
return {
title: this.searchValue
}
},
openPicker() {
this.$refs.picker.open()


+ 20
- 6
pages/index/user.vue View File

@ -5,11 +5,11 @@
<!-- 用户信息区域 -->
<view class="user-info">
<view class="avatar-section">
<image class="avatar" :src="userInfo.avatar" mode="aspectFill"></image>
<image class="avatar" :src="userInfo.headImage || '/static/默认头像.png'" mode="aspectFill"></image>
</view>
<view class="info-section">
<text class="username">{{userInfo.username}}</text>
<text class="user-id">ID:{{userInfo.userId}}</text>
<text class="username">{{userInfo.nickName}}</text>
<text class="user-id">ID:{{userInfo.id}}</text>
</view>
</view>
</view>
@ -57,12 +57,18 @@ export default {
data() {
return {
userInfo: {
avatar: '/static/默认头像.png',
username: '请先登录',
userId: 'XXXXX'
headImage: '/static/默认头像.png',
nickName: '请先登录',
id: 'XXXXX'
}
}
},
onShow() {
//
if (uni.getStorageSync('token')) {
this.getUserInfo();
}
},
methods: {
//
goLogin() {
@ -78,6 +84,14 @@ export default {
})
},
//
async getUserInfo() {
const res = await this.$api.user.queryUser();
if (res.code === 200) {
this.userInfo = res.result;
}
},
//
showPrivacyPolicy() {
this.$refs.privacyModal.open()


static/我的.png → static/默认图片.png View File


+ 22
- 7
stores/index.js View File

@ -8,13 +8,19 @@ const store = new Vuex.Store({
state: {
// 存放状态
configList: [],
departmentList: [],
},
mutations: {
// 分类列表
setCategoryList(state, data) {
state.categoryList = data
},
setConfigList(state, data) {
state.configList = data
},
setDepartmentList(state, data) {
state.departmentList = data
},
},
actions: {
@ -33,12 +39,20 @@ const store = new Vuex.Store({
commit('setConfigList', config)
},
// 查询部门列表
async getDepartment({ commit }) {
const res = await api.config.queryDepartmentList()
commit('setDepartmentList', res.result.records)
},
// 获取分类列表
async getCategory({ commit }) {
const res = await api.config.queryCategoryList()
commit('setCategoryList', res.result.records)
},
// 初始化数据
async initData({ dispatch, state }) {
// 检查是否已初始化
if (state.configList.length > 0) {
if (state.configList.length > 0 && state.departmentList.length > 0 && state.categoryList.length > 0) {
console.log('配置数据已初始化,无需重复初始化')
return
@ -47,8 +61,9 @@ const store = new Vuex.Store({
try {
await Promise.all([
dispatch('getConfig'),
])
dispatch('getDepartment'),
// dispatch('getCategory'),
])
console.log('所有配置数据初始化完成')
} catch (error) {
console.error('配置数据初始化失败:', error)


+ 1
- 1
subPages/home/RAArecord.vue View File

@ -128,7 +128,7 @@
<image
class="uploaded-image"
v-for="(img, imgIndex) in record.beforeImages"
:key="'before-' + imgIndex"
:key="imgIndex"
:src="img"
mode="aspectFill"
></image>


+ 82
- 66
subPages/home/maintainanceSubmit.vue View File

@ -10,9 +10,9 @@
<!-- 保养人 -->
<view class="form-item">
<text class="label">保养人</text>
<view class="input-area" @click="focusMaintainer">
<view class="input-area" >
<input
v-model="maintainer"
v-model="maintenanceName"
placeholder="请填写"
class="input-field"
ref="maintainerInput"
@ -35,7 +35,7 @@
</view>
<view class="textarea-container">
<uv-textarea
v-model="beforeStatus"
v-model="stateFrontText"
placeholder="请填写保养前的设备内容"
:maxlength="200"
:show-confirm-bar="false"
@ -47,8 +47,8 @@
<!-- 保养前图片 -->
<view class="image-upload">
<view v-for="(img, index) in beforeImageList" :key="index" class="image-item">
<image :src="img" mode="aspectFill" @click="previewImage(img, beforeImageList)"></image>
<view v-for="(img, index) in stateFrontImage" :key="index" class="image-item">
<image :src="img" mode="aspectFill" @click="previewImage(img, stateFrontImage)"></image>
<view class="delete-btn" @click="deleteBeforeImage(index)">
<uv-icon name="close" size="12" color="#fff"></uv-icon>
</view>
@ -64,7 +64,7 @@
</view>
<view class="textarea-container">
<uv-textarea
v-model="afterStatus"
v-model="stateBackText"
placeholder="请填写保养后的设备内容"
:maxlength="200"
:show-confirm-bar="false"
@ -76,8 +76,8 @@
<!-- 保养后图片 -->
<view class="image-upload">
<view v-for="(img, index) in afterImageList" :key="index" class="image-item">
<image :src="img" mode="aspectFill" @click="previewImage(img, afterImageList)"></image>
<view v-for="(img, index) in stateBackImage" :key="index" class="image-item">
<image :src="img" mode="aspectFill" @click="previewImage(img, stateBackImage)"></image>
<view class="delete-btn" @click="deleteAfterImage(index)">
<uv-icon name="close" size="12" color="#fff"></uv-icon>
</view>
@ -93,25 +93,25 @@
<view class="radio-options">
<view
class="radio-item"
:class="{ active: hasCost === true }"
:class="{ active: isExpend === true }"
@click="selectCost(true)"
>
<view class="radio-dot" :class="{ active: hasCost === true }"></view>
<text :class="{ active: hasCost === true }"></text>
<view class="radio-dot" :class="{ active: isExpend === true }"></view>
<text :class="{ active: isExpend === true }"></text>
</view>
<view
class="radio-item"
:class="{ active: hasCost === false }"
:class="{ active: isExpend === false }"
@click="selectCost(false)"
>
<view class="radio-dot" :class="{ active: hasCost === false }"></view>
<text :class="{ active: hasCost === false }"></text>
<view class="radio-dot" :class="{ active: isExpend === false }"></view>
<text :class="{ active: isExpend === false }"></text>
</view>
</view>
</view>
<!-- 产生费用 -->
<view v-if="hasCost" class="cost-section">
<view v-if="isExpend" class="cost-section">
<view class="form-item form-item-header">
<text class="label active">产生费用</text>
</view>
@ -183,7 +183,7 @@
</view>
<view class="textarea-container">
<uv-textarea
v-model="remark"
v-model="remarkText"
placeholder="请填写备注"
:maxlength="200"
:show-confirm-bar="false"
@ -195,8 +195,8 @@
<!-- 附件图片 -->
<view class="image-upload">
<view v-for="(img, index) in attachmentList" :key="index" class="image-item">
<image :src="img" mode="aspectFill" @click="previewImage(img, attachmentList)"></image>
<view v-for="(img, index) in remarkImage" :key="index" class="image-item">
<image :src="img" mode="aspectFill" @click="previewImage(img, remarkImage)"></image>
<view class="delete-btn" @click="deleteAttachment(index)">
<uv-icon name="close" size="12" color="#fff"></uv-icon>
</view>
@ -221,7 +221,7 @@
</view>
<view class="textarea-container">
<uv-textarea
v-model="finalRemark"
v-model="remark"
placeholder="请填写备注"
:maxlength="200"
:show-confirm-bar="false"
@ -237,6 +237,7 @@
<uv-button
type="primary"
text="立即提交"
:disabled="submiting"
:custom-style="{ backgroundColor: '#C70019', borderRadius: '25px' }"
@click="submitMaintenance"
></uv-button>
@ -266,22 +267,29 @@ export default {
data() {
return {
//
maintainer: '',
maintenanceName: '',
maintenanceDate: '',
beforeStatus: '',
beforeImageList: [],
afterStatus: '',
afterImageList: [],
hasCost: null,
stateFrontText: '',
stateFrontImage: [],
stateBackText: '',
stateBackImage: [],
isExpend: false,
newCostName: '',
costList: [],
remark: '',
attachmentList: [],
remarkText: '',
remarkImage: [],
nextMaintenanceDate: '',
finalRemark: ''
remark: '',
showpieceId: '',
submiting: false
}
},
computed: {
//
amount() {
return this.costList.reduce((sum, item) => sum + (parseFloat(item.amount) || 0), 0)
}
},
methods: {
//
showDatePicker() {
@ -315,7 +323,7 @@ export default {
//
selectCost(value) {
this.hasCost = value
this.isExpend = value
if (!value) {
this.costList = []
this.newCostName = ''
@ -365,7 +373,7 @@ export default {
if (result && result.success) {
console.log(result);
this.beforeImageList.push(result.url)
this.stateFrontImage.push(result.url)
}
} catch (error) {
console.error('图片上传失败:', error)
@ -378,7 +386,7 @@ export default {
//
deleteBeforeImage(index) {
this.beforeImageList.splice(index, 1)
this.stateFrontImage.splice(index, 1)
},
//
@ -388,7 +396,7 @@ export default {
if (result && result.success) {
console.log(result);
this.afterImageList.push(result.url)
this.stateBackImage.push(result.url)
}
} catch (error) {
console.error('头像上传失败:', error)
@ -401,7 +409,7 @@ export default {
//
deleteAfterImage(index) {
this.afterImageList.splice(index, 1)
this.stateBackImage.splice(index, 1)
},
//
@ -412,7 +420,7 @@ export default {
if (result && result.success) {
console.log(result);
this.attachmentList.push(result.url)
this.remarkImage.push(result.url)
}
} catch (error) {
console.error('头像上传失败:', error)
@ -425,7 +433,7 @@ export default {
//
deleteAttachment(index) {
this.attachmentList.splice(index, 1)
this.remarkImage.splice(index, 1)
},
//
@ -435,56 +443,64 @@ export default {
current: url
})
},
//
focusMaintainer() {
this.$refs.maintainerInput.focus()
},
//
submitMaintenance() {
async submitMaintenance() {
//
if (!this.maintainer.trim()) {
if (!this.maintenanceName.trim()) {
uni.showToast({ title: '请填写保养人', icon: 'none' })
return
}
if (!this.maintenanceDate) {
uni.showToast({ title: '请选择保养日期', icon: 'none' })
return
}
if (!this.beforeStatus.trim()) {
//
// if (!this.maintenanceDate) {
// uni.showToast({ title: '', icon: 'none' })
// return
// }
if (!this.stateFrontText.trim()) {
uni.showToast({ title: '请填写保养前状态', icon: 'none' })
return
}
if (!this.afterStatus.trim()) {
if (!this.stateBackText.trim()) {
uni.showToast({ title: '请填写保养后状态', icon: 'none' })
return
}
//
const formData = {
maintainer: this.maintainer,
maintenanceName: this.maintenanceName,
maintenanceDate: this.maintenanceDate,
beforeStatus: this.beforeStatus,
beforeImageList: this.beforeImageList,
afterStatus: this.afterStatus,
afterImageList: this.afterImageList,
hasCost: this.hasCost,
costList: this.costList,
remark: this.remark,
attachmentList: this.attachmentList,
stateFrontText: this.stateFrontText,
stateFrontImage: this.stateFrontImage?.join(',') || '',
stateBackText: this.stateBackText,
stateBackImage: this.stateBackImage?.join(',') || '',
isExpend: this.isExpend,
amount: this.amount,
remarkText: this.remarkText,
remarkImage: this.remarkImage?.join(',') || '',
nextMaintenanceDate: this.nextMaintenanceDate,
finalRemark: this.finalRemark
remark: this.remark,
showpieceId: this.showpieceId
}
console.log('提交数据:', formData)
uni.showToast({ title: '提交成功', icon: 'success' })
//
setTimeout(() => {
uni.navigateBack()
}, 1500)
this.submiting = true
const subRes = await this.$api.exhibit.addMaintenance(formData)
if(subRes.code == 200){
uni.showToast({ title: subRes.message, icon: 'success' })
//
setTimeout(() => {
uni.navigateBack()
}, 1000)
}else{
uni.showToast({ title: subRes.message, icon: 'none' })
}
this.submiting = false
}
},
onLoad(options) {
this.showpieceId = options.id
}
}
</script>


+ 58
- 13
subPages/login/login.vue View File

@ -29,7 +29,7 @@
<view class="checkbox-inner" v-if="isAgreed"></view>
</view>
</view>
<text>阅读并同意我们的
<text >阅读并同意我们的
<text class="link-text" @click="showServiceAgreement">服务协议与隐私条款</text>
<text></text>
<text class="link-text" @click="showPrivacyPolicy">个人信息保护指引</text>
@ -38,6 +38,24 @@
</view>
</view>
</view>
<!-- 用户协议和隐私政策弹窗 -->
<uv-modal ref="serviceModal" title="《服务协议与隐私条款》" :show-cancel-button="false" confirm-text="我知道了" confirm-color="#C70019" @confirm="isAgreed = true">
<view class="privacy-content">
<!-- 如果是富文本 -->
<rich-text :nodes="configParamTextarea('app_agreement')">
</rich-text>
</view>
</uv-modal>
<!-- 用户协议和隐私政策弹窗 -->
<uv-modal ref="guideModal" title="《个人信息保护指引》" :show-cancel-button="false" confirm-text="我知道了" confirm-color="#C70019" @confirm="isAgreed = true">
<view class="privacy-content">
<!-- 如果是富文本 -->
<rich-text :nodes="configParamTextarea('app_guideline')">
</rich-text>
</view>
</uv-modal>
</view>
</template>
@ -52,18 +70,43 @@ export default {
methods: {
//
handleLogin() {
//
console.log('授权登录');
// API
// uni.getUserProfile({
// desc: '',
// success: (res) => {
// //
// uni.switchTab({
// url: '/pages/index/home'
// });
// }
// });
if (!this.isAgreed) {
this.$refs.serviceModal.open();
this.$refs.guideModal.open();
return
}
uni.login({
provider: 'weixin',
success: async (res) => {
console.log('登录成功', res);
const { result: loginRes} = await this.$api.login.login({
code: res.code
})
uni.setStorageSync('token', loginRes.token)
const userInfo = loginRes.userInfo
if ( !userInfo.department || !userInfo.headImage || !userInfo.nickName || !userInfo.phone ){
uni.navigateTo({
url: '/subPages/login/userInfo'
})
return
}else {
uni.showToast({
title: '登录成功',
icon: 'success'
})
uni.switchTab({
url: '/pages/index/home'
});
}
//
},
fail: (err) => {
console.log('登录失败', err);
}
})
},
//
@ -77,12 +120,14 @@ export default {
//
showServiceAgreement() {
this.$refs.serviceModal.open();
console.log('查看服务协议');
//
},
//
showPrivacyPolicy() {
this.$refs.guideModal.open();
console.log('查看隐私条款');
//
},


+ 415
- 0
subPages/login/otherDemo.vue View File

@ -0,0 +1,415 @@
<template>
<view class="user-info-container">
<!-- 内容区域 -->
<view class="content">
<!-- 头像区域 -->
<view class="avatar-section">
<view class="app-info">
<image
class="app-logo"
:src="configParamImage('config_logo')"
mode="aspectFit"
></image>
<text class="app-name">{{ configParamText('config_app_name') }}</text>
</view>
</view>
<!-- 表单区域 -->
<view class="form-section">
<!-- 头像 -->
<!-- 在模板中修改头像区域 -->
<view class="form-item">
<text class="form-label">头像</text>
<view class="avatar-upload">
<button
class="avatar-button"
open-type="chooseAvatar"
@chooseavatar="onChooseAvatar"
>
<image
class="avatar-image"
:src="userInfo.headImage || '/static/待上传头像.png'"
mode="aspectFill"
></image>
</button>
</view>
</view>
<!-- 昵称 -->
<view class="form-item">
<text class="form-label">昵称</text>
<input
class="form-input"
v-model="userInfo.nickName"
placeholder="请输入昵称"
type="nickname"
@blur="onNicknameBlur"
/>
</view>
<!-- 手机号 -->
<view class="form-item">
<text class="form-label">手机号</text>
<view class="phone-input-container">
<input
class="form-input phone-input"
v-model="userInfo.phone"
placeholder="请输入手机号"
type="number"
maxlength="11"
/>
<button
class="get-phone-btn"
open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber"
>
<text class="btn-text">获取手机号</text>
</button>
</view>
</view>
</view>
<!-- 确定按钮 -->
<view class="submit-section">
<view class="submit-btn" @click="submitUserInfo">
<text class="submit-text">确定</text>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'UserInfo',
data() {
return {
userInfo: {
headImage: '',
nickName: '',
phone: ''
}
}
},
onLoad() {
//
this.getWechatUserInfo();
},
methods: {
//
async getWechatUserInfo() {
const { result } = await this.$api.user.queryUser()
this.userInfo.nickName = result.nickName
this.userInfo.headImage = result.headImage
this.userInfo.phone = result.phone
},
//
// OSS
async onChooseAvatar(e) {
console.log('选择头像回调', e);
if (e.detail.avatarUrl) {
try {
//
uni.showLoading({ title: '上传头像中...' });
//
const file = {
path: e.detail.avatarUrl,
tempFilePath: e.detail.avatarUrl
};
// OSS
const uploadResult = await this.$utils.uploadImage(file);
uni.hideLoading();
if (uploadResult.success) {
// URL
this.userInfo.headImage = uploadResult.url;
console.log('头像上传成功', uploadResult.url);
uni.showToast({
title: '头像上传成功',
icon: 'success'
});
} else {
// 使
// this.userInfo.headImage = e.detail.avatarUrl;
uni.showToast({
title: '头像上传失败!请稍后重试!',
icon: 'none'
});
}
} catch (error) {
uni.hideLoading();
console.error('头像上传异常:', error);
// 使
this.userInfo.headImage = e.detail.avatarUrl;
uni.showToast({
title: '头像处理异常,使用本地头像',
icon: 'none'
});
}
} else {
uni.showToast({
title: '头像选择失败',
icon: 'none'
});
}
},
//
onNicknameBlur() {
if (!this.userInfo.nickname.trim()) {
uni.showToast({
title: '请输入昵称',
icon: 'none'
});
}
},
//
async getPhoneNumber(e) {
console.log('获取手机号回调', e);
if (e.detail.errMsg === 'getPhoneNumber:ok') {
// e.detail.code
console.log('获取手机号成功', e.detail);
const res = await this.$api.login.bindPhone({
phoneCode: e.detail.code
})
const str = JSON.parse(res.result);
this.userInfo.phone = str.phone_info.phoneNumber
uni.showToast({
title: '手机号获取成功',
icon: 'success'
});
// e.detail.code
//
// this.userInfo.phone = '138****8888';
} else {
//
uni.showToast({
title: '手机号获取失败',
icon: 'error'
})
}
},
//
async submitUserInfo() {
if (!this.userInfo.nickName.trim()) {
uni.showToast({
title: '请输入昵称',
icon: 'none'
});
return;
}
if (!this.userInfo.phone.trim()) {
uni.showToast({
title: '请输入手机号',
icon: 'none'
});
return;
}
if (!/^1[3-9]\d{9}$/.test(this.userInfo.phone)) {
uni.showToast({
title: '请输入正确的手机号',
icon: 'none'
});
return;
}
console.log('提交用户信息', this.userInfo);
//
await this.$api.user.updateUser({
nickName: this.userInfo.nickName,
phone: this.userInfo.phone,
headImage: this.userInfo.headImage,
address: ''
})
// API
uni.showToast({
title: '信息保存成功',
icon: 'success'
});
//
setTimeout(() => {
uni.switchTab({
url: '/pages/index/index'
});
}, 1000);
}
}
}
</script>
<style lang="scss" scoped>
.user-info-container {
min-height: 100vh;
background-color: #f5f5f5;
}
.custom-navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
background-color: #1488DB;
.navbar-content {
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
padding-top: var(--status-bar-height, 44rpx);
.navbar-title {
font-size: 36rpx;
font-weight: 500;
color: #ffffff;
}
}
}
.content {
padding-top: calc(88rpx + var(--status-bar-height, 44rpx));
padding: calc(88rpx + var(--status-bar-height, 44rpx)) 40rpx 40rpx;
}
.avatar-section {
display: flex;
justify-content: center;
margin-bottom: 80rpx;
.app-info {
display: flex;
flex-direction: column;
align-items: center;
.app-logo {
width: 160rpx;
height: 160rpx;
border-radius: 20rpx;
border: 4rpx dashed #cccccc;
margin-bottom: 20rpx;
}
.app-name {
font-size: 32rpx;
font-weight: 500;
color: #333333;
}
}
}
.form-section {
background-color: #ffffff;
border-radius: 20rpx;
padding: 40rpx;
margin-bottom: 60rpx;
}
.form-item {
display: flex;
align-items: center;
margin-bottom: 40rpx;
&:last-child {
margin-bottom: 0;
}
.form-label {
width: 120rpx;
font-size: 32rpx;
color: #333333;
font-weight: 500;
}
.form-input {
flex: 3;
height: 80rpx;
padding: 0 20rpx;
// border: 2rpx solid #e0e0e0;
border-radius: 10rpx;
font-size: 30rpx;
color: #333333;
&.phone-input {
margin-right: 20rpx;
}
}
.avatar-upload {
.avatar-image {
width: 120rpx;
height: 120rpx;
border-radius: 10rpx;
border: 2rpx dashed #cccccc;
}
}
.phone-input-container {
flex: 1;
display: flex;
align-items: center;
.get-phone-btn {
flex: 2;
// padding: 0rpx 0rpx;
background-color: #1488DB;
border-radius: 40rpx;
border: none;
outline: none;
&::after {
border: none;
}
.btn-text {
font-size: 26rpx;
color: #ffffff;
}
}
}
}
.submit-section {
padding: 0 40rpx;
.submit-btn {
width: 100%;
height: 88rpx;
background-color: #1488DB;
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
.submit-text {
font-size: 32rpx;
font-weight: 500;
color: #ffffff;
}
}
}
//
.avatar-button {
padding: 0;
margin: 0;
background: transparent;
border: none;
outline: none;
&::after {
border: none;
}
}
</style>

+ 275
- 142
subPages/login/userInfo.vue View File

@ -1,6 +1,94 @@
<template>
<view>
用户信息
<view class="user-info-container" :style="{ minHeight: containerHeight + 'rpx' }">
<!-- 内容区域 -->
<view class="content">
<!-- 头像区域 -->
<view class="avatar-section">
<view class="app-info">
<image class="app-logo" :src="configParamImage('app_logo')" mode="aspectFit"></image>
<text class="app-name">{{ configParamText('app_name') }}</text>
<text class="app-subname">申请获取您的头像昵称</text>
</view>
</view>
<!-- 表单区域 -->
<view class="form-section">
<!-- 头像 -->
<view class="form-item">
<text class="form-label">头像</text>
<button class="avatar-button" open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
<image
class="avatar-image"
:src="userInfo.headImage || '/static/默认图片.png'"
mode="aspectFill"
></image>
</button>
</view>
<!-- 昵称 -->
<view class="form-item">
<text class="form-label">昵称</text>
<input
class="form-input"
type="nickname"
v-model="userInfo.nickName"
placeholder="请输入昵称"
@blur="onNicknameBlur"
/>
</view>
<!-- 手机号 -->
<view class="form-item">
<text class="form-label">手机号</text>
<view class="phone-container" v-if="!userInfo.phone">
<button
class="get-phone-btn"
open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber"
>
获取手机号
</button>
</view>
<text class="form-label" v-else>{{ userInfo.phone }}</text>
</view>
<!-- 所在部门 -->
<view class="form-item">
<text class="form-label">所在部门</text>
<view class="department-container" @click="showDepartmentPicker">
<text class="department-text" :class="{ 'active': userInfo.department.title }">{{ userInfo.department.title || '请选择' }}</text>
<uv-icon name="arrow-down" size="20" color="#000"></uv-icon>
</view>
</view>
<!-- 备注 -->
<view class="form-item ">
<text class="form-label">备注<text class="form-remark">(非必填)</text></text>
<input
class="form-input"
v-model="userInfo.remark"
/>
</view>
</view>
<!-- 确定按钮 -->
<view class="submit-section">
<view class="submit-btn" @click="submitUserInfo">
<text class="submit-text">确定</text>
</view>
</view>
</view>
<!-- 部门选择器 -->
<uv-picker
confirm-color="#C70019"
ref="departmentPicker"
:columns="departmentColumns"
keyName="title"
@confirm="confirmDepartment"
@cancel="cancelDepartment"
></uv-picker>
</view>
</template>
@ -12,13 +100,36 @@ export default {
userInfo: {
headImage: '',
nickName: '',
phone: ''
}
phone: '',
department: {
title: '',
id: ''
},
remark: ''
},
showDepartmentSheet: false,
//
}
},
onLoad() {
//
// this.getWechatUserInfo();
async onLoad() {
this.calculateContainerHeight();
await this.getUserInfo();
},
computed: {
//
departmentColumns() {
if (!this.$store.state.departmentList || this.$store.state.departmentList.length === 0) {
return [[]]
}
const departmentNames = this.$store.state.departmentList.map(item => {
return {
title: item.title,
id: item.id
}
})
return [departmentNames]
}
},
methods: {
//
@ -30,6 +141,12 @@ export default {
},
//
async getUserInfo(){
const { result:info } = await this.$api.user.queryUser()
this.userInfo.nickName = info.nickName
this.userInfo.headImage = info.headImage
this.userInfo.phone = info.phone
},
// OSS
async onChooseAvatar(e) {
@ -59,12 +176,7 @@ export default {
icon: 'success'
});
} else {
// 使
// this.userInfo.headImage = e.detail.avatarUrl;
uni.showToast({
title: '头像上传失败!请稍后重试!',
icon: 'none'
});
}
} catch (error) {
uni.hideLoading();
@ -87,7 +199,7 @@ export default {
//
onNicknameBlur() {
if (!this.userInfo.nickname.trim()) {
if (!this.userInfo.nickName.trim()) {
uni.showToast({
title: '请输入昵称',
icon: 'none'
@ -95,6 +207,27 @@ export default {
}
},
//
showDepartmentPicker() {
this.$refs.departmentPicker.open();
},
//
confirmDepartment(e) {
this.userInfo.department = e.value[0];
},
//
cancelDepartment() {
//
},
// 使
selectDepartment(item) {
this.userInfo.department = item.value;
this.showDepartmentSheet = false;
},
//
async getPhoneNumber(e) {
console.log('获取手机号回调', e);
@ -110,9 +243,6 @@ export default {
title: '手机号获取成功',
icon: 'success'
});
// e.detail.code
//
// this.userInfo.phone = '138****8888';
} else {
//
uni.showToast({
@ -134,15 +264,15 @@ export default {
if (!this.userInfo.phone.trim()) {
uni.showToast({
title: '请输入手机号',
title: '请获取手机号',
icon: 'none'
});
return;
}
if (!/^1[3-9]\d{9}$/.test(this.userInfo.phone)) {
if (!this.userInfo.department.title.trim()) {
uni.showToast({
title: '请输入正确的手机号',
title: '请选择所在部门',
icon: 'none'
});
return;
@ -150,26 +280,34 @@ export default {
console.log('提交用户信息', this.userInfo);
//
await this.$api.user.updateUser({
nickName: this.userInfo.nickName,
phone: this.userInfo.phone,
headImage: this.userInfo.headImage,
address: ''
})
try {
//
await this.$api.user.updateUser({
nickName: this.userInfo.nickName,
phone: this.userInfo.phone,
headImage: this.userInfo.headImage,
department: this.userInfo.department.id,
remark: this.userInfo.remark
});
// API
uni.showToast({
title: '信息保存成功',
icon: 'success'
});
//
setTimeout(() => {
uni.switchTab({
url: '/pages/index/index'
uni.showToast({
title: '信息保存成功',
icon: 'success'
});
}, 1000);
//
setTimeout(() => {
uni.switchTab({
url: '/pages/index/home'
});
}, 1000);
} catch (error) {
console.error('保存用户信息失败:', error);
uni.showToast({
title: '保存失败,请重试',
icon: 'none'
});
}
}
}
}
@ -177,164 +315,159 @@ export default {
<style lang="scss" scoped>
.user-info-container {
min-height: 100vh;
background-color: #f5f5f5;
}
.custom-navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
background-color: #1488DB;
.navbar-content {
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
padding-top: var(--status-bar-height, 44rpx);
.navbar-title {
font-size: 36rpx;
font-weight: 500;
color: #ffffff;
}
}
background-color: #fff;
box-sizing: border-box;
}
.content {
padding-top: calc(88rpx + var(--status-bar-height, 44rpx));
padding: calc(88rpx + var(--status-bar-height, 44rpx)) 40rpx 40rpx;
padding: 0 66rpx;
}
.avatar-section {
display: flex;
justify-content: center;
margin-bottom: 80rpx;
margin-bottom: 90rpx;
margin-top: 192rpx;
.app-info {
display: flex;
flex-direction: column;
align-items: center;
gap: 22rpx;
.app-logo {
width: 160rpx;
height: 160rpx;
border-radius: 20rpx;
border: 4rpx dashed #cccccc;
margin-bottom: 20rpx;
width: 92rpx;
height: 92rpx;
// border-radius: 20rpx;
// margin-bottom: 23rpx;
}
.app-name {
font-size: 32rpx;
font-weight: 500;
color: #333333;
font-size: 36rpx;
font-weight: bold;
color: $primary-text-color;
}
}
}
.form-section {
background-color: #ffffff;
border-radius: 20rpx;
padding: 40rpx;
margin-bottom: 60rpx;
.app-subname {
font-size: 30rpx;
color: $primary-text-color;
}
}
}
.form-item {
// background: green;
display: flex;
align-items: center;
margin-bottom: 40rpx;
// margin-bottom: 30rpx;
height: 114rpx;
justify-content: space-between;
&:last-child {
margin-bottom: 0;
margin-bottom: 45rpx;
}
.form-label {
width: 120rpx;
// width: 120rpx;
// flex: 1;
font-size: 32rpx;
color: #333333;
font-weight: 500;
color: $primary-text-color;
// font-weight: 500;
}
.form-input {
flex: 3;
height: 80rpx;
padding: 0 20rpx;
// border: 2rpx solid #e0e0e0;
border-radius: 10rpx;
// flex: 1;
// height: 80rpx;
// padding: 0 20rpx;
// border-radius: 10rpx;
font-size: 30rpx;
color: #333333;
&.phone-input {
margin-right: 20rpx;
}
color: $primary-text-color;
border: none;
background-color: transparent;
text-align: right;
}
.avatar-upload {
.avatar-image {
width: 120rpx;
height: 120rpx;
border-radius: 10rpx;
border: 2rpx dashed #cccccc;
}
.form-remark {
margin-left: 16rpx;
color: $secondary-text-color;
}
.phone-input-container {
flex: 1;
.department-container{
display: flex;
align-items: center;
gap: 22rpx;
color: $secondary-text-color;
.department-text.active {
color: $primary-text-color;
}
}
}
//
.avatar-button {
margin: 0;
padding: 0;
background: transparent;
border: none;
width: 100rpx;
height: 100rpx;
border: 2rpx solid #cccccc;
&::after {
border: none;
}
.avatar-image {
width: 100rpx;
height: 100rpx;
// border-radius: 10rpx;
}
}
// background: white;
.get-phone-btn {
// margin-left: auto;
width: 176rpx;
height: 76rpx;
background-color: $primary-color;
color: white;
border-radius: 38rpx;
font-size: 22rpx;
line-height: 76rpx;
border: none;
.get-phone-btn {
flex: 2;
// padding: 0rpx 0rpx;
background-color: #1488DB;
border-radius: 40rpx;
&::after {
border: none;
outline: none;
&::after {
border: none;
}
.btn-text {
font-size: 26rpx;
color: #ffffff;
}
}
&:active {
opacity: 0.8;
}
}
}
// }
.submit-section {
padding: 0 40rpx;
width: 594rpx;
height: 94rpx;
margin: 0 auto;
.submit-btn {
width: 100%;
height: 88rpx;
background-color: #1488DB;
border-radius: 44rpx;
height: 94rpx;
background-color: $primary-color;
border-radius: 41rpx;
display: flex;
align-items: center;
justify-content: center;
.submit-text {
font-size: 32rpx;
font-weight: 500;
// font-weight: 500;
color: #ffffff;
}
}
}
//
.avatar-button {
padding: 0;
margin: 0;
background: transparent;
border: none;
outline: none;
&::after {
border: none;
}
}
</style>

+ 34
- 16
subPages/user/profile.vue View File

@ -12,8 +12,8 @@
<view class="avatar-container" @click="uploadAvatar">
<image
v-if="userInfo.avatar"
:src="userInfo.avatar"
v-if="userInfo.headImage"
:src="userInfo.headImage"
class="avatar-image"
mode="aspectFill"
/>
@ -30,7 +30,7 @@
<view class="form-item">
<text class="form-label">昵称</text>
<uv-input
v-model="userInfo.nickname"
v-model="userInfo.nickName"
placeholder="请输入"
border="none"
class="form-input"
@ -67,8 +67,8 @@ export default {
data() {
return {
userInfo: {
avatar: '',
nickname: '',
headImage: '',
nickName: '',
phone: ''
},
saveButtonStyle: {
@ -93,7 +93,7 @@ export default {
if (result && result.success) {
console.log(result);
this.userInfo.avatar = result.url
this.userInfo.headImage = result.url
}
} catch (error) {
console.error('头像上传失败:', error)
@ -106,8 +106,8 @@ export default {
},
//
saveProfile() {
if (!this.userInfo.nickname.trim()) {
async saveProfile() {
if (!this.userInfo.nickName.trim()) {
uni.showToast({
title: '请输入昵称',
icon: 'none'
@ -134,16 +134,34 @@ export default {
}
// TODO: API
uni.showToast({
title: '保存成功',
icon: 'success'
const res = await this.$api.user.updateUser({
headImage: this.userInfo.headImage,
nickName: this.userInfo.nickName,
phone: this.userInfo.phone
})
//
setTimeout(() => {
uni.navigateBack()
}, 1500)
if (res.code === 200) {
uni.showToast({
title: '保存成功',
icon: 'success'
})
//
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
},
//
async getProfile() {
const res = await this.$api.user.queryUser()
if (res.code === 200) {
this.userInfo = res.result
}
}
},
onLoad() {
this.getProfile()
}
}
</script>


Loading…
Cancel
Save