Commit 2414cd4b by yuzhenWang

已完成所有需求,提交测试

parent b04da0a6
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
import {interceptor} from "@/util/interceptor"; import {interceptor} from "@/util/interceptor";
import {baseURL,apiURL,cffpURL,companyInfo} from "@/environments/environment"; import {baseURL,apiURL,cffpURL,companyInfo} from "@/environments/environment";
import api from './api/api'; import api from './api/api';
import {hshare} from '@/util/fiveshare';
export default { export default {
data() { data() {
return { return {
...@@ -12,6 +13,7 @@ ...@@ -12,6 +13,7 @@
onLaunch: function() { onLaunch: function() {
console.log('App Launch'); console.log('App Launch');
if(!uni.getStorageSync('loginType')){ if(!uni.getStorageSync('loginType')){
console.log('走进来了');
uni.clearStorageSync(); uni.clearStorageSync();
uni.setStorageSync('loginType','visitor'); uni.setStorageSync('loginType','visitor');
} }
...@@ -115,7 +117,8 @@ ...@@ -115,7 +117,8 @@
'/pages/courseDetail/courseDetail', '/pages/courseDetail/courseDetail',
'/pages/orderDetail/orderDetail', '/pages/orderDetail/orderDetail',
'/pages/orderStatus/orderStatus', '/pages/orderStatus/orderStatus',
'/pages/index/index' '/pages/index/index',
'/myPackageA/poster/poster',
] // 根据需要调整 ] // 根据需要调整
if(!whiteList.includes(currentRoute)) { if(!whiteList.includes(currentRoute)) {
uni.navigateTo({ uni.navigateTo({
...@@ -130,8 +133,10 @@ ...@@ -130,8 +133,10 @@
mobile: res['data']['mobile'], mobile: res['data']['mobile'],
partnerType:res['data']['partnerType'], partnerType:res['data']['partnerType'],
nickName:res['data']['nickName'], nickName:res['data']['nickName'],
levelCode:res['data']['levelCode'],
} }
uni.setStorageSync('cffp_userInfo', JSON.stringify(cffp_userInfo)) uni.setStorageSync('cffp_userInfo', JSON.stringify(cffp_userInfo))
uni.setStorageSync('userinfodataForm', res.data);
} }
} catch (err) { } catch (err) {
console.error('检查用户状态失败:', err); console.error('检查用户状态失败:', err);
......
...@@ -175,6 +175,7 @@ ...@@ -175,6 +175,7 @@
mobile: res['data']['mobile'], mobile: res['data']['mobile'],
partnerType:res['data']['partnerType'], partnerType:res['data']['partnerType'],
nickName:res['data']['nickName'], nickName:res['data']['nickName'],
levelCode:res['data']['levelCode'],
} }
uni.setStorageSync('cffp_userInfo', JSON.stringify(cffp_userInfo)) uni.setStorageSync('cffp_userInfo', JSON.stringify(cffp_userInfo))
this.closebootpage() this.closebootpage()
......
/** 从 0x20 开始到 0x80 的字符宽度数据 */
export declare const CHAR_WIDTH_SCALE_MAP: number[];
import { ObjectFit, ObjectPosition, Size } from "../value";
/**
* 用于计算图片的宽高比例
* @see https://drafts.csswg.org/css-images-3/#sizing-terms
*
* ## 名词解释
* ### intrinsic dimensions
* 图片本身的尺寸
*
* ### specified size
* 用户指定的元素尺寸
*
* ### concrete object size
* 应用了 `objectFit` 之后图片的显示尺寸
*
* ### default object size
*/
export declare function calculateConcreteRect(style: {
/** @see https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-fit */
objectFit?: ObjectFit;
/** @see https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-position */
objectPosition?: ObjectPosition;
}, intrinsicSize: Size, specifiedSize: Size): {
sx: number;
sy: number;
sw: number;
sh: number;
dx: number;
dy: number;
dw: number;
dh: number;
};
<template>
<!-- 微信小程序专用 -->
<!-- #ifdef MP-WEIXIN -->
<painter
:palette="finalPalette"
@imgOK="onImgOK"
@imgErr="onImgErr"
:customStyle="customStyle"
/>
<!-- #endif -->
<!-- H5和APP端使用Canvas兼容 -->
<!-- #ifndef MP-WEIXIN -->
<view class="painter-container">
<canvas
id="posterCanvas"
canvas-id="posterCanvas"
:style="{
width: customStyle.width,
height: customStyle.height,
position: 'fixed',
left: '-9999px'
}"
></canvas>
<image v-if="posterUrl" :src="posterUrl" mode="widthFix" class="poster-image" />
<view v-if="generating" class="loading">海报生成中...</view>
</view>
<!-- #endif -->
</template>
<script>
// 小程序端直接使用原生组件
// #ifdef MP-WEIXIN
import Painter from './painter.js';
export default {
components: { Painter },
props: {
palette: Object,
customStyle: {
type: Object,
default: () => ({ width: '750rpx', height: '1334rpx' })
}
},
computed: {
finalPalette() {
return this.palette;
}
},
methods: {
onImgOK(e) {
this.$emit('imgOK', e);
},
onImgErr(err) {
this.$emit('imgErr', err);
}
}
};
// #endif
// H5和APP端兼容实现
// #ifndef MP-WEIXIN
import QRCode from 'qrcode';
export default {
props: {
palette: Object,
customStyle: Object
},
data() {
return {
posterUrl: '',
generating: false
};
},
mounted() {
this.generateH5Poster();
},
methods: {
async generateH5Poster() {
this.generating = true;
try {
console.log('开始生成海报');
// 1. 生成二维码
const qrCodeUrl = await this.generateQRCode();
console.log('二维码生成完成', qrCodeUrl ? '成功' : '失败');
// 2. 获取Canvas上下文
const ctx = uni.createCanvasContext('posterCanvas', this);
// 3. 绘制背景
ctx.setFillStyle('#ffffff');
ctx.fillRect(0, 0, 750, 1334);
// 4. 绘制背景图
const bg = this.palette.views.find(v => v.type === 'image');
if (bg) {
console.log('开始绘制背景图:', bg.url);
try {
await this.drawImage(ctx, bg.url, 0, 0, 750, 1334);
} catch (err) {
console.warn('背景图绘制失败:', err);
}
}
// 5. 绘制文字
const texts = this.palette.views.filter(v => v.type === 'text');
texts.forEach(text => {
ctx.setFontSize(parseInt(text.css.fontSize));
ctx.setFillStyle(text.css.color || '#000000');
ctx.setTextAlign(text.css.left === 'center' ? 'center' : 'left');
const x = text.css.left === 'center' ? 375 : parseInt(text.css.left);
const y = parseInt(text.css.top);
ctx.fillText(text.text, x, y);
});
// 6. 绘制二维码
const qrView = this.palette.views.find(v => v.type === 'qrcode');
if (qrView && qrCodeUrl) {
console.log('开始绘制二维码');
try {
await this.drawImage(ctx, qrCodeUrl,
parseInt(qrView.css.left),
parseInt(qrView.css.top),
parseInt(qrView.css.width),
parseInt(qrView.css.height)
);
} catch (err) {
console.warn('二维码绘制失败:', err);
}
}
// 7. 生成最终图片
console.log('开始生成最终图片');
await new Promise(resolve => {
ctx.draw(false, () => {
setTimeout(() => {
uni.canvasToTempFilePath({
canvasId: 'posterCanvas',
success: (res) => {
console.log('图片生成成功', res);
this.posterUrl = res.tempFilePath;
this.$emit('imgOK', { detail: { path: res.tempFilePath } });
resolve();
},
fail: (err) => {
console.error('图片生成失败:', err);
this.$emit('imgErr', err);
resolve();
}
}, this);
}, 300);
});
});
} catch (err) {
console.error('海报生成失败:', err);
this.$emit('imgErr', err);
} finally {
this.generating = false;
}
},
drawImage(ctx, url, x, y, width, height) {
return new Promise((resolve, reject) => {
// 如果是base64数据,直接绘制
if (url.startsWith('data:image')) {
console.log('绘制base64图片');
const img = new Image();
img.onload = () => {
ctx.drawImage(img, x, y, width, height);
resolve();
};
img.onerror = (err) => {
console.warn('base64图片加载失败', err);
reject(err);
};
img.src = url;
return;
}
console.log('加载网络图片:', url);
uni.getImageInfo({
src: url,
success: (res) => {
console.log('图片加载成功', res);
const img = new Image();
img.onload = () => {
ctx.drawImage(img, x, y, width, height);
resolve();
};
img.onerror = (err) => {
console.warn('图片绘制失败', err);
reject(err);
};
img.src = res.path;
},
fail: (err) => {
console.warn('图片加载失败:', err);
reject(err);
}
});
});
},
generateQRCode() {
return new Promise((resolve) => {
const qrView = this.palette.views.find(v => v.type === 'qrcode');
if (!qrView || !qrView.content) {
console.warn('未找到有效的二维码配置');
resolve(null);
return;
}
console.log('开始生成二维码,内容:', qrView.content);
// 创建临时Canvas用于生成二维码
const canvas = document.createElement('canvas');
canvas.width = 200;
canvas.height = 200;
// 使用Promise风格的API
QRCode.toCanvas(canvas, qrView.content, {
width: 200,
margin: 2,
color: {
dark: '#000000',
light: '#ffffff'
}
}, (error) => {
if (error) {
console.error('二维码生成失败:', error);
resolve(null);
} else {
console.log('二维码生成成功');
resolve(canvas.toDataURL('image/png'));
}
});
});
}
}
};
// #endif
</script>
<style scoped>
.painter-container {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
canvas {
display: none;
}
.poster-image {
width: 100%;
max-width: 750rpx;
display: block;
margin: 0 auto;
}
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20rpx 40rpx;
background-color: rgba(0, 0, 0, 0.7);
color: white;
border-radius: 10rpx;
font-size: 28rpx;
}
</style>
\ No newline at end of file
import Painter from "./painter";
import { PainterContext } from "./painter-context/index";
interface ILineSpliterContextOption {
fontSize: number;
lineClamp: number;
width: number;
painter: Painter;
content: string;
}
export default class LineSpliterContext {
fontSize: number;
lineClamp: number;
width: number;
ctx: PainterContext;
painter: Painter;
content: string;
lines: string[];
currentLineText: string;
position: number;
endPostion: number;
isOverflow: boolean;
isDry: boolean;
isFull: boolean;
constructor(option: ILineSpliterContextOption);
split(): string[];
minCharNumberInWidth(width: number): number;
freeSpaceInCurrentLine(): number;
adjustCharNumberInCurrentLine(charNumber: number): void;
commitLine(): void;
handleOverflow(): void;
fillText(): void;
}
export {};
import Painter from "../painter";
import { BaseLine, FillStrokeStyle, TextAlign } from "../value";
import { PainterContext } from "./index";
declare const PainterH5Context_base: new (prototype: CanvasRenderingContext2D) => CanvasRenderingContext2D & {
context: CanvasRenderingContext2D;
};
export declare class PainterH5Context extends PainterH5Context_base implements PainterContext {
private painter;
constructor(painter: Painter, context: CanvasRenderingContext2D);
draw(reserve: boolean, callback: () => void): void;
setFillStyle(color: FillStrokeStyle): void;
setStrokeStyle(color: FillStrokeStyle): void;
drawImageWithSrc(imageResource: string, sx: number, sy: number, sWidth: number, sHeight: number, dx?: number, dy?: number, dWidth?: number, dHeight?: number): Promise<void>;
setTextAlign(align: TextAlign): void;
setTextBaseline(baseline: BaseLine): void;
setFontSize(fontSize: number): void;
measureTextWidth(text: string, fontSize: number): number;
}
export declare function normalizeImageResource(src: string): Promise<HTMLImageElement>;
export {};
import { PainterUniContext } from "./context-uni";
import { PainterContext } from "./index";
export declare class PainterUniMpAlipayContext extends PainterUniContext implements PainterContext {
measureTextWidth(text: string, fontSize: number): number;
}
import { PainterUniContext } from "./context-uni";
import { PainterContext } from "./index";
export declare class PainterUniMpBaiduContext extends PainterUniContext implements PainterContext {
measureTextWidth(text: string, fontSize: number): number;
drawImageWithSrc(imageResource: string, sx: number, sy: number, sWidth: number, sHeigt: number, dx?: number, dy?: number, dWidth?: number, dHeight?: number): Promise<void>;
}
import { PainterUniContext } from "./context-uni";
import { PainterContext } from "./index";
export declare class PainterUniMpWeixinContext extends PainterUniContext implements PainterContext {
/** 兼容地,根据控制点和半径绘制圆弧路径 */
arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void;
measureTextWidth(text: string, fontSize: number): number;
}
/// <reference types="uni-app" />
import Painter from "../painter";
import { PainterContext } from "./index";
declare const CanvasContext: new (prototype: CanvasContext) => CanvasContext & {
context: CanvasContext;
};
export declare class PainterUniContext extends CanvasContext implements PainterContext {
private painter;
constructor(painter: Painter, context: CanvasContext);
drawImageWithSrc(imageResource: string, sx: number, sy: number, sWidth: number, sHeight: number, dx?: number, dy?: number, dWidth?: number, dHeight?: number): Promise<void>;
measureTextWidth(text: string, fontSize: number): number;
}
export {};
export declare function createClass<T extends object>(): new (prototype: T) => T & {
context: T;
};
/// <reference types="uni-app" />
import Painter from "../painter";
export declare type CompatableContext = CanvasContext | CanvasRenderingContext2D;
export declare type PainterContext = Pick<CanvasContext, "arcTo" | "clip" | "draw" | "fillStyle" | "font" | "lineTo" | "restore" | "save" | "setFillStyle" | "setFontSize" | "setStrokeStyle" | "setTextAlign" | "setTextBaseline" | "strokeStyle" | "beginPath" | "closePath" | "moveTo" | "createLinearGradient" | "stroke" | "fill" | "strokeRect" | "fillRect" | "lineWidth" | "setLineDash" | "fillText" | "setTransform" | "scale" | "rotate" | "translate"> & {
measureTextWidth(text: string, fontSize: number): number;
drawImageWithSrc(imageResource: string, sx: number, sy: number, sWidth: number, sHeigt: number): Promise<void>;
drawImageWithSrc(imageResource: string, sx: number, sy: number, sWidth: number, sHeigt: number, dx: number, dy: number, dWidth: number, dHeight: number): Promise<void>;
};
export declare function adaptContext(painter: Painter, ctx: CompatableContext): PainterContext;
import Painter from "../painter";
import { Size, Position } from "../value";
export interface PainterElementOption {
type: string;
/** 定位方式 */
position: Position;
left: number;
top: number;
}
export declare abstract class PainterElement {
parent?: PainterElement;
offsetTop: number;
offsetLeft: number;
left: number;
top: number;
contentHeight: number;
contentWidth: number;
position: Position;
painter: Painter;
constructor(painter: Painter, option: Partial<PainterElementOption>, parent?: PainterElement);
protected abstract _layout(): Promise<Size> | Size;
layout(): Promise<Size>;
abstract paint(): void;
get anchorX(): number;
get anchorY(): number;
get elementX(): number;
get elementY(): number;
get offsetHeight(): number;
get offsetWidth(): number;
}
import Painter from "../painter";
import { PainterElementOption, PainterElement } from "./base";
import { BuiltInPainterElementOption } from "./index";
import { BuiltInPainterPathOption } from "../painter-path/index";
import { PainterPath } from "../painter-path/base";
export interface PainterClipElementOption extends PainterElementOption {
type: "clip";
/** 裁剪使用的路径 */
path: BuiltInPainterPathOption;
/** 被裁剪的内容 */
content: BuiltInPainterElementOption;
}
export declare class PainterClipElement extends PainterElement {
contentElement: PainterElement;
clipPath: PainterPath;
constructor(painter: Painter, option: PainterClipElementOption, parent?: PainterElement);
_layout(): Promise<import("../value").Size>;
paint(): Promise<void>;
}
import Painter from "../painter";
import { PainterElementOption, PainterElement } from "./base";
import { OmitBaseOption } from "../value";
import { BuiltInPainterElementOption } from "./index";
export interface PainterContainerElementOption extends PainterElementOption {
type: "container";
/** 子元素的排列方式 */
direction: "vertical" | "horizontal";
width: number | "auto";
height: number | "auto";
/** 子元素 */
children: BuiltInPainterElementOption[];
}
export declare class PainterContainerElement extends PainterElement {
option: OmitBaseOption<PainterContainerElementOption>;
children: PainterElement[];
childOffsetTop: number;
childOffsetLeft: number;
constructor(painter: Painter, option: PainterContainerElementOption, parent?: PainterElement);
_layout(): Promise<{
width: number;
height: number;
}>;
private layoutChildren;
private layoutChild;
private calcContainerWidth;
private calcContainerHeight;
paint(): Promise<void>;
childrenMaxWidth(): number;
childrenMaxHeight(): number;
}
import Painter from "../painter";
import { PainterElementOption, PainterElement } from "./base";
import { ObjectFit, OmitBaseOption, ObjectPosition } from "../value";
export interface PainterImageElementOption extends PainterElementOption {
type: "image";
/**
* 图片地址
*
* 可以使用网络图片地址或下载后的临时图片地址
*/
src: string;
width: number;
height: number;
/**
* 图片的适应方式
* - fill 拉伸图片以铺满容器
* - contain 等比缩放,使图片刚好能完整显示出来
* - cover 等比缩放,使图片刚好能占满容器
*/
objectFit?: ObjectFit;
/**
* 图片的位置
* 默认为 `["center","center"]`
*/
objectPosition?: ObjectPosition;
}
export declare class PainterImageElement extends PainterElement {
option: OmitBaseOption<PainterImageElementOption>;
constructor(painter: Painter, option: PainterImageElementOption, parent?: PainterElement);
_layout(): {
width: number;
height: number;
};
paint(): Promise<void>;
}
import Painter from "../painter";
import { PainterElementOption, PainterElement } from "./base";
import { OmitBaseOption } from "../value";
import { BuiltInPainterFillStrokeOption } from "../painter-fill-stroke/index";
export interface PainterLineElementOption extends PainterElementOption {
type: "line";
/** 直线终点距离起点在水平方向上的距离 */
dx: number;
/** 直线终点距离起点在垂直方向上的距离 */
dy: number;
/** 直线的颜色 */
color: BuiltInPainterFillStrokeOption;
/** 直线的宽度 */
lineWidth: number;
/** 虚线样式,默认为实线 */
dashPattern: number[];
}
export declare class PainterLineElement extends PainterElement {
option: OmitBaseOption<PainterLineElementOption>;
constructor(painter: Painter, option: Partial<PainterLineElementOption>, parent?: PainterElement);
_layout(): {
width: number;
height: number;
};
paint(): void;
}
import Painter from "../painter";
import { PainterElementOption, PainterElement } from "./base";
import { OmitBaseOption, BorderRadius, BorderStyle, Color } from "../value";
import { BuiltInPainterFillStrokeOption } from "../painter-fill-stroke/index";
export interface PainterRectagleElementOption extends PainterElementOption {
type: "rect";
width: number;
height: number;
/**
* 圆角
*
* - `number` 一个数字,四个角的圆角都使用设置的值
* - `[number, number, number, number]` 四个数字的元组分别代表 **左上**、**右上**、**右下** 和 **左下** 的圆角值。
*/
borderRadius: BorderRadius;
/**
* 背景颜色
*/
background: BuiltInPainterFillStrokeOption;
borderWidth: number;
borderStyle: BorderStyle;
borderColor: Color;
}
export declare class PainterRectagleElement extends PainterElement {
option: OmitBaseOption<PainterRectagleElementOption>;
constructor(painter: Painter, option: PainterRectagleElementOption, parent?: PainterElement);
_layout(): {
width: number;
height: number;
};
paint(): void;
private get shouldFill();
private get shouldStroke();
private applyFillStyle;
private applyStrokeStyle;
private paintByRect;
private paintByPath;
}
import Painter from "../painter";
import { PainterTextElementOption } from "./element-text";
import { PainterElement } from "./base";
import { OmitBaseOption } from "../value";
export interface PainterTextBlockElementOption extends Omit<PainterTextElementOption, "type"> {
type: "text-block";
/** 行高 */
lineHeight: number;
/** 文本块的最大行数 */
lineClamp: number;
/** 文本块的宽度 */
width: number;
height: number | "auto";
}
export declare class PainterTextBlockElement extends PainterElement {
option: OmitBaseOption<Partial<PainterTextBlockElementOption> & Pick<PainterTextBlockElementOption, "fontSize" | "width" | "height" | "lineClamp" | "content" | "lineHeight" | "top" | "color">>;
lines: string[];
constructor(painter: Painter, option: PainterTextBlockElementOption, parent?: PainterElement);
_layout(): {
width: number;
height: number;
};
paint(): Promise<void>;
}
import Painter from "../painter";
import { FontWeight, BaseLine, TextAlign, OmitBaseOption, FontStyle, TextDecoration } from "../value";
import { PainterElementOption, PainterElement } from "./base";
import { BuiltInPainterFillStrokeOption } from "../painter-fill-stroke/index";
export interface PainterTextElementOption extends PainterElementOption {
type: "text";
/** 文字的颜色 */
color: BuiltInPainterFillStrokeOption;
/** 文字的字号 */
fontSize: number;
/** 文字的字重 */
fontWeight: FontWeight;
/** 文字的字型 */
fontStyle: FontStyle;
/** 文字的字体 */
fontFamily: string;
/** 锚点在垂直方向相对文字的位置 */
baseline: BaseLine;
/** 锚点在水平方向相对文字的位置 */
align: TextAlign;
/** 文本内容 */
content: string;
/** 文字的宽度,为空则根据文本内容及字号自动计算宽度 */
width?: number;
textDecoration: TextDecoration;
}
export declare class PainterTextElement extends PainterElement {
option: OmitBaseOption<PainterTextElementOption>;
constructor(painter: Painter, option: PainterTextElementOption, parent?: PainterElement);
_layout(): {
width: number;
height: number;
};
paint({ inTextBlock }?: {
inTextBlock?: boolean | undefined;
}): void;
private paintTextDecoration;
}
import Painter from "../painter";
import { PainterElementOption, PainterElement } from "./base";
import { BuiltInPainterElementOption } from "./index";
export interface PainterTransformElementOption extends PainterElementOption {
type: "transform";
/** 变换的内容 */
content: BuiltInPainterElementOption;
/** 变换的选项 */
transform: BuiltInPainterTransformOption[];
/** 变换的原点、默认为元素的中心 */
transformOrigin: PaitnerTransformOriginOption;
}
declare type BuiltInPainterTransformOption = PainterTransformTranslateOption | PainterTransformRotateOption | PainterTransformScaleOption | PainterTransformMatrixOption;
interface PainterTransformTranslateOption {
type: "translate";
x?: number;
y?: number;
}
interface PainterTransformRotateOption {
type: "rotate";
/** degree */
rotate: number;
}
interface PainterTransformScaleOption {
type: "scale";
scaleWidth?: number;
scaleHeight?: number;
}
interface PainterTransformMatrixOption {
type: "set-matrix";
translateX: number;
translateY: number;
scaleX: number;
scaleY: number;
skewX: number;
skewY: number;
}
declare type PaitnerTransformOriginOption = [
PaitnerTransformOriginHorizontalOption,
PaitnerTransformOriginVerticalOption
];
declare type PaitnerTransformOriginHorizontalOption = "left" | "center" | "right";
declare type PaitnerTransformOriginVerticalOption = "top" | "center" | "bottom";
/**
* - fixme: Cascade transform not supported yet.
* Cause when we set tansform origin in `withTransformOrigin`,
* We don't know the transform state of outer element.
*/
export declare class PainterTransformElement extends PainterElement {
transformOrigin: PaitnerTransformOriginOption;
transformOptions: BuiltInPainterTransformOption[];
contentElement: PainterElement;
constructor(painter: Painter, option: PainterTransformElementOption, parent?: PainterElement);
_layout(): Promise<import("../value").Size>;
paint(): Promise<void>;
private transform;
private singleTransform;
private withTransformOrigin;
}
export {};
import { PainterTextElementOption, PainterTextElement } from "./element-text";
import { PainterTextBlockElementOption, PainterTextBlockElement } from "./element-text-block";
import { PainterImageElementOption, PainterImageElement } from "./element-image";
import { PainterLineElementOption, PainterLineElement } from "./element-line";
import { PainterRectagleElementOption, PainterRectagleElement } from "./element-rect";
import { PainterContainerElementOption, PainterContainerElement } from "./element-container";
import { PainterClipElementOption, PainterClipElement } from "./element-clip";
import { PainterTransformElementOption, PainterTransformElement } from "./element-transform";
import Painter from "../painter";
import { PainterElement } from "./base";
export declare type BuiltInPainterElementOption = PainterTextElementOption | PainterTextBlockElementOption | PainterImageElementOption | PainterLineElementOption | PainterRectagleElementOption | PainterContainerElementOption | PainterClipElementOption | PainterTransformElementOption;
export declare function createElement(painter: Painter, option: BuiltInPainterElementOption, parent?: PainterElement): PainterTextElement | PainterTextBlockElement | PainterImageElement | PainterLineElement | PainterRectagleElement | PainterContainerElement | PainterClipElement | PainterTransformElement;
import { PainterLinearGradientOption } from "./linear-gradient";
import { PainterElement } from "../painter-element/base";
export interface PainterGradientPatternOption {
type: string;
}
export declare type BuiltInPainterGradientPatternOption = PainterLinearGradientOption;
export declare abstract class PainterGradientPatternStyle {
element: PainterElement;
constructor(element: PainterElement);
get painter(): import("../painter").default;
abstract get style(): CanvasGradient | CanvasPattern;
}
import { Color } from "../value";
import { PainterLinearGradientOption } from "./linear-gradient";
import { PainterElement } from "../painter-element/base";
export declare type BuiltInPainterFillStrokeOption = Color | PainterLinearGradientOption;
export declare function createFillStrokeStyle(element: PainterElement, option: BuiltInPainterFillStrokeOption): string | CanvasGradient;
import { ColorStop } from "../value";
import { PainterGradientPatternOption, PainterGradientPatternStyle } from "./base";
import { PainterElement } from "../painter-element/base";
export interface PainterLinearGradientOption extends PainterGradientPatternOption {
type: "linear-gradient";
colorStops: ColorStop[];
x1: number;
y1: number;
x2: number;
y2: number;
}
export declare class PainterLinearGradientStyle extends PainterGradientPatternStyle {
option: PainterLinearGradientOption;
constructor(element: PainterElement, option: PainterLinearGradientOption);
get style(): CanvasGradient;
}
import { PainterElement } from "../painter-element/base";
export interface PainterPathOption {
type: string;
left?: number;
top?: number;
}
export declare abstract class PainterPath {
element: PainterElement;
left: number;
top: number;
constructor(element: PainterElement, option: PainterPathOption);
get painter(): import("../painter").default;
get pathX(): number;
get pathY(): number;
abstract paint(): void;
}
import { PainterElement } from "../painter-element/base";
import { PainterRoundedRectanglePath, PainterRoundedRectanglePathOption } from "./path-rounded-rect";
export declare type BuiltInPainterPathOption = PainterRoundedRectanglePathOption;
export declare function createPath(element: PainterElement, option: BuiltInPainterPathOption): PainterRoundedRectanglePath;
import { PainterPath, PainterPathOption } from "./base";
import { BorderRadius } from "../value";
import { PainterElement } from "../painter-element/base";
export interface PainterRoundedRectanglePathOption extends PainterPathOption {
type: "rounded-rect";
/** 路径的宽度 */
width: number;
/** 路径的高度 */
height: number;
/** 路径的圆角 */
borderRadius: BorderRadius;
}
export declare class PainterRoundedRectanglePath extends PainterPath {
option: PainterRoundedRectanglePathOption;
constructor(element: PainterElement, option: PainterRoundedRectanglePathOption);
private assertBorderRadius;
paint(): void;
private get normalizedBorderRadius();
/**
* @see https://www.w3.org/TR/css-backgrounds-3/#corner-overlap
* Corner curves must not overlap: When the sum of any two adjacent border
* radii exceeds the size of the border box, UAs must proportionally reduce
* the used values of all border radii until none of them overlap.
*/
private reduceBorderRadius;
}
/// <reference types="uni-app" />
import { UniPlatforms } from "../utils/platform";
import { BuiltInPainterElementOption } from "./painter-element/index";
import { PainterContext } from "./painter-context/index";
interface IPanterOption {
platform?: UniPlatforms;
upx2px?: (upx: number) => number;
}
/** 单次绘制选项 */
interface IDrawOption {
}
export default class Painter {
ctx: PainterContext;
upx2px: NonNullable<IPanterOption["upx2px"]>;
platform: NonNullable<IPanterOption["platform"]>;
constructor(ctx: CanvasContext, { platform, upx2px }?: IPanterOption);
draw(elementOption: BuiltInPainterElementOption, drawOption?: IDrawOption): Promise<import("./value").Size>;
/** 创建元素对象 */
createElement(elementOption: BuiltInPainterElementOption): import("./painter-element/element-text").PainterTextElement | import("./painter-element/element-text-block").PainterTextBlockElement | import("./painter-element/element-image").PainterImageElement | import("./painter-element/element-line").PainterLineElement | import("./painter-element/element-rect").PainterRectagleElement | import("./painter-element/element-container").PainterContainerElement | import("./painter-element/element-clip").PainterClipElement | import("./painter-element/element-transform").PainterTransformElement;
/** 获取指定元素的内部尺寸, 结果不包含元素的 left 和 top */
layout(elementOption: BuiltInPainterElementOption): Promise<import("./value").Size>;
/** 兼容将 painter 实例保存在 uniapp this 上, 勿手动调用 */
toJSON(): string;
}
export {};
import { PainterElementOption } from "./painter-element/base";
export interface Size {
width: number;
height: number;
}
export interface Rect {
top: number;
left: number;
width: number;
height: number;
}
export declare type BaseLine = "top" | "middle" | "bottom" | "normal";
export declare type BorderRadius = number | BorderRadius4;
export declare type BorderRadius4 = [topLeft: number, topRight: number, bottomLeft: number, bottomRight: number];
export declare type BorderStyle = "solid" | "dashed";
/** @example "#rrggbb" | "#rgb" | "colorName" */
export declare type Color = string;
export interface ColorStop {
offset: number;
color: Color;
}
export declare type FillStrokeStyle = string | CanvasGradient | CanvasPattern;
export declare type FontWeight = "normal" | "bold";
export declare type FontStyle = "normal" | "italic";
export declare type ObjectFit = "fill" | "contain" | "cover";
export declare type ObjectPosition = ["left" | "center" | "right", "top" | "center" | "bottom"];
export declare type Position = "static" | "absolute";
export declare type TextAlign = "left" | "right" | "center";
export declare type TextDecoration = "none" | "line-through";
/** left-top right-top right-bottom left-bottom */
export declare type OmitBaseOption<T> = Omit<T, keyof PainterElementOption>;
export declare function cssBorderStyleToLinePattern(borderStyle: BorderStyle, borderWidth: number): [number, number];
...@@ -265,6 +265,7 @@ ...@@ -265,6 +265,7 @@
mobile: res['data']['mobile'], mobile: res['data']['mobile'],
partnerType:res['data']['partnerType'], partnerType:res['data']['partnerType'],
nickName:res['data']['nickName'], nickName:res['data']['nickName'],
levelCode:res['data']['levelCode'],
} }
uni.setStorageSync('cffp_userInfo', JSON.stringify(cffp_userInfo)) uni.setStorageSync('cffp_userInfo', JSON.stringify(cffp_userInfo))
......
...@@ -6,6 +6,7 @@ const dev = { ...@@ -6,6 +6,7 @@ const dev = {
cffp_url:'https://mdev.anjibao.cn/cffpApi/cffp', cffp_url:'https://mdev.anjibao.cn/cffpApi/cffp',
share_url:'https://mdev.anjibao.cn/cffp', share_url:'https://mdev.anjibao.cn/cffp',
sfp_url:'https://mdev.anjibao.cn/sfpApi', sfp_url:'https://mdev.anjibao.cn/sfpApi',
img_url:'https://mdev.zuihuibi.cn'
} }
const stage = { const stage = {
base_url:'https://mstage.zuihuibi.cn', base_url:'https://mstage.zuihuibi.cn',
...@@ -20,7 +21,8 @@ const prod = { ...@@ -20,7 +21,8 @@ const prod = {
api_url:'https://app.ydhomeoffice.cn/appApi', api_url:'https://app.ydhomeoffice.cn/appApi',
cffp_url:'https://app.ydhomeoffice.cn/appApi/cffp', cffp_url:'https://app.ydhomeoffice.cn/appApi/cffp',
share_url:'https://app.ydhomeoffice.cn/appYdhomeoffice', share_url:'https://app.ydhomeoffice.cn/appYdhomeoffice',
sfp_url:'https://hoservice.ydhomeoffice.cn/hoserviceApi' sfp_url:'https://hoservice.ydhomeoffice.cn/hoserviceApi',
img_url:'https://app.ydhomeoffice.cn'
} }
// companyType: '1', cffp // companyType: '1', cffp
// companyType: '2', appYdhomeoffice // companyType: '2', appYdhomeoffice
...@@ -51,6 +53,7 @@ let apiURL = config[env].api_url; ...@@ -51,6 +53,7 @@ let apiURL = config[env].api_url;
let cffpURL = config[env].cffp_url; let cffpURL = config[env].cffp_url;
let shareURL = config[env].share_url; let shareURL = config[env].share_url;
let sfpUrl = config[env].sfp_url; let sfpUrl = config[env].sfp_url;
let imgUrl = config[env].img_url;
export{ export{
baseURL, baseURL,
...@@ -59,4 +62,6 @@ export{ ...@@ -59,4 +62,6 @@ export{
companyInfo, companyInfo,
shareURL, shareURL,
sfpUrl, sfpUrl,
env,
imgUrl
} }
\ No newline at end of file
...@@ -348,6 +348,7 @@ ...@@ -348,6 +348,7 @@
mobile: res['data']['mobile'], mobile: res['data']['mobile'],
partnerType:res['data']['partnerType'], partnerType:res['data']['partnerType'],
nickName:res['data']['nickName'], nickName:res['data']['nickName'],
levelCode:res['data']['levelCode'],
} }
uni.setStorageSync('cffp_userInfo', JSON.stringify(cffp_userInfo)) uni.setStorageSync('cffp_userInfo', JSON.stringify(cffp_userInfo))
......
<template>
<view class="container">
<!-- 添加ref以便获取DOM -->
<view class="imgBox" ref="captureElement" v-if="!generatedImage">
<view class="imgContainer">
<image
style="display: block;"
src="@/static/registerBg.png"
mode="widthFix"
@load="handleBgImageLoad"
@error="handleBgImageError"
></image>
<!-- 二维码容器 -->
<view class="qrcode-container">
<canvas
canvas-id="qrcode"
class="qrcode-canvas"
:style="{width: qrcodeSize + 'px', height: qrcodeSize + 'px'}"
></canvas>
</view>
</view>
</view>
<!-- 添加生成的图片预览 -->
<view class="preview-container" v-if="generatedImage">
<image :src="generatedImage" mode="widthFix" class="preview-image"></image>
</view>
<view class="bottomBox">
长按保存图片分享给好友
</view>
</view>
</template>
<script>
import * as environment from "@/environments/environment";
import UQRCode from 'uqrcodejs';
import { elementToImage } from '@/util/htmlToImage';
import registerBg from '@/static/registerBg.png'
export default {
data() {
return {
companyInfo: environment.companyInfo,
imgType: environment.companyInfo.imgType,
baseURL: environment.baseURL,
shareId: '',
invitationCode: '',
codeUrl: '',
loginType: '',
userInfo: {},
qrcodeSize: 100, // 二维码大小(单位px)
generatedImage: '', // 生成的图片
isBgImageReady: false, // 背景图片是否准备就绪
retryCount: 0, // 重试次数
maxRetryCount: 3 // 最大重试次数
}
},
onShow() {
this.userInfo = uni.getStorageSync('userinfodataForm')
this.loginType = uni.getStorageSync('loginType')
if(!this.loginType || this.loginType == 'visitor'){
this.codeUrl = `${this.baseURL}/pages/index/index`
} else {
this.codeUrl = `${this.baseURL}/pages/index/index?invitationCode=${this.userInfo.invitationCode}&inviteUserId=${this.userInfo.userId}`
}
// 1. 首先检查是否有缓存的图片
const cachedImage = uni.getStorageSync('savedShareImage');
if (cachedImage) {
this.generatedImage = cachedImage;
return; // 如果有缓存,直接使用,不再执行后续生成逻辑
}
// 重置状态
this.isBgImageReady = false;
this.retryCount = 0;
// 开始加载背景图片(由@load事件触发后续流程)
console.log('开始加载背景图片...');
},
methods: {
// 背景图片加载成功
handleBgImageLoad() {
console.log('背景图片加载完成');
this.isBgImageReady = true;
this.generateQrcodeAndCapture();
},
// 背景图片加载失败
handleBgImageError() {
console.error('背景图片加载失败');
// 即使失败也继续流程,可能会有默认背景
this.isBgImageReady = true;
this.generateQrcodeAndCapture();
},
// 顺序执行:生成二维码 -> 截图
async generateQrcodeAndCapture() {
// 再次检查缓存(防止并发情况)
if (uni.getStorageSync('savedShareImage')) {
return;
}
try {
uni.showLoading({
title: '准备生成分享图...'
});
// 1. 先生成二维码
console.log('开始生成二维码...');
await this.makeQrcode();
console.log('二维码生成完成');
// 2. 等待500ms确保渲染完成
await new Promise(resolve => setTimeout(resolve, 500));
// 3. 执行截图
console.log('开始截图...');
await this.captureImage();
console.log('截图完成');
uni.hideLoading();
} catch (error) {
console.error('生成分享图失败:', error);
uni.hideLoading();
this.retryGenerate();
}
},
// 重试机制
retryGenerate() {
if (this.retryCount < this.maxRetryCount) {
this.retryCount++;
const delay = 1000 * this.retryCount;
console.log(`第${this.retryCount}次重试,${delay}ms后重试...`);
uni.showToast({
title: `生成中,请稍候...(${this.retryCount}/${this.maxRetryCount})`,
icon: 'none',
duration: delay
});
setTimeout(() => {
this.generateQrcodeAndCapture();
}, delay);
} else {
uni.showToast({
title: '生成分享图失败,请稍后再试',
icon: 'none'
});
}
},
// 生成二维码
makeQrcode() {
return new Promise((resolve, reject) => {
// 创建实例
const qr = new UQRCode();
// 设置二维码内容
qr.data = this.codeUrl;
// 设置二维码大小
qr.size = this.qrcodeSize;
// 设置前景色(二维码颜色)
qr.foregroundColor = '#000000';
// 设置背景色
qr.backgroundColor = '#FFFFFF';
// 设置边距
qr.margin = 10;
// 设置纠错等级
qr.errorCorrectLevel = UQRCode.errorCorrectLevel.H;
try {
// 调用制作二维码方法
qr.make();
// 获取canvas上下文
const ctx = uni.createCanvasContext('qrcode', this);
// 清空画布
ctx.clearRect(0, 0, this.qrcodeSize, this.qrcodeSize);
// 将二维码绘制到canvas上
qr.canvasContext = ctx;
qr.drawCanvas();
// 绘制完成
ctx.draw(true, () => {
console.log('二维码绘制完成');
resolve();
});
} catch (err) {
reject(err);
}
});
},
// 截图方法
async captureImage() {
try {
uni.showLoading({
title: '正在生成图片...'
});
// 获取DOM元素(在H5环境下)
const element = this.$refs.captureElement.$el;
// 调用工具函数生成图片
const imageData = await elementToImage(element);
this.generatedImage = imageData;
// 将生成的图片保存到本地存储
uni.setStorageSync('savedShareImage', imageData);
} catch (error) {
console.error('截图失败:', error);
throw error; // 抛出错误以便外部捕获
} finally {
uni.hideLoading();
}
},
saveImage(base64Data) {
// 实现保存图片到相册的逻辑
// 注意:在H5中可能需要不同的处理方式
if (uni.downloadFile) {
uni.downloadFile({
url: base64Data,
success: (res) => {
if (res.statusCode === 200) {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
uni.showToast({
title: '保存成功',
icon: 'success'
});
},
fail: (err) => {
console.error('保存失败:', err);
uni.showToast({
title: '保存失败',
icon: 'none'
});
}
});
}
},
fail: (err) => {
console.error('下载图片失败:', err);
}
});
} else {
// H5环境下的处理方式
const link = document.createElement('a');
link.href = base64Data;
link.download = 'share-image.png';
link.click();
}
}
}
}
</script>
<style lang="scss" scoped>
.container {
display: flex;
flex-direction: column;
.imgBox {
position: relative;
.imgContainer {
position: relative;
.qrcode-container {
position: absolute;
bottom: 10rpx;
right: 10rpx;
background: #fff;
padding: 10rpx;
border-radius: 10rpx;
box-shadow: 0 0 10rpx rgba(0,0,0,0.1);
.qrcode-canvas {
display: block;
}
}
}
}
.bottomBox {
// flex: 1;
border-radius: 20rpx 20rpx 0 0;
height: 300rpx;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-color: #fff;
font-size: 28rpx;
button {
margin-top: 20rpx;
}
}
.preview-container{
box-sizing: border-box;
flex: 1;
padding: 30rpx;
.preview-image {
width: 100%;
}
}
}
</style>
\ No newline at end of file
...@@ -15,11 +15,18 @@ ...@@ -15,11 +15,18 @@
] ]
}, },
"dependencies": { "dependencies": {
"@dcloudio/uni-ui": "^1.5.10",
"@uqrcode/js": "^4.0.7",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"echarts": "^5.4.1", "echarts": "^5.4.1",
"html2canvas": "^1.4.1",
"js-sha256": "^0.11.1", "js-sha256": "^0.11.1",
"nanoid": "^4.0.0" "mp-painter": "^1.0.1",
"nanoid": "^4.0.0",
"qrcode": "^1.5.4",
"qrcodejs2": "^0.0.2",
"uqrcodejs": "^4.0.7"
}, },
"devDependencies": { "devDependencies": {
"less": "^4.3.0" "less": "^4.3.0"
......
...@@ -539,6 +539,11 @@ ...@@ -539,6 +539,11 @@
"style": { "style": {
"navigationBarTitleText": "协议" "navigationBarTitleText": "协议"
} }
},{
"path": "poster/poster",
"style": {
"navigationBarTitleText": "分销海报"
}
} }
] ]
},{ },{
......
...@@ -317,6 +317,7 @@ ...@@ -317,6 +317,7 @@
this.continueShare() this.continueShare()
}, },
continueShare(){ continueShare(){
this.userId = uni.getStorageSync('cffp_userId')
const shareCode = nanoid() + this.userId const shareCode = nanoid() + this.userId
const jumptime = Date.parse(new Date()) / 1000 const jumptime = Date.parse(new Date()) / 1000
//app分享 //app分享
...@@ -626,7 +627,7 @@ ...@@ -626,7 +627,7 @@
//this.courseInfo.serviceContent = res['data']['data']['filePathOss']; //this.courseInfo.serviceContent = res['data']['data']['filePathOss'];
this.lecturerId = res['data']['data']['fileLecturerId']; this.lecturerId = res['data']['data']['fileLecturerId'];
// this.lecturerQuery(); // this.lecturerQuery();
this.relatedCoursesList(); // this.relatedCoursesList();
if (uni.getStorageSync('h5_coursesharing')) { if (uni.getStorageSync('h5_coursesharing')) {
this.coursesharing = uni.getStorageSync('h5_coursesharing') this.coursesharing = uni.getStorageSync('h5_coursesharing')
this.getshareData() this.getshareData()
...@@ -1028,7 +1029,7 @@ ...@@ -1028,7 +1029,7 @@
if(uni.getStorageSync('cffp_userInfo')){ if(uni.getStorageSync('cffp_userInfo')){
this.userInfo = JSON.parse(uni.getStorageSync('cffp_userInfo')) this.userInfo = JSON.parse(uni.getStorageSync('cffp_userInfo'))
} }
this.userId = uni.getStorageSync('cffp_userId')
}, },
mounted() { mounted() {
let _this = this; let _this = this;
......
...@@ -173,7 +173,7 @@ ...@@ -173,7 +173,7 @@
if(uni.getStorageSync('cffp_userInfo')){ if(uni.getStorageSync('cffp_userInfo')){
this.userInfo = JSON.parse(uni.getStorageSync('cffp_userInfo')) this.userInfo = JSON.parse(uni.getStorageSync('cffp_userInfo'))
} }
this.userId = uni.getStorageSync('cffp_userId')
}, },
created(){ created(){
this.queryName = uni.getStorageSync('queryName') || ''; this.queryName = uni.getStorageSync('queryName') || '';
...@@ -255,7 +255,7 @@ ...@@ -255,7 +255,7 @@
this.continueShare() this.continueShare()
}, },
continueShare(){ continueShare(){
console.log('this.shareItem',this.shareItem); this.userId = uni.getStorageSync('cffp_userId')
const shareCode = nanoid() + this.userId const shareCode = nanoid() + this.userId
const jumptime = Date.parse(new Date()) / 1000 const jumptime = Date.parse(new Date()) / 1000
//app分享 //app分享
...@@ -416,7 +416,8 @@ ...@@ -416,7 +416,8 @@
const cffp_userInfo = { const cffp_userInfo = {
name: res['data']['userReName'], name: res['data']['userReName'],
mobile: res['data']['mobile'], mobile: res['data']['mobile'],
partnerType:res['data']['partnerType'] partnerType:res['data']['partnerType'],
levelCode:res['data']['levelCode'],
} }
uni.setStorageSync('cffp_userInfo', JSON.stringify(cffp_userInfo)); uni.setStorageSync('cffp_userInfo', JSON.stringify(cffp_userInfo));
this.fileUploadItemCFFPList = uni.getStorageSync('fileUploadItemCFFPList'); this.fileUploadItemCFFPList = uni.getStorageSync('fileUploadItemCFFPList');
......
...@@ -196,9 +196,21 @@ ...@@ -196,9 +196,21 @@
</view> </view>
</view> </view>
</uni-popup> </uni-popup>
<!-- 不是新锐合伙人提示 -->
<partner-tip-popup
ref="PartnerTipPopup"
content="您还未升级为新锐合伙人,团队成员销售成功您不能拿到收益。购买产品升级为新锐合伙人 "
joinText="暂不升级,继续邀请"
continueText="购买产品,立即升级"
btnType="vertical"
:tipIcon="true"
@join="jumpPage('2')"
@continue="jumpPage('1')"
/>
</template> </template>
<script> <script>
import { initJssdkShare, setWechatShare } from '@/util/fiveshare';
import dataHandling from "@/util/dataHandling"; import dataHandling from "@/util/dataHandling";
import courselist from '@/pages/courselist/courselist.vue'; import courselist from '@/pages/courselist/courselist.vue';
import api from "../../api/api"; import api from "../../api/api";
...@@ -206,8 +218,10 @@ ...@@ -206,8 +218,10 @@
import carousel from '@/components/carousel/carousel.vue'; import carousel from '@/components/carousel/carousel.vue';
import search from '@/components/search/search.vue'; import search from '@/components/search/search.vue';
import courseItem from "@/components/courseItem/courseItem.vue"; import courseItem from "@/components/courseItem/courseItem.vue";
import {companyInfo} from "@/environments/environment"; import {companyInfo,baseURL,shareURL} from "@/environments/environment";
import JoinPopup from '@/components/commonPopup/JoinPopup.vue'; import JoinPopup from '@/components/commonPopup/JoinPopup.vue';
import PartnerTipPopup from "@/components/commonPopup/PartnerTipPopup.vue";
import {hshare} from '@/util/fiveshare';
export default { export default {
data() { data() {
return { return {
...@@ -267,7 +281,8 @@ ...@@ -267,7 +281,8 @@
loginornot: true, loginornot: true,
queryName: '', queryName: '',
loginType : uni.getStorageSync('loginType'), loginType : uni.getStorageSync('loginType'),
userInfo:{} //用户信息 userInfo:{} ,//用户信息,
productItem:{}
} }
}, },
components: { components: {
...@@ -276,11 +291,11 @@ ...@@ -276,11 +291,11 @@
carousel, carousel,
search, search,
courseItem, courseItem,
JoinPopup JoinPopup,
PartnerTipPopup
}, },
onShow() { onShow() {
console.log('11111');
this.loginType = uni.getStorageSync('loginType') this.loginType = uni.getStorageSync('loginType')
this.init(); this.init();
this.showSearch = false; this.showSearch = false;
...@@ -292,9 +307,11 @@ ...@@ -292,9 +307,11 @@
this.userInfo = JSON.parse(uni.getStorageSync('cffp_userInfo')) this.userInfo = JSON.parse(uni.getStorageSync('cffp_userInfo'))
} }
if(uni.getStorageSync('cffp_userId')){ if(uni.getStorageSync('cffp_userId')){
this.userInfo = uni.getStorageSync('cffp_userId') this.userId = uni.getStorageSync('cffp_userId')
} }
//分享
this.initShare();
}, },
onLoad(options) { onLoad(options) {
...@@ -314,6 +331,36 @@ ...@@ -314,6 +331,36 @@
uni.$off('loginUpdate', this.queryAreaCenterInfo); uni.$off('loginUpdate', this.queryAreaCenterInfo);
}, },
methods: { methods: {
// 初始化首页分享 注意sdk时序问题。传递的url一定要是当前页面的url window.location.href.split('#')[0]
// 不是window.location.href.split('#')[0]很可能不成功
initShare() {
// #ifdef H5
const currentUrl = `${shareURL}/pages/index/index`;
const shareData = {
title: '银盾家办家庭财务策划师联盟邀您加入',
desc: `${this.userInfo.name || ''}邀您加入【家庭财策师联盟】,资源+伙伴,共赢未来!`,
link: currentUrl,
imgUrl: `${shareURL}/static/images/shareBg.png`
};
initJssdkShare(() => {
setWechatShare(shareData);
}, window.location.href.split('#')[0]);
// #endif
},
jumpPage(type){
console.log('type',type);
if(type=='1'){
uni.navigateTo({
url:'/pages/inviteJoin/inviteJoin'
})
}else {
uni.navigateTo({
url: `/pages/courseDetail/courseDetail?fileId=${this.productItem.fileId}`
});
}
},
queryInfo() { queryInfo() {
api.queryInfo({userId:uni.getStorageSync('cffp_userId')}).then(res=>{ api.queryInfo({userId:uni.getStorageSync('cffp_userId')}).then(res=>{
...@@ -324,6 +371,7 @@ ...@@ -324,6 +371,7 @@
mobile: res['data']['mobile'], mobile: res['data']['mobile'],
partnerType:res['data']['partnerType'], partnerType:res['data']['partnerType'],
nickName:res['data']['nickName'], nickName:res['data']['nickName'],
levelCode:res['data']['levelCode'],
} }
uni.setStorageSync('cffp_userInfo', JSON.stringify(cffp_userInfo)); uni.setStorageSync('cffp_userInfo', JSON.stringify(cffp_userInfo));
console.log('cffp_userInfo.partnerType',cffp_userInfo.partnerType); console.log('cffp_userInfo.partnerType',cffp_userInfo.partnerType);
...@@ -419,7 +467,6 @@ ...@@ -419,7 +467,6 @@
}, },
] ]
console.log('this.featureLists',this.featureLists);
uni.removeTabBarBadge({ index: 3 }); uni.removeTabBarBadge({ index: 3 });
} }
if (uni.getStorageSync('isLogin')) { if (uni.getStorageSync('isLogin')) {
...@@ -512,6 +559,10 @@ ...@@ -512,6 +559,10 @@
if(this.cffpCourseInfos.length>0){ if(this.cffpCourseInfos.length>0){
this.cffpCourseInfos.forEach(item=>{ this.cffpCourseInfos.forEach(item=>{
item.coursePrice =Number(item.coursePrice).toFixed(2) || '0.00' item.coursePrice =Number(item.coursePrice).toFixed(2) || '0.00'
if(item.productCode&&item.productCode=='C09'){
this.productItem = item
uni.setStorageSync('productItem',this.productItem );
}
}) })
} }
} }
...@@ -560,23 +611,12 @@ ...@@ -560,23 +611,12 @@
this.getPartnerStatistic() this.getPartnerStatistic()
return return
} }
//当为见习合伙人的时候,弹出框提示
if (this.loginornot == false && featureItem.name != "学习认证" && featureItem.name != "更多功能") { if(featureItem.key == '04'&& this.userInfo.levelCode == 'P1'){
dataHandling.pocessTracking( this.$refs.PartnerTipPopup.open()
'点击', return
`用户在首页未登录时点击${featureItem.name}`,
'点击',
2,
'首页',
'pages/index/index'
)
uni.clearStorageSync();
uni.setStorageSync('loginType','visitor');
uni.redirectTo({
url: '/myPackageA/login/login?from=index'
})
return
} }
if (featureItem.key == '02') { if (featureItem.key == '02') {
if (this.cffpUserInfo.levelCode == 'C1' || this.cffpUserInfo.levelCode == 'C2' || this.cffpUserInfo if (this.cffpUserInfo.levelCode == 'C1' || this.cffpUserInfo.levelCode == 'C2' || this.cffpUserInfo
.levelCode == 'C3') { .levelCode == 'C3') {
...@@ -676,7 +716,6 @@ ...@@ -676,7 +716,6 @@
this.old.y = e.detail.y this.old.y = e.detail.y
} }
}, },
mounted() {}
} }
</script> </script>
......
...@@ -60,6 +60,11 @@ ...@@ -60,6 +60,11 @@
</view> </view>
</view> </view>
<view class="shareBtn" :class="{'shareBottom':shareBtnBottom}" @click="gotoPoster">
<view class="shareBox">
分享给好友
</view>
</view>
</view> </view>
<!-- 使用封装后的弹窗组件 --> <!-- 使用封装后的弹窗组件 -->
<join-popup <join-popup
...@@ -146,6 +151,17 @@ ...@@ -146,6 +151,17 @@
</view> </view>
</view> </view>
</uni-popup> </uni-popup>
<!-- 不是新锐合伙人提示 -->
<partner-tip-popup
ref="PartnerTipPopup"
content="您还未升级为新锐合伙人,团队成员销售成功您不能拿到收益。购买产品升级为新锐合伙人 "
joinText="暂不升级,继续邀请"
continueText="购买产品,立即升级"
btnType="vertical"
:tipIcon="true"
@join="jumpPage('2')"
@continue="jumpPage('1')"
/>
</view> </view>
</template> </template>
...@@ -157,7 +173,7 @@ ...@@ -157,7 +173,7 @@
import {companyInfo} from "@/environments/environment"; import {companyInfo} from "@/environments/environment";
import FloatingButton from '@/components/FloatingButton/FloatingButton.vue'; import FloatingButton from '@/components/FloatingButton/FloatingButton.vue';
import JoinPopup from '@/components/commonPopup/JoinPopup.vue'; import JoinPopup from '@/components/commonPopup/JoinPopup.vue';
import PartnerTipPopup from "@/components/commonPopup/PartnerTipPopup.vue";
export default { export default {
data() { data() {
return { return {
...@@ -186,7 +202,7 @@ ...@@ -186,7 +202,7 @@
{id:'01',categoryName:'团队', {id:'01',categoryName:'团队',
children:[ children:[
{title:'申请加盟',icon:'icon-hezuo',link:'/myPackageA/applyFranchise/applyFranchise?',isOpen:true,isShow:true,isApply:true}, {title:'申请加盟',icon:'icon-hezuo',link:'/myPackageA/applyFranchise/applyFranchise?',isOpen:true,isShow:true,isApply:true},
{title:'邀请加盟',icon:'icon-yaoqing',link:'/pages/inviteJoin/inviteJoin',isOpen:true,isShow:true,identity: true}, {key:'06',title:'邀请加盟',icon:'icon-yaoqing',link:'/pages/inviteJoin/inviteJoin',isOpen:true,isShow:true,identity: true},
{title:'我的团队',icon:'icon-tuandui',link:'/pages/personalCenter/myTeam',isOpen:true,isShow:true,identity: true}, {title:'我的团队',icon:'icon-tuandui',link:'/pages/personalCenter/myTeam',isOpen:true,isShow:true,identity: true},
{title:'育成团队',icon:'icon-yuchengguanxi',link:'/pages/personalCenter/myTeamIncubate',isOpen:true,isShow:true,identity: true}, {title:'育成团队',icon:'icon-yuchengguanxi',link:'/pages/personalCenter/myTeamIncubate',isOpen:true,isShow:true,identity: true},
], ],
...@@ -206,12 +222,15 @@ ...@@ -206,12 +222,15 @@
{title:'我的消息',icon:'message',link:'/pages/systemMsg/system_msg',isOpen:true,isShow:true,islogin:true}, {title:'我的消息',icon:'message',link:'/pages/systemMsg/system_msg',isOpen:true,isShow:true,islogin:true},
{title:'系统设置',icon:'setting',link:'/pages/personalCenter/system/settings',isOpen:true,isShow:true} {title:'系统设置',icon:'setting',link:'/pages/personalCenter/system/settings',isOpen:true,isShow:true}
], ],
partnerStatistic:{} //申请加盟统计 partnerStatistic:{}, //申请加盟统计
userInfo:{} ,//用户信息,
shareBtnBottom:false
} }
}, },
components:{ components:{
tabBar, tabBar,
JoinPopup JoinPopup,
PartnerTipPopup
}, },
onShow() { onShow() {
this.loginType = uni.getStorageSync('loginType') this.loginType = uni.getStorageSync('loginType')
...@@ -247,9 +266,41 @@ ...@@ -247,9 +266,41 @@
// 移除监听事件 // 移除监听事件
uni.$off('handClick'); uni.$off('handClick');
}); });
if(uni.getStorageSync('cffp_userInfo')){
this.userInfo = JSON.parse(uni.getStorageSync('cffp_userInfo'))
}
// #ifdef H5
this.shareBtnBottom = dataHandling.isNotchIOS()
// #endif
}, },
methods: { methods: {
gotoPoster(){
if(!uni.getStorageSync('loginType') || uni.getStorageSync('loginType')=='visitor'){
uni.setStorageSync('loginType','visitor')
uni.setStorageSync('islogin','1')
}
uni.navigateTo({
url:'/myPackageA/poster/poster?from=personalCenter'
})
},
jumpPage(type){
if(type=='1'){
uni.navigateTo({
url:'/pages/inviteJoin/inviteJoin'
})
}else {
let productItem = {}
if(uni.getStorageSync('productItem')){
productItem = JSON.parse(JSON.stringify(uni.getStorageSync('productItem')))
}
uni.navigateTo({
url: `/pages/courseDetail/courseDetail?fileId=${productItem.fileId}`
});
}
},
getPartnerStatistic(){ getPartnerStatistic(){
if(this.userId){ if(this.userId){
api.partnerStatisticsGrade({userId:uni.getStorageSync('cffp_userId')}).then((res)=>{ api.partnerStatisticsGrade({userId:uni.getStorageSync('cffp_userId')}).then((res)=>{
...@@ -340,6 +391,9 @@ ...@@ -340,6 +391,9 @@
}, },
// 菜单跳转页面 // 菜单跳转页面
goDetail(item){ goDetail(item){
if(uni.getStorageSync('cffp_userInfo')){
this.userInfo = JSON.parse(uni.getStorageSync('cffp_userInfo'))
}
if(item.isApply&&(!uni.getStorageSync('loginType')||uni.getStorageSync('loginType')=='visitor')){ if(item.isApply&&(!uni.getStorageSync('loginType')||uni.getStorageSync('loginType')=='visitor')){
dataHandling.pocessTracking( dataHandling.pocessTracking(
'申请加盟', '申请加盟',
...@@ -405,6 +459,11 @@ ...@@ -405,6 +459,11 @@
return return
} }
//当为见习合伙人的时候,弹出框提示
if(item.key == '06'&& this.userInfo.levelCode == 'P1'){
this.$refs.PartnerTipPopup.open()
return
}
// 说明不需要登录就可以进 // 说明不需要登录就可以进
if(item.isLogin){ if(item.isLogin){
dataHandling.pocessTracking( dataHandling.pocessTracking(
...@@ -702,6 +761,25 @@ ...@@ -702,6 +761,25 @@
.kuaiBox:last-child{ .kuaiBox:last-child{
margin-bottom: 100rpx; margin-bottom: 100rpx;
} }
.shareBtn{
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 50rpx 0rpx;
.shareBox{
background-color: #20269B;
color: #fff;
font-size: 30rpx;
border-radius: 60rpx;
width: 50%;
text-align: center;
padding: 20rpx 30rpx;
}
}
.shareBottom{
padding-bottom: 100rpx;
}
} }
.joinContent{ .joinContent{
width: 500rpx; width: 500rpx;
...@@ -814,5 +892,31 @@ ...@@ -814,5 +892,31 @@
} }
} }
} }
/* 新增:iPad mini 和 iPad Air 竖屏的媒体查询 */
/* iPad mini 竖屏 */
@media only screen
and (min-device-width: 768px)
and (max-device-width: 1024px)
and (orientation: portrait)
and (-webkit-min-device-pixel-ratio: 1) {
.container {
.myContent .shareBtn{
padding-bottom: 120rpx;
}
}
}
/* iPad Air 竖屏 */
@media only screen
and (min-device-width: 820px)
and (max-device-width: 1180px)
and (orientation: portrait)
and (-webkit-min-device-pixel-ratio: 1) {
.container {
.myContent .shareBtn{
padding-bottom: 120rpx !important;
}
}
}
</style> </style>
...@@ -364,4 +364,33 @@ export default{ ...@@ -364,4 +364,33 @@ export default{
} }
}) })
}, },
/**
* 检测是否是iOS刘海屏设备
* @returns {boolean} true-是刘海屏, false-不是刘海屏
*/
isNotchIOS() {
// 1. 先判断是否是iOS设备
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) ||
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
if (!isIOS) return false;
// 2. 通过CSS环境变量检测
// 创建一个测试元素
const div = document.createElement('div');
div.style.position = 'fixed';
div.style.top = '0';
div.style.left = '0';
div.style.width = '1px';
div.style.height = 'constant(safe-area-inset-top)'; // 兼容旧版iOS
div.style.height = 'env(safe-area-inset-top)'; // 新版iOS
document.body.appendChild(div);
// 获取计算的高度值
const computedHeight = parseInt(window.getComputedStyle(div).height, 10);
document.body.removeChild(div);
// 3. 刘海屏设备safe-area-inset-top通常大于20(非刘海屏为0)
return computedHeight > 20;
}
} }
\ No newline at end of file
...@@ -2,49 +2,113 @@ ...@@ -2,49 +2,113 @@
// import $H from '@/api/request.js' //封装好的接口请求 // import $H from '@/api/request.js' //封装好的接口请求
// import wx from 'weixin-js-sdk' // import wx from 'weixin-js-sdk'
import { log } from "console";
import api from "../api/api"; import api from "../api/api";
//初始化 //初始化
export function initJssdkShare(callback, url) { // export function initJssdkShare(callback, url) {
var url = url // console.log('签名',url);
// var url = url
//这一步需要调用后台接口,返回需要的签名 签名时间戳 随机串 和公众号appid // //这一步需要调用后台接口,返回需要的签名 签名时间戳 随机串 和公众号appid
//注意url:window.location.href.split('#')[0] // // //注意url:window.location.href.split('#')[0] //
// request.post("", { // // request.post("", {
// url // url是当前页面的url // // url // url是当前页面的url
// }, // // },
let WxConfigRequestVO = { // let WxConfigRequestVO = {
url:url, // url:url,
systemType:uni.getStorageSync('addSystemType')?uni.getStorageSync('addSystemType'):'1' // systemType:uni.getStorageSync('addSystemType')?uni.getStorageSync('addSystemType'):'1'
} // }
// @ts-ignore // // @ts-ignore
api.Wxshare(WxConfigRequestVO).then(res => { // api.Wxshare(WxConfigRequestVO).then(res => {
jWeixin.config({ // jWeixin.config({
debug: false,//调试的时候需要 在app上回弹出errmg:config ok 的时候就证明没问题了 这时候就可以改为false // debug: false,//调试的时候需要 在app上回弹出errmg:config ok 的时候就证明没问题了 这时候就可以改为false
appId: res.data.appId,//appid // appId: res.data.appId,//appid
timestamp: res.data.timestamp,//时间戳 // timestamp: res.data.timestamp,//时间戳
nonceStr: res.data.nonceStr,//随机串 // nonceStr: res.data.nonceStr,//随机串
signature: res.data.signature,//签名 // signature: res.data.signature,//签名
jsApiList: res.data.jsApiList//必填 是下面需要用到的方法集合 // jsApiList: res.data.jsApiList//必填 是下面需要用到的方法集合
}) // })
if(callback){ // if(callback){
callback() // callback()
} // }
}) // })
} // }
// 初始化SDK
export function initJssdkShare(callback, url) {
const WxConfigRequestVO = {
url: url,
systemType: uni.getStorageSync('addSystemType') || '1'
};
api.Wxshare(WxConfigRequestVO).then(res => {
jWeixin.config({
debug: false, // 生产环境关闭调试
appId: res.data.appId,
timestamp: res.data.timestamp,
nonceStr: res.data.nonceStr,
signature: res.data.signature,
jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData', 'onMenuShareAppMessage']
});
jWeixin.ready(() => {
console.log('微信SDK初始化完成');
if (callback) callback();
});
jWeixin.error(err => {
console.error('微信SDK初始化失败', err);
});
});
}
// 设置微信分享内容(不自动覆盖,需手动调用)
export function setWechatShare(data) {
if (!jWeixin) {
console.error('微信SDK未初始化');
return;
}
jWeixin.ready(() => {
// 新API(推荐)
jWeixin.updateAppMessageShareData({
title: data.title,
desc: data.desc,
link: data.link,
imgUrl: data.imgUrl,
success: () => console.log('好友分享设置成功')
});
jWeixin.updateTimelineShareData({
title: data.title,
link: data.link,
imgUrl: data.imgUrl,
success: () => console.log('朋友圈分享设置成功')
});
// 旧API(兼容)
jWeixin.onMenuShareAppMessage({
title: data.title,
desc: data.desc,
link: data.link,
imgUrl: data.imgUrl,
success: () => console.log('旧版分享设置成功')
});
});
}
// data是穿的参数 url是当前页面的链接 // data是穿的参数 url是当前页面的链接
export function hshare(data,url){ export function hshare(data,url){
console.log('data,url',data,url); console.log('data,url',data,url);
// 确保分享的链接不包含时间戳 // 确保分享的链接不包含时间戳
const cleanLink = data.link.split('&t_reload=')[0];
// initJssdkShare(data, url) // initJssdkShare(data, url)
initJssdkShare(function(){ initJssdkShare(function(){
jWeixin.ready(function(){ jWeixin.ready(function(){
var sharedata={ var sharedata={
title: data.title, //标题 title: data.title, //标题
desc: data.desc, //描述 desc: data.desc, //描述
link: cleanLink ,//分享链接 link: data.link ,//分享链接
imgUrl:data.imgUrl, //图片 imgUrl:data.imgUrl, //图片
success:(res=>{ success:(res=>{
}) })
...@@ -54,4 +118,5 @@ export function initJssdkShare(callback, url) { ...@@ -54,4 +118,5 @@ export function initJssdkShare(callback, url) {
jWeixin.onMenuShareAppMessage(sharedata);//获取“分享给朋友”按钮点击状态及自定义分享内容接口(即将废弃) jWeixin.onMenuShareAppMessage(sharedata);//获取“分享给朋友”按钮点击状态及自定义分享内容接口(即将废弃)
}) })
},url) },url)
} }
\ No newline at end of file
\ No newline at end of file
import html2canvas from 'html2canvas';
/**
* 将DOM元素转换为图片
* @param {HTMLElement} element DOM元素
* @param {Object} options html2canvas配置选项
* @returns {Promise<string>} 返回图片的base64数据
*/
export const elementToImage = async (element, options = {}) => {
try {
// 默认配置
const defaultOptions = {
backgroundColor: null, // 透明背景
scale: 2, // 提高缩放以获得更清晰的图片
useCORS: true, // 允许跨域图片
allowTaint: true, // 允许污染图片
logging: false // 关闭日志
};
const canvas = await html2canvas(element, { ...defaultOptions, ...options });
return canvas.toDataURL('image/png');
} catch (error) {
console.error('生成图片失败:', error);
throw error;
}
};
\ No newline at end of file
...@@ -12,7 +12,8 @@ const whiteList = [ ...@@ -12,7 +12,8 @@ const whiteList = [
'/pages/courseDetail/courseDetail', '/pages/courseDetail/courseDetail',
'/pages/courselist/courselist', '/pages/courselist/courselist',
'/pages/personalCenter/helpCenter', '/pages/personalCenter/helpCenter',
'/pages/index/index' '/pages/index/index',
'/myPackageA/poster/poster',
] ]
export default function initApp(){ export default function initApp(){
let date = Date.now() let date = Date.now()
...@@ -21,10 +22,10 @@ export default function initApp(){ ...@@ -21,10 +22,10 @@ export default function initApp(){
uni.addInterceptor('navigateTo', { uni.addInterceptor('navigateTo', {
// 页面跳转前进行拦截, invoke根据返回值进行判断是否继续执行跳转 // 页面跳转前进行拦截, invoke根据返回值进行判断是否继续执行跳转
invoke (e) { invoke (e) {
console.log(e);
let pages = getCurrentPages() let pages = getCurrentPages()
let pagesLength = pages.length let pagesLength = pages.length
console.log('11111',whiteList.indexOf(e.url)==-1&&!uni.getStorageSync('loginType'));
if(whiteList.indexOf(e.url)==-1&&!uni.getStorageSync('loginType')){ if(whiteList.indexOf(e.url)==-1&&!uni.getStorageSync('loginType')){
uni.clearStorageSync(); uni.clearStorageSync();
uni.setStorageSync('loginType','visitor') uni.setStorageSync('loginType','visitor')
...@@ -50,6 +51,7 @@ export default function initApp(){ ...@@ -50,6 +51,7 @@ export default function initApp(){
mobile: res['data']['mobile'], mobile: res['data']['mobile'],
partnerType:res['data']['partnerType'], partnerType:res['data']['partnerType'],
nickName:res['data']['nickName'], nickName:res['data']['nickName'],
levelCode:res['data']['levelCode'],
} }
uni.setStorageSync('cffp_userInfo', JSON.stringify(cffp_userInfo)) uni.setStorageSync('cffp_userInfo', JSON.stringify(cffp_userInfo))
} }
...@@ -66,6 +68,7 @@ export default function initApp(){ ...@@ -66,6 +68,7 @@ export default function initApp(){
}; };
const fromParam = getQueryParam(e.url, 'from'); const fromParam = getQueryParam(e.url, 'from');
console.log('2222',!hasPermission(e.url));
if(!hasPermission(e.url)){ if(!hasPermission(e.url)){
// 如果 from 参数在 whiteArr 中,说明是tabbar页带着tabbar的标志参数跳转到登录页,以便未登录状态下回到对应的tabbar页 // 如果 from 参数在 whiteArr 中,说明是tabbar页带着tabbar的标志参数跳转到登录页,以便未登录状态下回到对应的tabbar页
if (fromParam && whiteArr.includes(fromParam)) { if (fromParam && whiteArr.includes(fromParam)) {
...@@ -73,6 +76,7 @@ export default function initApp(){ ...@@ -73,6 +76,7 @@ export default function initApp(){
url: `/myPackageA/login/login?from=${fromParam}` url: `/myPackageA/login/login?from=${fromParam}`
}) })
}else { }else {
console.log('zoujinlaile');
uni.redirectTo({ uni.redirectTo({
url: '/myPackageA/login/login' url: '/myPackageA/login/login'
}) })
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment