解决打开取色器页面抖动问题
此类问题一般再抖动过程中左右会出现滑动条,抖动的原因其实是因为页面滑动条的显示与隐藏,所以解决的方式找到滑动条所属的元素添加 overflow: hidden
通过js的方式设置元素的样式
const newNode = document.createElement('img');
newNode.setAttribute('class', 'upload-icon');
newNode.setAttribute('src', 'https://d322uc7y3fcjjx.cloudfront.net/test/easy-email/upload-icon.svg');
实现拖拽组件形成flow
实现手机短信样式
<div className={styles['sme-preview']}>
<div className={styles['sme-phone']}>
<div className={styles['sme-phone-header']}>
<div className={styles['sme-phone-header-input']}>
+19786794257
</div>
</div>
<div className={styles['sme-phone-content']}>
<div className={styles['sme-phone-message']} dangerouslySetInnerHTML={{
__html: segmentInfo.previewHtml
}}>
</div>
</div>
<div className={styles['sme-phone-bottom']}>
<div className={styles['sme-phone-bottom-item-1']}></div>
<div className={styles['sme-phone-bottom-item-2']}></div>
</div>
</div>
</div>
.sme-preview {
width: 50%;
height: 100%;
background: #F6F5FF;
overflow-y: auto;
}
.sme-phone {
margin: 24px auto;
width: 360px;
height: 710px;
border: 10px solid #7C7B85;
overflow: hidden;
border-radius: 50px;
box-sizing: border-box;
}
.sme-phone-header {
width: 100%;
height: 72px;
padding: 20px 50px;
background: #E4E3EB;
box-sizing: border-box;
}
.sme-phone-header-input {
background: #F5F5F7;
text-align: center;
line-height: 32px;
overflow: hidden;
color: #51505A;
font-weight: 500;
font-size: 16px;
border-radius: 20px;
}
.sme-phone-content {
width: 100%;
height: 538px;
background-color: #FFFFFF;
overflow: auto;
box-sizing: border-box;
}
.sme-phone-message {
white-space: pre-wrap;
position: relative;
margin: 32px 44px 32px 16px;
padding: 16px 8px 16px 22px;
min-height: 72px;
text-align: left;
border-radius: 14px;
background-color: #EDEBFF;
line-height: 20px;
color: #21202A;
font-size: 14px;
overflow-wrap: break-word;
word-break: break-word;
}
// 此处为实现对话框左边的那个左尖角
.sme-phone-message::before {
content: ' ';
position: absolute;
top: 32px;
left: -4px;
width: 8px;
height: 8px;
background-color: #EDEBFF;
transform: rotate(45deg);
}
.sme-phone-bottom {
padding: 24px 32px;
width: 100%;
height: 80px;
background: #E4E3EB;
box-sizing: border-box;
}
.sme-phone-bottom-item-1 {
display: inline-block;
width: 32px;
height: 32px;
border-radius: 16px;
background-color: #F9F9FC;
}
.sme-phone-bottom-item-2 {
margin-left: 24px;
display: inline-block;
width: 220px;
height: 32px;
border-radius: 16px;
background-color: #F9F9FC;
}
实现下拉小箭头的上下翻转
通过点击然后改变transform的旋转属性即可
<Button
className={styles['header-button-fold']}
type="text"
style={{ transform: height === '0' ? 'rotate(180deg)' : '' }}
onClick={() => {
height === '0' ? setHeight('inherit') : setHeight('0');
}}
前端埋点脚本
三种事件 1.页面访问(Site/Visit) 2. 浏览商品(Site/ViewProduct) 3.加购(Cart/Add)
! function (w,c, u) {
u = 'https://59b517704ce43f0f.cartx.cloud/cartxtrack';
c = w.cartq;
c.params = {};
c.helper = {
getCookie: function(c_name) {
if (document.cookie.length>0) {
var c_start=document.cookie.indexOf(c_name + "=")
if (c_start!==-1) {
c_start=c_start + c_name.length+1
var c_end=document.cookie.indexOf(";",c_start)
if (c_end===-1) c_end=document.cookie.length
return unescape(document.cookie.substring(c_start,c_end))
}
}
return ""
},
getQueryVariable(variable) {
const urlSearchParams = new URLSearchParams(location.search.replace("+", "%2B"));
return urlSearchParams.get(variable) || '';
},
S4: function() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
},
guid: function(S4) {
S4 = c.helper.S4;
return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
},
ajax: {
get: function(data, fn, errFn) {
var xhr = new XMLHttpRequest();
xhr.open('GET', data.url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4){
var responseText;
try {
responseText = JSON.parse(xhr.responseText)
} catch (e) {
responseText = xhr.responseText;
}
if(xhr.status === 200 || xhr.status === 304) {
fn && fn.call(this, responseText);
} else {
errFn && errFn.call(this, responseText);
}
}
};
xhr.send();
},
post: function (data, fn) {
var xhr = new XMLHttpRequest();
xhr.open("POST", data.url, true);
xhr.setRequestHeader("Content-Type", data.contentType||"application/json");
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 304)) {
var responseText;
try {
responseText = JSON.parse(xhr.responseText)
} catch (e) {
responseText = xhr.responseText;
}
fn && fn.call(this, responseText);
}
};
xhr.send(data.data);
}
},
getCart: function (fn) {
c.helper.ajax.get({
// todo 获取购物车信息
url: location.origin + '/cart.js?promoter=cartrack'
},function(res) {
// todo 购物车信息返回值
res && (c.params.cart = res);
fn && fn();
})
},
getProduct: function (fn) {
if(c.params.product) {
fn();
} else {
c.helper.ajax.get({
// todo 获取产品信息
url: location.origin.concat(location.pathname, ".js")
},function (res) {
// todo 产品信息返回值
res && (c.params.product = res);
fn && fn();
},function () {
fn && fn();
})
}
},
handleAddCart: function () {
if(sessionStorage.getItem('carttrackAddCard') === 'false') {
c('track', 'Cart/Add', {
email: c.params?.customer?.email || '',
phone: c.params?.customer?.phone || '',
pathName: location.href,
productId: String(c.params?.product?.id || ''),
productName: c.params?.product?.title || '',
price: c.params?.product?.price || null,
compareAtPrice: c.params?.product?.compare_at_price || '',
variantId: sessionStorage.getItem('carttrackVariantId') || null,
// todo 获取购物车token
cartToken: c.params?.cart?.token || '', // 购物车token
cartJson: c.params?.cart || null // 购物车
})
sessionStorage.setItem('carttrackAddCard', 'true')
}
}
}
c.eventList = {
init: function(e,id,email) {
c.id = id;
c.params = {...c.params, customer: {...c.params?.customer, email: email}};
},
set: function(e,key,value) {
c.params = {...c.params, [key]: value};
},
track: function (e,v,d) {
const p = {
// todo 唯一用户标识
uid: c.helper.getCookie("_shopify_y"), // 唯一用户标识
requestId: c.helper.guid(), // 每次request的uuid
timestamp: Date.now(),
eventType: v,
companyId: c.id||'',
// todo 集成id
integrationId: 1,
cid: localStorage.getItem('cid')||null,
customerId: String(c.params?.customer?.id||''), // shopify用户id,在已登录情形下才会有
data: d
};
c.helper.ajax.post({
url: u,
contentType: "application/json",
data: JSON.stringify(p)
});
}
}
if(c.helper.getQueryVariable('cid')) {
localStorage.setItem('cid', c.helper.getQueryVariable('cid'))
}
c.callMethod = function() {
if (arguments.length === 0 || !Object.keys(c.eventList).includes(arguments[0])) return console.warn('no event');
c.eventList[arguments[0]].apply(c,arguments);
}
c.queue.forEach(function(a) {
c.apply(c,a);
})
c.queue = [];
// 站点活跃Active on Site: 用户进入站点任意页面
c('track', 'Site/Visit', {
pathName: location.href,
email: c.params?.customer?.email||'',
phone: c.params?.customer?.phone||''
})
c.helper.getCart(null);
// todo 商品页面链接
if (location.pathname.indexOf('/products') !== -1) {
c.helper.getProduct(function () {
// 浏览商品Viewed Product: 用户进入集成站点product详情页
c('track', 'Site/ViewProduct', {
email: c.params?.customer?.email||'',
phone: c.params?.customer?.phone||'',
pathName: location.href,
productId: String(c.params?.product?.id || ''),
productName: c.params?.product?.title || '',
price: c.params?.product?.price || null,
compareAtPrice: c.params?.product?.compare_at_price || ''
})
})
}
c.listenAdd = function() {
if (document.readyState === 'complete') {
const button = document.getElementsByTagName("button");
const input = document.getElementsByTagName("input");
c.handleEle(button);
c.handleEle(input);
}
}
c.handleEle = function(elements) {
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
const name = element.getAttribute("name");
const type = element.getAttribute("type");
const cssClass = element.getAttribute("class");
const id = element.getAttribute("id");
if(
(id && id.toLowerCase().indexOf("addtocart") !== -1)
|| (id && id.indexOf('add_to_cart') !== -1)
|| (id && id.indexOf('add-to-cart') !== -1)
|| (cssClass && cssClass.toLowerCase().indexOf('addtocart') !== -1)
|| (cssClass && cssClass.indexOf('add_to_cart') !== -1)
|| (cssClass && cssClass.indexOf('add-to-cart') !== -1)
|| (name && name === "add" && type && type === "submit")
) {
element.addEventListener("click", c.listenerEvent);
}
}
}
c.listenerEvent = function () {
sessionStorage.setItem('carttrackAddCard', 'false')
// todo 获取sku信息
sessionStorage.setItem('carttrackVariantId', c.helper.getQueryVariable('variant') || c.params?.product?.variants?.[0]?.id || '',)
let times = 0;
const fn = function () {
times += 1;
// todo 获取购物车产品数量
if (c?.params?.cart?.item_count) {
c.helper.handleAddCart();
} else if(times < 10){
setTimeout(() => {c.helper.getCart(fn)}, 2000);
}
}
c.helper.getCart(fn);
}
// todo 购物车链接
if (location.pathname.indexOf('/cart') !== -1) {
c.helper.getCart(c.helper.handleAddCart);
}
// 默认事件获取
c.listenAdd();
if(document.onreadystatechange){
const oldFn = document.onreadystatechange;
document.onreadystatechange = function (...args){
oldFn(...args);
c.listenAdd();
}
} else {
document.onreadystatechange = c.listenAdd;
}
}(window);
Arco design Select组件实现分组
利用其dropdownRender自定义下拉项菜单,参数menu就是当前的所有Select.option的子项的虚拟dom对象,所以对其分组只需要新建两个对象然后对两组数据进行分类,这里一定要新建两个对象进行构造。
<Select
style={{ width: '320px', height: '32px' }}
dropdownRender={
(menu) => {
if (menu) {
const group1 = [];
const group2 = [];
menu?.props?.data?.forEach((item) =>
!item?.key.includes('bottom') ? group1.push(item) : group2.push(item))
// 这里不可直接循环menu去修改,因为menu是一个immutable对象,直接修改会报错
// 自定义的dom结构
const customermenu = {
...menu,
props: {
...menu.props,
data: group1
}
}
// 已存在的dom结构
const exsitMenu = {
...menu,
props: {
...menu.props,
data: group2
}
}
return (
<div>
{customermenu}
<Button style={{ margin: '0px 100px' }}
type='text'
icon={<i className='iconfont icon-bianji'></i>}
onClick={() => { setIsCustomModal(true) }}
>自定义添加
</Button>
<Divider style={{ margin: 0 }} />
{exsitMenu}
</div>
)
}
}
}
onVisibleChange={(visible) => {
visible && getUtmCodeList('flow')
}}
renderFormat={(option, value) => {
return (
<div className={styles.defineOptions}>
{value}
</div>
)
}}
>
{utmCodeList?.map((item, index) => {
if (item === 'CartSee' || item === 'SMS' || item === 'Email' || item === 'Message' || item === 'Flow_ID' || item === 'FlowSMS' || item === 'FlowEmail') {
return <Select.Option key={`${item}-bottom`} value={item}>{item}</Select.Option>
} else {
// 这一组每一项添加删除按钮
return <Select.Option key={index} value={item}>
<div className={styles.defineOptions}>
{item}
{
<i className="iconfont icon-guanbi" onClick={(e) => {
e.stopPropagation();
// featRemoveEmail(item.email)
}} style={{ float: 'right' }} />
}
</div>
</Select.Option>
}
//
})}
</Select>
实现可变色的评分组件(Rate)
需求:选择不同的星时需要有不通的颜色。
实现思路: 监听不通的input标签的点击和鼠标hover 切换不同的图片
const [src, setSrc] = useState(
'https://cdn.trustpilot.net/brand-assets/4.1.0/stars/stars-0.svg'
);
const [index, setIndex] = useState(null);
// 鼠标离开时 判断上一次点击的是第几个radio
const onMouseLeave = () => {
switch (index) {
case 0:
setSrc(
'https://cdn.trustpilot.net/brand-assets/4.1.0/stars/stars-1.svg'
);
break;
case 1:
setSrc(
'https://cdn.trustpilot.net/brand-assets/4.1.0/stars/stars-2.svg'
);
break;
case 2:
setSrc(
'https://cdn.trustpilot.net/brand-assets/4.1.0/stars/stars-3.svg'
);
break;
case 3:
setSrc(
'https://cdn.trustpilot.net/brand-assets/4.1.0/stars/stars-4.svg'
);
break;
case 4:
setSrc(
'https://cdn.trustpilot.net/brand-assets/4.1.0/stars/stars-5.svg'
);
break;
default:
setSrc(
'https://cdn.trustpilot.net/brand-assets/4.1.0/stars/stars-0.svg'
);
break;
// 可以继续添加更多的情况
}
};
<div>
<div
style={{ position: 'absolute', width: '216px' }}
id="rate-c"
onMouseLeave={onMouseLeave}
>
<input
type="radio"
name="star-selector"
style={{
width: '42px',
display: 'inline-block',
margin: 0,
border: 0,
height: '40px',
zIndex: 1,
cursor: 'pointer',
appearance: 'none',
writingMode: 'horizontal-tb',
}}
aria-label="1 star: Bad"
data-star-selector-star-1="true"
tabIndex={0}
value="1"
onClick={() => {
setIndex(0);
setPlaceholder(
'What went wrong this time?How can this company imporve?Remember to be honest, helpful, and constructive!'
);
}}
onMouseOver={() => {
setSrc(
'https://cdn.trustpilot.net/brand-assets/4.1.0/stars/stars-1.svg'
);
}}
/>
<input
type="radio"
name="star-selector"
style={{
width: '42px',
display: 'inline-block',
margin: 0,
border: 0,
height: '40px',
zIndex: 1,
cursor: 'pointer',
appearance: 'none',
writingMode: 'horizontal-tb',
}}
aria-label="2 stars: Poor"
data-star-selector-star-2="true"
tabIndex={0}
value="2"
onClick={() => {
setIndex(1);
setPlaceholder(
'What went wrong this time?How can this company imporve?Remember to be honest, helpful, and constructive!'
);
}}
onMouseOver={() => {
setSrc(
'https://cdn.trustpilot.net/brand-assets/4.1.0/stars/stars-2.svg'
);
}}
/>
<input
type="radio"
name="star-selector"
style={{
width: '42px',
display: 'inline-block',
margin: 0,
border: 0,
height: '40px',
zIndex: 1,
cursor: 'pointer',
appearance: 'none',
writingMode: 'horizontal-tb',
}}
aria-label="3 stars: Average"
data-star-selector-star-3="true"
tabIndex={0}
value="3"
onClick={() => {
setIndex(2);
setPlaceholder(
'What did you like or dislike?What is this company doing well, or how can they improve? Remember to be honest, helpful, and constructive!'
);
}}
onMouseOver={() => {
setSrc(
'https://cdn.trustpilot.net/brand-assets/4.1.0/stars/stars-3.svg'
);
}}
/>
<input
type="radio"
name="star-selector"
style={{
width: '42px',
display: 'inline-block',
margin: 0,
border: 0,
height: '40px',
zIndex: 1,
cursor: 'pointer',
appearance: 'none',
writingMode: 'horizontal-tb',
}}
aria-label="4 stars: Great"
data-star-selector-star-4="true"
tabIndex={0}
value="4"
onClick={() => {
setIndex(3);
setPlaceholder(
'What made your experience great? What is this company doing well?Remember to be honest, helpful, and constructive!'
);
}}
onMouseOver={() => {
setSrc(
'https://cdn.trustpilot.net/brand-assets/4.1.0/stars/stars-4.svg'
);
}}
/>
<input
type="radio"
name="star-selector"
style={{
width: '42px',
display: 'inline-block',
margin: 0,
border: 0,
height: '40px',
zIndex: 1,
cursor: 'pointer',
appearance: 'none',
writingMode: 'horizontal-tb',
}}
aria-label="5 stars: Excellent"
data-star-selector-star-5="true"
tabIndex={0}
value="5"
onClick={() => {
setIndex(4);
setPlaceholder(
'What made your experience great? What is this company doing well?Remember to be honest, helpful, and constructive!'
);
}}
onMouseOver={() => {
setSrc(
'https://cdn.trustpilot.net/brand-assets/4.1.0/stars/stars-5.svg'
);
}}
/>
</div>
<div
style={{
width: '216px',
height: '40px',
display: 'flex',
minWidth: '90px',
}}
>
<img alt="" src={src} />
</div>
</div>
解决页面闪烁问题
以下这种情况就是常见的页面闪烁的情况,因为初始状态isOpenSetting为true,在第二次渲染完以后又把isOpenSetting 设置为了false, 这种情况的解决办法就是加一个loading的状态,之后isOpenSetting 的判断要为isOpenSetting && loading ,也就是等state最终确定了(loading为false)以后再去用它判断
// 效果提升的折叠默认关闭
const [isOpenSetting, setIsOpenSetting] = useState(true);
// 增加loading 状态解决 页面闪烁问题
const [loading, setLoading] = useState(false);
useEffect(() => {
// 在一个请求后 set这个依赖项
setLoading(true);
getGuideStatus({}).then((res) => {
setLoading(false);
if (res.data) {
setEffectNum(eNum);
}
});
}, [effectNum, settingNum]);
// 这里会在第一次渲染完后第二次渲染settingNum达到了4这个条件 把isOpenSetting 从 true 变为 false
useEffect(() => {
if (settingNum === 4) {
setIsOpenSetting(false);
}
}, [effectNum, settingNum]);
return isOpenSetting && loading <渲染的内容>
css篇
通过设置BFC创建自适应布局(利用流体特性)
1.当设置position:absolute 会触发BFC且 当对立属性同时存在比如left: 1px right:1px 该元素就具有流体特性即元素的宽度自适应于父元素,随着父元素变大变小 他的content-wdith也会变大变小
2.设置overflow: hidden 触发BFC 也能使得块元素自适应布局,里面的子元素的宽度自适应于父元素
还有就是可以设置float absolute inline-block 只是这三个属性具有包裹性(即它的宽度会默认为内部元素的大小)
zindex生效条件
zindex 只有在定位元素和flex元素上才会生效
关系选择器
- 空格表示后代选择器,选择所有后代
-
表示直接子元素选择器
- ~ 选择当前元素后面的相邻的兄弟元素
-
- 仅和当前元素相邻的的兄弟元素
深藏不露的width:auto
width 的默认值是 auto,包含4种
- 充分利用可用空间 比如div默认宽度为父级的100%
- 收缩与包裹 比如浮动、绝对定位、inline-block 元素或 table 的auto表现为内容的宽度,也就是包裹性
- 收缩到最小 个最容易出现在 table-layout 为 auto 的表格中,也就是min-content
- 超出容器限制 ,上面 3 种情况尺寸都不会主动超过父级容器宽度的,比如设置了max-content 就是超出父元素的宽度
流体特性
1.正常流宽度
所谓流动性,并不是看上去的宽度 100%显示这么简单,而是一种 margin/border/padding 和 content 内容区域自动分配水平空间的机制,块级元素一旦设置了宽度,流动性就丢失了
为流体而生的 min-width/max-width
min-width/max-width 出现的场景一定是自适应布局或者流体布局中,如果是那种 width/height 定死的砖头式布局,min-width/max-width 就没有任何出现的价值,因为它们是具有边界行为的属性,所以没有变化自然无法触发,也就没有使用价值。
比如限制图片的样式为 img {max-width: 100%; height: auto!important;} ,height:auto 是必需的,否则,如果原始图片有设定 height,max-width 生效的时候,图片就会被水平压缩
margin:auto 用于块级元素居中
overflow 与滚动条
HTML 中有两个标签是默认可以产生滚动条的,一个是根元素<html>
,另一个是文本域<textarea>
,因为他们默认的属性值是auto而不是visible。
这里分享一个可以让页面滚动条不发生晃动的小技巧
html {
overflow-y: scroll; /* for IE8 */
}
:root {
overflow-y: auto;
overflow-x: hidden;
}
:root body {
position: absolute;
}
body {
width: 100vw;
overflow: hidden;
}
Table组件的column如何根据条件动态渲染
根据表达式判断列时候显示,最后columns数组用filter(Boolean)方法来移除 columns
数组中的 false
值,以确保只有有效的列会被渲染到表格中
import React from 'react';
import { Table } from '@arco-design/arco-ui';
function MyTable({ data, showAge }) {
const columns = [
{ title: '姓名', dataIndex: 'name' },
{ title: '性别', dataIndex: 'gender' },
// 条件渲染的列
showAge && { title: '年龄', dataIndex: 'age' },
].filter(Boolean); // 移除值为 false 的列
return <Table dataSource={data} columns={columns} />;
}
抓取三方网站所有请求(参数)
可以使用基于Porxy的库,ajax-proxy
import ajaxProxy from '@lazyduke/ajax-proxy'
const { proxyAjax, unProxyAjax } = ajaxProxy
try {
proxyAjax({
open: function(args, xhr) {
console.log('case 1.拦截 open 方法: ', args[0]);
console.log(typeof args, 'typeof args');
if (args[0] === 'GET') {
checkUrlAndParams(args[1], '');
}
},
send: function(data, xhr) {
console.log('case 2.拦截 open 方法: ', data);
console.log(JSON.parse(data[0]).formUid, 'data?.email');
console.log(typeof JSON.parse(data[0]), 'typeof data');
checkUrlAndParams('', JSON.parse(data[0]));
}
})
} catch (error) {
console.log(error, 'proxyAjax -- error');
}
css确保图片始终为正方形(自适应屏幕的变化)
<div class="demo">
<div class="item">
<div class="img-wrap"><img /></div>
</div>
<div class="item">
<div class="img-wrap"><img /></div>
</div>
....
</div>
* {
box-sizing: border-box;
}
.demo {
display: flex;
width: 100%;
flex-wrap: wrap;
padding: 5px;
}
.item {
position: relative;
width: 25%;
padding-bottom: 25%;
}
.img-wrap {
position: absolute;
top: 5px;
left: 5px;
right: 5px;
bottom: 5px;
}
img {
width: 100%;
height: 100%;
}
- padding-bottom 设置 25% 即为宽度的 25%,这样使宽高动态保持一致。
- padding 占据了空间,使用绝对定位铺满父元素,从而使内容能正常展示,还可以通过 top/right/bottom/left 来设置图片之前的间距。
那就是当 margin/padding 设置百分比的值时,无论是左右间距,还是上下间距,它始终相对的是宽度,利用这个知识点就可以完美解决了。
参考文章:www.qinshenxue.com/article/css…
svg图标如何改变颜色
import Edit from '@/assets/img/flow/bianji-temp.svg';
<Edit
className={styles['custom-icon']}
></Edit>
.custom-icon:hover {
filter: drop-shadow(0 0 0 #6B5BFF); // 通过drop-shadow属性可以实现hoversvg图标变色的需求
}
移动端自适应
1.如果index.html 中有这样的代码 ,就需要先把他注释掉 2.给最外面的元素比如#app加这么一段代码
#app {
min-width: 1440px; //设计稿是多大就写多大
overflow: hidden;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
然后就可以适配任何屏幕大小的设备,包括微信和飞书内置的浏览器
微信风向链接呈现卡片形式
以vue为例
import {getSing} from '../services/index';
created() {
// 调用微信分型的事件
this.getShareInfo();
},
getShareInfo() {
let res1;
//获取url链接
const url = window.location.href.split('#')[0]
getSing(url).then(res => {
wx.config({
debug: false, // 开启调试模式,调用的所有 api 的返回值会在客户端 alert 出来,若要查看传入的参数,可以在 pc 端打开,参数信息会通过 log 打出,仅在 pc 端时才会打印。
appId: res.data.data.appId, // 必填,公众号的唯一标识
timestamp: parseInt(res.data.data.timestamp), // 必填,生成签名的时间戳
nonceStr: res.data.data.nonceStr, // 必填,生成签名的随机串
signature: res.data.data.signature, // 必填,签名
jsApiList: [
"updateAppMessageShareData",
"updateTimelineShareData"
] // 必填,需要使用的 JS 接口列表
});
wx.ready(() => {
const shareData = {
title: "更适合中国海外卖家的营销自动化引擎",
desc: "选择更适合中国卖家的Cartsee 意味着不费吹灰之力,即可提高你的营收",
// link: window.location.href,
link: window.location.href.split('#')[0],
imgUrl: "https://image.cartx.cloud/cartsee-website/20240117-180705.jpeg",
};
//自定义“分享给朋友”及“分享到QQ”按钮的分享内容
wx.updateAppMessageShareData(shareData);
//自定义“分享到朋友圈”及“分享到 QQ 空间”按钮的分享内容(1.4.0)
wx.updateTimelineShareData(shareData);
});
//错误了会走 这里
wx.error(function (res) {
res1 = res;
});
});
},
这里的getSing 是后端接口,需要后端去对接微信获取签名参数
注意:1。用微信开发者工具在本地调试是看不出效果的,因为要求环境的域名也必须和url的域名一样(本地是localhost)
2、微信分享卡片现微信有限制,只能将链接先收藏,然后从收藏里分享才可以(ios收藏后分享也会失效)
手动实现elements组件中的tabs
以vue为例,实现的原理就是下面标识哪一项选中是通过移动下面的横线,然后再给横线的移动增加动画
<div
style="
display: flex;
width: 1170px;
justify-content: space-between;
margin-top: 60px;
padding: 0px 35px;
border-bottom: 1px solid #ccc;
"
>
<div
style="width: 200px; cursor: pointer"
:class="{ selected: selectedOption === '1' }"
@click="selectOption('1')"
>
<div
style="
color: #292930;
font-family: Noto Sans;
font-size: 10px;
font-style: normal;
font-weight: 500;
line-height: 156.5%;
letter-spacing: 1.515px;
text-transform: uppercase;
width: 200px;
"
>
// 01 . user segmentation
</div>
<div
style="
font-family: PingFang SC;
font-size: 28px;
font-style: normal;
font-weight: 600;
line-height: 110.5%;
margin-top: 10px;
"
:style="{ color: selectedOption === '1' ? '#333af4' : '#292930' }"
>
智能用户分层
</div>
<transition name="fade">
<div
style="width: 168px; margin-top: 24px;"
:style="{ transform: `translateX(${underlinePosition}px)` }"
class="underline"
>
<div
style="
width: 74px;
height: 4px;
flex-shrink: 0;
background: #333af4;
margin: 0 auto;
"
></div>
</div>
</transition>
</div>
<div
style="width: 200px; cursor: pointer"
:class="{ selected: selectedOption === '2' }"
@click="selectOption('2')"
>
<div
style="
color: #292930;
font-family: Noto Sans;
font-size: 10px;
font-style: normal;
font-weight: 500;
line-height: 156.5%;
letter-spacing: 1.515px;
text-transform: uppercase;
width: 200px;
"
>
// 02 . Product PUSH
</div>
<div
style="
font-family: PingFang SC;
font-size: 28px;
font-style: normal;
font-weight: 600;
line-height: 110.5%;
margin-top: 10px;
"
:style="{ color: selectedOption === '2' ? '#333af4' : '#292930' }"
>
商品精准推荐
</div>
<!-- <transition name="fade">
<div style="width: 168px; margin-top: 24px;" :style="{ transform: `translateX(${underlinePosition}px)` }">
<div
style="
width: 74px;
height: 4px;
flex-shrink: 0;
background: #333af4;
margin: 0 auto;
"
></div>
</div>
</transition> -->
</div>
<div
style="width: 200px; cursor: pointer"
:class="{ selected: selectedOption === '3' }"
@click="selectOption('3')"
>
<div
style="
color: #292930;
font-family: Noto Sans;
font-size: 10px;
font-style: normal;
font-weight: 500;
line-height: 156.5%;
letter-spacing: 1.515px;
text-transform: uppercase;
width: 200px;
"
>
// 03 . Email Composition
</div>
<div
style="
font-family: PingFang SC;
font-size: 28px;
font-style: normal;
font-weight: 600;
line-height: 110.5%;
margin-top: 10px;
"
:style="{ color: selectedOption === '3' ? '#333af4' : '#292930' }"
>
高效邮件创作
</div>
<!-- <transition name="fade">
<div style="width: 168px; margin-top: 24px" v-if="selectedOption === '3'">
<div
style="
width: 74px;
height: 4px;
flex-shrink: 0;
background: #333af4;
margin: 0 auto;
"
></div>
</div>
</transition> -->
</div>
<div
style="width: 200px; cursor: pointer"
:class="{ selected: selectedOption === '4' }"
@click="selectOption('4')"
>
<div
style="
color: #292930;
font-family: Noto Sans;
font-size: 10px;
font-style: normal;
font-weight: 500;
line-height: 156.5%;
letter-spacing: 1.515px;
text-transform: uppercase;
width: 200px;
"
>
// 04 . Excellent Channels
</div>
<div
style="
font-family: PingFang SC;
font-size: 28px;
font-style: normal;
font-weight: 600;
line-height: 110.5%;
margin-top: 10px;
"
:style="{ color: selectedOption === '4' ? '#333af4' : '#292930' }"
>
卓越通道效果
</div>
<!-- <transition name="fade">
<div style="width: 168px; margin-top: 24px" v-if="selectedOption === '4'">
<div
style="
width: 74px;
height: 4px;
flex-shrink: 0;
background: #333af4;
margin: 0 auto;
"
></div>
</div>
</transition> -->
</div>
</div>
<script>
updateUnderlinePosition() {
if (this.selectedOption === '1') {
this.underlinePosition = 0;
} else if (this.selectedOption === '2') {
this.underlinePosition = (Number(this.selectedOption) - 1) * 300; // Assuming each option has a width of 200px
} else if (this.selectedOption === '3') {
this.underlinePosition = (Number(this.selectedOption) - 1) * 300;
} else if (this.selectedOption === '4') {
this.underlinePosition = (Number(this.selectedOption) - 1) * 300;
}
},
</script>
<style>
.fade-enter-active {
transition: all .3s ease;
}
.fade-leave-active {
transition: all .1s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.fade-enter, .fade-leave-to
/* .slide-fade-leave-active for below version 2.1.8 */ {
transform: translateX(10px);
opacity: 0;
}
.underline {
transition: transform 0.3s;
}
</style>
前端国际化
如果项目很大手动翻译需要大量时间的话,可以使用自动化翻译插件实现 vite-plugin-auto-i18n
具体配置如下,以react项目 vite为例
vite.config.ts
import vuePluginsAutoI18n from "vite-plugin-auto-i18n";
vuePluginsAutoI18n({
option:{
globalPath: './lang',
namespace: 'lang',
distPath: './dist/assets',
distKey: 'index',
langKey: ['zh-cn', 'en'],
originLang: 'zh-cn',
excludedPath:[/node_modules/,],
includePath:[/src/,],
}
})
然后只要运行项目就会开始翻译,然后在lang/index.json中生成翻译的内容, 只要翻译过的部分就不会再重新翻译 可以在本地执行npm run build 然后一次性全部翻译,这样就不需要后续在运行阶段翻译了(可能会网络失败,换个节点比如美国节点就可以成功)
手动实现tab 点击锚点定位到具体区域,及 tab 栏吸顶的效果
const handleChange = (value) => {
const cardEle = document.getElementById(`${value}`)
if (cardEle) {
cardEle.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
{/* tab 组合 */}
<div className={styles.tabColumn}>
<Tabs defaultActiveKey="1" onChange={handleChange}>
{cardTitles.map(item => <TabPane tab={item.title} key={item.value} style={{ height: 200 }}>
</TabPane>)}
</Tabs>
</div>
.tabColumn {
:global(.ant-tabs-content) {
display: none !important;
}
:global(.ant-tabs-bar) {
margin-bottom: 0px !important;
}
position: sticky;
top: 0;
z-index: 10000;
background-color: #FFFFFF;
}
// 解决锚点定位 tab 栏遮挡问题
.customerInfo,
.houseInfo,
.recommendInfo,
.demandInfo,
.intergralDecoraProject,
.organizationInfo,
.serviceInfo {
margin-top: 9px;
scroll-margin-top: 40px;
}