程序二维码分享组件, 支持传入color,bgColor,disabled

This commit is contained in:
wkj 2025-09-13 20:48:27 +08:00
parent d89a0a27ef
commit 256f0eefa1
30 changed files with 345 additions and 70 deletions

View File

@ -11,5 +11,8 @@ declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends key
url: string; url: string;
loading?: boolean | undefined; loading?: boolean | undefined;
disableDownload?: boolean | undefined; disableDownload?: boolean | undefined;
disabled: boolean;
color: string;
bgColor: string;
}>) => React.ReactElement; }>) => React.ReactElement;
export default _default; export default _default;

View File

@ -13,10 +13,13 @@ export default OakComponent({
disableDownload: false, disableDownload: false,
onRefresh: undefined, onRefresh: undefined,
onDownload: undefined, onDownload: undefined,
disabled: false,
color: '#000000',
bgColor: '#ffffff',
}, },
lifetimes: { lifetimes: {
ready() { ready() {
const { url, size } = this.props; const { url, size, color, bgColor } = this.props;
if (process.env.OAK_PLATFORM === 'wechatMp') { if (process.env.OAK_PLATFORM === 'wechatMp') {
const isBase64 = /data:image\/[\w|\W]+(;base64,)[\w|\W]*/.test(url); const isBase64 = /data:image\/[\w|\W]+(;base64,)[\w|\W]*/.test(url);
if (isBase64) { if (isBase64) {
@ -26,6 +29,8 @@ export default OakComponent({
typeNumber: 4, // 密度 typeNumber: 4, // 密度
errorCorrectLevel: 'L', // 纠错等级 errorCorrectLevel: 'L', // 纠错等级
size: size, // 白色边框 size: size, // 白色边框
color,
bgColor
}); });
this.setState({ this.setState({
qrcodeURL, qrcodeURL,

View File

@ -7,11 +7,32 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
.qrcode {
position: relative;
}
.image { .image {
margin-top: 16px; margin-top: 16px;
margin-bottom: 16px; margin-bottom: 16px;
} }
.mask {
position: absolute;
inset-block-start: 0;
inset-inline-start: 0;
z-index: 10;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
color: rgba(0,0,0,0.88);
line-height: 1.5;
background: rgba(255,255,255,0.96);
text-align: center;
}
.caption { .caption {
color: @oak-text-color-secondary; color: @oak-text-color-secondary;
font-size: 12px; font-size: 12px;

View File

@ -1,9 +1,23 @@
<view class="qrCodeBox"> <view class="qrCodeBox">
<block wx:if="{{isBase64}}"> <block wx:if="{{isBase64}}">
<image src="{{url}}" mode="aspectFit" class="image" /> <view class="qrcode" >
<image src="{{url}}" mode="aspectFit" class="image" style="width: {{size}}px; height: {{size}}px;" />
<block wx:if="{{disabled}}">
<view class="mask">
{{t('disabled')}}
</view>
</block>
</view>
</block> </block>
<block wx:elif="{{qrcodeURL}}"> <block wx:elif="{{qrcodeURL}}">
<image src="{{qrcodeURL}}" mode="aspectFit" class="image" /> <view class="qrcode" >
<image src="{{qrcodeURL}}" mode="aspectFit" class="image" style="width: {{size}}px; height: {{size}}px;" />
<block wx:if="{{disabled}}">
<view class="mask">
{{t('disabled')}}
</view>
</block>
</view>
</block> </block>
<block wx:if="{{expiresAtStr}}"> <block wx:if="{{expiresAtStr}}">
@ -21,7 +35,7 @@
</block> </block>
<view class="actions"> <view class="actions">
<view wx:if="{{!disableDownload}}" class="action" bind:tap="onDownload" > <view wx:if="{{!disableDownload && !disabled}}" class="action" bind:tap="onDownload" >
<l-icon name="download" size="40" /> <l-icon name="download" size="40" />
</view> </view>
</view> </view>

View File

@ -8,5 +8,6 @@
"Album permission denied, please reauthorize": "相册权限被拒绝,请重新授权", "Album permission denied, please reauthorize": "相册权限被拒绝,请重新授权",
"Save fail": "保存失败", "Save fail": "保存失败",
"Save successful": "保存成功", "Save successful": "保存成功",
"Authorization successful": "授权成功" "Authorization successful": "授权成功",
"disabled": "已禁用"
} }

View File

@ -20,8 +20,8 @@
* @Modified: Pudon * @Modified: Pudon
* @demoURL: https://github.com/Pudon/weapp-qrcode * @demoURL: https://github.com/Pudon/weapp-qrcode
* @Date: 2018-09-11 14:00:05 * @Date: 2018-09-11 14:00:05
* @Last Modified by: Pudon * @Last Modified by: mikey.zhaopeng
* @Last Modified time: 2018-09-12 16:33:19 * @Last Modified time: 2025-09-13 19:22:01
*/ */
//--------------------------------------------------------------------- //---------------------------------------------------------------------
@ -448,11 +448,16 @@ var qrcode = function(typeNumber, errorCorrectLevel) {
return qrHtml; return qrHtml;
}; };
_this.createImgTag = function(cellSize, margin, size) { _this.createImgTag = function(cellSize, margin, size, foregroundColor, backgroundColor) {
cellSize = cellSize || 2; cellSize = cellSize || 2;
margin = (typeof margin == 'undefined')? cellSize * 4 : margin; margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
// 设置默认颜色
foregroundColor = foregroundColor || '#000000';
backgroundColor = backgroundColor || '#ffffff';
var min = margin; var min = margin;
var max = _this.getModuleCount() * cellSize + margin; var max = _this.getModuleCount() * cellSize + margin;
@ -464,7 +469,7 @@ var qrcode = function(typeNumber, errorCorrectLevel) {
} else { } else {
return 1; return 1;
} }
} ); }, foregroundColor, backgroundColor );
}; };
return _this; return _this;
@ -1351,7 +1356,10 @@ var base64DecodeInputStream = function(str) {
// gifImage (B/W) // gifImage (B/W)
//--------------------------------------------------------------------- //---------------------------------------------------------------------
var gifImage = function(width, height) { var gifImage = function(width, height, foregroundColor, backgroundColor) {
// 设置默认颜色
foregroundColor = foregroundColor || [0, 0, 0]; // 默认黑色
backgroundColor = backgroundColor || [255, 255, 255]; // 默认白色
var _width = width; var _width = width;
var _height = height; var _height = height;
@ -1383,15 +1391,24 @@ var gifImage = function(width, height) {
//--------------------------------- //---------------------------------
// Global Color Map // Global Color Map
// /使用自定义的前景色和背景色
out.writeByte(foregroundColor[0]);
out.writeByte(foregroundColor[1]);
out.writeByte(foregroundColor[2]);
out.writeByte(backgroundColor[0]);
out.writeByte(backgroundColor[1]);
out.writeByte(backgroundColor[2]);
// black // black
out.writeByte(0x00); // out.writeByte(0x00);
out.writeByte(0x00); // out.writeByte(0x00);
out.writeByte(0x00); // out.writeByte(0x00);
// white // white
out.writeByte(0xff); // out.writeByte(0xff);
out.writeByte(0xff); // out.writeByte(0xff);
out.writeByte(0xff); // out.writeByte(0xff);
//--------------------------------- //---------------------------------
// Image Descriptor // Image Descriptor
@ -1561,9 +1578,20 @@ var gifImage = function(width, height) {
return _this; return _this;
}; };
var createImgTag = function(width, height, getPixel, alt) { var createImgTag = function(width, height, getPixel, foregroundColor, backgroundColor) {
var gif = gifImage(width, height); // 设置默认颜色
foregroundColor = foregroundColor || '#000000';
backgroundColor = backgroundColor || '#ffffff';
// 将颜色转换为RGB数组
var fgRgb = hexToRgb(foregroundColor);
var bgRgb = hexToRgb(backgroundColor);
if (!fgRgb) fgRgb = [0, 0, 0]; // 默认黑色
if (!bgRgb) bgRgb = [255, 255, 255]; // 默认白色
var gif = gifImage(width, height, fgRgb, bgRgb);
for (var y = 0; y < height; y += 1) { for (var y = 0; y < height; y += 1) {
for (var x = 0; x < width; x += 1) { for (var x = 0; x < width; x += 1) {
gif.setPixel(x, y, getPixel(x, y) ); gif.setPixel(x, y, getPixel(x, y) );
@ -1587,6 +1615,28 @@ var createImgTag = function(width, height, getPixel, alt) {
return img; return img;
}; };
// 添加十六进制颜色转RGB函数
function hexToRgb(hex) {
// 移除#号
hex = hex.replace('#', '');
// 处理简写形式 #abc → #aabbcc
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
// 验证是否为6位十六进制颜色
if (!/^[0-9A-F]{6}$/i.test(hex)) {
return null;
}
var r = parseInt(hex.substring(0, 2), 16);
var g = parseInt(hex.substring(2, 4), 16);
var b = parseInt(hex.substring(4, 6), 16);
return [r, g, b];
}
//--------------------------------------------------------------------- //---------------------------------------------------------------------
// returns qrcode function. // returns qrcode function.
@ -1595,6 +1645,8 @@ var drawImg = function(text, options) {
var typeNumber = options.typeNumber || 4; var typeNumber = options.typeNumber || 4;
var errorCorrectLevel = options.errorCorrectLevel || 'M'; var errorCorrectLevel = options.errorCorrectLevel || 'M';
var size = options.size || 500; var size = options.size || 500;
var foregroundColor = options.color || '#000000';
var backgroundColor = options.bgColor || '#ffffff';
var qr; var qr;
@ -1609,7 +1661,9 @@ var drawImg = function(text, options) {
return drawImg(text, { return drawImg(text, {
size: size, size: size,
errorCorrectLevel: errorCorrectLevel, errorCorrectLevel: errorCorrectLevel,
typeNumber: typeNumber + 1 typeNumber: typeNumber + 1,
color: foregroundColor,
bgColor: backgroundColor
}); });
} }
} }
@ -1618,7 +1672,7 @@ var drawImg = function(text, options) {
var cellsize = parseInt(size / qr.getModuleCount()); var cellsize = parseInt(size / qr.getModuleCount());
var margin = parseInt((size - qr.getModuleCount() * cellsize) / 2); var margin = parseInt((size - qr.getModuleCount() * cellsize) / 2);
return qr.createImgTag(cellsize, margin, size); return qr.createImgTag(cellsize, margin, size, foregroundColor, backgroundColor);
}; };
module.exports = { module.exports = {
drawImg: drawImg drawImg: drawImg

View File

@ -12,6 +12,9 @@ type IQrCodeProps = {
url: string; url: string;
loading?: boolean; loading?: boolean;
disableDownload?: boolean; disableDownload?: boolean;
disabled: boolean;
color: string;
bgColor: string;
}; };
export default function Render(props: WebComponentProps<EntityDict, keyof EntityDict, false, IQrCodeProps & { export default function Render(props: WebComponentProps<EntityDict, keyof EntityDict, false, IQrCodeProps & {
isBase64: boolean; isBase64: boolean;

View File

@ -6,7 +6,7 @@ import './web.less';
export default function Render(props) { export default function Render(props) {
const { data, methods } = props; const { data, methods } = props;
const { t } = methods; const { t } = methods;
const { filename = 'qrCode.png', expiresAt, tips, onDownload, onRefresh, size = 280, url, loading = false, disableDownload = false, isBase64, expired, expiresAtStr } = data; const { filename = 'qrCode.png', expiresAt, tips, onDownload, onRefresh, size = 280, url, loading = false, disableDownload = false, disabled = false, isBase64, expired, expiresAtStr, color, bgColor, } = data;
const prefixCls = 'oak'; const prefixCls = 'oak';
let V; let V;
if (expiresAtStr) { if (expiresAtStr) {
@ -24,21 +24,24 @@ export default function Render(props) {
} }
} }
return (<div id="oakQrCode" className={`${prefixCls}-qrCodeBox`}> return (<div id="oakQrCode" className={`${prefixCls}-qrCodeBox`}>
<div className={`${prefixCls}-qrCodeBox_imgBox`} style={{ <div className={`${prefixCls}-qrCodeBox_qrcode`} style={{
width: size, // width: size,
height: size, // height: size,
marginBottom: 16, marginBottom: 16,
marginTop: 16, marginTop: 16,
}}> }}>
<Spin spinning={loading}> <Spin spinning={loading}>
{isBase64 ? (<img src={url} alt="qrCode" width={size} height={size}/>) : url ? (<QRCodeCanvas value={url} size={size}/>) : null} {isBase64 ? (<img src={url} alt="qrCode" width={size} height={size}/>) : url ? (<QRCodeCanvas value={url} size={size} fgColor={color} bgColor={bgColor}/>) : null}
</Spin> </Spin>
{disabled ? <div className={`${prefixCls}-qrCodeBox_mask`}>
{t('disabled')}
</div> : null}
</div> </div>
{V} {V}
{tips} {tips}
{<Space className={`${prefixCls}-qrCodeBox_actions`}> {<Space className={`${prefixCls}-qrCodeBox_actions`}>
{!!url && !disableDownload && (<Button type="text" onClick={() => { {!!url && !disableDownload && !disabled && (<Button type="text" onClick={() => {
if (typeof onDownload === 'function') { if (typeof onDownload === 'function') {
onDownload(url, filename); onDownload(url, filename);
return; return;

View File

@ -4,12 +4,29 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
&_imgBox { &_qrcode {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
position: relative;
}
&_mask {
position: absolute;
inset-block-start: 0;
inset-inline-start: 0;
z-index: 10;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
color: rgba(0,0,0,0.88);
line-height: 1.5;
background: rgba(255,255,255,0.96);
text-align: center;
} }
&_caption { &_caption {

View File

@ -1,5 +1,8 @@
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "wechatQrCode", false, { declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "wechatQrCode", false, {
disableDownload: boolean; disableDownload: boolean;
size: number; size: number;
disabled: boolean;
color: string;
bgColor: string;
}>) => React.ReactElement; }>) => React.ReactElement;
export default _default; export default _default;

View File

@ -36,6 +36,9 @@ export default OakComponent({
const base64Str = window.btoa(binary); const base64Str = window.btoa(binary);
qrCodeUrl = 'data:image/png;base64,' + base64Str; qrCodeUrl = 'data:image/png;base64,' + base64Str;
} }
if (process.env.NODE_ENV === 'development') {
console.warn('使用微信api生成二维码不支持二维码颜色更换[color、bgColor]');
}
} }
return { return {
entity: wechatQrCode?.entity, entity: wechatQrCode?.entity,
@ -46,6 +49,9 @@ export default OakComponent({
}, },
properties: { properties: {
disableDownload: false, disableDownload: false,
size: 280 size: 280,
disabled: false,
color: '#000000',
bgColor: '#ffffff',
} }
}); });

View File

@ -5,7 +5,7 @@
<l-loading show="{{true}}" type="circle"></l-loading> <l-loading show="{{true}}" type="circle"></l-loading>
</block> </block>
<block wx:elif="{{url}}"> <block wx:elif="{{url}}">
<qrCode url="{{url}}" expiresAt="{{expiresAt}}" disableDownload="{{disableDownload}}" size="{{size}}" /> <qrCode url="{{url}}" expiresAt="{{expiresAt}}" disableDownload="{{disableDownload}}" size="{{size}}" disabled="{{disabled}}" color="{{color}}" bgColor="{{bgColor}}" />
</block> </block>
<block wx:else> <block wx:else>
</block> </block>

View File

@ -6,4 +6,7 @@ export default function Render(props: WebComponentProps<EntityDict, 'wechatQrCod
expiresAt: number; expiresAt: number;
disableDownload: boolean; disableDownload: boolean;
size: number; size: number;
disabled: boolean;
color: string;
bgColor: string;
}, {}>): React.JSX.Element | null; }, {}>): React.JSX.Element | null;

View File

@ -2,12 +2,12 @@ import React from 'react';
import QrCode from '../../common/qrCode'; import QrCode from '../../common/qrCode';
import { DotLoading } from 'antd-mobile'; import { DotLoading } from 'antd-mobile';
export default function Render(props) { export default function Render(props) {
const { url, expiresAt, oakLoading, disableDownload, size } = props.data; const { url, expiresAt, oakLoading, disableDownload, size, disabled, color, bgColor } = props.data;
if (oakLoading) { if (oakLoading) {
return <DotLoading color="primary"/>; return <DotLoading color="primary"/>;
} }
if (url) { if (url) {
return <QrCode url={url} expiresAt={expiresAt} disableDownload={disableDownload} size={size}/>; return <QrCode url={url} expiresAt={expiresAt} disableDownload={disableDownload} size={size} disabled={disabled} color={color} bgColor={bgColor}/>;
} }
return null; return null;
} }

View File

@ -6,4 +6,7 @@ export default function Render(props: WebComponentProps<EntityDict, 'wechatQrCod
expiresAt: number; expiresAt: number;
disableDownload: boolean; disableDownload: boolean;
size: number; size: number;
disabled: boolean;
color: string;
bgColor: string;
}, {}>): React.JSX.Element | null; }, {}>): React.JSX.Element | null;

View File

@ -2,12 +2,12 @@ import React from 'react';
import QrCode from '../../common/qrCode'; import QrCode from '../../common/qrCode';
import { Spin } from 'antd'; import { Spin } from 'antd';
export default function Render(props) { export default function Render(props) {
const { url, expiresAt, oakLoading, disableDownload, size } = props.data; const { url, expiresAt, oakLoading, disableDownload, size, disabled, color, bgColor } = props.data;
if (oakLoading) { if (oakLoading) {
return <Spin />; return <Spin />;
} }
if (url) { if (url) {
return <QrCode url={url} expiresAt={expiresAt} disableDownload={disableDownload} size={size}/>; return <QrCode url={url} expiresAt={expiresAt} disableDownload={disableDownload} size={size} disabled={disabled} color={color} bgColor={bgColor}/>;
} }
return null; return null;
} }

View File

@ -63,7 +63,8 @@ const i18ns = [
"Album permission denied, please reauthorize": "相册权限被拒绝,请重新授权", "Album permission denied, please reauthorize": "相册权限被拒绝,请重新授权",
"Save fail": "保存失败", "Save fail": "保存失败",
"Save successful": "保存成功", "Save successful": "保存成功",
"Authorization successful": "授权成功" "Authorization successful": "授权成功",
"disabled": "已禁用"
} }
}, },
{ {

View File

@ -65,7 +65,8 @@ const i18ns = [
"Album permission denied, please reauthorize": "相册权限被拒绝,请重新授权", "Album permission denied, please reauthorize": "相册权限被拒绝,请重新授权",
"Save fail": "保存失败", "Save fail": "保存失败",
"Save successful": "保存成功", "Save successful": "保存成功",
"Authorization successful": "授权成功" "Authorization successful": "授权成功",
"disabled": "已禁用"
} }
}, },
{ {

View File

@ -7,11 +7,32 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
.qrcode {
position: relative;
}
.image { .image {
margin-top: 16px; margin-top: 16px;
margin-bottom: 16px; margin-bottom: 16px;
} }
.mask {
position: absolute;
inset-block-start: 0;
inset-inline-start: 0;
z-index: 10;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
color: rgba(0,0,0,0.88);
line-height: 1.5;
background: rgba(255,255,255,0.96);
text-align: center;
}
.caption { .caption {
color: @oak-text-color-secondary; color: @oak-text-color-secondary;
font-size: 12px; font-size: 12px;

View File

@ -19,10 +19,13 @@ export default OakComponent({
onDownload: undefined as onDownload: undefined as
| ((qrCodeImage: string, filename?: string) => void) | ((qrCodeImage: string, filename?: string) => void)
| undefined, | undefined,
disabled: false,
color: '#000000',
bgColor: '#ffffff',
}, },
lifetimes: { lifetimes: {
ready() { ready() {
const { url, size } = this.props; const { url, size, color, bgColor } = this.props;
if (process.env.OAK_PLATFORM === 'wechatMp') { if (process.env.OAK_PLATFORM === 'wechatMp') {
const isBase64 = /data:image\/[\w|\W]+(;base64,)[\w|\W]*/.test( const isBase64 = /data:image\/[\w|\W]+(;base64,)[\w|\W]*/.test(
url! url!
@ -34,6 +37,8 @@ export default OakComponent({
typeNumber: 4, // 密度 typeNumber: 4, // 密度
errorCorrectLevel: 'L', // 纠错等级 errorCorrectLevel: 'L', // 纠错等级
size: size, // 白色边框 size: size, // 白色边框
color,
bgColor
}); });
this.setState({ this.setState({
@ -221,7 +226,10 @@ export default OakComponent({
size?: number; size?: number;
url: string; url: string;
loading?: boolean; loading?: boolean;
disableDownload?: boolean; disableDownload?: boolean; //下载禁用
disabled: boolean; //二维码禁用
color: string,
bgColor: string,
} }
> >
) => React.ReactElement; ) => React.ReactElement;

View File

@ -1,9 +1,23 @@
<view class="qrCodeBox"> <view class="qrCodeBox">
<block wx:if="{{isBase64}}"> <block wx:if="{{isBase64}}">
<image src="{{url}}" mode="aspectFit" class="image" /> <view class="qrcode" >
<image src="{{url}}" mode="aspectFit" class="image" style="width: {{size}}px; height: {{size}}px;" />
<block wx:if="{{disabled}}">
<view class="mask">
{{t('disabled')}}
</view>
</block>
</view>
</block> </block>
<block wx:elif="{{qrcodeURL}}"> <block wx:elif="{{qrcodeURL}}">
<image src="{{qrcodeURL}}" mode="aspectFit" class="image" /> <view class="qrcode" >
<image src="{{qrcodeURL}}" mode="aspectFit" class="image" style="width: {{size}}px; height: {{size}}px;" />
<block wx:if="{{disabled}}">
<view class="mask">
{{t('disabled')}}
</view>
</block>
</view>
</block> </block>
<block wx:if="{{expiresAtStr}}"> <block wx:if="{{expiresAtStr}}">
@ -21,7 +35,7 @@
</block> </block>
<view class="actions"> <view class="actions">
<view wx:if="{{!disableDownload}}" class="action" bind:tap="onDownload" > <view wx:if="{{!disableDownload && !disabled}}" class="action" bind:tap="onDownload" >
<l-icon name="download" size="40" /> <l-icon name="download" size="40" />
</view> </view>
</view> </view>

View File

@ -8,5 +8,6 @@
"Album permission denied, please reauthorize": "相册权限被拒绝,请重新授权", "Album permission denied, please reauthorize": "相册权限被拒绝,请重新授权",
"Save fail": "保存失败", "Save fail": "保存失败",
"Save successful": "保存成功", "Save successful": "保存成功",
"Authorization successful": "授权成功" "Authorization successful": "授权成功",
"disabled": "已禁用"
} }

View File

@ -20,8 +20,8 @@
* @Modified: Pudon * @Modified: Pudon
* @demoURL: https://github.com/Pudon/weapp-qrcode * @demoURL: https://github.com/Pudon/weapp-qrcode
* @Date: 2018-09-11 14:00:05 * @Date: 2018-09-11 14:00:05
* @Last Modified by: Pudon * @Last Modified by: mikey.zhaopeng
* @Last Modified time: 2018-09-12 16:33:19 * @Last Modified time: 2025-09-13 19:22:01
*/ */
//--------------------------------------------------------------------- //---------------------------------------------------------------------
@ -448,11 +448,16 @@ var qrcode = function(typeNumber, errorCorrectLevel) {
return qrHtml; return qrHtml;
}; };
_this.createImgTag = function(cellSize, margin, size) { _this.createImgTag = function(cellSize, margin, size, foregroundColor, backgroundColor) {
cellSize = cellSize || 2; cellSize = cellSize || 2;
margin = (typeof margin == 'undefined')? cellSize * 4 : margin; margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
// 设置默认颜色
foregroundColor = foregroundColor || '#000000';
backgroundColor = backgroundColor || '#ffffff';
var min = margin; var min = margin;
var max = _this.getModuleCount() * cellSize + margin; var max = _this.getModuleCount() * cellSize + margin;
@ -464,7 +469,7 @@ var qrcode = function(typeNumber, errorCorrectLevel) {
} else { } else {
return 1; return 1;
} }
} ); }, foregroundColor, backgroundColor );
}; };
return _this; return _this;
@ -1351,7 +1356,10 @@ var base64DecodeInputStream = function(str) {
// gifImage (B/W) // gifImage (B/W)
//--------------------------------------------------------------------- //---------------------------------------------------------------------
var gifImage = function(width, height) { var gifImage = function(width, height, foregroundColor, backgroundColor) {
// 设置默认颜色
foregroundColor = foregroundColor || [0, 0, 0]; // 默认黑色
backgroundColor = backgroundColor || [255, 255, 255]; // 默认白色
var _width = width; var _width = width;
var _height = height; var _height = height;
@ -1383,15 +1391,24 @@ var gifImage = function(width, height) {
//--------------------------------- //---------------------------------
// Global Color Map // Global Color Map
// /使用自定义的前景色和背景色
out.writeByte(foregroundColor[0]);
out.writeByte(foregroundColor[1]);
out.writeByte(foregroundColor[2]);
out.writeByte(backgroundColor[0]);
out.writeByte(backgroundColor[1]);
out.writeByte(backgroundColor[2]);
// black // black
out.writeByte(0x00); // out.writeByte(0x00);
out.writeByte(0x00); // out.writeByte(0x00);
out.writeByte(0x00); // out.writeByte(0x00);
// white // white
out.writeByte(0xff); // out.writeByte(0xff);
out.writeByte(0xff); // out.writeByte(0xff);
out.writeByte(0xff); // out.writeByte(0xff);
//--------------------------------- //---------------------------------
// Image Descriptor // Image Descriptor
@ -1561,9 +1578,20 @@ var gifImage = function(width, height) {
return _this; return _this;
}; };
var createImgTag = function(width, height, getPixel, alt) { var createImgTag = function(width, height, getPixel, foregroundColor, backgroundColor) {
var gif = gifImage(width, height); // 设置默认颜色
foregroundColor = foregroundColor || '#000000';
backgroundColor = backgroundColor || '#ffffff';
// 将颜色转换为RGB数组
var fgRgb = hexToRgb(foregroundColor);
var bgRgb = hexToRgb(backgroundColor);
if (!fgRgb) fgRgb = [0, 0, 0]; // 默认黑色
if (!bgRgb) bgRgb = [255, 255, 255]; // 默认白色
var gif = gifImage(width, height, fgRgb, bgRgb);
for (var y = 0; y < height; y += 1) { for (var y = 0; y < height; y += 1) {
for (var x = 0; x < width; x += 1) { for (var x = 0; x < width; x += 1) {
gif.setPixel(x, y, getPixel(x, y) ); gif.setPixel(x, y, getPixel(x, y) );
@ -1587,6 +1615,28 @@ var createImgTag = function(width, height, getPixel, alt) {
return img; return img;
}; };
// 添加十六进制颜色转RGB函数
function hexToRgb(hex) {
// 移除#号
hex = hex.replace('#', '');
// 处理简写形式 #abc → #aabbcc
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
// 验证是否为6位十六进制颜色
if (!/^[0-9A-F]{6}$/i.test(hex)) {
return null;
}
var r = parseInt(hex.substring(0, 2), 16);
var g = parseInt(hex.substring(2, 4), 16);
var b = parseInt(hex.substring(4, 6), 16);
return [r, g, b];
}
//--------------------------------------------------------------------- //---------------------------------------------------------------------
// returns qrcode function. // returns qrcode function.
@ -1595,6 +1645,8 @@ var drawImg = function(text, options) {
var typeNumber = options.typeNumber || 4; var typeNumber = options.typeNumber || 4;
var errorCorrectLevel = options.errorCorrectLevel || 'M'; var errorCorrectLevel = options.errorCorrectLevel || 'M';
var size = options.size || 500; var size = options.size || 500;
var foregroundColor = options.color || '#000000';
var backgroundColor = options.bgColor || '#ffffff';
var qr; var qr;
@ -1609,7 +1661,9 @@ var drawImg = function(text, options) {
return drawImg(text, { return drawImg(text, {
size: size, size: size,
errorCorrectLevel: errorCorrectLevel, errorCorrectLevel: errorCorrectLevel,
typeNumber: typeNumber + 1 typeNumber: typeNumber + 1,
color: foregroundColor,
bgColor: backgroundColor
}); });
} }
} }
@ -1618,7 +1672,7 @@ var drawImg = function(text, options) {
var cellsize = parseInt(size / qr.getModuleCount()); var cellsize = parseInt(size / qr.getModuleCount());
var margin = parseInt((size - qr.getModuleCount() * cellsize) / 2); var margin = parseInt((size - qr.getModuleCount() * cellsize) / 2);
return qr.createImgTag(cellsize, margin, size); return qr.createImgTag(cellsize, margin, size, foregroundColor, backgroundColor);
}; };
module.exports = { module.exports = {
drawImg: drawImg drawImg: drawImg

View File

@ -4,12 +4,29 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
&_imgBox { &_qrcode {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
position: relative;
}
&_mask {
position: absolute;
inset-block-start: 0;
inset-inline-start: 0;
z-index: 10;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
color: rgba(0,0,0,0.88);
line-height: 1.5;
background: rgba(255,255,255,0.96);
text-align: center;
} }
&_caption { &_caption {

View File

@ -17,6 +17,9 @@ type IQrCodeProps = {
url: string; url: string;
loading?: boolean; loading?: boolean;
disableDownload?: boolean; disableDownload?: boolean;
disabled: boolean;
color: string;
bgColor: string;
}; };
export default function Render( export default function Render(
@ -45,9 +48,12 @@ export default function Render(
url, url,
loading = false, loading = false,
disableDownload = false, disableDownload = false,
disabled= false,
isBase64, isBase64,
expired, expired,
expiresAtStr expiresAtStr,
color,
bgColor,
} = data; } = data;
const prefixCls = 'oak'; const prefixCls = 'oak';
let V; let V;
@ -72,10 +78,10 @@ export default function Render(
return ( return (
<div id="oakQrCode" className={`${prefixCls}-qrCodeBox`}> <div id="oakQrCode" className={`${prefixCls}-qrCodeBox`}>
<div <div
className={`${prefixCls}-qrCodeBox_imgBox`} className={`${prefixCls}-qrCodeBox_qrcode`}
style={{ style={{
width: size, // width: size,
height: size, // height: size,
marginBottom: 16, marginBottom: 16,
marginTop: 16, marginTop: 16,
}} }}
@ -89,16 +95,19 @@ export default function Render(
height={size} height={size}
/> />
) : url ? ( ) : url ? (
<QRCodeCanvas value={url} size={size} /> <QRCodeCanvas value={url} size={size} fgColor={color} bgColor={bgColor} />
) : null} ) : null}
</Spin> </Spin>
{disabled ? <div className={`${prefixCls}-qrCodeBox_mask`}>
{t('disabled')}
</div> : null}
</div> </div>
{V} {V}
{tips} {tips}
{ {
<Space className={`${prefixCls}-qrCodeBox_actions`}> <Space className={`${prefixCls}-qrCodeBox_actions`}>
{!!url && !disableDownload && ( {!!url && !disableDownload && !disabled && (
<Button <Button
type="text" type="text"
onClick={() => { onClick={() => {

View File

@ -37,6 +37,9 @@ export default OakComponent({
const base64Str = window.btoa(binary); const base64Str = window.btoa(binary);
qrCodeUrl = 'data:image/png;base64,' + base64Str; qrCodeUrl = 'data:image/png;base64,' + base64Str;
} }
if (process.env.NODE_ENV === 'development') {
console.warn('使用微信api生成二维码不支持二维码颜色更换[color、bgColor]')
}
} }
return { return {
entity: wechatQrCode?.entity, entity: wechatQrCode?.entity,
@ -47,6 +50,9 @@ export default OakComponent({
}, },
properties: { properties: {
disableDownload: false, disableDownload: false,
size: 280 size: 280,
disabled: false,
color: '#000000',
bgColor: '#ffffff',
} }
}); });

View File

@ -5,7 +5,7 @@
<l-loading show="{{true}}" type="circle"></l-loading> <l-loading show="{{true}}" type="circle"></l-loading>
</block> </block>
<block wx:elif="{{url}}"> <block wx:elif="{{url}}">
<qrCode url="{{url}}" expiresAt="{{expiresAt}}" disableDownload="{{disableDownload}}" size="{{size}}" /> <qrCode url="{{url}}" expiresAt="{{expiresAt}}" disableDownload="{{disableDownload}}" size="{{size}}" disabled="{{disabled}}" color="{{color}}" bgColor="{{bgColor}}" />
</block> </block>
<block wx:else> <block wx:else>
</block> </block>

View File

@ -14,16 +14,19 @@ export default function Render(
expiresAt: number; expiresAt: number;
disableDownload: boolean; disableDownload: boolean;
size: number; size: number;
disabled: boolean;
color: string;
bgColor: string;
}, },
{} {}
> >
) { ) {
const { url, expiresAt, oakLoading, disableDownload, size } = props.data; const { url, expiresAt, oakLoading, disableDownload, size, disabled, color, bgColor } = props.data;
if (oakLoading) { if (oakLoading) {
return <Spin />; return <Spin />;
} }
if (url) { if (url) {
return <QrCode url={url} expiresAt={expiresAt} disableDownload={disableDownload} size={size} />; return <QrCode url={url} expiresAt={expiresAt} disableDownload={disableDownload} size={size} disabled={disabled} color={color} bgColor={bgColor} />;
} }
return null; return null;
} }

View File

@ -14,16 +14,19 @@ export default function Render(
expiresAt: number; expiresAt: number;
disableDownload: boolean; disableDownload: boolean;
size: number; size: number;
disabled: boolean;
color: string;
bgColor: string;
}, },
{} {}
> >
) { ) {
const { url, expiresAt, oakLoading, disableDownload, size } = props.data; const { url, expiresAt, oakLoading, disableDownload, size, disabled, color, bgColor } = props.data;
if (oakLoading) { if (oakLoading) {
return <DotLoading color="primary" />; return <DotLoading color="primary" />;
} }
if (url) { if (url) {
return <QrCode url={url} expiresAt={expiresAt} disableDownload={disableDownload} size={size} />; return <QrCode url={url} expiresAt={expiresAt} disableDownload={disableDownload} size={size} disabled={disabled} color={color} bgColor={bgColor} />;
} }
return null; return null;
} }

View File

@ -65,7 +65,8 @@ const i18ns: I18n[] = [
"Album permission denied, please reauthorize": "相册权限被拒绝,请重新授权", "Album permission denied, please reauthorize": "相册权限被拒绝,请重新授权",
"Save fail": "保存失败", "Save fail": "保存失败",
"Save successful": "保存成功", "Save successful": "保存成功",
"Authorization successful": "授权成功" "Authorization successful": "授权成功",
"disabled": "已禁用"
} }
}, },
{ {