diff --git a/App.vue b/App.vue index ede0bba..5612133 100644 --- a/App.vue +++ b/App.vue @@ -44,6 +44,23 @@ overflow-y: scroll; } } + + /* 设置所有页面最大宽度为 650px */ + .uni-app { + max-width: 650px; + margin: 0 auto; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); + position: relative; + } + + /* 确保页面内容也受最大宽度限制 */ + uni-page-body { + max-width: 650px; + margin: 0 auto; + background-color: #F5F5F5 !important; + min-height: 100% !important; + height: auto !important; + } /* 顶栏通栏样式 */ /* .uni-top-window { @@ -51,16 +68,19 @@ right: 0; } */ - uni-page-body { - background-color: #F5F5F5 !important; - min-height: 100% !important; - height: auto !important; - } - .uni-top-window uni-tabbar .uni-tabbar { background-color: #fff !important; } + /* 隐藏默认 tabBar(使用自定义 tabBar 时) */ + /* #ifdef H5 */ + .uni-tabbar, + .uni-tabbar__bd, + uni-tabbar { + display: none !important; + } + /* #endif */ + .uni-app--showleftwindow .hideOnPc { display: none !important; } diff --git a/common/tabbar.js b/common/tabbar.js new file mode 100644 index 0000000..e4f93ca --- /dev/null +++ b/common/tabbar.js @@ -0,0 +1,106 @@ +/** + * TabBar 配置管理 + * 根据用户角色动态显示不同的 tabbar + * + * 使用说明: + * 1. 在 tabBarConfig 中配置所有 tabbar 项 + * 2. 通过 roles 数组配置哪些角色可以看到该 tabbar + * - roles: [] 或 roles: undefined 表示所有角色都可以看到 + * - roles: ['user', 'admin'] 表示只有 user 和 admin 角色可以看到 + * 3. 用户角色通过 getUserRole() 函数获取 + * - 默认从 uni.getStorageSync('userInfo').role 获取 + * - 也可以从 uni.getStorageSync('userRole') 或 uni.getStorageSync('role') 获取 + * - 如果都没有,默认返回 'user' + * 4. 修改用户角色后,需要重新调用 getVisibleTabBar() 来刷新 tabbar + */ + +// 所有可用的 tabbar 配置 +export const tabBarConfig = [ + { + pagePath: 'pages/index/index', + iconPath: '/static/image/home1.png', + selectedIconPath: '/static/image/home.png', + text: '首页', + roles: [] // 空数组表示所有角色都可以看到 + }, + { + pagePath: 'pages/index/publish', + iconPath: '/static/image/publish1.png', + selectedIconPath: '/static/image/publish.png', + text: '发布', + roles: ['2'] // 只有 user 和 admin 角色可以看到,可以根据实际需求修改 + }, + { + pagePath: 'pages/index/mine', + iconPath: '/static/image/mine1.png', + selectedIconPath: '/static/image/mine.png', + text: '我的', + roles: [] // 所有角色都可以看到 + } +] + +/** + * 根据用户角色获取可见的 tabbar 列表 + * @param {string} userRole - 用户角色,如 'user', 'admin', 'guest' 等 + * @returns {Array} 过滤后的 tabbar 列表 + */ +export function getVisibleTabBar(userRole = '2') { + return tabBarConfig.filter(item => { + // 如果没有配置 roles 或 roles 为空数组,表示所有角色都可以看到 + if (!item.roles || item.roles.length === 0) { + return true + } + // 检查用户角色是否在允许的角色列表中 + return item.roles.includes(userRole) + }) +} + +/** + * 获取当前用户角色 + * @returns {string} 用户角色 + */ +export function getUserRole() { + try { + // 方式1: 从存储中获取 + const userInfo = uni.getStorageSync('userInfo') + if (userInfo && userInfo.role) { + return userInfo.role + } + + // 方式2: 从其他存储字段获取(根据实际项目调整) + // 可能存储在 userRole、user_role、role 等字段 + const role = uni.getStorageSync('userRole') || uni.getStorageSync('role') + if (role) { + return role + } + + // 方式3: 从 token 或其他地方获取 + // const token = uni.getStorageSync('token') + // 可以解析 token 获取角色信息 + + // 默认返回 'user'(可以根据实际需求调整默认角色) + return '2' + } catch (e) { + return '2' + } +} + +/** + * 获取当前 tabbar 的索引 + * @param {string} currentPath - 当前页面路径 + * @returns {number} tabbar 索引,-1 表示不在 tabbar 中 + */ +export function getCurrentTabIndex(currentPath) { + const userRole = getUserRole() + const visibleTabs = getVisibleTabBar(userRole) + + // 移除路径前缀的 / 进行匹配 + const path = currentPath.startsWith('/') ? currentPath.slice(1) : currentPath + + const index = visibleTabs.findIndex(item => { + const itemPath = item.pagePath.startsWith('/') ? item.pagePath.slice(1) : item.pagePath + return itemPath === path + }) + + return index +} diff --git a/common/tabbarMixin.js b/common/tabbarMixin.js new file mode 100644 index 0000000..7becc49 --- /dev/null +++ b/common/tabbarMixin.js @@ -0,0 +1,49 @@ +/** + * TabBar Mixin + * 用于在页面中自动显示/隐藏自定义 tabbar + */ +import { getVisibleTabBar, getCurrentTabIndex } from '@/common/tabbar.js' + +export default { + data() { + return { + showTabBar: false, + tabBarCurrent: 0 + } + }, + onLoad() { + this.checkShowTabBar() + }, + onShow() { + this.checkShowTabBar() + this.updateTabBarCurrent() + }, + methods: { + checkShowTabBar() { + // 获取当前页面路径 + const pages = getCurrentPages() + const currentPage = pages[pages.length - 1] + const currentPath = currentPage.route || currentPage.$route?.path || '' + + // 获取可见的 tabbar 列表 + const visibleTabs = getVisibleTabBar() + + // 检查当前页面是否在 tabbar 列表中 + const path = currentPath.startsWith('/') ? currentPath.slice(1) : currentPath + const isTabBarPage = visibleTabs.some(item => { + const itemPath = item.pagePath.startsWith('/') ? item.pagePath.slice(1) : item.pagePath + return itemPath === path + }) + + this.showTabBar = isTabBarPage + }, + updateTabBarCurrent() { + if (this.showTabBar) { + const pages = getCurrentPages() + const currentPage = pages[pages.length - 1] + const currentPath = currentPage.route || currentPage.$route?.path || '' + this.tabBarCurrent = getCurrentTabIndex(currentPath) + } + } + } +} diff --git a/components/CarCard/CarCard.vue b/components/CarCard/CarCard.vue index 2665050..74f61fb 100644 --- a/components/CarCard/CarCard.vue +++ b/components/CarCard/CarCard.vue @@ -52,6 +52,9 @@ + + + @@ -89,6 +92,33 @@ pdfchanges() { this.$emit('pdfchange') }, + handleSet(item){ + const that = this + uni.showModal({ + title: '提示', + content: '确认标记已售出?', + success: res => { + if (res.confirm) { + request({ + url:'common/usedcar/update/status', + method:'post', + data: {id:item.id,status:2} + }).then(res => { + uni.showToast({ + title: res.data.message, + icon: 'none' + }); + setTimeout(() => { + that.$emit('restchange') + }, 1000); + + }) + } else if (res.cancel) { + console.log('用户点击取消'); + } + } + }); + }, handleCardTap() { // 直接处理跳转,避免事件传递问题 if (this.item && this.item.id) { diff --git a/components/CustomTabBar/CustomTabBar.vue b/components/CustomTabBar/CustomTabBar.vue new file mode 100644 index 0000000..42737c8 --- /dev/null +++ b/components/CustomTabBar/CustomTabBar.vue @@ -0,0 +1,143 @@ + + + + + diff --git a/custom-tab-bar/index.vue b/custom-tab-bar/index.vue new file mode 100644 index 0000000..2817960 --- /dev/null +++ b/custom-tab-bar/index.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/pages.json b/pages.json index e019642..0f80ba1 100644 --- a/pages.json +++ b/pages.json @@ -94,6 +94,12 @@ "enablePullDownRefresh": true, "navigationStyle":"custom" } + }, + { + "path": "pages/index/ywPublish", + "style": { + "navigationBarTitleText": "" + } } ], "subPackages":[], @@ -115,26 +121,22 @@ } }, "tabBar": { + "custom": true, "color": "#7A7E83", "selectedColor": "#3F435B", "borderStyle": "white", "backgroundColor": "#fff", - "list": [{ + "list": [ + { "pagePath": "pages/index/index", - "iconPath": "/static/image/home1.png", - "selectedIconPath": "static/image/home.png", "text": "首页" }, { "pagePath": "pages/index/publish", - "iconPath": "/static/image/publish1.png", - "selectedIconPath": "static/image/publish.png", "text": "发布" }, { "pagePath": "pages/index/mine", - "iconPath": "/static/image/mine1.png", - "selectedIconPath": "/static/image/mine.png", "text": "我的" } ] diff --git a/pages/index/changinfo.vue b/pages/index/changinfo.vue index fe0bafe..2a2701d 100644 --- a/pages/index/changinfo.vue +++ b/pages/index/changinfo.vue @@ -4,28 +4,41 @@ - + 点击更换头像 + + 英文名 + + + + 手机号 - + 邮箱 - + + + + + + whatsapp + + 二维码 - + 微信 WhatsApp @@ -35,7 +48,7 @@ 上传二维码 - + @@ -71,12 +84,13 @@ that.userinfo = res.data.data; }) }, - uploadfn(imgUrl,scene){ + uploadfn(imgUrl,name,name1){ + const that = this uni.showLoading({ title: '上传中' }); uni.uploadFile({ - url: 'https://userdcar-server.hnxinyouhe.com/upload?scene='+scene, + url: 'https://userdcar-server.hnxinyouhe.com/upload?scene=usedCarimg', filePath: imgUrl, name: 'file', header:{'Fdesk-Auth-Token': uni.getStorageSync('token'),'Fdesk-Login-Type': 'feishu','Fdesk-System-Code':'fdesk'}, @@ -88,44 +102,45 @@ icon:'none' }) if(res && res.data && res.data.file_name){ - that.userinfo[name1] = path.data.image_url - that.userinfo[name] = path.data.path + that.userinfo[name1] = res.data.file_url + that.userinfo[name] = res.data.file_name } }, fail: (err) => reject(err) }); }, + selectewm(e){ + const tempFilePaths = e.tempFilePaths; + const imgUrl=tempFilePaths[0] + this.uploadfn(imgUrl,'social_code','social_code_pull') + }, select(e){ const tempFilePaths = e.tempFilePaths; const imgUrl=tempFilePaths[0] - this.uploadfn(imgUrl,'avatar') + this.uploadfn(imgUrl,'avatar','avatar_url') }, leftClick(){ uni.navigateBack({}) }, bcFn(){ request({ - url:'user/save', + url:'common/user/edit', method:'POST', data:{ avatar:this.userinfo.avatar, - avatar_url:this.userinfo.avatar_url, - sex:this.userinfo.sex, - name:this.userinfo.name, - mobile:this.userinfo.mobile, - position:this.userinfo.position, - country_id:this.userinfo.country_id, - sign:this.userinfo.sign, - socialize:this.userinfo.socialize, - socialize_pic:this.userinfo.socialize_pic, - socialize_pic_url:this.userinfo.socialize_pic_url, + phone:this.userinfo.phone, + email:this.userinfo.email, + whatsapp:this.userinfo.whatsapp, + social:this.userinfo.social, + social_code:this.userinfo.social_code, + name_en:this.userinfo.name_en } }).then(res => { uni.showToast({ title: res.data.message, icon: 'none' }); - if(res.data.code ==0){ + if(res.data.code ==0||res.data.code ==200){ setTimeout(() => { uni.reLaunch({ url: '/pages/index/mine',// 替换为你要跳转的页面路径 diff --git a/pages/index/index - 副本.vue b/pages/index/index - 副本.vue deleted file mode 100644 index 79e490d..0000000 --- a/pages/index/index - 副本.vue +++ /dev/null @@ -1,374 +0,0 @@ - - - - - diff --git a/pages/index/index.vue b/pages/index/index.vue index 7146a03..ea834cd 100644 --- a/pages/index/index.vue +++ b/pages/index/index.vue @@ -17,7 +17,12 @@ @search="onSearch" @clear="onSearch" /> + + {{status_name || '状态'}} + + + @@ -95,18 +101,39 @@ + + + + diff --git a/pages/index/mine.vue b/pages/index/mine.vue index f2154e1..241b4da 100644 --- a/pages/index/mine.vue +++ b/pages/index/mine.vue @@ -1,603 +1,183 @@ diff --git a/pages/index/myPublish.vue b/pages/index/myPublish.vue index b697274..2531d67 100644 --- a/pages/index/myPublish.vue +++ b/pages/index/myPublish.vue @@ -2,6 +2,13 @@ + @@ -15,7 +22,12 @@ @search="onSearch" @clear="onSearch" /> + + {{status_name || '状态'}} + + + - + + 设置价格 + + 贸易条款 + + + + + + 价格 + + + + + + + + + + + + + + 语言 + + + + + + + + 取消 + 确认 + + + + + + + 抓取信息 + + 正在抓取中... + + + + 车辆名称 + {{ crawlData.product_name }} + + + 车源地 + {{ crawlData.source_place }} + + + 车身颜色 + {{ crawlData.body_color }} + + + 上牌时间 + {{ crawlData.plate_time }} + + + 里程 + {{ crawlData.mileage }} + + + 价格 + {{ crawlData.price }} + + + 图片 + + + + + + + 关闭 + 保存 + + @@ -54,6 +167,24 @@ }, data() { return { + urls:'', + show: false, + status_id:1, + status_name:'', + crawlData: null, + crawlLoading: false, + columns: [ + [{ + label: '正常', + id: 1 + }, { + label: '已售出', + id: 2 + }, { + label: '已归档', + id: 3 + }] + ], priceDialogItem: null, priceDialogIndex: -1, priceDialogValue: '', @@ -62,12 +193,6 @@ style: { backgroundColor: '#007aff' } - }, - { - text: '删除', - style: { - backgroundColor: '#F56C6C' - } } ], keyword: '', @@ -75,7 +200,35 @@ pageSize: 10, loading: false, finished: false, - list: [] + list: [], + priceForm: { + trade_term:'', + price: "", // 只存数字字符串 + currency: "USD", // USD | RUB | AED | EUR + lang: "en" // en | ru | es | ar + }, + currencyOptions: [ + { value: "USD", text: "美元" }, + { value: "CNY", text: "人民币" }, + { value: "RUB", text: "卢布" }, + { value: "AED", text: "迪拉姆" }, + { value: "EUR", text: "欧元" } + ], + langOptions: [ + { text: "英语", value: "en" }, + { text: "俄语", value: "ru" }, + { text: "西班牙语", value: "es" }, + { text: "阿拉伯语", value: "ar" } + ], + tradeTermOptions: [ + { value: "FOB", text: "FOB" }, + { value: "CIF", text: "CIF" }, + { value: "EXW", text: "EXW" }, + { value: "DDP", text: "DDP" }, + { value: "CIP", text: "CIP" }, + { value: "FCA", text: "FCA" }, + { value: "CFR", text: "CFR" } + ] } }, onLoad() { @@ -187,6 +340,12 @@ this.priceDialogItem = payload && payload.item ? payload.item : null this.priceDialogIndex = payload && typeof payload.index === 'number' ? payload.index : -1 this.priceDialogValue = '' + this.priceForm = { + trade_term:'', + price:"", + currency: "USD", + lang: "en" + } this.$nextTick(() => { this.$refs.inputDialog && this.$refs.inputDialog.open() }) @@ -217,29 +376,77 @@ } }) }, + closePriceDialog() { + this.$refs.inputDialog.close() + }, + + // 只允许数字 + 可选1个小数点(你要“纯整数”也行,我后面给你一行改法) + onPriceInput(e) { + let v = (e.detail && e.detail.value) ? String(e.detail.value) : String(this.priceForm.price || "") + + // 过滤掉非数字和点 + v = v.replace(/[^\d.]/g, "") + + // 只保留第一个小数点 + const parts = v.split(".") + if (parts.length > 2) { + v = parts[0] + "." + parts.slice(1).join("") + } + + // 去掉开头多余 0(保留 0.xxx) + v = v.replace(/^0+(\d)/, "$1") + if (v.startsWith(".")) v = "0" + v + + this.priceForm.price = v + }, + + onCurrencyPickerChange(e) { + const idx = Number(e.detail.value || 0) + this.currencyIndex = idx + this.priceForm.currency = this.currencyOptions[idx]?.value || "USD" + }, + + onLangChange(e) { + this.priceForm.lang = e.detail.value + }, + + confirmPriceDialog() { + if (!this.priceForm.trade_term) { + return uni.showToast({ title: "请选择贸易条款", icon: "none" }) + } + // 校验 + if (!this.priceForm.price) { + return uni.showToast({ title: "请输入价格", icon: "none" }) + } + + const payload = { + trade_term:this.priceForm.trade_term, + price: this.priceForm.price, // 数字字符串 + currency: this.priceForm.currency, // USD/RUB/AED/EUR + lang: this.priceForm.lang // en/ru/es/ar + } + console.log(payload) + this.closePriceDialog() + this.dialogInputConfirm(payload) + }, dialogInputConfirm(val) { const item = this.priceDialogItem const index = this.priceDialogIndex - const price = String(val || '').trim() if (!item) return - if (!price) { + if (!val.price) { uni.showToast({ title: '请输入价格', icon: 'none' }) return } - if (isNaN(price) || Number(price) <= 0) { - uni.showToast({ title: '请输入有效的价格', icon: 'none' }) - return - } - request({ url: 'common/usedcar/produce/pdf', method: 'get', - data: { id: item.id, price } + data: { id: item.id, price:val.price,currency:val.currency,lang:val.lang,trade_term:val.trade_term } }).then(res => { if (res.data.code == 200) { // 通知刷新列表(你原来就是靠这个事件刷新) this.onSearch() + this.downloadpdf(item) } else { uni.showToast({ title: res.data.msg || '生成失败', icon: 'none' }) @@ -257,15 +464,15 @@ }, 300) }, onSearch() { - this.queryList(this.page,this.pageSize) + this.$refs.paging && this.$refs.paging.reload() }, queryList(pageNo, pageSize, from) { this.loading = true; var that = this; const params = { current:pageNo, - status:1, - size:10 + status:this.status_id, + size:pageSize }; params.keyword = this.keyword; params.scene = 'self'; @@ -283,49 +490,167 @@ // }) }, + onstatus(e){ + if (e.value && e.value[0]) { + const selectedItem = typeof e.value[0] === 'object' ? e.value[0] : { value: e.value[0], label: e.value[0].toString() }; + this.status_name = selectedItem.label; + this.status_id = selectedItem.id; + this.show = false; + this.$refs.paging && this.$refs.paging.reload() + } + }, swipeClick(e,item){ let { content } = e; - if (content.text === '删除') { - uni.showModal({ - title: '提示', - content: '是否删除', - success: res => { - if (res.confirm) { - request({ - url:'common/usedcar/destroy', - method:'post', - data: {id:item.id} - }).then(res => { - uni.showToast({ - title: res.data.message, - icon: 'none' - }); - this.queryList() - }) - } else if (res.cancel) { - console.log('用户点击取消'); - } - } - }); - } else { - const id = item.id - uni.navigateTo({ - url: `/pages/index/publishedit?id=${id}` - }) + const id = item.id + uni.navigateTo({ + url: `/pages/index/publishedit?id=${id}` + }) + // if (content.text === '删除') { + // uni.showModal({ + // title: '提示', + // content: '是否删除', + // success: res => { + // if (res.confirm) { + // request({ + // url:'common/usedcar/destroy', + // method:'post', + // data: {id:item.id} + // }).then(res => { + // uni.showToast({ + // title: res.data.message, + // icon: 'none' + // }); + // this.queryList() + // }) + // } else if (res.cancel) { + // console.log('用户点击取消'); + // } + // } + // }); + // } else { + // const id = item.id + // uni.navigateTo({ + // url: `/pages/index/publishedit?id=${id}` + // }) + // } + }, + onBindByName(){ + if (!this.urls || !this.urls.trim()) { + uni.showToast({ title: '请输入抓取链接', icon: 'none' }) + return } + + this.crawlLoading = true + this.crawlData = null + this.$nextTick(() => { + this.$refs.crawlDialog && this.$refs.crawlDialog.open() + }) + + request({ + url:'common/usedcar/url_crawl', + method:'get', + data: {url:this.urls} + }).then(res => { + this.crawlLoading = false + if (res.data.code == 200) { + this.crawlData = res.data.data || res.data + uni.showToast({ title: '抓取成功', icon: 'success' }) + } else { + uni.showToast({ title: res.data.msg || '抓取失败', icon: 'none' }) + this.crawlData = null + } + }).catch(err => { + this.crawlLoading = false + this.crawlData = null + console.error('抓取失败', err) + uni.showToast({ title: '抓取失败', icon: 'none' }) + }) + }, + closeCrawlDialog() { + this.$refs.crawlDialog && this.$refs.crawlDialog.close() + this.crawlData = null + this.crawlLoading = false + }, + saveCrawlDialog(){ + function formatCnYearMonth(str) { + const match = str.match(/(\d{4})年(\d{1,2})月/) + if (!match) return str + + const year = match[1] + const month = match[2].padStart(2, '0') + + return `${year}-${month}-01` + } + const payload = { + ...this.crawlData, + plate_time:formatCnYearMonth(this.crawlData.plate_time), + // images 数组 => "url1,url2,url3" + images: (this.crawlData.images || []) + .map(s => String(s || '').trim()) + .filter(Boolean) + .join(',') + } + request({ + url:'common/usedcar/store', + method:'post', + data:payload + }).then(res => { + this.$refs.crawlDialog && this.$refs.crawlDialog.close() + this.crawlData = null + this.crawlLoading = false + uni.showToast({ + title: res.data.message, + icon: 'none' + }); + setTimeout(() => { + this.$refs.paging && this.$refs.paging.reload() + }, 1000); + + }) } } } + diff --git a/pages/index/publish.vue b/pages/index/publish.vue index 42412f2..fb0a3cc 100644 --- a/pages/index/publish.vue +++ b/pages/index/publish.vue @@ -176,11 +176,19 @@ + + + + + + \ No newline at end of file diff --git a/pages/login/feishu-callback.vue b/pages/login/feishu-callback.vue index ff00e5a..717de38 100644 --- a/pages/login/feishu-callback.vue +++ b/pages/login/feishu-callback.vue @@ -102,6 +102,16 @@ uni.setStorageSync('token', token) + // ===== 角色/权限:用于动态 TabBar ===== + // 兼容多种后端返回结构:role / user.role / userInfo.role + const role = res?.data?.data?.role + if (role) { + // 1) 给 tabbar.js 直接读取 + uni.setStorageSync('userRole', role) + // 通知自定义 TabBar 立刻刷新 + uni.$emit && uni.$emit('userRoleChanged') + } + this.title = '登录成功' this.desc = '正在进入页面' diff --git a/static/image/mine/erwm.png b/static/image/mine/erwm.png new file mode 100644 index 0000000..dff4b0e Binary files /dev/null and b/static/image/mine/erwm.png differ diff --git a/static/image/user.png b/static/image/user.png new file mode 100644 index 0000000..479aa95 Binary files /dev/null and b/static/image/user.png differ