小程序提交
This commit is contained in:
801
pages/channel/add.vue
Normal file
801
pages/channel/add.vue
Normal file
@@ -0,0 +1,801 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<!-- 验证中提示 -->
|
||||
<view v-if="validating" class="validating-container">
|
||||
<view class="validating-content">
|
||||
<uni-icons type="spinner-cycle" size="40" color="#6366f1"></uni-icons>
|
||||
<text class="validating-text">正在验证...</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 错误提示页面 -->
|
||||
<view v-if="!validating && !tokenValid && errorMessage" class="error-container">
|
||||
<view class="error-content">
|
||||
<view class="error-icon-wrapper">
|
||||
<uni-icons type="closeempty" size="80" color="#ef4444"></uni-icons>
|
||||
</view>
|
||||
<text class="error-title">验证失败</text>
|
||||
<text class="error-message">{{ errorMessage }}</text>
|
||||
<button class="error-button" @click="goBack">返回</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 创建方式选择 -->
|
||||
<view class="method-selector" v-if="tokenValid && !validating && !errorMessage">
|
||||
<view class="selector-item" :class="{'active': createMethod === 'manual'}" @click="createMethod = 'manual'">
|
||||
<view class="selector-dot" v-if="createMethod === 'manual'"></view>
|
||||
<text class="selector-text">手动创建</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 表单内容 -->
|
||||
<view class="form-container" v-if="tokenValid && !validating && !errorMessage">
|
||||
<view class="form-header">
|
||||
<text class="form-title">填写渠道信息</text>
|
||||
<text class="form-subtitle">请完善下方信息以手动添加新的分销渠道。</text>
|
||||
</view>
|
||||
|
||||
<view class="form-body">
|
||||
<!-- 渠道名称 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">
|
||||
<uni-icons type="shop" size="18" color="#6366f1"></uni-icons>
|
||||
<text class="label-text">渠道名称</text>
|
||||
<text class="required-mark">必填</text>
|
||||
</view>
|
||||
<view class="input-wrapper">
|
||||
<input
|
||||
class="form-input"
|
||||
v-model="form.channelName"
|
||||
placeholder="请输入渠道名称"
|
||||
maxlength="50"
|
||||
:focus="focusChannelName"
|
||||
@blur="focusChannelName = false"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 联系电话 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">
|
||||
<uni-icons type="phone" size="18" color="#6366f1"></uni-icons>
|
||||
<text class="label-text">手机号</text>
|
||||
<text class="required-mark">必填</text>
|
||||
</view>
|
||||
<view class="input-wrapper">
|
||||
<input
|
||||
class="form-input"
|
||||
v-model="form.contactPhone"
|
||||
placeholder="请输入手机号"
|
||||
type="number"
|
||||
maxlength="11"
|
||||
:focus="focusContactPhone"
|
||||
@blur="focusContactPhone = false"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 微信号 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">
|
||||
<uni-icons type="chat" size="18" color="#6366f1"></uni-icons>
|
||||
<text class="label-text">微信号</text>
|
||||
</view>
|
||||
<view class="input-wrapper">
|
||||
<input
|
||||
class="form-input"
|
||||
v-model="form.wechatId"
|
||||
placeholder="请输入微信号"
|
||||
maxlength="50"
|
||||
:focus="focusWechatId"
|
||||
@blur="focusWechatId = false"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 备注 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">
|
||||
<uni-icons type="info" size="18" color="#6366f1"></uni-icons>
|
||||
<text class="label-text">备注</text>
|
||||
</view>
|
||||
<view class="input-wrapper">
|
||||
<textarea
|
||||
class="form-textarea"
|
||||
v-model="form.remark"
|
||||
placeholder="请输入备注信息 (选填)"
|
||||
maxlength="200"
|
||||
:auto-height="true"
|
||||
:focus="focusRemark"
|
||||
@blur="focusRemark = false"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="button-container" v-if="tokenValid && !validating && !errorMessage">
|
||||
<button class="cancel-button" @click="cancel">取消</button>
|
||||
<button class="submit-button" :class="{'loading': isSubmitting}" @click="submit" :disabled="isSubmitting || !tokenValid">
|
||||
<uni-icons v-if="!isSubmitting" type="checkmarkempty" size="20" color="#fff"></uni-icons>
|
||||
<text class="button-text">{{ isSubmitting ? '提交中...' : '创建' }}</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from '@/config';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
token: '',
|
||||
createMethod: 'manual',
|
||||
form: {
|
||||
channelName: '',
|
||||
contactPhone: '',
|
||||
wechatId: '',
|
||||
remark: ''
|
||||
},
|
||||
focusChannelName: false,
|
||||
focusContactPhone: false,
|
||||
focusWechatId: false,
|
||||
focusRemark: false,
|
||||
isSubmitting: false,
|
||||
tokenValid: false,
|
||||
validating: true,
|
||||
errorMessage: ''
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
// 获取参数
|
||||
let scene = options.scene;
|
||||
let token = options.token;
|
||||
|
||||
console.log('页面参数:', { scene, token });
|
||||
|
||||
// 小程序端解析 scene 参数
|
||||
// #ifdef MP-WEIXIN
|
||||
if (scene != undefined) {
|
||||
// 先对 scene 进行 URL 解码(处理 %2C 等编码字符)
|
||||
try {
|
||||
scene = decodeURIComponent(scene);
|
||||
} catch (e) {
|
||||
console.warn('scene 解码失败,使用原始值:', e);
|
||||
}
|
||||
|
||||
// scene 中可能包含 token,或者直接就是 token
|
||||
// 如果 scene 包含逗号,可能需要分割(根据实际业务需求调整)
|
||||
if (scene.includes(',')) {
|
||||
// 如果 scene 包含逗号,取第一部分作为 token
|
||||
const parts = scene.split(',');
|
||||
this.token = parts[0] || '';
|
||||
} else {
|
||||
// 如果不包含逗号,scene 本身就是 token
|
||||
this.token = scene;
|
||||
}
|
||||
} else if (token != undefined) {
|
||||
// 兼容直接传 token 参数
|
||||
this.token = token;
|
||||
} else {
|
||||
// 没有参数,显示错误
|
||||
this.token = '';
|
||||
}
|
||||
// #endif
|
||||
|
||||
// H5 端兼容处理
|
||||
// #ifdef H5
|
||||
if (token != undefined) {
|
||||
// H5 端优先使用 token 参数
|
||||
this.token = token;
|
||||
} else if (scene != undefined) {
|
||||
// H5 也可能有 scene 参数(从分享链接等)
|
||||
// 先对 scene 进行 URL 解码
|
||||
try {
|
||||
scene = decodeURIComponent(scene);
|
||||
} catch (e) {
|
||||
console.warn('scene 解码失败,使用原始值:', e);
|
||||
}
|
||||
|
||||
if (scene.includes(',')) {
|
||||
const parts = scene.split(',');
|
||||
this.token = parts[0] || '';
|
||||
} else {
|
||||
this.token = scene;
|
||||
}
|
||||
} else {
|
||||
// 没有参数,显示错误
|
||||
this.token = '';
|
||||
}
|
||||
// #endif
|
||||
|
||||
console.log('解析结果:', { token: this.token });
|
||||
|
||||
if (!this.token) {
|
||||
this.validating = false;
|
||||
this.tokenValid = false;
|
||||
this.errorMessage = '缺少token参数,请重新扫码';
|
||||
return;
|
||||
}
|
||||
|
||||
uni.setNavigationBarTitle({
|
||||
title: '新增渠道'
|
||||
});
|
||||
|
||||
// 验证token有效性
|
||||
this.validateToken();
|
||||
|
||||
// H5和小程序兼容处理
|
||||
// #ifdef H5
|
||||
// H5端回车键提交
|
||||
document.onkeydown = (event) => {
|
||||
const e = event || window.event;
|
||||
if (e && e.keyCode === 13) {
|
||||
this.submit();
|
||||
}
|
||||
};
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
// 验证token有效性
|
||||
validateToken() {
|
||||
this.validating = true;
|
||||
uni.showLoading({
|
||||
title: '验证中...',
|
||||
mask: true
|
||||
});
|
||||
|
||||
uni.request({
|
||||
method: 'GET',
|
||||
url: config.apiUrl2 + '/v1/frontend/distribution/channel/register',
|
||||
data: {
|
||||
token: this.token
|
||||
},
|
||||
header: {
|
||||
'content-type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
success: (response) => {
|
||||
uni.hideLoading();
|
||||
this.validating = false;
|
||||
|
||||
if (response.data.code === 10000 || response.data.code === 200) {
|
||||
this.tokenValid = true;
|
||||
this.errorMessage = '';
|
||||
// token验证成功,可以填写表单
|
||||
} else {
|
||||
this.tokenValid = false;
|
||||
this.errorMessage = response.data.message || 'token无效或已过期,请重新扫码';
|
||||
}
|
||||
},
|
||||
fail: (error) => {
|
||||
uni.hideLoading();
|
||||
this.validating = false;
|
||||
this.tokenValid = false;
|
||||
console.error('验证token失败:', error);
|
||||
this.errorMessage = '网络错误,请检查网络连接后重试';
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 表单验证
|
||||
validateForm() {
|
||||
if (!this.form.channelName || !this.form.channelName.trim()) {
|
||||
this.focusChannelName = true;
|
||||
uni.showToast({
|
||||
title: '请输入渠道名称',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// 手机号必填验证
|
||||
if (!this.form.contactPhone || !this.form.contactPhone.trim()) {
|
||||
this.focusContactPhone = true;
|
||||
uni.showToast({
|
||||
title: '请输入手机号',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证手机号格式
|
||||
const phoneReg = /^1[3-9]\d{9}$/;
|
||||
if (!phoneReg.test(this.form.contactPhone.trim())) {
|
||||
this.focusContactPhone = true;
|
||||
uni.showToast({
|
||||
title: '请输入正确的手机号',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
// 提交表单
|
||||
submit() {
|
||||
if (this.isSubmitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.tokenValid) {
|
||||
uni.showToast({
|
||||
title: 'token无效,请重新扫码',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.isSubmitting = true;
|
||||
|
||||
const data = {
|
||||
token: this.token,
|
||||
name: this.form.channelName.trim(),
|
||||
phone: this.form.contactPhone.trim(),
|
||||
wechatId: this.form.wechatId ? this.form.wechatId.trim() : '',
|
||||
remarks: this.form.remark ? this.form.remark.trim() : ''
|
||||
};
|
||||
|
||||
uni.request({
|
||||
method: 'POST',
|
||||
url: config.apiUrl2 + '/v1/frontend/distribution/channel/register',
|
||||
data: data,
|
||||
header: {
|
||||
'content-type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
success: (response) => {
|
||||
this.isSubmitting = false;
|
||||
|
||||
if (response.data.code === 10000 || response.data.code === 200) {
|
||||
// 获取返回的companyId
|
||||
const responseData = response.data.data || {};
|
||||
const companyId = responseData.companyId || '';
|
||||
|
||||
console.log('渠道创建成功,返回数据:', responseData);
|
||||
console.log('获取到的companyId:', companyId);
|
||||
|
||||
uni.showToast({
|
||||
title: '提交成功',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
// 如果有companyId,跳转到渠道登录页
|
||||
if (companyId) {
|
||||
uni.redirectTo({
|
||||
url: `/pages/channel/login?companyId=${companyId}`
|
||||
});
|
||||
} else {
|
||||
// 如果没有companyId,返回上一页
|
||||
uni.navigateBack();
|
||||
}
|
||||
}, 1500);
|
||||
} else {
|
||||
// 显示后端返回的错误信息
|
||||
const errorMsg = response.data.message || response.data.msg || response.data.error || '提交失败,请重试';
|
||||
uni.showToast({
|
||||
title: errorMsg,
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
},
|
||||
fail: (error) => {
|
||||
this.isSubmitting = false;
|
||||
console.error('提交失败:', error);
|
||||
uni.showToast({
|
||||
title: '网络错误,请重试',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 取消
|
||||
cancel() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
// 返回
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 160rpx;
|
||||
}
|
||||
|
||||
/* 创建方式选择器 */
|
||||
.method-selector {
|
||||
padding: 30rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.selector-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
background-color: #f9fafb;
|
||||
border-radius: 12rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.selector-item.active {
|
||||
background-color: #f0f4ff;
|
||||
}
|
||||
|
||||
.selector-dot {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
background-color: #6366f1;
|
||||
border-radius: 50%;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.selector-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 表单容器 */
|
||||
.form-container {
|
||||
background-color: #fff;
|
||||
margin: 20rpx;
|
||||
border-radius: 16rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.form-header {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.form-title {
|
||||
display: block;
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.form-subtitle {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.label-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.required-mark {
|
||||
font-size: 24rpx;
|
||||
color: #ef4444;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
padding: 0 24rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
background-color: #f9fafb;
|
||||
border: 2rpx solid #e5e7eb;
|
||||
border-radius: 12rpx;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
/* #ifdef H5 */
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #6366f1;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 0 4rpx rgba(99, 102, 241, 0.1);
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef MP-WEIXIN */
|
||||
&:focus {
|
||||
border-color: #6366f1;
|
||||
background-color: #fff;
|
||||
}
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.form-input::placeholder {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
width: 100%;
|
||||
min-height: 160rpx;
|
||||
padding: 24rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
background-color: #f9fafb;
|
||||
border: 2rpx solid #e5e7eb;
|
||||
border-radius: 12rpx;
|
||||
box-sizing: border-box;
|
||||
line-height: 1.6;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
/* #ifdef H5 */
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #6366f1;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 0 4rpx rgba(99, 102, 241, 0.1);
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef MP-WEIXIN */
|
||||
&:focus {
|
||||
border-color: #6366f1;
|
||||
background-color: #fff;
|
||||
}
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.form-textarea::placeholder {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 底部按钮容器 */
|
||||
.button-container {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 20rpx;
|
||||
background-color: #fff;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
/* #ifdef H5 */
|
||||
box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
/* #endif */
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
background-color: #f5f5f5;
|
||||
border: none;
|
||||
border-radius: 12rpx;
|
||||
color: #666;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: #e5e5e5;
|
||||
}
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
flex: 2;
|
||||
height: 88rpx;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
border: none;
|
||||
border-radius: 12rpx;
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8rpx 24rpx rgba(99, 102, 241, 0.3);
|
||||
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
transform: translateY(-2rpx);
|
||||
box-shadow: 0 12rpx 32rpx rgba(99, 102, 241, 0.4);
|
||||
}
|
||||
|
||||
&:active:not(:disabled) {
|
||||
transform: translateY(0);
|
||||
}
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.submit-button.loading {
|
||||
opacity: 0.7;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.submit-button:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.button-text {
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
/* H5适配 */
|
||||
/* #ifdef H5 */
|
||||
.page-container {
|
||||
padding-bottom: 180rpx;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
padding: 30rpx 40rpx;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* 小程序适配 */
|
||||
/* #ifdef MP-WEIXIN */
|
||||
.button-container {
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* 验证中容器 */
|
||||
.validating-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.validating-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.validating-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 错误容器 */
|
||||
.error-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.error-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
max-width: 600rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.error-icon-wrapper {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #fef2f2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 40rpx;
|
||||
border: 4rpx solid #fee2e2;
|
||||
}
|
||||
|
||||
.error-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
font-size: 28rpx;
|
||||
color: #6b7280;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 60rpx;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.error-button {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
border: none;
|
||||
border-radius: 12rpx;
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8rpx 24rpx rgba(99, 102, 241, 0.3);
|
||||
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2rpx);
|
||||
box-shadow: 0 12rpx 32rpx rgba(99, 102, 241, 0.4);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
/* 响应式适配 */
|
||||
@media (max-width: 375px) {
|
||||
.form-container {
|
||||
padding: 30rpx 20rpx;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
319
pages/channel/list.vue
Normal file
319
pages/channel/list.vue
Normal file
@@ -0,0 +1,319 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<!-- 列表内容 -->
|
||||
<view class="list-container">
|
||||
<u-list @scrolltolower="scrolltolower">
|
||||
<u-list-item v-for="(item, index) in channelList" :key="index" class="list-item">
|
||||
<view class="item-content" @click="editChannel(item)">
|
||||
<view class="item-header">
|
||||
<view class="item-title">{{ item.channelName || '未命名渠道' }}</view>
|
||||
<view class="item-status" :class="{'status-active': item.status === 1}">
|
||||
{{ item.status === 1 ? '启用' : '禁用' }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="item-info">
|
||||
<view class="info-row" v-if="item.contactPhone">
|
||||
<uni-icons type="phone" size="16" color="#999"></uni-icons>
|
||||
<text class="info-text">{{ item.contactPhone }}</text>
|
||||
</view>
|
||||
<view class="info-row" v-if="item.wechatId">
|
||||
<uni-icons type="chat" size="16" color="#999"></uni-icons>
|
||||
<text class="info-text">{{ item.wechatId }}</text>
|
||||
</view>
|
||||
<view class="info-row" v-if="item.remark">
|
||||
<uni-icons type="info" size="16" color="#999"></uni-icons>
|
||||
<text class="info-text">{{ item.remark }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="item-footer">
|
||||
<text class="item-time">{{ formatTime(item.createTime) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</u-list-item>
|
||||
<u-loadmore :status="loadStatus" />
|
||||
</u-list>
|
||||
</view>
|
||||
|
||||
<!-- 底部添加按钮 -->
|
||||
<view class="fab-container">
|
||||
<button class="fab-button" @click="addChannel">
|
||||
<uni-icons type="plus" size="24" color="#fff"></uni-icons>
|
||||
<text class="fab-text">新增渠道</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
request
|
||||
} from '@/utils/request';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
channelList: [],
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
loadStatus: 'loadmore', // loadmore | loading | nomore
|
||||
total: 0
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
uni.setNavigationBarTitle({
|
||||
title: '渠道管理'
|
||||
});
|
||||
this.loadChannelList();
|
||||
},
|
||||
onShow() {
|
||||
// 从新增/编辑页返回时刷新列表
|
||||
if (this.page === 1) {
|
||||
this.loadChannelList();
|
||||
}
|
||||
},
|
||||
onPullDownRefresh() {
|
||||
// #ifdef MP-WEIXIN
|
||||
this.page = 1;
|
||||
this.channelList = [];
|
||||
this.loadChannelList();
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
// 加载渠道列表
|
||||
loadChannelList() {
|
||||
this.loadStatus = 'loading';
|
||||
request({
|
||||
url: '/v1/frontend/business/channel/list',
|
||||
data: {
|
||||
page: this.page,
|
||||
limit: this.pageSize
|
||||
}
|
||||
}).then((response) => {
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.stopPullDownRefresh();
|
||||
// #endif
|
||||
|
||||
if (response.data.code === 10000 || response.data.code === 200) {
|
||||
const list = response.data.data.list || response.data.data.records || [];
|
||||
if (this.page === 1) {
|
||||
this.channelList = list;
|
||||
} else {
|
||||
this.channelList = [...this.channelList, ...list];
|
||||
}
|
||||
this.total = response.data.data.total || 0;
|
||||
|
||||
if (this.channelList.length >= this.total) {
|
||||
this.loadStatus = 'nomore';
|
||||
} else {
|
||||
this.loadStatus = 'loadmore';
|
||||
}
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: response.data.message || '加载失败',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
this.loadStatus = 'loadmore';
|
||||
}
|
||||
}).catch((error) => {
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.stopPullDownRefresh();
|
||||
// #endif
|
||||
|
||||
console.error('加载渠道列表失败:', error);
|
||||
uni.showToast({
|
||||
title: '网络错误,请重试',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
this.loadStatus = 'loadmore';
|
||||
});
|
||||
},
|
||||
|
||||
// 滚动到底部加载更多
|
||||
scrolltolower() {
|
||||
if (this.loadStatus === 'loadmore') {
|
||||
this.page++;
|
||||
this.loadChannelList();
|
||||
}
|
||||
},
|
||||
|
||||
// 新增渠道
|
||||
addChannel() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/channel/add'
|
||||
});
|
||||
},
|
||||
|
||||
// 编辑渠道
|
||||
editChannel(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/channel/add?id=${item.id || item.lId}`
|
||||
});
|
||||
},
|
||||
|
||||
// 格式化时间
|
||||
formatTime(time) {
|
||||
if (!time) return '';
|
||||
const date = new Date(time);
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 100rpx;
|
||||
}
|
||||
|
||||
.list-container {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
margin-bottom: 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.item-content {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.item-status {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
padding: 8rpx 16rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
color: #22c55e;
|
||||
background-color: #f0fdf4;
|
||||
}
|
||||
|
||||
.item-info {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.info-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-left: 12rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.item-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: 20rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.item-time {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 底部悬浮按钮 */
|
||||
.fab-container {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 20rpx;
|
||||
background-color: #fff;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
/* #ifdef H5 */
|
||||
box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
/* #endif */
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.fab-button {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
border: none;
|
||||
border-radius: 12rpx;
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8rpx 24rpx rgba(99, 102, 241, 0.3);
|
||||
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2rpx);
|
||||
box-shadow: 0 12rpx 32rpx rgba(99, 102, 241, 0.4);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.fab-text {
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
/* H5适配 */
|
||||
/* #ifdef H5 */
|
||||
.page-container {
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.fab-container {
|
||||
padding: 30rpx 40rpx;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* 小程序适配 */
|
||||
/* #ifdef MP-WEIXIN */
|
||||
.fab-container {
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
/* #endif */
|
||||
</style>
|
||||
|
||||
336
pages/channel/login.vue
Normal file
336
pages/channel/login.vue
Normal file
@@ -0,0 +1,336 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="hero">
|
||||
<view class="hero-title">渠道登录</view>
|
||||
<view class="hero-subtitle">登录后进入数据面板</view>
|
||||
</view>
|
||||
|
||||
<!-- 账号密码登录(H5 + 小程序通用) -->
|
||||
<view class="card">
|
||||
<view class="card-title">账号密码登录</view>
|
||||
<uni-forms ref="h5Form" :modelValue="form">
|
||||
<uni-forms-item name="phone">
|
||||
<uni-easyinput
|
||||
v-model="form.phone"
|
||||
placeholder="请输入手机号"
|
||||
type="number"
|
||||
maxlength="11"
|
||||
:inputBorder="false"
|
||||
class="input"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item name="password">
|
||||
<uni-easyinput
|
||||
v-model="form.password"
|
||||
placeholder="请输入密码"
|
||||
:inputBorder="false"
|
||||
type="password"
|
||||
class="input"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
<view class="login-tip">
|
||||
<text>默认初始密码为 </text>
|
||||
<text class="login-tip-strong">123456</text>
|
||||
<text>,登录后请及时修改。</text>
|
||||
</view>
|
||||
<button class="primary-btn" :disabled="isSubmitting" @click="handleH5Login">
|
||||
<text>{{ isSubmitting ? '登录中...' : '登录' }}</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from '@/config'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
phone: '',
|
||||
password: ''
|
||||
},
|
||||
isSubmitting: false,
|
||||
companyId: ''
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
// 获取参数
|
||||
let scene = options.scene;
|
||||
let companyId = options.companyId;
|
||||
|
||||
console.log('页面参数:', { scene, companyId });
|
||||
|
||||
// 小程序端解析 scene 参数
|
||||
// #ifdef MP-WEIXIN
|
||||
if (scene != undefined) {
|
||||
// 先对 scene 进行 URL 解码(处理 %2C 等编码字符)
|
||||
try {
|
||||
scene = decodeURIComponent(scene);
|
||||
} catch (e) {
|
||||
console.warn('scene 解码失败,使用原始值:', e);
|
||||
}
|
||||
|
||||
// scene 中可能包含 companyId,或者直接就是 companyId
|
||||
// 如果 scene 包含逗号,可能需要分割(根据实际业务需求调整)
|
||||
if (scene.includes(',')) {
|
||||
// 如果 scene 包含逗号,取第一部分作为 companyId
|
||||
const parts = scene.split(',');
|
||||
this.companyId = parts[0] || '';
|
||||
} else {
|
||||
// 如果不包含逗号,scene 本身就是 companyId
|
||||
this.companyId = scene;
|
||||
}
|
||||
} else if (companyId != undefined) {
|
||||
// 兼容直接传 companyId 参数
|
||||
this.companyId = companyId;
|
||||
} else {
|
||||
// 尝试从本地存储获取(之前可能保存过)
|
||||
const storedCompanyId = uni.getStorageSync('channelCompanyId');
|
||||
if (storedCompanyId) {
|
||||
this.companyId = storedCompanyId;
|
||||
console.log('从本地存储获取companyId:', storedCompanyId);
|
||||
} else {
|
||||
this.companyId = '';
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// H5 端兼容处理
|
||||
// #ifdef H5
|
||||
if (companyId != undefined) {
|
||||
// H5 端优先使用 companyId 参数
|
||||
this.companyId = companyId;
|
||||
} else if (scene != undefined) {
|
||||
// H5 也可能有 scene 参数(从分享链接等)
|
||||
// 先对 scene 进行 URL 解码
|
||||
try {
|
||||
scene = decodeURIComponent(scene);
|
||||
} catch (e) {
|
||||
console.warn('scene 解码失败,使用原始值:', e);
|
||||
}
|
||||
|
||||
if (scene.includes(',')) {
|
||||
const parts = scene.split(',');
|
||||
this.companyId = parts[0] || '';
|
||||
} else {
|
||||
this.companyId = scene;
|
||||
}
|
||||
} else {
|
||||
// 尝试从本地存储获取
|
||||
const storedCompanyId = uni.getStorageSync('channelCompanyId');
|
||||
if (storedCompanyId) {
|
||||
this.companyId = storedCompanyId;
|
||||
console.log('从本地存储获取companyId:', storedCompanyId);
|
||||
} else {
|
||||
this.companyId = '';
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
console.log('解析结果:', { companyId: this.companyId });
|
||||
},
|
||||
methods: {
|
||||
// 账号密码登录(H5 + 小程序)
|
||||
handleH5Login() {
|
||||
if (this.isSubmitting) return
|
||||
if (!this.form.phone || !this.form.password) {
|
||||
uni.showToast({ title: '请输入手机号和密码', icon: 'none', duration: 2000 })
|
||||
return
|
||||
}
|
||||
if (!this.companyId) {
|
||||
uni.showToast({ title: '缺少companyId参数', icon: 'none', duration: 2000 })
|
||||
return
|
||||
}
|
||||
this.isSubmitting = true
|
||||
|
||||
// 使用和列表接口一样的请求方式和参数格式
|
||||
uni.request({
|
||||
method: 'POST',
|
||||
url: config.apiUrl2 + '/v1/frontend/distribution/user/login',
|
||||
data: {
|
||||
companyId: this.companyId,
|
||||
phone: this.form.phone,
|
||||
password: this.form.password
|
||||
},
|
||||
header: {
|
||||
'content-type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
success: (res) => {
|
||||
this.isSubmitting = false
|
||||
console.log('登录接口完整返回:', res)
|
||||
console.log('登录接口返回数据:', res.data)
|
||||
|
||||
if (res.data.code === 10000 || res.data.code === 200) {
|
||||
const data = res.data.data || {}
|
||||
const token = data.token || ''
|
||||
const channelInfo = data.channelInfo || {}
|
||||
|
||||
console.log('解析后的data:', data)
|
||||
console.log('解析后的token:', token)
|
||||
console.log('解析后的channelInfo:', channelInfo)
|
||||
|
||||
// 保存token
|
||||
if (token) {
|
||||
uni.setStorageSync('channelToken', token)
|
||||
console.log('已保存token到本地存储')
|
||||
} else {
|
||||
console.error('token为空,无法保存')
|
||||
}
|
||||
|
||||
// 保存完整的登录数据(备用)
|
||||
uni.setStorageSync('channelLoginData', JSON.stringify(data))
|
||||
console.log('已保存完整登录数据到本地存储')
|
||||
|
||||
// 保存渠道信息 - 从channelInfo中获取
|
||||
if (channelInfo.channelCode) {
|
||||
uni.setStorageSync('channelCode', channelInfo.channelCode)
|
||||
console.log('已保存channelCode:', channelInfo.channelCode)
|
||||
} else {
|
||||
console.warn('channelInfo.channelCode为空,无法保存')
|
||||
}
|
||||
|
||||
if (channelInfo.channelName) {
|
||||
uni.setStorageSync('channelName', channelInfo.channelName)
|
||||
console.log('已保存channelName:', channelInfo.channelName)
|
||||
} else {
|
||||
console.warn('channelInfo.channelName为空,无法保存')
|
||||
}
|
||||
|
||||
// 验证保存是否成功
|
||||
const savedCode = uni.getStorageSync('channelCode')
|
||||
const savedName = uni.getStorageSync('channelName')
|
||||
const savedToken = uni.getStorageSync('channelToken')
|
||||
console.log('验证保存结果:', { savedCode, savedName, savedToken })
|
||||
|
||||
if (!savedCode) {
|
||||
console.error('警告:channelCode保存失败!')
|
||||
uni.showToast({
|
||||
title: '渠道编码保存失败,请重试',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.toStatistics()
|
||||
} else {
|
||||
const msg = res.data.message || res.data.msg || '登录失败'
|
||||
console.error('登录失败:', msg)
|
||||
uni.showToast({ title: msg, icon: 'none', duration: 3000 })
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('登录失败', err)
|
||||
this.isSubmitting = false
|
||||
uni.showToast({ title: '网络错误,请重试', icon: 'none', duration: 2000 })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
toStatistics() {
|
||||
uni.redirectTo({
|
||||
url: '/pages/channel/statistics'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #f5f7ff 0%, #f8f8f8 100%);
|
||||
padding: 40rpx 30rpx 80rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.hero {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 44rpx;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
margin-top: 8rpx;
|
||||
font-size: 26rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx 28rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.input {
|
||||
background: #f5f6fa;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.login-tip {
|
||||
margin-top: 16rpx;
|
||||
margin-bottom: 8rpx;
|
||||
font-size: 24rpx;
|
||||
color: #6b7280;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.login-tip-strong {
|
||||
font-weight: 600;
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.primary-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
border: none;
|
||||
border-radius: 12rpx;
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 12rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(99, 102, 241, 0.25);
|
||||
}
|
||||
|
||||
.tips {
|
||||
margin-top: 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: #6b7280;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* #ifdef H5 */
|
||||
.primary-btn {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
&:hover {
|
||||
transform: translateY(-2rpx);
|
||||
box-shadow: 0 10rpx 28rpx rgba(99, 102, 241, 0.3);
|
||||
}
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
/* #endif */
|
||||
</style>
|
||||
|
||||
177
pages/channel/project-select.vue
Normal file
177
pages/channel/project-select.vue
Normal file
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="header">
|
||||
<view class="title">选择项目</view>
|
||||
<view class="subtitle">请选择要查看的渠道项目</view>
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="loading">
|
||||
<uni-icons type="spinner-cycle" size="32" color="#6366f1"></uni-icons>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<view v-else-if="projects.length === 0" class="empty">
|
||||
<uni-icons type="info" size="40" color="#ccc"></uni-icons>
|
||||
<text class="empty-text">暂无可选项目</text>
|
||||
</view>
|
||||
|
||||
<view v-else class="list">
|
||||
<view
|
||||
v-for="(item, index) in projects"
|
||||
:key="index"
|
||||
class="project-card"
|
||||
@click="selectProject(item)"
|
||||
>
|
||||
<view class="project-main">
|
||||
<view class="project-name">{{ item.name || '未命名项目' }}</view>
|
||||
<view class="project-code">编码:{{ item.code || '--' }}</view>
|
||||
</view>
|
||||
<uni-icons type="arrowright" size="20" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from '@/config'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
projects: []
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
this.fetchProjects()
|
||||
},
|
||||
methods: {
|
||||
fetchProjects() {
|
||||
const token = uni.getStorageSync('channelToken')
|
||||
if (!token) {
|
||||
uni.showToast({ title: '请先登录', icon: 'none', duration: 2000 })
|
||||
uni.redirectTo({ url: '/pages/channel/login' })
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
uni.request({
|
||||
method: 'GET',
|
||||
url: config.apiUrl2 + '/v1/frontend/distribution/channel/projects',
|
||||
header: {
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
token
|
||||
},
|
||||
success: (res) => {
|
||||
this.loading = false
|
||||
if (res.data.code === 10000 || res.data.code === 200) {
|
||||
this.projects = res.data.data?.list || res.data.data || []
|
||||
} else {
|
||||
const msg = res.data.message || res.data.msg || '加载失败'
|
||||
uni.showToast({ title: msg, icon: 'none', duration: 3000 })
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('加载项目失败', err)
|
||||
this.loading = false
|
||||
uni.showToast({ title: '网络错误,请重试', icon: 'none', duration: 2000 })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
selectProject(item) {
|
||||
const name = encodeURIComponent(item.name || '')
|
||||
const code = encodeURIComponent(item.code || '')
|
||||
uni.navigateTo({
|
||||
url: `/pages/channel/statistics?name=${name}&code=${code}`
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background: #f5f6fb;
|
||||
padding: 32rpx 28rpx 60rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 38rpx;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin-top: 8rpx;
|
||||
font-size: 26rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.loading,
|
||||
.empty {
|
||||
margin-top: 120rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.loading-text,
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.project-card {
|
||||
background: #fff;
|
||||
border-radius: 14rpx;
|
||||
padding: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-shadow: 0 4rpx 14rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.project-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.project-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.project-code {
|
||||
font-size: 26rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
/* #ifdef H5 */
|
||||
.project-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
&:hover {
|
||||
transform: translateY(-2rpx);
|
||||
box-shadow: 0 8rpx 18rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
}
|
||||
/* #endif */
|
||||
</style>
|
||||
|
||||
2183
pages/channel/statistics.vue
Normal file
2183
pages/channel/statistics.vue
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user