commit 959b01b9fbfbd014302e2c256f367da0f85ab7f9 Author: sundongyu <2811054731@qq.com> Date: Fri May 24 09:16:17 2024 +0800 ✨ feat:初始化项目 diff --git a/.env b/.env new file mode 100644 index 0000000..459dac4 --- /dev/null +++ b/.env @@ -0,0 +1,21 @@ +# 版本号 +SHOPRO_VERSION = v1.8.3 + +# 后端接口 - 正式环境(通过 process.env.NODE_ENV 非 development) +SHOPRO_BASE_URL = http://api-dashboard.yudao.iocoder.cn + +# 后端接口 - 测试环境(通过 process.env.NODE_ENV = development) +SHOPRO_DEV_BASE_URL = http://192.168.1.14:48080 +### SHOPRO_DEV_BASE_URL = http://192.168.1.14:48080 + +# 后端接口前缀(一般不建议调整) +SHOPRO_API_PATH = /app-api + +# 开发环境运行端口 +SHOPRO_DEV_PORT = 3000 + +# 客户端静态资源地址 空=默认使用服务端指定的CDN资源地址前缀 | local=本地 | http(s)://xxx.xxx=自定义静态资源地址前缀 +SHOPRO_STATIC_URL = https://file.sheepjs.com + +# 是否开启直播 1 开启直播 | 0 关闭直播 (小程序官方后台未审核开通直播权限时请勿开启) +SHOPRO_MPLIVE_ON = 0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..43dda18 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +unpackage/* +node_modules/* +.idea/* +deploy.sh +.hbuilderx/ +.vscode/ +**/.DS_Store +yarn.lock +package-lock.json +*.keystore +pnpm-lock.yaml diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..7384df0 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +/unpackage/* +/node_modules/** +/uni_modules/** +/public/* +**/*.svg +**/*.sh diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..fe0a3f9 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "printWidth": 100, + "semi": true, + "vueIndentScriptAndStyle": true, + "singleQuote": true, + "trailingComma": "all", + "proseWrap": "never", + "htmlWhitespaceSensitivity": "strict", + "endOfLine": "auto" +} diff --git a/App.vue b/App.vue new file mode 100644 index 0000000..862c871 --- /dev/null +++ b/App.vue @@ -0,0 +1,39 @@ + + + diff --git a/androidPrivacy.json b/androidPrivacy.json new file mode 100644 index 0000000..0d726ca --- /dev/null +++ b/androidPrivacy.json @@ -0,0 +1,3 @@ +{ + "prompt" : "template" +} diff --git a/api/infra/file.js b/api/infra/file.js new file mode 100644 index 0000000..a4bac46 --- /dev/null +++ b/api/infra/file.js @@ -0,0 +1,45 @@ +import { baseUrl, apiPath } from '@/sheep/config'; + +const FileApi = { + // 上传文件 + uploadFile: (file) => { + // TODO 芋艿:访问令牌的接入; + const token = uni.getStorageSync('token'); + uni.showLoading({ + title: '上传中', + }); + return new Promise((resolve, reject) => { + uni.uploadFile({ + url: baseUrl + apiPath + '/infra/file/upload', + filePath: file, + name: 'file', + header: { + // Accept: 'text/json', + Accept : '*/*', + 'tenant-id' :'1', + // Authorization: 'Bearer test247', + }, + success: (uploadFileRes) => { + let result = JSON.parse(uploadFileRes.data); + if (result.error === 1) { + uni.showToast({ + icon: 'none', + title: result.msg, + }); + } else { + return resolve(result); + } + }, + fail: (error) => { + console.log('上传失败:', error); + return resolve(false); + }, + complete: () => { + uni.hideLoading(); + }, + }); + }); + }, +}; + +export default FileApi; diff --git a/api/member/address.js b/api/member/address.js new file mode 100644 index 0000000..d0c16ce --- /dev/null +++ b/api/member/address.js @@ -0,0 +1,53 @@ +import request from '@/sheep/request'; + +const AddressApi = { + // 获得用户收件地址列表 + getAddressList: () => { + return request({ + url: '/member/address/list', + method: 'GET' + }); + }, + // 创建用户收件地址 + createAddress: (data) => { + return request({ + url: '/member/address/create', + method: 'POST', + data, + custom: { + showSuccess: true, + successMsg: '保存成功' + }, + }); + }, + // 更新用户收件地址 + updateAddress: (data) => { + return request({ + url: '/member/address/update', + method: 'PUT', + data, + custom: { + showSuccess: true, + successMsg: '更新成功' + }, + }); + }, + // 获得用户收件地址 + getAddress: (id) => { + return request({ + url: '/member/address/get', + method: 'GET', + params: { id } + }); + }, + // 删除用户收件地址 + deleteAddress: (id) => { + return request({ + url: '/member/address/delete', + method: 'DELETE', + params: { id } + }); + }, +}; + +export default AddressApi; diff --git a/api/member/auth.js b/api/member/auth.js new file mode 100644 index 0000000..a1c0660 --- /dev/null +++ b/api/member/auth.js @@ -0,0 +1,132 @@ +import request from '@/sheep/request'; + +const AuthUtil = { + // 使用手机 + 密码登录 + login: (data) => { + return request({ + url: '/member/auth/login', + method: 'POST', + data, + custom: { + showSuccess: true, + loadingMsg: '登录中', + successMsg: '登录成功', + }, + }); + }, + // 使用手机 + 验证码登录 + smsLogin: (data) => { + return request({ + url: '/member/auth/sms-login', + method: 'POST', + data, + custom: { + showSuccess: true, + loadingMsg: '登录中', + successMsg: '登录成功', + }, + }); + }, + // 发送手机验证码 + sendSmsCode: (mobile, scene) => { + return request({ + url: '/member/auth/send-sms-code', + method: 'POST', + data: { + mobile, + scene, + }, + custom: { + loadingMsg: '发送中', + showSuccess: true, + successMsg: '发送成功', + }, + }); + }, + // 登出系统 + logout: () => { + return request({ + url: '/member/auth/logout', + method: 'POST', + }); + }, + // 刷新令牌 + refreshToken: (refreshToken) => { + return request({ + url: '/member/auth/refresh-token', + method: 'POST', + params: { + refreshToken + }, + custom: { + loading: false, // 不用加载中 + showError: false, // 不展示错误提示 + }, + }); + }, + // 社交授权的跳转 + socialAuthRedirect: (type, redirectUri) => { + return request({ + url: '/member/auth/social-auth-redirect', + method: 'GET', + params: { + type, + redirectUri, + }, + custom: { + showSuccess: true, + loadingMsg: '登陆中', + }, + }); + }, + // 社交快捷登录 + socialLogin: (type, code, state) => { + return request({ + url: '/member/auth/social-login', + method: 'POST', + data: { + type, + code, + state, + }, + custom: { + showSuccess: true, + loadingMsg: '登陆中', + }, + }); + }, + // 微信小程序的一键登录 + weixinMiniAppLogin: (phoneCode, loginCode, state) => { + return request({ + url: '/member/auth/weixin-mini-app-login', + method: 'POST', + data: { + phoneCode, + loginCode, + state + }, + custom: { + showSuccess: true, + loadingMsg: '登陆中', + successMsg: '登录成功', + }, + }); + }, + // 创建微信 JS SDK 初始化所需的签名 + createWeixinMpJsapiSignature: (url) => { + return request({ + url: '/member/auth/create-weixin-jsapi-signature', + method: 'POST', + params: { + url + }, + custom: { + showError: false, + showLoading: false, + }, + }) + }, + // +}; + +export default AuthUtil; diff --git a/api/member/point.js b/api/member/point.js new file mode 100644 index 0000000..188ffd4 --- /dev/null +++ b/api/member/point.js @@ -0,0 +1,19 @@ +import request from '@/sheep/request'; + +const PointApi = { + // 获得用户积分记录分页 + getPointRecordPage: (params) => { + if (params.addStatus === undefined) { + delete params.addStatus + } + const queryString = Object.keys(params) + .map((key) => encodeURIComponent(key) + '=' + params[key]) + .join('&'); + return request({ + url: `/member/point/record/page?${queryString}`, + method: 'GET', + }); + } +}; + +export default PointApi; diff --git a/api/member/signin.js b/api/member/signin.js new file mode 100644 index 0000000..35169ef --- /dev/null +++ b/api/member/signin.js @@ -0,0 +1,37 @@ +import request from '@/sheep/request'; + +const SignInApi = { + // 获得签到规则列表 + getSignInConfigList: () => { + return request({ + url: '/member/sign-in/config/list', + method: 'GET', + }); + }, + // 获得个人签到统计 + getSignInRecordSummary: () => { + return request({ + url: '/member/sign-in/record/get-summary', + method: 'GET', + }); + }, + // 签到 + createSignInRecord: () => { + return request({ + url: '/member/sign-in/record/create', + method: 'POST', + }); + }, + // 获得签到记录分页 + getSignRecordPage: (params) => { + const queryString = Object.keys(params) + .map((key) => encodeURIComponent(key) + '=' + params[key]) + .join('&'); + return request({ + url: `/member/sign-in/record/page?${queryString}`, + method: 'GET', + }); + }, +}; + +export default SignInApi; \ No newline at end of file diff --git a/api/member/social.js b/api/member/social.js new file mode 100644 index 0000000..f7ab259 --- /dev/null +++ b/api/member/social.js @@ -0,0 +1,54 @@ +import request from '@/sheep/request'; + +const SocialApi = { + // 获得社交用户 + getSocialUser: (type) => { + return request({ + url: '/member/social-user/get', + method: 'GET', + params: { + type + }, + custom: { + showLoading: false, + }, + }); + }, + // 社交绑定 + socialBind: (type, code, state) => { + return request({ + url: '/member/social-user/bind', + method: 'POST', + data: { + type, + code, + state + }, + custom: { + custom: { + showSuccess: true, + loadingMsg: '绑定中', + successMsg: '绑定成功', + }, + }, + }); + }, + // 社交绑定 + socialUnbind: (type, openid) => { + return request({ + url: '/member/social-user/unbind', + method: 'DELETE', + data: { + type, + openid + }, + custom: { + showLoading: false, + loadingMsg: '解除绑定', + successMsg: '解绑成功', + }, + }); + }, +}; + +export default SocialApi; \ No newline at end of file diff --git a/api/member/user.js b/api/member/user.js new file mode 100644 index 0000000..5f06e42 --- /dev/null +++ b/api/member/user.js @@ -0,0 +1,85 @@ +import request from '@/sheep/request'; + +const UserApi = { + // 获得基本信息 + getUserInfo: () => { + return request({ + url: '/member/user/get', + method: 'GET', + custom: { + showLoading: false, + auth: true, + }, + }); + }, + // 修改基本信息 + updateUser: (data) => { + return request({ + url: '/member/user/update', + method: 'PUT', + data, + custom: { + auth: true, + showSuccess: true, + successMsg: '更新成功' + }, + }); + }, + // 修改用户手机 + updateUserMobile: (data) => { + return request({ + url: '/member/user/update-mobile', + method: 'PUT', + data, + custom: { + loadingMsg: '验证中', + showSuccess: true, + successMsg: '修改成功' + }, + }); + }, + // 基于微信小程序的授权码,修改用户手机 + updateUserMobileByWeixin: (code) => { + return request({ + url: '/member/user/update-mobile-by-weixin', + method: 'PUT', + data: { + code + }, + custom: { + showSuccess: true, + loadingMsg: '获取中', + successMsg: '修改成功' + }, + }); + }, + // 修改密码 + updateUserPassword: (data) => { + return request({ + url: '/member/user/update-password', + method: 'PUT', + data, + custom: { + loadingMsg: '验证中', + showSuccess: true, + successMsg: '修改成功' + }, + }); + }, + // 重置密码 + resetUserPassword: (data) => { + return request({ + url: '/member/user/reset-password', + method: 'PUT', + data, + custom: { + loadingMsg: '验证中', + showSuccess: true, + successMsg: '修改成功' + } + }); + }, + +}; + +export default UserApi; diff --git a/api/migration/app.js b/api/migration/app.js new file mode 100644 index 0000000..158414f --- /dev/null +++ b/api/migration/app.js @@ -0,0 +1,21 @@ +import request from '@/sheep/request'; + +// TODO 芋艿:小程序直播还不支持 +export default { + //小程序直播 + mplive: { + getRoomList: (ids) => + request({ + url: 'app/mplive/getRoomList', + method: 'GET', + params: { + ids: ids.join(','), + } + }), + getMpLink: () => + request({ + url: 'app/mplive/getMpLink', + method: 'GET' + }), + }, +}; diff --git a/api/migration/chat.js b/api/migration/chat.js new file mode 100644 index 0000000..140dd75 --- /dev/null +++ b/api/migration/chat.js @@ -0,0 +1,14 @@ +import request from '@/sheep/request'; + +// TODO 芋艿:暂不支持 socket 聊天 +export default { + // 获取聊天token + unifiedToken: () => + request({ + url: 'unifiedToken', + custom: { + showError: false, + showLoading: false, + }, + }), +}; diff --git a/api/migration/index.js b/api/migration/index.js new file mode 100644 index 0000000..31a85ef --- /dev/null +++ b/api/migration/index.js @@ -0,0 +1,10 @@ +const files = import.meta.glob('./*.js', { eager: true }); +let api = {}; +Object.keys(files).forEach((key) => { + api = { + ...api, + [key.replace(/(.*\/)*([^.]+).*/gi, '$2')]: files[key].default, + }; +}); + +export default api; \ No newline at end of file diff --git a/api/migration/third.js b/api/migration/third.js new file mode 100644 index 0000000..85db8cc --- /dev/null +++ b/api/migration/third.js @@ -0,0 +1,48 @@ +import request from '@/sheep/request'; + +export default { + // 微信相关 + wechat: { + // 小程序订阅消息 + subscribeTemplate: (params) => + request({ + url: 'third/wechat/subscribeTemplate', + method: 'GET', + params: { + platform: 'miniProgram', + }, + custom: { + showError: false, + showLoading: false, + }, + }), + + // 获取微信小程序码 + getWxacode: async (path, query) => { + return await request({ + url: '/member/social-user/wxa-qrcode', + method: 'POST', + data: { + scene: query, + path, + checkPath: false, // TODO 开发环境暂不检查 path 是否存在 + }, + }); + }, + }, + + // 苹果相关 + apple: { + // 第三方登录 + login: (data) => + request({ + url: 'third/apple/login', + method: 'POST', + data, + custom: { + showSuccess: true, + loadingMsg: '登陆中', + }, + }), + }, +}; diff --git a/api/pay/channel.js b/api/pay/channel.js new file mode 100644 index 0000000..4e7bfc5 --- /dev/null +++ b/api/pay/channel.js @@ -0,0 +1,14 @@ +import request from '@/sheep/request'; + +const PayChannelApi = { + // 获得指定应用的开启的支付渠道编码列表 + getEnableChannelCodeList: (appId) => { + return request({ + url: '/pay/channel/get-enable-code-list', + method: 'GET', + params: { appId } + }); + }, +}; + +export default PayChannelApi; diff --git a/api/pay/order.js b/api/pay/order.js new file mode 100644 index 0000000..f985359 --- /dev/null +++ b/api/pay/order.js @@ -0,0 +1,22 @@ +import request from '@/sheep/request'; + +const PayOrderApi = { + // 获得支付订单 + getOrder: (id) => { + return request({ + url: '/pay/order/get', + method: 'GET', + params: { id } + }); + }, + // 提交支付订单 + submitOrder: (data) => { + return request({ + url: '/pay/order/submit', + method: 'POST', + data + }); + } +}; + +export default PayOrderApi; diff --git a/api/pay/wallet.js b/api/pay/wallet.js new file mode 100644 index 0000000..023902a --- /dev/null +++ b/api/pay/wallet.js @@ -0,0 +1,68 @@ +import request from '@/sheep/request'; + +const PayWalletApi = { + // 获取钱包 + getPayWallet() { + return request({ + url: '/pay/wallet/get', + method: 'GET', + custom: { + showLoading: false, + auth: true, + }, + }); + }, + // 获得钱包流水分页 + getWalletTransactionPage: (params) => { + const queryString = Object.keys(params) + .map((key) => encodeURIComponent(key) + '=' + params[key]) + .join('&'); + return request({ + url: `/pay/wallet-transaction/page?${queryString}`, + method: 'GET', + }); + }, + // 获得钱包流水统计 + getWalletTransactionSummary: (params) => { + const queryString = `createTime=${params.createTime[0]}&createTime=${params.createTime[1]}`; + return request({ + url: `/pay/wallet-transaction/get-summary?${queryString}`, + // url: `/pay/wallet-transaction/get-summary`, + method: 'GET', + // params: params + }); + }, + // 获得钱包充值套餐列表 + getWalletRechargePackageList: () => { + return request({ + url: '/pay/wallet-recharge-package/list', + method: 'GET', + custom: { + showError: false, + showLoading: false, + }, + }); + }, + // 创建钱包充值记录(发起充值) + createWalletRecharge: (data) => { + return request({ + url: '/pay/wallet-recharge/create', + method: 'POST', + data, + }); + }, + // 获得钱包充值记录分页 + getWalletRechargePage: (params) => { + return request({ + url: '/pay/wallet-recharge/page', + method: 'GET', + params, + custom: { + showError: false, + showLoading: false, + }, + }); + }, +}; + +export default PayWalletApi; diff --git a/api/product/category.js b/api/product/category.js new file mode 100644 index 0000000..3b6f8f0 --- /dev/null +++ b/api/product/category.js @@ -0,0 +1,21 @@ +import request from '@/sheep/request'; + +const CategoryApi = { + // 查询分类列表 + getCategoryList: () => { + return request({ + url: '/product/category/list', + method: 'GET', + }); + }, + // 查询分类列表,指定编号 + getCategoryListByIds: (ids) => { + return request({ + url: '/product/category/list-by-ids', + method: 'GET', + params: { ids }, + }); + }, +}; + +export default CategoryApi; diff --git a/api/product/comment.js b/api/product/comment.js new file mode 100644 index 0000000..2bbffd1 --- /dev/null +++ b/api/product/comment.js @@ -0,0 +1,22 @@ +import request from '@/sheep/request'; + +const CommentApi = { + // 获得商品评价分页 + getCommentPage: (spuId, pageNo, pageSize, type) => { + return request({ + url: '/product/comment/page', + method: 'GET', + params: { + spuId, + pageNo, + pageSize, + type, + }, + custom: { + showLoading: false, + showError: false, + }, + }); + }, +}; +export default CommentApi; diff --git a/api/product/favorite.js b/api/product/favorite.js new file mode 100644 index 0000000..134c231 --- /dev/null +++ b/api/product/favorite.js @@ -0,0 +1,54 @@ +import request from '@/sheep/request'; + +const FavoriteApi = { + // 获得商品收藏分页 + getFavoritePage: (data) => { + return request({ + url: '/product/favorite/page', + method: 'GET', + params: data, + }); + }, + // 检查是否收藏过商品 + isFavoriteExists: (spuId) => { + return request({ + url: '/product/favorite/exits', + method: 'GET', + params: { + spuId, + }, + }); + }, + // 添加商品收藏 + createFavorite: (spuId) => { + return request({ + url: '/product/favorite/create', + method: 'POST', + data: { + spuId, + }, + custom: { + auth: true, + showSuccess: true, + successMsg: '收藏成功', + }, + }); + }, + // 取消商品收藏 + deleteFavorite: (spuId) => { + return request({ + url: '/product/favorite/delete', + method: 'DELETE', + data: { + spuId, + }, + custom: { + auth: true, + showSuccess: true, + successMsg: '取消成功', + }, + }); + }, +}; + +export default FavoriteApi; diff --git a/api/product/history.js b/api/product/history.js new file mode 100644 index 0000000..9ed53e3 --- /dev/null +++ b/api/product/history.js @@ -0,0 +1,39 @@ +import request from '@/sheep/request'; + +const SpuHistoryApi = { + // 删除商品浏览记录 + deleteBrowseHistory: (spuIds) => { + return request({ + url: '/product/browse-history/delete', + method: 'DELETE', + data: { spuIds }, + custom: { + showSuccess: true, + successMsg: '删除成功', + }, + }); + }, + // 清空商品浏览记录 + cleanBrowseHistory: () => { + return request({ + url: '/product/browse-history/clean', + method: 'DELETE', + custom: { + showSuccess: true, + successMsg: '清空成功', + }, + }); + }, + // 获得商品浏览记录分页 + getBrowseHistoryPage: (data) => { + return request({ + url: '/product/browse-history/page', + method: 'GET', + data, + custom: { + showLoading: false + }, + }); + }, +}; +export default SpuHistoryApi; diff --git a/api/product/spu.js b/api/product/spu.js new file mode 100644 index 0000000..3b84c1d --- /dev/null +++ b/api/product/spu.js @@ -0,0 +1,41 @@ +import request from '@/sheep/request'; + +const SpuApi = { + // 获得商品 SPU 列表 + getSpuListByIds: (ids) => { + return request({ + url: '/product/spu/list-by-ids', + method: 'GET', + params: { ids }, + custom: { + showLoading: false, + showError: false, + }, + }); + }, + // 获得商品 SPU 分页 + getSpuPage: (params) => { + return request({ + url: '/product/spu/page', + method: 'GET', + params, + custom: { + showLoading: false, + showError: false, + }, + }); + }, + // 查询商品 + getSpuDetail: (id) => { + return request({ + url: '/product/spu/get-detail', + method: 'GET', + params: { id }, + custom: { + showLoading: false, + showError: false, + }, + }); + }, +}; +export default SpuApi; diff --git a/api/promotion/activity.js b/api/promotion/activity.js new file mode 100644 index 0000000..eb47ce1 --- /dev/null +++ b/api/promotion/activity.js @@ -0,0 +1,16 @@ +import request from '@/sheep/request'; + +const ActivityApi = { + // 获得单个商品,近期参与的每个活动 + getActivityListBySpuId: (spuId) => { + return request({ + url: '/promotion/activity/list-by-spu-id', + method: 'GET', + params: { + spuId, + }, + }); + }, +}; + +export default ActivityApi; diff --git a/api/promotion/article.js b/api/promotion/article.js new file mode 100644 index 0000000..ded5fb1 --- /dev/null +++ b/api/promotion/article.js @@ -0,0 +1,12 @@ +import request from '@/sheep/request'; + +export default { + // 获得文章详情 + getArticle: (id, title) => { + return request({ + url: '/promotion/article/get', + method: 'GET', + params: { id, title } + }); + } +} diff --git a/api/promotion/combination.js b/api/promotion/combination.js new file mode 100644 index 0000000..44f61e9 --- /dev/null +++ b/api/promotion/combination.js @@ -0,0 +1,76 @@ +import request from '@/sheep/request'; + +// 拼团 API +const CombinationApi = { + // 获得拼团活动列表 + getCombinationActivityList: (count) => { + return request({ + url: '/promotion/combination-activity/list', + method: 'GET', + params: { count }, + }); + }, + + // 获得拼团活动分页 + getCombinationActivityPage: (params) => { + return request({ + url: '/promotion/combination-activity/page', + method: 'GET', + params, + }); + }, + + // 获得拼团活动明细 + getCombinationActivity: (id) => { + return request({ + url: '/promotion/combination-activity/get-detail', + method: 'GET', + params: { + id, + }, + }); + }, + + // 获得最近 n 条拼团记录(团长发起的) + getHeadCombinationRecordList: (activityId, status, count) => { + return request({ + url: '/promotion/combination-record/get-head-list', + method: 'GET', + params: { + activityId, + status, + count, + }, + }); + }, + + // 获得我的拼团记录分页 + getCombinationRecordPage: (params) => { + return request({ + url: "/promotion/combination-record/page", + method: 'GET', + params + }); + }, + + // 获得拼团记录明细 + getCombinationRecordDetail: (id) => { + return request({ + url: '/promotion/combination-record/get-detail', + method: 'GET', + params: { + id, + }, + }); + }, + + // 获得拼团记录的概要信息 + getCombinationRecordSummary: () => { + return request({ + url: '/promotion/combination-record/get-summary', + method: 'GET', + }); + }, +}; + +export default CombinationApi; diff --git a/api/promotion/coupon.js b/api/promotion/coupon.js new file mode 100644 index 0000000..c9dfa52 --- /dev/null +++ b/api/promotion/coupon.js @@ -0,0 +1,101 @@ +import request from '@/sheep/request'; + +const CouponApi = { + // 获得优惠劵模板列表 + getCouponTemplateListByIds: (ids) => { + return request({ + url: '/promotion/coupon-template/list-by-ids', + method: 'GET', + params: { ids }, + custom: { + showLoading: false, // 不展示 Loading,避免领取优惠劵时,不成功提示 + showError: false, + }, + }); + }, + // 获得优惠劵模版列表 + getCouponTemplateList: (spuId, productScope, count) => { + return request({ + url: '/promotion/coupon-template/list', + method: 'GET', + params: { spuId, productScope, count }, + }); + }, + // 获得优惠劵模版分页 + getCouponTemplatePage: (params) => { + return request({ + url: '/promotion/coupon-template/page', + method: 'GET', + params, + }); + }, + // 获得优惠劵模版 + getCouponTemplate: (id) => { + return request({ + url: '/promotion/coupon-template/get', + method: 'GET', + params: { id }, + }); + }, + // 我的优惠劵列表 + getCouponPage: (params) => { + return request({ + url: '/promotion/coupon/page', + method: 'GET', + params, + }); + }, + // 领取优惠券 + takeCoupon: (templateId) => { + return request({ + url: '/promotion/coupon/take', + method: 'POST', + data: { templateId }, + custom: { + auth: true, + showLoading: true, + loadingMsg: '领取中', + showSuccess: true, + successMsg: '领取成功', + }, + }); + }, + // 获得优惠劵 + getCoupon: (id) => { + return request({ + url: '/promotion/coupon/get', + method: 'GET', + params: { id }, + }); + }, + // 获得未使用的优惠劵数量 + getUnusedCouponCount: () => { + return request({ + url: '/promotion/coupon/get-unused-count', + method: 'GET', + custom: { + showLoading: false, + auth: true, + }, + }); + }, + // 获得匹配指定商品的优惠劵列表 + getMatchCouponList: (price, spuIds, skuIds, categoryIds) => { + return request({ + url: '/promotion/coupon/match-list', + method: 'GET', + params: { + price, + spuIds: spuIds.join(','), + skuIds: skuIds.join(','), + categoryIds: categoryIds.join(','), + }, + custom: { + showError: false, + showLoading: false, // 避免影响 settlementOrder 结算的结果 + }, + }); + } +}; + +export default CouponApi; diff --git a/api/promotion/diy.js b/api/promotion/diy.js new file mode 100644 index 0000000..deff330 --- /dev/null +++ b/api/promotion/diy.js @@ -0,0 +1,38 @@ +import request from '@/sheep/request'; + +const DiyApi = { + getUsedDiyTemplate: () => { + return request({ + url: '/promotion/diy-template/used', + method: 'GET', + custom: { + showError: false, + showLoading: false, + }, + }); + }, + getDiyTemplate: (id) => { + return request({ + url: '/promotion/diy-template/get', + method: 'GET', + params: { + id, + }, + custom: { + showError: false, + showLoading: false, + }, + }); + }, + getDiyPage: (id) => { + return request({ + url: '/promotion/diy-page/get', + method: 'GET', + params: { + id, + }, + }); + }, +}; + +export default DiyApi; diff --git a/api/promotion/rewardActivity.js b/api/promotion/rewardActivity.js new file mode 100644 index 0000000..5f74db7 --- /dev/null +++ b/api/promotion/rewardActivity.js @@ -0,0 +1,14 @@ +import request from '@/sheep/request'; + +const RewardActivityApi = { + // 获得满减送活动 + getRewardActivity: (id) => { + return request({ + url: '/promotion/reward-activity/get', + method: 'GET', + params: { id }, + }); + } +}; + +export default RewardActivityApi; \ No newline at end of file diff --git a/api/promotion/seckill.js b/api/promotion/seckill.js new file mode 100644 index 0000000..4d41e8b --- /dev/null +++ b/api/promotion/seckill.js @@ -0,0 +1,33 @@ +import request from "@/sheep/request"; + +const SeckillApi = { + // 获得秒杀时间段列表 + getSeckillConfigList: () => { + return request({ url: 'promotion/seckill-config/list', method: 'GET' }); + }, + + // 获得当前秒杀活动 + getNowSeckillActivity: () => { + return request({ url: 'promotion/seckill-activity/get-now', method: 'GET' }); + }, + + // 获得秒杀活动分页 + getSeckillActivityPage: (params) => { + return request({ url: 'promotion/seckill-activity/page', method: 'GET', params }); + }, + + /** + * 获得秒杀活动明细 + * @param {number} id 秒杀活动编号 + * @return {*} + */ + getSeckillActivity: (id) => { + return request({ + url: 'promotion/seckill-activity/get-detail', + method: 'GET', + params: { id } + }); + } +} + +export default SeckillApi; diff --git a/api/system/area.js b/api/system/area.js new file mode 100644 index 0000000..7c41eff --- /dev/null +++ b/api/system/area.js @@ -0,0 +1,13 @@ +import request from '@/sheep/request'; + +const AreaApi = { + // 获得地区树 + getAreaTree: () => { + return request({ + url: '/system/area/tree', + method: 'GET' + }); + }, +}; + +export default AreaApi; diff --git a/api/trade/afterSale.js b/api/trade/afterSale.js new file mode 100644 index 0000000..44c4dd3 --- /dev/null +++ b/api/trade/afterSale.js @@ -0,0 +1,63 @@ +import request from '@/sheep/request'; + +const AfterSaleApi = { + // 获得售后分页 + getAfterSalePage: (params) => { + return request({ + url: `/trade/after-sale/page`, + method: 'GET', + params, + custom: { + showLoading: false, + }, + }); + }, + // 创建售后 + createAfterSale: (data) => { + return request({ + url: `/trade/after-sale/create`, + method: 'POST', + data, + }); + }, + // 获得售后 + getAfterSale: (id) => { + return request({ + url: `/trade/after-sale/get`, + method: 'GET', + params: { + id, + }, + }); + }, + // 取消售后 + cancelAfterSale: (id) => { + return request({ + url: `/trade/after-sale/cancel`, + method: 'DELETE', + params: { + id, + }, + }); + }, + // 获得售后日志列表 + getAfterSaleLogList: (afterSaleId) => { + return request({ + url: `/trade/after-sale-log/list`, + method: 'GET', + params: { + afterSaleId, + }, + }); + }, + // 退回货物 + deliveryAfterSale: (data) => { + return request({ + url: `/trade/after-sale/delivery`, + method: 'PUT', + data, + }); + } +}; + +export default AfterSaleApi; diff --git a/api/trade/brokerage.js b/api/trade/brokerage.js new file mode 100644 index 0000000..f7ac986 --- /dev/null +++ b/api/trade/brokerage.js @@ -0,0 +1,85 @@ +import request from '@/sheep/request'; + +const BrokerageApi = { + // 获得个人分销信息 + getBrokerageUser: () => { + return request({ + url: '/trade/brokerage-user/get', + method: 'GET' + }); + }, + // 获得个人分销统计 + getBrokerageUserSummary: () => { + return request({ + url: '/trade/brokerage-user/get-summary', + method: 'GET', + }); + }, + // 获得分销记录分页 + getBrokerageRecordPage: params => { + if (params.status === undefined) { + delete params.status + } + const queryString = Object.keys(params) + .map(key => encodeURIComponent(key) + '=' + params[key]) + .join('&'); + return request({ + url: `/trade/brokerage-record/page?${queryString}`, + method: 'GET', + }); + }, + // 创建分销提现 + createBrokerageWithdraw: data => { + return request({ + url: '/trade/brokerage-withdraw/create', + method: 'POST', + data, + }); + }, + // 获得商品的分销金额 + getProductBrokeragePrice: spuId => { + return request({ + url: '/trade/brokerage-record/get-product-brokerage-price', + method: 'GET', + params: { spuId } + }); + }, + // 获得分销用户排行(基于佣金) + getRankByPrice: params => { + const queryString = `times=${params.times[0]}×=${params.times[1]}`; + return request({ + url: `/trade/brokerage-user/get-rank-by-price?${queryString}`, + method: 'GET', + }); + }, + // 获得分销用户排行分页(基于佣金) + getBrokerageUserChildSummaryPageByPrice: params => { + const queryString = Object.keys(params) + .map(key => encodeURIComponent(key) + '=' + params[key]) + .join('&'); + return request({ + url: `/trade/brokerage-user/rank-page-by-price?${queryString}`, + method: 'GET', + }); + }, + // 获得分销用户排行分页(基于用户量) + getBrokerageUserRankPageByUserCount: params => { + const queryString = Object.keys(params) + .map(key => encodeURIComponent(key) + '=' + params[key]) + .join('&'); + return request({ + url: `/trade/brokerage-user/rank-page-by-user-count?${queryString}`, + method: 'GET', + }); + }, + // 获得下级分销统计分页 + getBrokerageUserChildSummaryPage: params => { + return request({ + url: '/trade/brokerage-user/child-summary-page', + method: 'GET', + params, + }) + } +} + +export default BrokerageApi diff --git a/api/trade/cart.js b/api/trade/cart.js new file mode 100644 index 0000000..63cea18 --- /dev/null +++ b/api/trade/cart.js @@ -0,0 +1,50 @@ +import request from '@/sheep/request'; + +const CartApi = { + addCart: (data) => { + return request({ + url: '/trade/cart/add', + method: 'POST', + data: data, + custom: { + showSuccess: true, + successMsg: '已添加到购物车~', + } + }); + }, + updateCartCount: (data) => { + return request({ + url: '/trade/cart/update-count', + method: 'PUT', + data: data + }); + }, + updateCartSelected: (data) => { + return request({ + url: '/trade/cart/update-selected', + method: 'PUT', + data: data + }); + }, + deleteCart: (ids) => { + return request({ + url: '/trade/cart/delete', + method: 'DELETE', + params: { + ids + } + }); + }, + getCartList: () => { + return request({ + url: '/trade/cart/list', + method: 'GET', + custom: { + showLoading: false, + auth: true, + }, + }); + }, +}; + +export default CartApi; \ No newline at end of file diff --git a/api/trade/config.js b/api/trade/config.js new file mode 100644 index 0000000..16806ed --- /dev/null +++ b/api/trade/config.js @@ -0,0 +1,13 @@ +import request from '@/sheep/request'; + +const TradeConfigApi = { + // 获得交易配置 + getTradeConfig: () => { + return request({ + url: `/trade/config/get`, + method: 'GET', + }); + }, +}; + +export default TradeConfigApi; diff --git a/api/trade/delivery.js b/api/trade/delivery.js new file mode 100644 index 0000000..27a08d9 --- /dev/null +++ b/api/trade/delivery.js @@ -0,0 +1,13 @@ +import request from '@/sheep/request'; + +const DeliveryApi = { + // 获得快递公司列表 + getDeliveryExpressList: () => { + return request({ + url: `/trade/delivery/express/list`, + method: 'get', + }); + } +}; + +export default DeliveryApi; diff --git a/api/trade/order.js b/api/trade/order.js new file mode 100644 index 0000000..4d57125 --- /dev/null +++ b/api/trade/order.js @@ -0,0 +1,139 @@ +import request from '@/sheep/request'; + +const OrderApi = { + // 计算订单信息 + settlementOrder: (data) => { + const data2 = { + ...data, + }; + // 移除多余字段 + if (!(data.couponId > 0)) { + delete data2.couponId; + } + if (!(data.addressId > 0)) { + delete data2.addressId; + } + if (!(data.combinationActivityId > 0)) { + delete data2.combinationActivityId; + } + if (!(data.combinationHeadId > 0)) { + delete data2.combinationHeadId; + } + if (!(data.seckillActivityId > 0)) { + delete data2.seckillActivityId; + } + // 解决 SpringMVC 接受 List 参数的问题 + delete data2.items; + for (let i = 0; i < data.items.length; i++) { + data2[encodeURIComponent('items[' + i + '' + '].skuId')] = data.items[i].skuId + ''; + data2[encodeURIComponent('items[' + i + '' + '].count')] = data.items[i].count + ''; + if (data.items[i].cartId) { + data2[encodeURIComponent('items[' + i + '' + '].cartId')] = data.items[i].cartId + ''; + } + } + const queryString = Object.keys(data2) + .map((key) => key + '=' + data2[key]) + .join('&'); + return request({ + url: `/trade/order/settlement?${queryString}`, + method: 'GET', + custom: { + showError: true, + showLoading: true, + }, + }); + }, + // 创建订单 + createOrder: (data) => { + return request({ + url: `/trade/order/create`, + method: 'POST', + data, + }); + }, + // 获得订单 + getOrder: (id) => { + return request({ + url: `/trade/order/get-detail`, + method: 'GET', + params: { + id, + }, + custom: { + showLoading: false, + }, + }); + }, + // 订单列表 + getOrderPage: (params) => { + return request({ + url: '/trade/order/page', + method: 'GET', + params, + custom: { + showLoading: false, + }, + }); + }, + // 确认收货 + receiveOrder: (id) => { + return request({ + url: `/trade/order/receive`, + method: 'PUT', + params: { + id, + }, + }); + }, + // 取消订单 + cancelOrder: (id) => { + return request({ + url: `/trade/order/cancel`, + method: 'DELETE', + params: { + id, + }, + }); + }, + // 删除订单 + deleteOrder: (id) => { + return request({ + url: `/trade/order/delete`, + method: 'DELETE', + params: { + id, + }, + }); + }, + // 获得交易订单的物流轨迹 + getOrderExpressTrackList: (id) => { + return request({ + url: `/trade/order/get-express-track-list`, + method: 'GET', + params: { + id, + }, + }); + }, + // 获得交易订单数量 + getOrderCount: () => { + return request({ + url: '/trade/order/get-count', + method: 'GET', + custom: { + showLoading: false, + auth: true, + }, + }); + }, + // 创建单个评论 + createOrderItemComment: (data) => { + return request({ + url: `/trade/order/item/create-comment`, + method: 'POST', + data, + }); + }, +}; + +export default OrderApi; diff --git a/index.html b/index.html new file mode 100644 index 0000000..2269a69 --- /dev/null +++ b/index.html @@ -0,0 +1,17 @@ + + + + + + + + + + +
+ + + diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..b1968ee --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "jsx": "preserve", + "baseUrl": ".", + "paths": { + "@/*": ["./*"] + } + } +} diff --git a/main.js b/main.js new file mode 100644 index 0000000..2680ac6 --- /dev/null +++ b/main.js @@ -0,0 +1,15 @@ +import App from './App'; +import { createSSRApp } from 'vue'; +import { setupPinia } from './sheep/store'; + + +export function createApp() { + + const app = createSSRApp(App); + + setupPinia(app); + + return { + app, + }; +} diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..b2d61ae --- /dev/null +++ b/manifest.json @@ -0,0 +1,239 @@ +{ + "name": "商城", + "appid": "__UNI__460BC4C", + "description": "基于 uni-app + Vue3 技术驱动的在线商城系统,内含诸多功能与丰富的活动,期待您的使用和反馈。", + "versionName": "2.1.0", + "versionCode": 183, + "transformPx": false, + "app-plus": { + "usingComponents": true, + "nvueCompiler": "uni-app", + "nvueStyleCompiler": "uni-app", + "compilerVersion": 3, + "nvueLaunchMode": "fast", + "splashscreen": { + "alwaysShowBeforeRender": true, + "waiting": true, + "autoclose": true, + "delay": 0 + }, + "safearea": { + "bottom": { + "offset": "none" + } + }, + "modules": { + "Payment": {}, + "Share": {}, + "VideoPlayer": {}, + "OAuth": {} + }, + "distribute": { + "android": { + "permissions": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "minSdkVersion": 21, + "schemes": "shopro" + }, + "ios": { + "urlschemewhitelist": [ + "baidumap", + "iosamap" + ], + "dSYMs": false, + "privacyDescription": { + "NSPhotoLibraryUsageDescription": "需要同意访问您的相册选取图片才能完善该条目", + "NSPhotoLibraryAddUsageDescription": "需要同意访问您的相册才能保存该图片", + "NSCameraUsageDescription": "需要同意访问您的摄像头拍摄照片才能完善该条目", + "NSUserTrackingUsageDescription": "开启追踪并不会获取您在其它站点的隐私信息,该行为仅用于标识设备,保障服务安全和提升浏览体验" + }, + "urltypes": "shopro", + "capabilities": { + "entitlements": { + "com.apple.developer.associated-domains": [ + "applinks:shopro.sheepjs.com" + ] + } + }, + "idfa": true + }, + "sdkConfigs": { + "speech": { + "ifly": {} + }, + "ad": {}, + "oauth": { + "apple": {}, + "weixin": { + "appid": "wxae7a0c156da9383b", + "UniversalLinks": "https://shopro.sheepjs.com/uni-universallinks/__UNI__082C0BA/" + } + }, + "payment": { + "weixin": { + "__platform__": [ + "ios", + "android" + ], + "appid": "wxae7a0c156da9383b", + "UniversalLinks": "https://shopro.sheepjs.com/uni-universallinks/__UNI__082C0BA/" + }, + "alipay": { + "__platform__": [ + "ios", + "android" + ] + } + }, + "share": { + "weixin": { + "appid": "wxae7a0c156da9383b", + "UniversalLinks": "https://shopro.sheepjs.com/uni-universallinks/__UNI__082C0BA/" + } + } + }, + "orientation": [ + "portrait-primary" + ], + "splashscreen": { + "androidStyle": "common", + "iosStyle": "common", + "useOriginalMsgbox": true + }, + "icons": { + "android": { + "hdpi": "unpackage/res/icons/72x72.png", + "xhdpi": "unpackage/res/icons/96x96.png", + "xxhdpi": "unpackage/res/icons/144x144.png", + "xxxhdpi": "unpackage/res/icons/192x192.png" + }, + "ios": { + "appstore": "unpackage/res/icons/1024x1024.png", + "ipad": { + "app": "unpackage/res/icons/76x76.png", + "app@2x": "unpackage/res/icons/152x152.png", + "notification": "unpackage/res/icons/20x20.png", + "notification@2x": "unpackage/res/icons/40x40.png", + "proapp@2x": "unpackage/res/icons/167x167.png", + "settings": "unpackage/res/icons/29x29.png", + "settings@2x": "unpackage/res/icons/58x58.png", + "spotlight": "unpackage/res/icons/40x40.png", + "spotlight@2x": "unpackage/res/icons/80x80.png" + }, + "iphone": { + "app@2x": "unpackage/res/icons/120x120.png", + "app@3x": "unpackage/res/icons/180x180.png", + "notification@2x": "unpackage/res/icons/40x40.png", + "notification@3x": "unpackage/res/icons/60x60.png", + "settings@2x": "unpackage/res/icons/58x58.png", + "settings@3x": "unpackage/res/icons/87x87.png", + "spotlight@2x": "unpackage/res/icons/80x80.png", + "spotlight@3x": "unpackage/res/icons/120x120.png" + } + } + } + } + }, + "quickapp": {}, + "quickapp-native": { + "icon": "/static/logo.png", + "package": "com.example.demo", + "features": [ + { + "name": "system.clipboard" + } + ] + }, + "quickapp-webview": { + "icon": "/static/logo.png", + "package": "com.example.demo", + "minPlatformVersion": 1070, + "versionName": "1.0.0", + "versionCode": 100 + }, + "mp-weixin": { + "appid": "wx98df718e528399d2", + "setting": { + "urlCheck": false, + "minified": true, + "postcss": true + }, + "optimization": { + "subPackages": true + }, + "plugins": {}, + "lazyCodeLoading": "requiredComponents", + "usingComponents": {}, + "permission": {}, + "requiredPrivateInfos": [ + "chooseAddress" + ] + }, + "mp-alipay": { + "usingComponents": true + }, + "mp-baidu": { + "usingComponents": true + }, + "mp-toutiao": { + "usingComponents": true + }, + "mp-jd": { + "usingComponents": true + }, + "h5": { + "template": "index.html", + "router": { + "mode": "hash", + "base": "./" + }, + "sdkConfigs": { + "maps": {} + }, + "async": { + "timeout": 20000 + }, + "title": "商城", + "optimization": { + "treeShaking": { + "enable": true + } + } + }, + "vueVersion": "3", + "_spaceID": "192b4892-5452-4e1d-9f09-eee1ece40639", + "locale": "zh-Hans", + "fallbackLocale": "zh-Hans" +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..2783cd2 --- /dev/null +++ b/package.json @@ -0,0 +1,103 @@ +{ + "id": "shopro", + "name": "shopro", + "displayName": "商城", + "version": "2.1.0", + "description": "商城,一套代码,同时发行到iOS、Android、H5、微信小程序多个平台,请使用手机扫码快速体验强大功能", + "scripts": { + "prettier": "prettier --write \"{pages,sheep}/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"" + }, + "repository": "https://github.com/sheepjs/shop.git", + "keywords": [ + "商城", + "B2C", + "商城模板" + ], + "author": "", + "license": "MIT", + "bugs": { + "url": "https://github.com/sheepjs/shop/issues" + }, + "homepage": "https://github.com/dcloudio/hello-uniapp#readme", + "dcloudext": { + "category": [ + "前端页面模板", + "uni-app前端项目模板" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "u", + "aliyun": "u" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "u" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "u", + "百度": "u", + "字节跳动": "u", + "QQ": "u", + "京东": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "u", + "vue3": "y" + } + } + } + }, + "dependencies": { + "@hyoga/uni-socket.io": "^3.0.4", + "dayjs": "^1.11.11", + "lodash": "^4.17.21", + "luch-request": "^3.1.1", + "pinia": "^2.1.7", + "pinia-plugin-persist-uni": "^1.3.1", + "weixin-js-sdk": "^1.6.5" + }, + "devDependencies": { + "prettier": "^3.2.5", + "vconsole": "^3.15.1" + } +} \ No newline at end of file diff --git a/pages.json b/pages.json new file mode 100644 index 0000000..2239b2d --- /dev/null +++ b/pages.json @@ -0,0 +1,655 @@ +{ + "easycom": { + "autoscan": true, + "custom": { + "^s-(.*)": "@/sheep/components/s-$1/s-$1.vue", + "^su-(.*)": "@/sheep/ui/su-$1/su-$1.vue" + } + }, + "pages": [{ + "path": "pages/index/index", + "aliasPath": "/", + "style": { + "navigationBarTitleText": "首页", + "enablePullDownRefresh": true + }, + "meta": { + "auth": false, + "sync": true, + "title": "首页", + "group": "商城" + } + }, + { + "path": "pages/index/user", + "style": { + "navigationBarTitleText": "个人中心", + "enablePullDownRefresh": true + }, + "meta": { + "sync": true, + "title": "个人中心", + "group": "商城" + } + }, + { + "path": "pages/index/category", + "style": { + "navigationBarTitleText": "商品分类" + }, + "meta": { + "sync": true, + "title": "商品分类", + "group": "商城" + } + }, + { + "path": "pages/index/cart", + "style": { + "navigationBarTitleText": "购物车" + }, + "meta": { + "sync": true, + "title": "购物车", + "group": "商城" + } + }, + { + "path": "pages/index/login", + "style": { + "navigationBarTitleText": "登录" + } + }, + { + "path": "pages/index/search", + "style": { + "navigationBarTitleText": "搜索" + }, + "meta": { + "sync": true, + "title": "搜索", + "group": "商城" + } + }, + { + "path": "pages/index/page", + "style": { + "navigationBarTitleText": "" + }, + "meta": { + "auth": false, + "sync": true, + "title": "自定义页面", + "group": "商城" + } + } + ], + "subPackages": [{ + "root": "pages/goods", + "pages": [{ + "path": "index", + "style": { + "navigationBarTitleText": "商品详情" + }, + "meta": { + "sync": true, + "title": "普通商品", + "group": "商品" + } + }, + { + "path": "groupon", + "style": { + "navigationBarTitleText": "拼团商品" + }, + "meta": { + "sync": true, + "title": "拼团商品", + "group": "商品" + } + }, + + { + "path": "seckill", + "style": { + "navigationBarTitleText": "秒杀商品" + }, + "meta": { + "sync": true, + "title": "秒杀商品", + "group": "商品" + } + }, + { + "path": "list", + "style": { + "navigationBarTitleText": "商品列表" + }, + "meta": { + "sync": true, + "title": "商品列表", + "group": "商品" + } + }, + { + "path": "comment/add", + "style": { + "navigationBarTitleText": "评价商品" + }, + "meta": { + "auth": true + } + }, + { + "path": "comment/list", + "style": { + "navigationBarTitleText": "商品评价" + } + } + ] + }, + { + "root": "pages/order", + "pages": [{ + "path": "detail", + "style": { + "navigationBarTitleText": "订单详情" + }, + "meta": { + "auth": true, + "title": "订单详情" + } + }, + { + "path": "confirm", + "style": { + "navigationBarTitleText": "确认订单" + }, + "meta": { + "auth": true, + "title": "确认订单" + } + }, + { + "path": "list", + "style": { + "navigationBarTitleText": "我的订单", + "enablePullDownRefresh": true + }, + "meta": { + "auth": true, + "sync": true, + "title": "用户订单", + "group": "订单中心" + } + }, + { + "path": "aftersale/apply", + "style": { + "navigationBarTitleText": "申请售后" + }, + "meta": { + "auth": true, + "title": "申请售后" + } + }, + { + "path": "aftersale/return-delivery", + "style": { + "navigationBarTitleText": "退货物流" + }, + "meta": { + "auth": true, + "title": "退货物流" + } + }, + { + "path": "aftersale/list", + "style": { + "navigationBarTitleText": "售后列表" + }, + "meta": { + "auth": true, + "sync": true, + "title": "售后订单", + "group": "订单中心" + } + }, + { + "path": "aftersale/detail", + "style": { + "navigationBarTitleText": "售后详情" + }, + "meta": { + "auth": true, + "title": "售后详情" + } + }, + { + "path": "aftersale/log", + "style": { + "navigationBarTitleText": "售后进度" + }, + "meta": { + "auth": true, + "title": "售后进度" + } + }, + { + "path": "express/log", + "style": { + "navigationBarTitleText": "物流轨迹" + }, + "meta": { + "auth": true, + "title": "物流轨迹" + } + } + ] + }, + { + "root": "pages/user", + "pages": [{ + "path": "info", + "style": { + "navigationBarTitleText": "我的信息" + }, + "meta": { + "auth": true, + "sync": true, + "title": "用户信息", + "group": "用户中心" + } + }, + { + "path": "goods-collect", + "style": { + "navigationBarTitleText": "我的收藏" + }, + "meta": { + "auth": true, + "sync": true, + "title": "商品收藏", + "group": "用户中心" + } + }, + { + "path": "goods-log", + "style": { + "navigationBarTitleText": "我的足迹" + }, + "meta": { + "auth": true, + "sync": true, + "title": "浏览记录", + "group": "用户中心" + } + }, + { + "path": "address/list", + "style": { + "navigationBarTitleText": "收货地址" + }, + "meta": { + "auth": true, + "sync": true, + "title": "地址管理", + "group": "用户中心" + } + }, + { + "path": "address/edit", + "style": { + "navigationBarTitleText": "编辑地址" + }, + "meta": { + "auth": true, + "title": "编辑地址" + } + }, + { + "path": "wallet/money", + "style": { + "navigationBarTitleText": "我的余额" + }, + "meta": { + "auth": true, + "sync": true, + "title": "用户余额", + "group": "用户中心" + } + }, + { + "path": "wallet/score", + "style": { + "navigationBarTitleText": "我的积分" + }, + "meta": { + "auth": true, + "sync": true, + "title": "用户积分", + "group": "用户中心" + } + } + ] + }, + { + "root": "pages/commission", + "pages": [{ + "path": "index", + "style": { + "navigationBarTitleText": "分销" + }, + "meta": { + "auth": true, + "sync": true, + "title": "分销中心", + "group": "分销商城" + } + }, + { + "path": "wallet", + "style": { + "navigationBarTitleText": "我的佣金" + }, + "meta": { + "auth": true, + "sync": true, + "title": "用户佣金", + "group": "分销中心" + } + }, + { + "path": "goods", + "style": { + "navigationBarTitleText": "推广商品" + }, + "meta": { + "auth": true, + "sync": true, + "title": "推广商品", + "group": "分销商城" + } + }, + { + "path": "order", + "style": { + "navigationBarTitleText": "分销订单" + }, + "meta": { + "auth": true, + "sync": true, + "title": "分销订单", + "group": "分销商城" + } + }, + { + "path": "team", + "style": { + "navigationBarTitleText": "我的团队" + }, + "meta": { + "auth": true, + "sync": true, + "title": "我的团队", + "group": "分销商城" + } + }, { + "path": "promoter", + "style": { + "navigationBarTitleText": "推广人排行榜" + }, + "meta": { + "auth": true, + "sync": true, + "title": "推广人排行榜", + "group": "分销商城" + } + }, { + "path": "commission-ranking", + "style": { + "navigationBarTitleText": "佣金排行榜" + }, + "meta": { + "auth": true, + "sync": true, + "title": "佣金排行榜", + "group": "分销商城" + } + }, { + "path": "withdraw", + "style": { + "navigationBarTitleText": "申请提现" + }, + "meta": { + "auth": true, + "sync": true, + "title": "申请提现", + "group": "分销商城" + } + } + ] + }, + { + "root": "pages/app", + "pages": [{ + "path": "sign", + "style": { + "navigationBarTitleText": "签到中心" + }, + "meta": { + "auth": true, + "sync": true, + "title": "签到中心", + "group": "应用" + } + }] + }, + { + "root": "pages/public", + "pages": [{ + "path": "setting", + "style": { + "navigationBarTitleText": "系统设置" + }, + "meta": { + "sync": true, + "title": "系统设置", + "group": "通用" + } + }, + { + "path": "richtext", + "style": { + "navigationBarTitleText": "富文本" + }, + "meta": { + "sync": true, + "title": "富文本", + "group": "通用" + } + }, + { + "path": "faq", + "style": { + "navigationBarTitleText": "常见问题" + }, + "meta": { + "sync": true, + "title": "常见问题", + "group": "通用" + } + }, + { + "path": "error", + "style": { + "navigationBarTitleText": "错误页面" + } + }, + { + "path": "webview", + "style": { + "navigationBarTitleText": "" + } + } + ] + }, + { + "root": "pages/coupon", + "pages": [{ + "path": "list", + "style": { + "navigationBarTitleText": "领券中心" + }, + "meta": { + "sync": true, + "title": "领券中心", + "group": "优惠券" + } + }, + { + "path": "detail", + "style": { + "navigationBarTitleText": "优惠券" + }, + "meta": { + "auth": false, + "sync": true, + "title": "优惠券详情", + "group": "优惠券" + } + } + ] + }, + { + "root": "pages/chat", + "pages": [{ + "path": "index", + "style": { + "navigationBarTitleText": "客服" + }, + "meta": { + "auth": true, + "sync": true, + "title": "客服", + "group": "客服" + } + }] + }, + { + "root": "pages/pay", + "pages": [{ + "path": "index", + "style": { + "navigationBarTitleText": "收银台" + } + }, + { + "path": "result", + "style": { + "navigationBarTitleText": "支付结果" + } + }, + { + "path": "recharge", + "style": { + "navigationBarTitleText": "充值余额" + }, + "meta": { + "auth": true, + "sync": true, + "title": "充值余额", + "group": "支付" + } + }, + { + "path": "recharge-log", + "style": { + "navigationBarTitleText": "充值记录" + }, + "meta": { + "auth": true, + "sync": true, + "title": "充值记录", + "group": "支付" + } + } + ] + }, + { + "root": "pages/activity", + "pages": [{ + "path": "groupon/detail", + "style": { + "navigationBarTitleText": "拼团详情" + } + }, + { + "path": "groupon/order", + "style": { + "navigationBarTitleText": "我的拼团", + "enablePullDownRefresh": true + }, + "meta": { + "auth": true, + "sync": true, + "title": "拼团订单", + "group": "营销活动" + } + }, + { + "path": "index", + "style": { + "navigationBarTitleText": "营销商品" + }, + "meta": { + "sync": true, + "title": "营销商品", + "group": "营销活动" + } + }, + { + "path": "groupon/list", + "style": { + "navigationBarTitleText": "拼团活动" + }, + "meta": { + "sync": true, + "title": "拼团活动", + "group": "营销活动" + } + }, + { + "path": "seckill/list", + "style": { + "navigationBarTitleText": "秒杀活动" + }, + "meta": { + "sync": true, + "title": "秒杀活动", + "group": "营销活动" + } + } + ] + } + ], + "globalStyle": { + "navigationBarTextStyle": "black", + "navigationBarTitleText": "商城", + "navigationBarBackgroundColor": "#FFFFFF", + "backgroundColor": "#FFFFFF", + "navigationStyle": "custom" + }, + "tabBar": { + "list": [{ + "pagePath": "pages/index/index" + }, + { + "pagePath": "pages/index/cart" + }, + { + "pagePath": "pages/index/user" + } + ] + } +} \ No newline at end of file diff --git a/pages/activity/groupon/detail.vue b/pages/activity/groupon/detail.vue new file mode 100644 index 0000000..dbeab7c --- /dev/null +++ b/pages/activity/groupon/detail.vue @@ -0,0 +1,508 @@ + + + + + + diff --git a/pages/activity/groupon/list.vue b/pages/activity/groupon/list.vue new file mode 100644 index 0000000..a8b77a3 --- /dev/null +++ b/pages/activity/groupon/list.vue @@ -0,0 +1,225 @@ + + + + diff --git a/pages/activity/groupon/order.vue b/pages/activity/groupon/order.vue new file mode 100644 index 0000000..c4005d1 --- /dev/null +++ b/pages/activity/groupon/order.vue @@ -0,0 +1,239 @@ + + + + + + diff --git a/pages/activity/index.vue b/pages/activity/index.vue new file mode 100644 index 0000000..dbf1706 --- /dev/null +++ b/pages/activity/index.vue @@ -0,0 +1,206 @@ + + + + diff --git a/pages/activity/seckill/list.vue b/pages/activity/seckill/list.vue new file mode 100644 index 0000000..7a0abf3 --- /dev/null +++ b/pages/activity/seckill/list.vue @@ -0,0 +1,385 @@ + + + + diff --git a/pages/app/sign.vue b/pages/app/sign.vue new file mode 100644 index 0000000..c6a5ee4 --- /dev/null +++ b/pages/app/sign.vue @@ -0,0 +1,451 @@ + + + + + + diff --git a/pages/chat/components/goods.vue b/pages/chat/components/goods.vue new file mode 100644 index 0000000..7fbc95c --- /dev/null +++ b/pages/chat/components/goods.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/pages/chat/components/order.vue b/pages/chat/components/order.vue new file mode 100644 index 0000000..cf57f63 --- /dev/null +++ b/pages/chat/components/order.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/pages/chat/components/select-popup.vue b/pages/chat/components/select-popup.vue new file mode 100644 index 0000000..f8f0f3e --- /dev/null +++ b/pages/chat/components/select-popup.vue @@ -0,0 +1,152 @@ + + + + + diff --git a/pages/chat/emoji.js b/pages/chat/emoji.js new file mode 100644 index 0000000..83e6e84 --- /dev/null +++ b/pages/chat/emoji.js @@ -0,0 +1,58 @@ +export const emojiList = [ + { name: '[笑掉牙]', file: 'xiaodiaoya.png' }, + { name: '[可爱]', file: 'keai.png' }, + { name: '[冷酷]', file: 'lengku.png' }, + { name: '[闭嘴]', file: 'bizui.png' }, + { name: '[生气]', file: 'shengqi.png' }, + { name: '[惊恐]', file: 'jingkong.png' }, + { name: '[瞌睡]', file: 'keshui.png' }, + { name: '[大笑]', file: 'daxiao.png' }, + { name: '[爱心]', file: 'aixin.png' }, + { name: '[坏笑]', file: 'huaixiao.png' }, + { name: '[飞吻]', file: 'feiwen.png' }, + { name: '[疑问]', file: 'yiwen.png' }, + { name: '[开心]', file: 'kaixin.png' }, + { name: '[发呆]', file: 'fadai.png' }, + { name: '[流泪]', file: 'liulei.png' }, + { name: '[汗颜]', file: 'hanyan.png' }, + { name: '[惊悚]', file: 'jingshu.png' }, + { name: '[困~]', file: 'kun.png' }, + { name: '[心碎]', file: 'xinsui.png' }, + { name: '[天使]', file: 'tianshi.png' }, + { name: '[晕]', file: 'yun.png' }, + { name: '[啊]', file: 'a.png' }, + { name: '[愤怒]', file: 'fennu.png' }, + { name: '[睡着]', file: 'shuizhuo.png' }, + { name: '[面无表情]', file: 'mianwubiaoqing.png' }, + { name: '[难过]', file: 'nanguo.png' }, + { name: '[犯困]', file: 'fankun.png' }, + { name: '[好吃]', file: 'haochi.png' }, + { name: '[呕吐]', file: 'outu.png' }, + { name: '[龇牙]', file: 'ziya.png' }, + { name: '[懵比]', file: 'mengbi.png' }, + { name: '[白眼]', file: 'baiyan.png' }, + { name: '[饿死]', file: 'esi.png' }, + { name: '[凶]', file: 'xiong.png' }, + { name: '[感冒]', file: 'ganmao.png' }, + { name: '[流汗]', file: 'liuhan.png' }, + { name: '[笑哭]', file: 'xiaoku.png' }, + { name: '[流口水]', file: 'liukoushui.png' }, + { name: '[尴尬]', file: 'ganga.png' }, + { name: '[惊讶]', file: 'jingya.png' }, + { name: '[大惊]', file: 'dajing.png' }, + { name: '[不好意思]', file: 'buhaoyisi.png' }, + { name: '[大闹]', file: 'danao.png' }, + { name: '[不可思议]', file: 'bukesiyi.png' }, + { name: '[爱你]', file: 'aini.png' }, + { name: '[红心]', file: 'hongxin.png' }, + { name: '[点赞]', file: 'dianzan.png' }, + { name: '[恶魔]', file: 'emo.png' }, +]; + +export let emojiPage = {}; +emojiList.forEach((item, index) => { + if (!emojiPage[Math.floor(index / 30) + 1]) { + emojiPage[Math.floor(index / 30) + 1] = []; + } + emojiPage[Math.floor(index / 30) + 1].push(item); +}); diff --git a/pages/chat/index.vue b/pages/chat/index.vue new file mode 100644 index 0000000..1d3dfac --- /dev/null +++ b/pages/chat/index.vue @@ -0,0 +1,870 @@ + + + + + + diff --git a/pages/chat/socket.js b/pages/chat/socket.js new file mode 100644 index 0000000..b95478f --- /dev/null +++ b/pages/chat/socket.js @@ -0,0 +1,821 @@ +import { reactive, ref, unref } from 'vue'; +import sheep from '@/sheep'; +// import chat from '@/api/chat'; +import dayjs from 'dayjs'; +import io from '@hyoga/uni-socket.io'; + +export function useChatWebSocket(socketConfig) { + let SocketIo = null; + + // chat状态数据 + const state = reactive({ + chatDotNum: 0, //总状态红点 + chatList: [], //会话信息 + customerUserInfo: {}, //用户信息 + customerServerInfo: { + //客服信息 + title: '连接中...', + state: 'connecting', + avatar: null, + nickname: '', + }, + socketState: { + isConnect: true, //是否连接成功 + isConnecting: false, //重连中,不允许新的socket开启。 + tip: '', + }, + chatHistoryPagination: { + page: 0, //当前页 + list_rows: 10, //每页条数 + last_id: 0, //最后条ID + lastPage: 0, //总共多少页 + loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态 + }, + templateChatList: [], //猜你想问 + + chatConfig: {}, // 配置信息 + + isSendSucces: -1, // 是否发送成功 -1=发送中|0=发送成功|1发送失败 + }); + + /** + * 连接初始化 + * @param {Object} config - 配置信息 + * @param {Function} callBack -回调函数,有新消息接入,保持底部 + */ + const socketInit = (config, callBack) => { + state.chatConfig = config; + if (SocketIo && SocketIo.connected) return; // 如果socket已经连接,返回false + if (state.socketState.isConnecting) return; // 重连中,返回false + + // 启动初始化 + SocketIo = io(config.chat_domain, { + reconnection: true, // 默认 true 是否断线重连 + reconnectionAttempts: 5, // 默认无限次 断线尝试次数 + reconnectionDelay: 1000, // 默认 1000,进行下一次重连的间隔。 + reconnectionDelayMax: 5000, // 默认 5000, 重新连接等待的最长时间 默认 5000 + randomizationFactor: 0.5, // 默认 0.5 [0-1],随机重连延迟时间 + timeout: 20000, // 默认 20s + transports: ['websocket', 'polling'], // websocket | polling, + ...config, + }); + + // 监听连接 + SocketIo.on('connect', async (res) => { + socketReset(callBack); + // socket连接 + // 用户登录 + // 顾客登录 + console.log('socket:connect'); + }); + // 监听消息 + SocketIo.on('message', (res) => { + if (res.error === 0) { + const { message, sender } = res.data; + state.chatList.push(formatMessage(res.data.message)); + + // 告诉父级页面 + // window.parent.postMessage({ + // chatDotNum: ++state.chatDotNum + // }) + callBack && callBack(); + } + }); + // 监听客服接入成功 + SocketIo.on('customer_service_access', (res) => { + if (res.error === 0) { + editCustomerServerInfo({ + title: res.data.customer_service.name, + state: 'online', + avatar: res.data.customer_service.avatar, + }); + state.chatList.push(formatMessage(res.data.message)); + // callBack && callBack() + } + }); + // 监听排队等待 + SocketIo.on('waiting_queue', (res) => { + if (res.error === 0) { + editCustomerServerInfo({ + title: res.data.title, + state: 'waiting', + avatar: '', + }); + // callBack && callBack() + } + }); + // 监听没有客服在线 + SocketIo.on('no_customer_service', (res) => { + if (res.error === 0) { + editCustomerServerInfo({ + title: '暂无客服在线...', + state: 'waiting', + avatar: '', + }); + } + state.chatList.push(formatMessage(res.data.message)); + // callBack && callBack() + }); + // 监听客服上线 + SocketIo.on('customer_service_online', (res) => { + if (res.error === 0) { + editCustomerServerInfo({ + title: res.data.customer_service.name, + state: 'online', + avatar: res.data.customer_service.avatar, + }); + } + }); + // 监听客服下线 + SocketIo.on('customer_service_offline', (res) => { + if (res.error === 0) { + editCustomerServerInfo({ + title: res.data.customer_service.name, + state: 'offline', + avatar: res.data.customer_service.avatar, + }); + } + }); + // 监听客服忙碌 + SocketIo.on('customer_service_busy', (res) => { + if (res.error === 0) { + editCustomerServerInfo({ + title: res.data.customer_service.name, + state: 'busy', + avatar: res.data.customer_service.avatar, + }); + } + }); + // 监听客服断开链接 + SocketIo.on('customer_service_break', (res) => { + if (res.error === 0) { + editCustomerServerInfo({ + title: '客服服务结束', + state: 'offline', + avatar: '', + }); + state.socketState.isConnect = false; + state.socketState.tip = '当前服务已结束'; + } + state.chatList.push(formatMessage(res.data.message)); + // callBack && callBack() + }); + // 监听自定义错误 custom_error + SocketIo.on('custom_error', (error) => { + editCustomerServerInfo({ + title: error.msg, + state: 'offline', + avatar: '', + }); + console.log('custom_error:', error); + }); + // 监听错误 error + SocketIo.on('error', (error) => { + console.log('error:', error); + }); + // 重连失败 connect_error + SocketIo.on('connect_error', (error) => { + console.log('connect_error'); + }); + // 连接上,但无反应 connect_timeout + SocketIo.on('connect_timeout', (error) => { + console.log(error, 'connect_timeout'); + }); + // 服务进程销毁 disconnect + SocketIo.on('disconnect', (error) => { + console.log(error, 'disconnect'); + }); + // 服务重启重连上reconnect + SocketIo.on('reconnect', (error) => { + console.log(error, 'reconnect'); + }); + // 开始重连reconnect_attempt + SocketIo.on('reconnect_attempt', (error) => { + state.socketState.isConnect = false; + state.socketState.isConnecting = true; + editCustomerServerInfo({ + title: `重连中,第${error}次尝试...`, + state: 'waiting', + avatar: '', + }); + console.log(error, 'reconnect_attempt'); + }); + // 重新连接中reconnecting + SocketIo.on('reconnecting', (error) => { + console.log(error, 'reconnecting'); + }); + // 重新连接错误reconnect_error + SocketIo.on('reconnect_error', (error) => { + console.log('reconnect_error'); + }); + // 重新连接失败reconnect_failed + SocketIo.on('reconnect_failed', (error) => { + state.socketState.isConnecting = false; + editCustomerServerInfo({ + title: `重连失败,请刷新重试~`, + state: 'waiting', + avatar: '', + }); + console.log(error, 'reconnect_failed'); + + // setTimeout(() => { + state.isSendSucces = 1; + // }, 500) + }); + }; + + // 重置socket + const socketReset = (callBack) => { + state.chatList = []; + state.chatHistoryList = []; + state.chatHistoryPagination = { + page: 0, + per_page: 10, + last_id: 0, + totalPage: 0, + }; + socketConnection(callBack); // 连接 + }; + + // 退出连接 + const socketClose = () => { + SocketIo.emit('customer_logout', {}, (res) => { + console.log('socket:退出', res); + }); + }; + + // 测试事件 + const socketTest = () => { + SocketIo.emit('test', {}, (res) => { + console.log('test:test', res); + }); + }; + + // 发送消息 + const socketSendMsg = (data, sendMsgCallBack) => { + state.isSendSucces = -1; + state.chatList.push(data); + sendMsgCallBack && sendMsgCallBack(); + SocketIo.emit( + 'message', + { + message: formatInput(data), + ...data.customData, + }, + (res) => { + // setTimeout(() => { + state.isSendSucces = res.error; + // }, 500) + + // console.log(res, 'socket:send'); + // sendMsgCallBack && sendMsgCallBack() + }, + ); + }; + + // 连接socket,存入sessionId + const socketConnection = (callBack) => { + SocketIo.emit( + 'connection', + { + auth: 'user', + token: uni.getStorageSync('socketUserToken') || '', + session_id: uni.getStorageSync('socketSessionId') || '', + }, + (res) => { + if (res.error === 0) { + socketCustomerLogin(callBack); + uni.setStorageSync('socketSessionId', res.data.session_id); + // uni.getStorageSync('socketUserToken') && socketLogin(uni.getStorageSync( + // 'socketUserToken')) // 如果有用户token,绑定 + state.customerUserInfo = res.data.chat_user; + state.socketState.isConnect = true; + } else { + editCustomerServerInfo({ + title: `服务器异常!`, + state: 'waiting', + avatar: '', + }); + state.socketState.isConnect = false; + } + }, + ); + }; + + // 用户id,获取token + const getUserToken = async (id) => { + const res = await chat.unifiedToken(); + if (res.error === 0) { + uni.setStorageSync('socketUserToken', res.data.token); + // SocketIo && SocketIo.connected && socketLogin(res.data.token) + } + return res; + }; + + // 用户登录 + const socketLogin = (token) => { + SocketIo.emit( + 'login', + { + token: token, + }, + (res) => { + console.log(res, 'socket:login'); + state.customerUserInfo = res.data.chat_user; + }, + ); + }; + + // 顾客登录 + const socketCustomerLogin = (callBack) => { + SocketIo.emit( + 'customer_login', + { + room_id: state.chatConfig.room_id, + }, + (res) => { + state.templateChatList = res.data.questions.length ? res.data.questions : []; + state.chatList.push({ + from: 'customer_service', // 用户customer右 | 顾客customer_service左 | 系统system中间 + mode: 'template', // goods,order,image,text,system + date: new Date().getTime(), //时间 + content: { + //内容 + list: state.templateChatList, + }, + }); + res.error === 0 && socketHistoryList(callBack); + }, + ); + }; + + // 获取历史消息 + const socketHistoryList = (historyCallBack) => { + state.chatHistoryPagination.loadStatus = 'loading'; + state.chatHistoryPagination.page += 1; + SocketIo.emit('messages', state.chatHistoryPagination, (res) => { + if (res.error === 0) { + state.chatHistoryPagination.total = res.data.messages.total; + state.chatHistoryPagination.lastPage = res.data.messages.last_page; + state.chatHistoryPagination.page = res.data.messages.current_page; + res.data.messages.data.forEach((item) => { + item.message_type && state.chatList.unshift(formatMessage(item)); + }); + state.chatHistoryPagination.loadStatus = + state.chatHistoryPagination.page < state.chatHistoryPagination.lastPage + ? 'loadmore' + : 'nomore'; + if (state.chatHistoryPagination.last_id == 0) { + state.chatHistoryPagination.last_id = res.data.messages.data.length + ? res.data.messages.data[0].id + : 0; + } + state.chatHistoryPagination.page === 1 && historyCallBack && historyCallBack(); + } + + // 历史记录之后,猜你想问 + // state.chatList.push({ + // from: 'customer_service', // 用户customer右 | 顾客customer_service左 | 系统system中间 + // mode: 'template', // goods,order,image,text,system + // date: new Date().getTime(), //时间 + // content: { //内容 + // list: state.templateChatList + // } + // }) + }); + }; + + // 修改客服信息 + const editCustomerServerInfo = (data) => { + state.customerServerInfo = { + ...state.customerServerInfo, + ...data, + }; + }; + + /** + * ================ + * 工具函数 ↓ + * =============== + */ + + /** + * 是否显示时间 + * @param {*} item - 数据 + * @param {*} index - 索引 + */ + const showTime = (item, index) => { + if (unref(state.chatList)[index + 1]) { + let dateString = dayjs(unref(state.chatList)[index + 1].date).fromNow(); + if (dateString === dayjs(unref(item).date).fromNow()) { + return false; + } else { + dateString = dayjs(unref(item).date).fromNow(); + return true; + } + } + return false; + }; + + /** + * 格式化时间 + * @param {*} time - 时间戳 + */ + const formatTime = (time) => { + let diffTime = new Date().getTime() - time; + if (diffTime > 28 * 24 * 60 * 1000) { + return dayjs(time).format('MM/DD HH:mm'); + } + if (diffTime > 360 * 28 * 24 * 60 * 1000) { + return dayjs(time).format('YYYY/MM/DD HH:mm'); + } + return dayjs(time).fromNow(); + }; + + /** + * 获取焦点 + * @param {*} virtualNode - 节点信息 ref + */ + const getFocus = (virtualNode) => { + if (window.getSelection) { + let chatInput = unref(virtualNode); + chatInput.focus(); + let range = window.getSelection(); + range.selectAllChildren(chatInput); + range.collapseToEnd(); + } else if (document.selection) { + let range = document.selection.createRange(); + range.moveToElementText(chatInput); + range.collapse(false); + range.select(); + } + }; + + /** + * 文件上传 + * @param {Blob} file -文件数据流 + * @return {path,fullPath} + */ + + const upload = (name, file) => { + return new Promise((resolve, reject) => { + let data = new FormData(); + data.append('file', file, name); + data.append('group', 'chat'); + ajax({ + url: '/upload', + method: 'post', + headers: { + 'Content-Type': 'multipart/form-data', + }, + data, + success: function (res) { + resolve(res); + }, + error: function (err) { + reject(err); + }, + }); + }); + }; + + /** + * 粘贴到输入框 + * @param {*} e - 粘贴内容 + * @param {*} uploadHttp - 上传图片地址 + */ + const onPaste = async (e) => { + let paste = e.clipboardData || window.clipboardData; + let filesArr = Array.from(paste.files); + filesArr.forEach(async (child) => { + if (child && child.type.includes('image')) { + e.preventDefault(); //阻止默认 + let file = child; + const img = await readImg(file); + const blob = await compressImg(img, file.type); + const { data } = await upload(file.name, blob); + let image = ``; + document.execCommand('insertHTML', false, image); + } else { + document.execCommand('insertHTML', false, paste.getData('text')); + } + }); + }; + + /** + * 拖拽到输入框 + * @param {*} e - 粘贴内容 + * @param {*} uploadHttp - 上传图片地址 + */ + const onDrop = async (e) => { + e.preventDefault(); //阻止默认 + let filesArr = Array.from(e.dataTransfer.files); + filesArr.forEach(async (child) => { + if (child && child.type.includes('image')) { + let file = child; + const img = await readImg(file); + const blob = await compressImg(img, file.type); + const { data } = await upload(file.name, blob); + let image = ``; + document.execCommand('insertHTML', false, image); + } else { + ElMessage({ + message: '禁止拖拽非图片资源', + type: 'warning', + }); + } + }); + }; + + /** + * 解析富文本输入框内容 + * @param {*} virtualNode -节点信息 + * @param {Function} formatInputCallBack - cb 回调 + */ + const formatChatInput = (virtualNode, formatInputCallBack) => { + let res = ''; + let elemArr = Array.from(virtualNode.childNodes); + elemArr.forEach((child, index) => { + if (child.nodeName === '#text') { + //如果为文本节点 + res += child.nodeValue; + if ( + //文本节点的后面是图片,并且不是emoji,分开发送。输入框中的图片和文本表情分开。 + elemArr[index + 1] && + elemArr[index + 1].nodeName === 'IMG' && + elemArr[index + 1] && + elemArr[index + 1].name !== 'emoji' + ) { + const data = { + from: 'customer', + mode: 'text', + date: new Date().getTime(), + content: { + text: filterXSS(res), + }, + }; + formatInputCallBack && formatInputCallBack(data); + res = ''; + } + } else if (child.nodeName === 'BR') { + res += '
'; + } else if (child.nodeName === 'IMG') { + // 有emjio 和 一般图片 + // 图片解析后直接发送,不跟文字表情一组 + if (child.name !== 'emoji') { + let srcReg = /src=[\'\']?([^\'\']*)[\'\']?/i; + let src = child.outerHTML.match(srcReg); + const data = { + from: 'customer', + mode: 'image', + date: new Date().getTime(), + content: { + url: src[1], + path: src[1].replace(/http:\/\/[^\/]*/, ''), + }, + }; + formatInputCallBack && formatInputCallBack(data); + } else { + // 非表情图片跟文字一起发送 + res += child.outerHTML; + } + } else if (child.nodeName === 'DIV') { + res += `
${child.outerHTML}
`; + } + }); + if (res) { + const data = { + from: 'customer', + mode: 'text', + date: new Date().getTime(), + content: { + text: filterXSS(res), + }, + }; + formatInputCallBack && formatInputCallBack(data); + } + unref(virtualNode).innerHTML = ''; + }; + + /** + * 状态回调 + * @param {*} res -接口返回数据 + */ + const callBackNotice = (res) => { + ElNotification({ + title: 'socket', + message: res.msg, + showClose: true, + type: res.error === 0 ? 'success' : 'warning', + duration: 1200, + }); + }; + + /** + * 格式化发送信息 + * @param {Object} message + * @returns obj - 消息对象 + */ + const formatInput = (message) => { + let obj = {}; + switch (message.mode) { + case 'text': + obj = { + message_type: 'text', + message: message.content.text, + }; + break; + case 'image': + obj = { + message_type: 'image', + message: message.content.path, + }; + break; + case 'goods': + obj = { + message_type: 'goods', + message: message.content.item, + }; + break; + case 'order': + obj = { + message_type: 'order', + message: message.content.item, + }; + break; + default: + break; + } + return obj; + }; + /** + * 格式化接收信息 + * @param {*} message + * @returns obj - 消息对象 + */ + const formatMessage = (message) => { + let obj = {}; + switch (message.message_type) { + case 'system': + obj = { + from: 'system', // 用户customer左 | 顾客customer_service右 | 系统system中间 + mode: 'system', // goods,order,image,text,system + date: message.create_time * 1000, //时间 + content: { + //内容 + text: message.message, + }, + }; + break; + case 'text': + obj = { + from: message.sender_identify, + mode: message.message_type, + date: message.create_time * 1000, //时间 + sender: message.sender, + content: { + text: message.message, + messageId: message.id, + }, + }; + break; + case 'image': + obj = { + from: message.sender_identify, + mode: message.message_type, + date: message.create_time * 1000, //时间 + sender: message.sender, + content: { + url: sheep.$url.cdn(message.message), + messageId: message.id, + }, + }; + break; + case 'goods': + obj = { + from: message.sender_identify, + mode: message.message_type, + date: message.create_time * 1000, //时间 + sender: message.sender, + content: { + item: message.message, + messageId: message.id, + }, + }; + break; + case 'order': + obj = { + from: message.sender_identify, + mode: message.message_type, + date: message.create_time * 1000, //时间 + sender: message.sender, + content: { + item: message.message, + messageId: message.id, + }, + }; + break; + default: + break; + } + return obj; + }; + + /** + * file 转换为 img + * @param {*} file - file 文件 + * @returns img - img标签 + */ + const readImg = (file) => { + return new Promise((resolve, reject) => { + const img = new Image(); + const reader = new FileReader(); + reader.onload = function (e) { + img.src = e.target.result; + }; + reader.onerror = function (e) { + reject(e); + }; + reader.readAsDataURL(file); + img.onload = function () { + resolve(img); + }; + img.onerror = function (e) { + reject(e); + }; + }); + }; + + /** + * 压缩图片 + *@param img -被压缩的img对象 + * @param type -压缩后转换的文件类型 + * @param mx -触发压缩的图片最大宽度限制 + * @param mh -触发压缩的图片最大高度限制 + * @returns blob - 文件流 + */ + const compressImg = (img, type = 'image/jpeg', mx = 1000, mh = 1000, quality = 1) => { + return new Promise((resolve, reject) => { + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + const { width: originWidth, height: originHeight } = img; + // 最大尺寸限制 + const maxWidth = mx; + const maxHeight = mh; + // 目标尺寸 + let targetWidth = originWidth; + let targetHeight = originHeight; + if (originWidth > maxWidth || originHeight > maxHeight) { + if (originWidth / originHeight > 1) { + // 宽图片 + targetWidth = maxWidth; + targetHeight = Math.round(maxWidth * (originHeight / originWidth)); + } else { + // 高图片 + targetHeight = maxHeight; + targetWidth = Math.round(maxHeight * (originWidth / originHeight)); + } + } + canvas.width = targetWidth; + canvas.height = targetHeight; + context.clearRect(0, 0, targetWidth, targetHeight); + // 图片绘制 + context.drawImage(img, 0, 0, targetWidth, targetHeight); + canvas.toBlob( + function (blob) { + resolve(blob); + }, + type, + quality, + ); + }); + }; + + return { + compressImg, + readImg, + formatMessage, + formatInput, + callBackNotice, + + socketInit, + socketSendMsg, + socketClose, + socketHistoryList, + + getFocus, + formatChatInput, + onDrop, + onPaste, + upload, + + getUserToken, + + state, + + socketTest, + + showTime, + formatTime, + }; +} \ No newline at end of file diff --git a/pages/commission/commission-ranking.vue b/pages/commission/commission-ranking.vue new file mode 100644 index 0000000..c9faa03 --- /dev/null +++ b/pages/commission/commission-ranking.vue @@ -0,0 +1,249 @@ + + + + + \ No newline at end of file diff --git a/pages/commission/components/account-info.vue b/pages/commission/components/account-info.vue new file mode 100644 index 0000000..ef8c8c3 --- /dev/null +++ b/pages/commission/components/account-info.vue @@ -0,0 +1,125 @@ + + + + + + \ No newline at end of file diff --git a/pages/commission/components/account-type-select.vue b/pages/commission/components/account-type-select.vue new file mode 100644 index 0000000..e79e417 --- /dev/null +++ b/pages/commission/components/account-type-select.vue @@ -0,0 +1,160 @@ + + + + + + diff --git a/pages/commission/components/commission-auth.vue b/pages/commission/components/commission-auth.vue new file mode 100644 index 0000000..429b377 --- /dev/null +++ b/pages/commission/components/commission-auth.vue @@ -0,0 +1,101 @@ + + + + + + diff --git a/pages/commission/components/commission-info.vue b/pages/commission/components/commission-info.vue new file mode 100644 index 0000000..65f2c59 --- /dev/null +++ b/pages/commission/components/commission-info.vue @@ -0,0 +1,113 @@ + + + + + + \ No newline at end of file diff --git a/pages/commission/components/commission-log.vue b/pages/commission/components/commission-log.vue new file mode 100644 index 0000000..43d0ae6 --- /dev/null +++ b/pages/commission/components/commission-log.vue @@ -0,0 +1,165 @@ + + + + + + \ No newline at end of file diff --git a/pages/commission/components/commission-menu.vue b/pages/commission/components/commission-menu.vue new file mode 100644 index 0000000..5655ca7 --- /dev/null +++ b/pages/commission/components/commission-menu.vue @@ -0,0 +1,138 @@ + + + + + + \ No newline at end of file diff --git a/pages/commission/goods.vue b/pages/commission/goods.vue new file mode 100644 index 0000000..786a154 --- /dev/null +++ b/pages/commission/goods.vue @@ -0,0 +1,150 @@ + + + + + + diff --git a/pages/commission/index.vue b/pages/commission/index.vue new file mode 100644 index 0000000..94b4121 --- /dev/null +++ b/pages/commission/index.vue @@ -0,0 +1,37 @@ + + + + + + \ No newline at end of file diff --git a/pages/commission/order.vue b/pages/commission/order.vue new file mode 100644 index 0000000..a2984b4 --- /dev/null +++ b/pages/commission/order.vue @@ -0,0 +1,331 @@ + + + + + + diff --git a/pages/commission/promoter.vue b/pages/commission/promoter.vue new file mode 100644 index 0000000..8b1668f --- /dev/null +++ b/pages/commission/promoter.vue @@ -0,0 +1,297 @@ + + + + + \ No newline at end of file diff --git a/pages/commission/team.vue b/pages/commission/team.vue new file mode 100644 index 0000000..f52de0d --- /dev/null +++ b/pages/commission/team.vue @@ -0,0 +1,581 @@ + + + + + + diff --git a/pages/commission/wallet.vue b/pages/commission/wallet.vue new file mode 100644 index 0000000..d0fb385 --- /dev/null +++ b/pages/commission/wallet.vue @@ -0,0 +1,470 @@ + + + + + + \ No newline at end of file diff --git a/pages/commission/withdraw.vue b/pages/commission/withdraw.vue new file mode 100644 index 0000000..fdd71d8 --- /dev/null +++ b/pages/commission/withdraw.vue @@ -0,0 +1,427 @@ + + + + + + diff --git a/pages/coupon/detail.vue b/pages/coupon/detail.vue new file mode 100644 index 0000000..2052783 --- /dev/null +++ b/pages/coupon/detail.vue @@ -0,0 +1,378 @@ + + + + + + diff --git a/pages/coupon/list.vue b/pages/coupon/list.vue new file mode 100644 index 0000000..a99b2bc --- /dev/null +++ b/pages/coupon/list.vue @@ -0,0 +1,218 @@ + + + + + diff --git a/pages/goods/comment/add.vue b/pages/goods/comment/add.vue new file mode 100644 index 0000000..199eada --- /dev/null +++ b/pages/goods/comment/add.vue @@ -0,0 +1,145 @@ + + + + + + \ No newline at end of file diff --git a/pages/goods/comment/list.vue b/pages/goods/comment/list.vue new file mode 100644 index 0000000..641329f --- /dev/null +++ b/pages/goods/comment/list.vue @@ -0,0 +1,167 @@ + + + + + + diff --git a/pages/goods/components/detail/comment-item.vue b/pages/goods/components/detail/comment-item.vue new file mode 100644 index 0000000..518db2a --- /dev/null +++ b/pages/goods/components/detail/comment-item.vue @@ -0,0 +1,94 @@ + + + + + + diff --git a/pages/goods/components/detail/detail-activity-tip.vue b/pages/goods/components/detail/detail-activity-tip.vue new file mode 100644 index 0000000..5d74c08 --- /dev/null +++ b/pages/goods/components/detail/detail-activity-tip.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/pages/goods/components/detail/detail-cell-sku.vue b/pages/goods/components/detail/detail-cell-sku.vue new file mode 100644 index 0000000..11f1b20 --- /dev/null +++ b/pages/goods/components/detail/detail-cell-sku.vue @@ -0,0 +1,31 @@ + + + diff --git a/pages/goods/components/detail/detail-cell.vue b/pages/goods/components/detail/detail-cell.vue new file mode 100644 index 0000000..b4c56dd --- /dev/null +++ b/pages/goods/components/detail/detail-cell.vue @@ -0,0 +1,60 @@ + + + + + + diff --git a/pages/goods/components/detail/detail-comment-card.vue b/pages/goods/components/detail/detail-comment-card.vue new file mode 100644 index 0000000..9fb8b7e --- /dev/null +++ b/pages/goods/components/detail/detail-comment-card.vue @@ -0,0 +1,106 @@ + + + + + + diff --git a/pages/goods/components/detail/detail-content-card.vue b/pages/goods/components/detail/detail-content-card.vue new file mode 100644 index 0000000..eaacc82 --- /dev/null +++ b/pages/goods/components/detail/detail-content-card.vue @@ -0,0 +1,52 @@ + + + + + + diff --git a/pages/goods/components/detail/detail-navbar.vue b/pages/goods/components/detail/detail-navbar.vue new file mode 100644 index 0000000..1d1153d --- /dev/null +++ b/pages/goods/components/detail/detail-navbar.vue @@ -0,0 +1,256 @@ + + + + + + diff --git a/pages/goods/components/detail/detail-progress.vue b/pages/goods/components/detail/detail-progress.vue new file mode 100644 index 0000000..a6210b1 --- /dev/null +++ b/pages/goods/components/detail/detail-progress.vue @@ -0,0 +1,40 @@ + + + + + + diff --git a/pages/goods/components/detail/detail-skeleton.vue b/pages/goods/components/detail/detail-skeleton.vue new file mode 100644 index 0000000..5eebf50 --- /dev/null +++ b/pages/goods/components/detail/detail-skeleton.vue @@ -0,0 +1,177 @@ + + + + + diff --git a/pages/goods/components/detail/detail-tabbar.vue b/pages/goods/components/detail/detail-tabbar.vue new file mode 100644 index 0000000..3de8a3a --- /dev/null +++ b/pages/goods/components/detail/detail-tabbar.vue @@ -0,0 +1,169 @@ + + + + + + diff --git a/pages/goods/components/groupon/groupon-card-list.vue b/pages/goods/components/groupon/groupon-card-list.vue new file mode 100644 index 0000000..0c07d0f --- /dev/null +++ b/pages/goods/components/groupon/groupon-card-list.vue @@ -0,0 +1,141 @@ + + + + + + diff --git a/pages/goods/components/list/list-goods-card.vue b/pages/goods/components/list/list-goods-card.vue new file mode 100644 index 0000000..d02519f --- /dev/null +++ b/pages/goods/components/list/list-goods-card.vue @@ -0,0 +1,103 @@ + + + + + + diff --git a/pages/goods/components/list/list-navbar.vue b/pages/goods/components/list/list-navbar.vue new file mode 100644 index 0000000..c21e679 --- /dev/null +++ b/pages/goods/components/list/list-navbar.vue @@ -0,0 +1,93 @@ + + + + + + diff --git a/pages/goods/groupon.vue b/pages/goods/groupon.vue new file mode 100644 index 0000000..c9378ba --- /dev/null +++ b/pages/goods/groupon.vue @@ -0,0 +1,532 @@ + + + + + + diff --git a/pages/goods/index.vue b/pages/goods/index.vue new file mode 100644 index 0000000..730f167 --- /dev/null +++ b/pages/goods/index.vue @@ -0,0 +1,412 @@ + + + + + diff --git a/pages/goods/list.vue b/pages/goods/list.vue new file mode 100644 index 0000000..d0c7a51 --- /dev/null +++ b/pages/goods/list.vue @@ -0,0 +1,362 @@ + + + + + \ No newline at end of file diff --git a/pages/goods/seckill.vue b/pages/goods/seckill.vue new file mode 100644 index 0000000..a1771b9 --- /dev/null +++ b/pages/goods/seckill.vue @@ -0,0 +1,555 @@ + + + + + + diff --git a/pages/index/cart.vue b/pages/index/cart.vue new file mode 100644 index 0000000..ae90777 --- /dev/null +++ b/pages/index/cart.vue @@ -0,0 +1,196 @@ + + + + + \ No newline at end of file diff --git a/pages/index/category.vue b/pages/index/category.vue new file mode 100644 index 0000000..c02f1ff --- /dev/null +++ b/pages/index/category.vue @@ -0,0 +1,236 @@ + + + + + + diff --git a/pages/index/components/first-one.vue b/pages/index/components/first-one.vue new file mode 100644 index 0000000..2decc86 --- /dev/null +++ b/pages/index/components/first-one.vue @@ -0,0 +1,26 @@ + + + + + + diff --git a/pages/index/components/first-two.vue b/pages/index/components/first-two.vue new file mode 100644 index 0000000..482931c --- /dev/null +++ b/pages/index/components/first-two.vue @@ -0,0 +1,66 @@ + + + + + + diff --git a/pages/index/components/second-one.vue b/pages/index/components/second-one.vue new file mode 100644 index 0000000..86b7078 --- /dev/null +++ b/pages/index/components/second-one.vue @@ -0,0 +1,80 @@ + + + + + + diff --git a/pages/index/index.vue b/pages/index/index.vue new file mode 100644 index 0000000..bde6bac --- /dev/null +++ b/pages/index/index.vue @@ -0,0 +1,91 @@ + + + + + + diff --git a/pages/index/login.vue b/pages/index/login.vue new file mode 100644 index 0000000..b2c82a4 --- /dev/null +++ b/pages/index/login.vue @@ -0,0 +1,38 @@ + + + + diff --git a/pages/index/page.vue b/pages/index/page.vue new file mode 100644 index 0000000..f4cde7f --- /dev/null +++ b/pages/index/page.vue @@ -0,0 +1,51 @@ + + + + + + diff --git a/pages/index/search.vue b/pages/index/search.vue new file mode 100644 index 0000000..8497298 --- /dev/null +++ b/pages/index/search.vue @@ -0,0 +1,119 @@ + + + + + + diff --git a/pages/index/user.vue b/pages/index/user.vue new file mode 100644 index 0000000..705451a --- /dev/null +++ b/pages/index/user.vue @@ -0,0 +1,42 @@ + + + + + + diff --git a/pages/order/aftersale/apply.vue b/pages/order/aftersale/apply.vue new file mode 100644 index 0000000..8914512 --- /dev/null +++ b/pages/order/aftersale/apply.vue @@ -0,0 +1,357 @@ + + + + + + diff --git a/pages/order/aftersale/detail.vue b/pages/order/aftersale/detail.vue new file mode 100644 index 0000000..aa90f47 --- /dev/null +++ b/pages/order/aftersale/detail.vue @@ -0,0 +1,342 @@ + + + + + + \ No newline at end of file diff --git a/pages/order/aftersale/list.vue b/pages/order/aftersale/list.vue new file mode 100644 index 0000000..edcbebf --- /dev/null +++ b/pages/order/aftersale/list.vue @@ -0,0 +1,187 @@ + + + + + + \ No newline at end of file diff --git a/pages/order/aftersale/log-item.vue b/pages/order/aftersale/log-item.vue new file mode 100644 index 0000000..01329df --- /dev/null +++ b/pages/order/aftersale/log-item.vue @@ -0,0 +1,77 @@ + + + + diff --git a/pages/order/aftersale/log.vue b/pages/order/aftersale/log.vue new file mode 100644 index 0000000..3c8078a --- /dev/null +++ b/pages/order/aftersale/log.vue @@ -0,0 +1,38 @@ + + + + + + diff --git a/pages/order/aftersale/return-delivery.vue b/pages/order/aftersale/return-delivery.vue new file mode 100644 index 0000000..bac2878 --- /dev/null +++ b/pages/order/aftersale/return-delivery.vue @@ -0,0 +1,194 @@ + + + + \ No newline at end of file diff --git a/pages/order/confirm.vue b/pages/order/confirm.vue new file mode 100644 index 0000000..ed8b233 --- /dev/null +++ b/pages/order/confirm.vue @@ -0,0 +1,405 @@ + + + + + diff --git a/pages/order/detail.vue b/pages/order/detail.vue new file mode 100644 index 0000000..fc640cf --- /dev/null +++ b/pages/order/detail.vue @@ -0,0 +1,633 @@ + + + + + + diff --git a/pages/order/express/log.vue b/pages/order/express/log.vue new file mode 100644 index 0000000..423a249 --- /dev/null +++ b/pages/order/express/log.vue @@ -0,0 +1,162 @@ + + + + + + diff --git a/pages/order/list.vue b/pages/order/list.vue new file mode 100644 index 0000000..7db64fa --- /dev/null +++ b/pages/order/list.vue @@ -0,0 +1,453 @@ + + + + + + \ No newline at end of file diff --git a/pages/pay/index.vue b/pages/pay/index.vue new file mode 100644 index 0000000..10edd5b --- /dev/null +++ b/pages/pay/index.vue @@ -0,0 +1,288 @@ + + + + + diff --git a/pages/pay/recharge-log.vue b/pages/pay/recharge-log.vue new file mode 100644 index 0000000..632aff9 --- /dev/null +++ b/pages/pay/recharge-log.vue @@ -0,0 +1,165 @@ + + + + + + diff --git a/pages/pay/recharge.vue b/pages/pay/recharge.vue new file mode 100644 index 0000000..482a119 --- /dev/null +++ b/pages/pay/recharge.vue @@ -0,0 +1,259 @@ + + + + + + \ No newline at end of file diff --git a/pages/pay/result.vue b/pages/pay/result.vue new file mode 100644 index 0000000..d860ee7 --- /dev/null +++ b/pages/pay/result.vue @@ -0,0 +1,287 @@ + + + + + + diff --git a/pages/public/error.vue b/pages/public/error.vue new file mode 100644 index 0000000..3ccc14d --- /dev/null +++ b/pages/public/error.vue @@ -0,0 +1,60 @@ + + + + + + diff --git a/pages/public/faq.vue b/pages/public/faq.vue new file mode 100644 index 0000000..af811c9 --- /dev/null +++ b/pages/public/faq.vue @@ -0,0 +1,118 @@ + + + + + + diff --git a/pages/public/richtext.vue b/pages/public/richtext.vue new file mode 100644 index 0000000..8816e7a --- /dev/null +++ b/pages/public/richtext.vue @@ -0,0 +1,54 @@ + + + + + + diff --git a/pages/public/setting.vue b/pages/public/setting.vue new file mode 100644 index 0000000..d0f376c --- /dev/null +++ b/pages/public/setting.vue @@ -0,0 +1,236 @@ + + + + + diff --git a/pages/public/webview.vue b/pages/public/webview.vue new file mode 100644 index 0000000..1327295 --- /dev/null +++ b/pages/public/webview.vue @@ -0,0 +1,18 @@ + + + + + + diff --git a/pages/user/address/edit.vue b/pages/user/address/edit.vue new file mode 100644 index 0000000..fd1d7ec --- /dev/null +++ b/pages/user/address/edit.vue @@ -0,0 +1,257 @@ + + + + + + \ No newline at end of file diff --git a/pages/user/address/list.vue b/pages/user/address/list.vue new file mode 100644 index 0000000..a798dd8 --- /dev/null +++ b/pages/user/address/list.vue @@ -0,0 +1,143 @@ + + + + + + \ No newline at end of file diff --git a/pages/user/goods-collect.vue b/pages/user/goods-collect.vue new file mode 100644 index 0000000..18b37a7 --- /dev/null +++ b/pages/user/goods-collect.vue @@ -0,0 +1,231 @@ + + + + + + diff --git a/pages/user/goods-log.vue b/pages/user/goods-log.vue new file mode 100644 index 0000000..f2b572d --- /dev/null +++ b/pages/user/goods-log.vue @@ -0,0 +1,306 @@ + + + + + + diff --git a/pages/user/info.vue b/pages/user/info.vue new file mode 100644 index 0000000..2b46ee2 --- /dev/null +++ b/pages/user/info.vue @@ -0,0 +1,471 @@ + + + + + + diff --git a/pages/user/wallet/money.vue b/pages/user/wallet/money.vue new file mode 100644 index 0000000..aec661d --- /dev/null +++ b/pages/user/wallet/money.vue @@ -0,0 +1,373 @@ + + + + + + diff --git a/pages/user/wallet/score.vue b/pages/user/wallet/score.vue new file mode 100644 index 0000000..5988c51 --- /dev/null +++ b/pages/user/wallet/score.vue @@ -0,0 +1,277 @@ + + + + + + \ No newline at end of file diff --git a/sheep/components/s-activity-pop/s-activity-pop.vue b/sheep/components/s-activity-pop/s-activity-pop.vue new file mode 100644 index 0000000..04e5c80 --- /dev/null +++ b/sheep/components/s-activity-pop/s-activity-pop.vue @@ -0,0 +1,105 @@ + + + + diff --git a/sheep/components/s-address-item/s-address-item.vue b/sheep/components/s-address-item/s-address-item.vue new file mode 100644 index 0000000..f1b54b3 --- /dev/null +++ b/sheep/components/s-address-item/s-address-item.vue @@ -0,0 +1,112 @@ + + + + + + diff --git a/sheep/components/s-auth-modal/components/account-login.vue b/sheep/components/s-auth-modal/components/account-login.vue new file mode 100644 index 0000000..48e7e61 --- /dev/null +++ b/sheep/components/s-auth-modal/components/account-login.vue @@ -0,0 +1,107 @@ + + + + + + diff --git a/sheep/components/s-auth-modal/components/change-mobile.vue b/sheep/components/s-auth-modal/components/change-mobile.vue new file mode 100644 index 0000000..b1a43f9 --- /dev/null +++ b/sheep/components/s-auth-modal/components/change-mobile.vue @@ -0,0 +1,127 @@ + + + + + + diff --git a/sheep/components/s-auth-modal/components/change-password.vue b/sheep/components/s-auth-modal/components/change-password.vue new file mode 100644 index 0000000..36f85ab --- /dev/null +++ b/sheep/components/s-auth-modal/components/change-password.vue @@ -0,0 +1,106 @@ + + + + + + diff --git a/sheep/components/s-auth-modal/components/mp-authorization.vue b/sheep/components/s-auth-modal/components/mp-authorization.vue new file mode 100644 index 0000000..fec4205 --- /dev/null +++ b/sheep/components/s-auth-modal/components/mp-authorization.vue @@ -0,0 +1,152 @@ + + + + + + diff --git a/sheep/components/s-auth-modal/components/reset-password.vue b/sheep/components/s-auth-modal/components/reset-password.vue new file mode 100644 index 0000000..ffe20d2 --- /dev/null +++ b/sheep/components/s-auth-modal/components/reset-password.vue @@ -0,0 +1,119 @@ + + + + + + diff --git a/sheep/components/s-auth-modal/components/sms-login.vue b/sheep/components/s-auth-modal/components/sms-login.vue new file mode 100644 index 0000000..9becd1a --- /dev/null +++ b/sheep/components/s-auth-modal/components/sms-login.vue @@ -0,0 +1,119 @@ + + + + + + diff --git a/sheep/components/s-auth-modal/index.scss b/sheep/components/s-auth-modal/index.scss new file mode 100644 index 0000000..c4424e7 --- /dev/null +++ b/sheep/components/s-auth-modal/index.scss @@ -0,0 +1,151 @@ +@keyframes title-animation { + 0% { + font-size: 32rpx; + } + 100% { + font-size: 36rpx; + } +} + +.login-wrap { + padding: 50rpx 34rpx; + min-height: 500rpx; + background-color: #fff; + border-radius: 20rpx 20rpx 0 0; +} + +.head-box { + .head-title { + min-width: 160rpx; + font-size: 36rpx; + font-weight: bold; + color: #333333; + line-height: 36rpx; + } + .head-title-active { + width: 160rpx; + font-size: 32rpx; + font-weight: 600; + color: #999; + line-height: 36rpx; + } + .head-title-animation { + animation-name: title-animation; + animation-duration: 0.1s; + animation-timing-function: ease-out; + animation-fill-mode: forwards; + } + .head-title-line { + position: relative; + &::before { + content: ''; + width: 1rpx; + height: 34rpx; + background-color: #e4e7ed; + position: absolute; + left: -30rpx; + top: 50%; + transform: translateY(-50%); + } + } + .head-subtitle { + font-size: 26rpx; + font-weight: 400; + color: #afb6c0; + text-align: left; + display: flex; + } +} + +// .code-btn[disabled] { +// background-color: #fff; +// } +.code-btn-start { + width: 160rpx; + height: 56rpx; + line-height: normal; + border: 2rpx solid var(--ui-BG-Main); + border-radius: 28rpx; + font-size: 26rpx; + font-weight: 400; + color: var(--ui-BG-Main); + opacity: 1; +} + +.forgot-btn { + width: 160rpx; + line-height: 56rpx; + font-size: 30rpx; + font-weight: 500; + color: #999; +} + +.login-btn-start { + width: 158rpx; + height: 56rpx; + line-height: normal; + background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient)); + border-radius: 28rpx; + font-size: 26rpx; + font-weight: 500; + color: #fff; +} + +.type-btn { + padding: 20rpx; + margin: 40rpx auto; + width: 200rpx; + font-size: 30rpx; + font-weight: 500; + color: #999999; +} + +.auto-login-box { + width: 100%; + .auto-login-btn { + width: 68rpx; + height: 68rpx; + border-radius: 50%; + margin: 0 30rpx; + } + .auto-login-img { + width: 68rpx; + height: 68rpx; + border-radius: 50%; + } +} + +.agreement-box { + margin: 80rpx auto 0; + .protocol-check { + transform: scale(0.7); + } + .agreement-text { + font-size: 26rpx; + font-weight: 500; + color: #999999; + .tcp-text { + color: var(--ui-BG-Main); + } + } +} + +// 修改密码 +.editPwd-btn-box { + .save-btn { + width: 690rpx; + line-height: 70rpx; + background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient)); + border-radius: 35rpx; + font-size: 28rpx; + font-weight: 500; + color: #ffffff; + } + .forgot-btn { + width: 690rpx; + line-height: 70rpx; + font-size: 28rpx; + font-weight: 500; + color: #999999; + } +} diff --git a/sheep/components/s-auth-modal/s-auth-modal.vue b/sheep/components/s-auth-modal/s-auth-modal.vue new file mode 100644 index 0000000..cf6a6ff --- /dev/null +++ b/sheep/components/s-auth-modal/s-auth-modal.vue @@ -0,0 +1,239 @@ + + + + + diff --git a/sheep/components/s-block-item/s-block-item.vue b/sheep/components/s-block-item/s-block-item.vue new file mode 100644 index 0000000..9cd6007 --- /dev/null +++ b/sheep/components/s-block-item/s-block-item.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/sheep/components/s-block/s-block.vue b/sheep/components/s-block/s-block.vue new file mode 100644 index 0000000..152b8a6 --- /dev/null +++ b/sheep/components/s-block/s-block.vue @@ -0,0 +1,54 @@ + + + + diff --git a/sheep/components/s-count-down/s-count-down.vue b/sheep/components/s-count-down/s-count-down.vue new file mode 100644 index 0000000..98b3a1f --- /dev/null +++ b/sheep/components/s-count-down/s-count-down.vue @@ -0,0 +1,173 @@ + + + + + \ No newline at end of file diff --git a/sheep/components/s-coupon-block/s-coupon-block.vue b/sheep/components/s-coupon-block/s-coupon-block.vue new file mode 100644 index 0000000..c0f98e9 --- /dev/null +++ b/sheep/components/s-coupon-block/s-coupon-block.vue @@ -0,0 +1,152 @@ + + + + + + diff --git a/sheep/components/s-coupon-card/s-coupon-card.vue b/sheep/components/s-coupon-card/s-coupon-card.vue new file mode 100644 index 0000000..bd5705b --- /dev/null +++ b/sheep/components/s-coupon-card/s-coupon-card.vue @@ -0,0 +1,79 @@ + + + + + + \ No newline at end of file diff --git a/sheep/components/s-coupon-get/s-coupon-get.vue b/sheep/components/s-coupon-get/s-coupon-get.vue new file mode 100644 index 0000000..6df6b55 --- /dev/null +++ b/sheep/components/s-coupon-get/s-coupon-get.vue @@ -0,0 +1,109 @@ + + + + diff --git a/sheep/components/s-coupon-list/s-coupon-list.vue b/sheep/components/s-coupon-list/s-coupon-list.vue new file mode 100644 index 0000000..411a4c0 --- /dev/null +++ b/sheep/components/s-coupon-list/s-coupon-list.vue @@ -0,0 +1,205 @@ + + + + + \ No newline at end of file diff --git a/sheep/components/s-coupon-select/s-coupon-select.vue b/sheep/components/s-coupon-select/s-coupon-select.vue new file mode 100644 index 0000000..e1a9db4 --- /dev/null +++ b/sheep/components/s-coupon-select/s-coupon-select.vue @@ -0,0 +1,138 @@ + + + + diff --git a/sheep/components/s-custom-navbar/components/navbar-item.vue b/sheep/components/s-custom-navbar/components/navbar-item.vue new file mode 100644 index 0000000..fce7ce7 --- /dev/null +++ b/sheep/components/s-custom-navbar/components/navbar-item.vue @@ -0,0 +1,66 @@ + + + + + + diff --git a/sheep/components/s-custom-navbar/components/navbar.vue b/sheep/components/s-custom-navbar/components/navbar.vue new file mode 100644 index 0000000..36050ec --- /dev/null +++ b/sheep/components/s-custom-navbar/components/navbar.vue @@ -0,0 +1,314 @@ + + + + + diff --git a/sheep/components/s-custom-navbar/s-custom-navbar.vue b/sheep/components/s-custom-navbar/s-custom-navbar.vue new file mode 100644 index 0000000..d5f2418 --- /dev/null +++ b/sheep/components/s-custom-navbar/s-custom-navbar.vue @@ -0,0 +1,196 @@ + + + + + + diff --git a/sheep/components/s-discount-list/s-discount-list.vue b/sheep/components/s-discount-list/s-discount-list.vue new file mode 100644 index 0000000..22fbd43 --- /dev/null +++ b/sheep/components/s-discount-list/s-discount-list.vue @@ -0,0 +1,114 @@ + + + diff --git a/sheep/components/s-empty/s-empty.vue b/sheep/components/s-empty/s-empty.vue new file mode 100644 index 0000000..27f8c4a --- /dev/null +++ b/sheep/components/s-empty/s-empty.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/sheep/components/s-float-menu/s-float-menu.vue b/sheep/components/s-float-menu/s-float-menu.vue new file mode 100644 index 0000000..b53db00 --- /dev/null +++ b/sheep/components/s-float-menu/s-float-menu.vue @@ -0,0 +1,88 @@ + + + + diff --git a/sheep/components/s-goods-card/s-goods-card.vue b/sheep/components/s-goods-card/s-goods-card.vue new file mode 100644 index 0000000..61f5b44 --- /dev/null +++ b/sheep/components/s-goods-card/s-goods-card.vue @@ -0,0 +1,286 @@ + + + + + + diff --git a/sheep/components/s-goods-column/s-goods-column.vue b/sheep/components/s-goods-column/s-goods-column.vue new file mode 100644 index 0000000..8939254 --- /dev/null +++ b/sheep/components/s-goods-column/s-goods-column.vue @@ -0,0 +1,721 @@ + + + + + + diff --git a/sheep/components/s-goods-item/s-goods-item.vue b/sheep/components/s-goods-item/s-goods-item.vue new file mode 100644 index 0000000..456bb16 --- /dev/null +++ b/sheep/components/s-goods-item/s-goods-item.vue @@ -0,0 +1,181 @@ + + + + + diff --git a/sheep/components/s-goods-scroll/s-goods-scroll.vue b/sheep/components/s-goods-scroll/s-goods-scroll.vue new file mode 100644 index 0000000..175cc66 --- /dev/null +++ b/sheep/components/s-goods-scroll/s-goods-scroll.vue @@ -0,0 +1,33 @@ + + + + + + diff --git a/sheep/components/s-goods-shelves/s-goods-shelves.vue b/sheep/components/s-goods-shelves/s-goods-shelves.vue new file mode 100644 index 0000000..189dd25 --- /dev/null +++ b/sheep/components/s-goods-shelves/s-goods-shelves.vue @@ -0,0 +1,147 @@ + + + + + + diff --git a/sheep/components/s-groupon-block/s-groupon-block.vue b/sheep/components/s-groupon-block/s-groupon-block.vue new file mode 100644 index 0000000..8af111a --- /dev/null +++ b/sheep/components/s-groupon-block/s-groupon-block.vue @@ -0,0 +1,154 @@ + + + + + + diff --git a/sheep/components/s-hotzone-block/s-hotzone-block.vue b/sheep/components/s-hotzone-block/s-hotzone-block.vue new file mode 100644 index 0000000..246d6d4 --- /dev/null +++ b/sheep/components/s-hotzone-block/s-hotzone-block.vue @@ -0,0 +1,46 @@ + + + + + + diff --git a/sheep/components/s-image-banner/s-image-banner.vue b/sheep/components/s-image-banner/s-image-banner.vue new file mode 100644 index 0000000..478e7af --- /dev/null +++ b/sheep/components/s-image-banner/s-image-banner.vue @@ -0,0 +1,44 @@ + + + + + + diff --git a/sheep/components/s-image-block/s-image-block.vue b/sheep/components/s-image-block/s-image-block.vue new file mode 100644 index 0000000..c898749 --- /dev/null +++ b/sheep/components/s-image-block/s-image-block.vue @@ -0,0 +1,27 @@ + + + + + + diff --git a/sheep/components/s-image-cube/s-image-cube.vue b/sheep/components/s-image-cube/s-image-cube.vue new file mode 100644 index 0000000..9794d8f --- /dev/null +++ b/sheep/components/s-image-cube/s-image-cube.vue @@ -0,0 +1,110 @@ + + + + + diff --git a/sheep/components/s-layout/s-layout.vue b/sheep/components/s-layout/s-layout.vue new file mode 100644 index 0000000..541abea --- /dev/null +++ b/sheep/components/s-layout/s-layout.vue @@ -0,0 +1,242 @@ + + + + + diff --git a/sheep/components/s-line-block/s-line-block.vue b/sheep/components/s-line-block/s-line-block.vue new file mode 100644 index 0000000..c0628f9 --- /dev/null +++ b/sheep/components/s-line-block/s-line-block.vue @@ -0,0 +1,15 @@ + + + + + + diff --git a/sheep/components/s-live-block/s-live-block.vue b/sheep/components/s-live-block/s-live-block.vue new file mode 100644 index 0000000..9d1ad03 --- /dev/null +++ b/sheep/components/s-live-block/s-live-block.vue @@ -0,0 +1,144 @@ + + + diff --git a/sheep/components/s-live-card/s-live-card.vue b/sheep/components/s-live-card/s-live-card.vue new file mode 100644 index 0000000..9cefee0 --- /dev/null +++ b/sheep/components/s-live-card/s-live-card.vue @@ -0,0 +1,234 @@ + + + + diff --git a/sheep/components/s-menu-button/s-menu-button.vue b/sheep/components/s-menu-button/s-menu-button.vue new file mode 100644 index 0000000..375a591 --- /dev/null +++ b/sheep/components/s-menu-button/s-menu-button.vue @@ -0,0 +1,363 @@ + + + + + + diff --git a/sheep/components/s-menu-grid/s-menu-grid.vue b/sheep/components/s-menu-grid/s-menu-grid.vue new file mode 100644 index 0000000..518d6ed --- /dev/null +++ b/sheep/components/s-menu-grid/s-menu-grid.vue @@ -0,0 +1,82 @@ + + + + + + diff --git a/sheep/components/s-menu-list/s-menu-list.vue b/sheep/components/s-menu-list/s-menu-list.vue new file mode 100644 index 0000000..ecbb396 --- /dev/null +++ b/sheep/components/s-menu-list/s-menu-list.vue @@ -0,0 +1,66 @@ + + + + + + diff --git a/sheep/components/s-menu-tools/s-menu-tools.vue b/sheep/components/s-menu-tools/s-menu-tools.vue new file mode 100644 index 0000000..ee2058c --- /dev/null +++ b/sheep/components/s-menu-tools/s-menu-tools.vue @@ -0,0 +1,118 @@ + + + + + + diff --git a/sheep/components/s-notice-block/s-notice-block.vue b/sheep/components/s-notice-block/s-notice-block.vue new file mode 100644 index 0000000..e7d74a0 --- /dev/null +++ b/sheep/components/s-notice-block/s-notice-block.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/sheep/components/s-order-card/s-order-card.vue b/sheep/components/s-order-card/s-order-card.vue new file mode 100644 index 0000000..095e6cf --- /dev/null +++ b/sheep/components/s-order-card/s-order-card.vue @@ -0,0 +1,108 @@ + + + + + + diff --git a/sheep/components/s-popup-image/s-popup-image.vue b/sheep/components/s-popup-image/s-popup-image.vue new file mode 100644 index 0000000..b10f477 --- /dev/null +++ b/sheep/components/s-popup-image/s-popup-image.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/sheep/components/s-richtext-block/s-richtext-block.vue b/sheep/components/s-richtext-block/s-richtext-block.vue new file mode 100644 index 0000000..0af1618 --- /dev/null +++ b/sheep/components/s-richtext-block/s-richtext-block.vue @@ -0,0 +1,40 @@ + + + diff --git a/sheep/components/s-search-block/s-search-block.vue b/sheep/components/s-search-block/s-search-block.vue new file mode 100644 index 0000000..1e9c2b5 --- /dev/null +++ b/sheep/components/s-search-block/s-search-block.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/sheep/components/s-seckill-block/s-seckill-block.vue b/sheep/components/s-seckill-block/s-seckill-block.vue new file mode 100644 index 0000000..6c5eaec --- /dev/null +++ b/sheep/components/s-seckill-block/s-seckill-block.vue @@ -0,0 +1,160 @@ + + + + + + diff --git a/sheep/components/s-select-groupon-sku/s-select-groupon-sku.vue b/sheep/components/s-select-groupon-sku/s-select-groupon-sku.vue new file mode 100644 index 0000000..e2a2950 --- /dev/null +++ b/sheep/components/s-select-groupon-sku/s-select-groupon-sku.vue @@ -0,0 +1,472 @@ + + + + + diff --git a/sheep/components/s-select-seckill-sku/s-select-seckill-sku.vue b/sheep/components/s-select-seckill-sku/s-select-seckill-sku.vue new file mode 100644 index 0000000..1764035 --- /dev/null +++ b/sheep/components/s-select-seckill-sku/s-select-seckill-sku.vue @@ -0,0 +1,432 @@ + + + + + + diff --git a/sheep/components/s-select-sku/s-select-sku.vue b/sheep/components/s-select-sku/s-select-sku.vue new file mode 100644 index 0000000..b339d4a --- /dev/null +++ b/sheep/components/s-select-sku/s-select-sku.vue @@ -0,0 +1,406 @@ + + + + + \ No newline at end of file diff --git a/sheep/components/s-share-modal/canvas-poster/index.vue b/sheep/components/s-share-modal/canvas-poster/index.vue new file mode 100644 index 0000000..5da08c0 --- /dev/null +++ b/sheep/components/s-share-modal/canvas-poster/index.vue @@ -0,0 +1,165 @@ + + + + + + diff --git a/sheep/components/s-share-modal/canvas-poster/poster/goods.js b/sheep/components/s-share-modal/canvas-poster/poster/goods.js new file mode 100644 index 0000000..0f59dd4 --- /dev/null +++ b/sheep/components/s-share-modal/canvas-poster/poster/goods.js @@ -0,0 +1,126 @@ +import sheep from '@/sheep'; +import third from '@/api/migration/third'; +import { formatImageUrlProtocol } from './index'; + +const goods = async (poster) => { + const width = poster.width; + const userInfo = sheep.$store('user').userInfo; + const wxa_qrcode = (await third.wechat.getWxacode(poster.shareInfo.path, poster.shareInfo.query)).data; + return [ + { + type: 'image', + src: formatImageUrlProtocol(sheep.$url.cdn(sheep.$store('app').platform.share.posterInfo.goods_bg)), + css: { + width, + position: 'fixed', + 'object-fit': 'contain', + top: '0', + left: '0', + zIndex: -1, + }, + }, + { + type: 'text', + text: userInfo.nickname, + css: { + color: '#333', + fontSize: 16, + fontFamily: 'sans-serif', + position: 'fixed', + top: width * 0.06, + left: width * 0.22, + }, + }, + { + type: 'image', + src: formatImageUrlProtocol(sheep.$url.cdn(userInfo.avatar)), + css: { + position: 'fixed', + left: width * 0.04, + top: width * 0.04, + width: width * 0.14, + height: width * 0.14, + }, + }, + { + type: 'image', + src: formatImageUrlProtocol(poster.shareInfo.poster.image), + css: { + position: 'fixed', + left: width * 0.03, + top: width * 0.21, + width: width * 0.94, + height: width * 0.94, + }, + }, + { + type: 'text', + text: poster.shareInfo.poster.title, + css: { + position: 'fixed', + left: width * 0.04, + top: width * 1.18, + color: '#333', + fontSize: 14, + lineHeight: 5, + maxWidth: width * 0.91, + }, + }, + { + type: 'text', + text: '¥' + poster.shareInfo.poster.price, + css: { + position: 'fixed', + left: width * 0.04, + top: width * 1.3, + fontSize: 20, + fontFamily: 'OPPOSANS', + color: '#333', + }, + }, + { + type: 'text', + text: + poster.shareInfo.poster.original_price > 0 + ? '¥' + poster.shareInfo.poster.original_price + : '', + css: { + position: 'fixed', + left: width * 0.3, + top: width * 1.32, + color: '#999', + fontSize: 10, + fontFamily: 'OPPOSANS', + textDecoration: 'line-through', + }, + }, + // #ifndef MP-WEIXIN + { + type: 'qrcode', + text: poster.shareInfo.link, + css: { + position: 'fixed', + left: width * 0.75, + top: width * 1.3, + width: width * 0.2, + height: width * 0.2, + }, + }, + // #endif + // #ifdef MP-WEIXIN + { + type: 'image', + src: wxa_qrcode, + css: { + position: 'fixed', + left: width * 0.75, + top: width * 1.3, + width: width * 0.2, + height: width * 0.2, + }, + }, + // #endif + ]; +}; + +export default goods; diff --git a/sheep/components/s-share-modal/canvas-poster/poster/groupon.js b/sheep/components/s-share-modal/canvas-poster/poster/groupon.js new file mode 100644 index 0000000..193934e --- /dev/null +++ b/sheep/components/s-share-modal/canvas-poster/poster/groupon.js @@ -0,0 +1,123 @@ +import sheep from '@/sheep'; +import { formatImageUrlProtocol } from './index'; +import third from '@/api/migration/third'; + +const groupon = async (poster) => { + const width = poster.width; + const userInfo = sheep.$store('user').userInfo; + const wxa_qrcode = (await third.wechat.getWxacode(poster.shareInfo.path, poster.shareInfo.query)).data; + return [ + { + type: 'image', + src: formatImageUrlProtocol(sheep.$url.cdn(sheep.$store('app').platform.share.posterInfo.groupon_bg)), + css: { + width, + position: 'fixed', + 'object-fit': 'contain', + top: '0', + left: '0', + zIndex: -1, + }, + }, + { + type: 'text', + text: userInfo.nickname, + css: { + color: '#333', + fontSize: 16, + fontFamily: 'sans-serif', + position: 'fixed', + top: width * 0.06, + left: width * 0.22, + }, + }, + { + type: 'image', + src: formatImageUrlProtocol(sheep.$url.cdn(userInfo.avatar)), + css: { + position: 'fixed', + left: width * 0.04, + top: width * 0.04, + width: width * 0.14, + height: width * 0.14, + }, + }, + { + type: 'image', + src: formatImageUrlProtocol(poster.shareInfo.poster.image), + css: { + position: 'fixed', + left: width * 0.03, + top: width * 0.21, + width: width * 0.94, + height: width * 0.94, + borderRadius: 10, + }, + }, + { + type: 'text', + text: poster.shareInfo.poster.title, + css: { + color: '#333', + fontSize: 14, + position: 'fixed', + top: width * 1.18, + left: width * 0.04, + maxWidth: width * 0.91, + lineHeight: 5, + }, + }, + { + type: 'text', + text: '¥' + poster.shareInfo.poster.price, + css: { + color: '#ff0000', + fontSize: 20, + fontFamily: 'OPPOSANS', + position: 'fixed', + top: width * 1.3, + left: width * 0.04, + }, + }, + { + type: 'text', + text: '2人团', + css: { + color: '#ff0000', + fontSize: 30, + fontFamily: 'OPPOSANS', + position: 'fixed', + left: width * 0.3, + top: width * 1.32, + }, + }, + // #ifndef MP-WEIXIN + { + type: 'qrcode', + text: poster.shareInfo.link, + css: { + position: 'fixed', + left: width * 0.75, + top: width * 1.3, + width: width * 0.2, + height: width * 0.2, + }, + }, + // #endif + // #ifdef MP-WEIXIN + { + type: 'image', + src: wxa_qrcode, + css: { + position: 'fixed', + left: width * 0.75, + top: width * 1.3, + width: width * 0.2, + height: width * 0.2, + }, + }, + // #endif + ]; +}; + +export default groupon; diff --git a/sheep/components/s-share-modal/canvas-poster/poster/index.js b/sheep/components/s-share-modal/canvas-poster/poster/index.js new file mode 100644 index 0000000..f6eab72 --- /dev/null +++ b/sheep/components/s-share-modal/canvas-poster/poster/index.js @@ -0,0 +1,59 @@ +import user from './user'; +import goods from './goods'; +import groupon from './groupon'; + +export function getPosterData(options) { + switch (options.shareInfo.poster.type) { + case 'user': + return user(options); + case 'goods': + return goods(options); + case 'groupon': + return groupon(options); + } +} + +export function formatImageUrlProtocol(url) { + // #ifdef H5 + // H5平台 https协议下需要转换 + if (window.location.protocol === 'https:' && url.indexOf('http:') === 0) { + url = url.replace('http:', 'https:'); + } + // #endif + + // #ifdef MP-WEIXIN + // 小程序平台 需要强制转换为https协议 + if (url.indexOf('http:') === 0) { + url = url.replace('http:', 'https:'); + } + // #endif + + return url; +} + + +export function getBase64Src(base64data, appType) { + const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64data) || []; + switch (appType) { + case 'wechat': + const filePath = `${wx.env.USER_DATA_PATH}/tmp_base64src.${format}`; + return new Promise((resolve, reject) => { + const fileManager = uni.getFileSystemManager(); + fileManager.writeFile({ + filePath: filePath, + data: bodyData, // base64 数据 + encoding: 'base64', // 字符编码 + success: () => { + resolve(filePath); + }, + file: (err) => { + console.log('base64 保存失败', err); + }, + }); + }); + default: + console.warn('获得 base64 图片地址只做了微信小程序端的转换,其它端请自行实现!!!'); + break; + } +} + diff --git a/sheep/components/s-share-modal/canvas-poster/poster/user.js b/sheep/components/s-share-modal/canvas-poster/poster/user.js new file mode 100644 index 0000000..f723496 --- /dev/null +++ b/sheep/components/s-share-modal/canvas-poster/poster/user.js @@ -0,0 +1,75 @@ +import sheep from '@/sheep'; +import { formatImageUrlProtocol } from './index'; +import third from '@/api/migration/third'; + +const user = async (poster) => { + const width = poster.width; + const userInfo = sheep.$store('user').userInfo; + const wxa_qrcode = (await third.wechat.getWxacode(poster.shareInfo.path, poster.shareInfo.query)).data; + return [ + { + type: 'image', + src: formatImageUrlProtocol(sheep.$url.cdn(sheep.$store('app').platform.share.posterInfo.user_bg)), + css: { + width, + position: 'fixed', + 'object-fit': 'contain', + top: '0', + left: '0', + zIndex: -1, + }, + }, + { + type: 'text', + text: userInfo.nickname, + css: { + color: '#333', + fontSize: 14, + textAlign: 'center', + fontFamily: 'sans-serif', + position: 'fixed', + top: width * 0.4, + left: width / 2, + }, + }, + { + type: 'image', + src: formatImageUrlProtocol(sheep.$url.cdn(userInfo.avatar)), + css: { + position: 'fixed', + left: width * 0.4, + top: width * 0.16, + width: width * 0.2, + height: width * 0.2, + }, + }, + // #ifndef MP-WEIXIN + { + type: 'qrcode', + text: poster.shareInfo.link, + css: { + position: 'fixed', + left: width * 0.35, + top: width * 0.84, + width: width * 0.3, + height: width * 0.3, + }, + }, + // #endif + // #ifdef MP-WEIXIN + { + type: 'image', + src: wxa_qrcode, + css: { + position: 'fixed', + left: width * 0.35, + top: width * 0.84, + width: width * 0.3, + height: width * 0.3, + }, + }, + // #endif + ]; +}; + +export default user; diff --git a/sheep/components/s-share-modal/s-share-modal.vue b/sheep/components/s-share-modal/s-share-modal.vue new file mode 100644 index 0000000..0f0b92d --- /dev/null +++ b/sheep/components/s-share-modal/s-share-modal.vue @@ -0,0 +1,195 @@ + + + + + diff --git a/sheep/components/s-statusbar/s-statusbar.vue b/sheep/components/s-statusbar/s-statusbar.vue new file mode 100644 index 0000000..8b58c97 --- /dev/null +++ b/sheep/components/s-statusbar/s-statusbar.vue @@ -0,0 +1,10 @@ + + + diff --git a/sheep/components/s-tabbar/s-tabbar.vue b/sheep/components/s-tabbar/s-tabbar.vue new file mode 100644 index 0000000..369a823 --- /dev/null +++ b/sheep/components/s-tabbar/s-tabbar.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/sheep/components/s-title-block/s-title-block.vue b/sheep/components/s-title-block/s-title-block.vue new file mode 100644 index 0000000..513a3cc --- /dev/null +++ b/sheep/components/s-title-block/s-title-block.vue @@ -0,0 +1,100 @@ + + + + + + diff --git a/sheep/components/s-uploader/choose-and-upload-file.js b/sheep/components/s-uploader/choose-and-upload-file.js new file mode 100644 index 0000000..fd9ffaa --- /dev/null +++ b/sheep/components/s-uploader/choose-and-upload-file.js @@ -0,0 +1,213 @@ +'use strict'; +import FileApi from '@/api/infra/file'; + +const ERR_MSG_OK = 'chooseAndUploadFile:ok'; +const ERR_MSG_FAIL = 'chooseAndUploadFile:fail'; + +function chooseImage(opts) { + const { + count, + sizeType = ['original', 'compressed'], + sourceType = ['album', 'camera'], + extension, + } = opts; + return new Promise((resolve, reject) => { + uni.chooseImage({ + count, + sizeType, + sourceType, + extension, + success(res) { + resolve(normalizeChooseAndUploadFileRes(res, 'image')); + }, + fail(res) { + reject({ + errMsg: res.errMsg.replace('chooseImage:fail', ERR_MSG_FAIL), + }); + }, + }); + }); +} + +function chooseVideo(opts) { + const { camera, compressed, maxDuration, sourceType = ['album', 'camera'], extension } = opts; + return new Promise((resolve, reject) => { + uni.chooseVideo({ + camera, + compressed, + maxDuration, + sourceType, + extension, + success(res) { + const { tempFilePath, duration, size, height, width } = res; + resolve( + normalizeChooseAndUploadFileRes( + { + errMsg: 'chooseVideo:ok', + tempFilePaths: [tempFilePath], + tempFiles: [ + { + name: (res.tempFile && res.tempFile.name) || '', + path: tempFilePath, + size, + type: (res.tempFile && res.tempFile.type) || '', + width, + height, + duration, + fileType: 'video', + cloudPath: '', + }, + ], + }, + 'video', + ), + ); + }, + fail(res) { + reject({ + errMsg: res.errMsg.replace('chooseVideo:fail', ERR_MSG_FAIL), + }); + }, + }); + }); +} + +function chooseAll(opts) { + const { count, extension } = opts; + return new Promise((resolve, reject) => { + let chooseFile = uni.chooseFile; + if (typeof wx !== 'undefined' && typeof wx.chooseMessageFile === 'function') { + chooseFile = wx.chooseMessageFile; + } + if (typeof chooseFile !== 'function') { + return reject({ + errMsg: ERR_MSG_FAIL + ' 请指定 type 类型,该平台仅支持选择 image 或 video。', + }); + } + chooseFile({ + type: 'all', + count, + extension, + success(res) { + resolve(normalizeChooseAndUploadFileRes(res)); + }, + fail(res) { + reject({ + errMsg: res.errMsg.replace('chooseFile:fail', ERR_MSG_FAIL), + }); + }, + }); + }); +} + +function normalizeChooseAndUploadFileRes(res, fileType) { + res.tempFiles.forEach((item, index) => { + if (!item.name) { + item.name = item.path.substring(item.path.lastIndexOf('/') + 1); + } + if (fileType) { + item.fileType = fileType; + } + item.cloudPath = Date.now() + '_' + index + item.name.substring(item.name.lastIndexOf('.')); + }); + if (!res.tempFilePaths) { + res.tempFilePaths = res.tempFiles.map((file) => file.path); + } + return res; +} + +function uploadCloudFiles(files, max = 5, onUploadProgress) { + files = JSON.parse(JSON.stringify(files)); + const len = files.length; + let count = 0; + let self = this; + return new Promise((resolve) => { + while (count < max) { + next(); + } + + function next() { + let cur = count++; + if (cur >= len) { + !files.find((item) => !item.url && !item.errMsg) && resolve(files); + return; + } + const fileItem = files[cur]; + const index = self.files.findIndex((v) => v.uuid === fileItem.uuid); + fileItem.url = ''; + delete fileItem.errMsg; + + uniCloud + .uploadFile({ + filePath: fileItem.path, + cloudPath: fileItem.cloudPath, + fileType: fileItem.fileType, + onUploadProgress: (res) => { + res.index = index; + onUploadProgress && onUploadProgress(res); + }, + }) + .then((res) => { + fileItem.url = res.fileID; + fileItem.index = index; + if (cur < len) { + next(); + } + }) + .catch((res) => { + fileItem.errMsg = res.errMsg || res.message; + fileItem.index = index; + if (cur < len) { + next(); + } + }); + } + }); +} + +function uploadFiles(choosePromise, { onChooseFile, onUploadProgress }) { + return choosePromise + .then((res) => { + if (onChooseFile) { + const customChooseRes = onChooseFile(res); + if (typeof customChooseRes !== 'undefined') { + return Promise.resolve(customChooseRes).then((chooseRes) => + typeof chooseRes === 'undefined' ? res : chooseRes, + ); + } + } + return res; + }) + .then((res) => { + if (res === false) { + return { + errMsg: ERR_MSG_OK, + tempFilePaths: [], + tempFiles: [], + }; + } + return res; + }) + .then(async (files) => { + for (let file of files.tempFiles) { + const { data } = await FileApi.uploadFile(file.path); + file.url = data; + } + return files; + }); +} + +function chooseAndUploadFile( + opts = { + type: 'all', + }, +) { + if (opts.type === 'image') { + return uploadFiles(chooseImage(opts), opts); + } else if (opts.type === 'video') { + return uploadFiles(chooseVideo(opts), opts); + } + return uploadFiles(chooseAll(opts), opts); +} + +export { chooseAndUploadFile, uploadCloudFiles }; diff --git a/sheep/components/s-uploader/s-uploader.vue b/sheep/components/s-uploader/s-uploader.vue new file mode 100644 index 0000000..95cfb05 --- /dev/null +++ b/sheep/components/s-uploader/s-uploader.vue @@ -0,0 +1,675 @@ + + + + + + diff --git a/sheep/components/s-uploader/upload-file.vue b/sheep/components/s-uploader/upload-file.vue new file mode 100644 index 0000000..233d281 --- /dev/null +++ b/sheep/components/s-uploader/upload-file.vue @@ -0,0 +1,335 @@ + + + + + diff --git a/sheep/components/s-uploader/upload-image.vue b/sheep/components/s-uploader/upload-image.vue new file mode 100644 index 0000000..b66956a --- /dev/null +++ b/sheep/components/s-uploader/upload-image.vue @@ -0,0 +1,306 @@ + + + + + diff --git a/sheep/components/s-uploader/utils.js b/sheep/components/s-uploader/utils.js new file mode 100644 index 0000000..c1e8073 --- /dev/null +++ b/sheep/components/s-uploader/utils.js @@ -0,0 +1,110 @@ +/** + * 获取文件名和后缀 + * @param {String} name + */ +export const get_file_ext = (name) => { + const last_len = name.lastIndexOf('.'); + const len = name.length; + return { + name: name.substring(0, last_len), + ext: name.substring(last_len + 1, len), + }; +}; + +/** + * 获取扩展名 + * @param {Array} fileExtname + */ +export const get_extname = (fileExtname) => { + if (!Array.isArray(fileExtname)) { + let extname = fileExtname.replace(/(\[|\])/g, ''); + return extname.split(','); + } else { + return fileExtname; + } + return []; +}; + +/** + * 获取文件和检测是否可选 + */ +export const get_files_and_is_max = (res, _extname) => { + let filePaths = []; + let files = []; + if (!_extname || _extname.length === 0) { + return { + filePaths, + files, + }; + } + res.tempFiles.forEach((v) => { + let fileFullName = get_file_ext(v.name); + const extname = fileFullName.ext.toLowerCase(); + if (_extname.indexOf(extname) !== -1) { + files.push(v); + filePaths.push(v.path); + } + }); + if (files.length !== res.tempFiles.length) { + uni.showToast({ + title: `当前选择了${res.tempFiles.length}个文件 ,${ + res.tempFiles.length - files.length + } 个文件格式不正确`, + icon: 'none', + duration: 5000, + }); + } + + return { + filePaths, + files, + }; +}; + +/** + * 获取图片信息 + * @param {Object} filepath + */ +export const get_file_info = (filepath) => { + return new Promise((resolve, reject) => { + uni.getImageInfo({ + src: filepath, + success(res) { + resolve(res); + }, + fail(err) { + reject(err); + }, + }); + }); +}; +/** + * 获取封装数据 + */ +export const get_file_data = async (files, type = 'image') => { + // 最终需要上传数据库的数据 + let fileFullName = get_file_ext(files.name); + const extname = fileFullName.ext.toLowerCase(); + let filedata = { + name: files.name, + uuid: files.uuid, + extname: extname || '', + cloudPath: files.cloudPath, + fileType: files.fileType, + url: files.path || files.path, + size: files.size, //单位是字节 + image: {}, + path: files.path, + video: {}, + }; + if (type === 'image') { + const imageinfo = await get_file_info(files.path); + delete filedata.video; + filedata.image.width = imageinfo.width; + filedata.image.height = imageinfo.height; + filedata.image.location = imageinfo.path; + } else { + delete filedata.image; + } + return filedata; +}; diff --git a/sheep/components/s-user-card/s-user-card.vue b/sheep/components/s-user-card/s-user-card.vue new file mode 100644 index 0000000..d234098 --- /dev/null +++ b/sheep/components/s-user-card/s-user-card.vue @@ -0,0 +1,167 @@ + + + + + + \ No newline at end of file diff --git a/sheep/components/s-video-block/s-video-block.vue b/sheep/components/s-video-block/s-video-block.vue new file mode 100644 index 0000000..38e5b84 --- /dev/null +++ b/sheep/components/s-video-block/s-video-block.vue @@ -0,0 +1,32 @@ + + + + + + diff --git a/sheep/components/s-wallet-card/s-wallet-card.vue b/sheep/components/s-wallet-card/s-wallet-card.vue new file mode 100644 index 0000000..54348e9 --- /dev/null +++ b/sheep/components/s-wallet-card/s-wallet-card.vue @@ -0,0 +1,93 @@ + + + + + + + diff --git a/sheep/config/index.js b/sheep/config/index.js new file mode 100644 index 0000000..b9b9bcf --- /dev/null +++ b/sheep/config/index.js @@ -0,0 +1,19 @@ +// 开发环境配置 +export let baseUrl; +export let version; +if (process.env.NODE_ENV === 'development') { + baseUrl = import.meta.env.SHOPRO_DEV_BASE_URL; +} else { + baseUrl = import.meta.env.SHOPRO_BASE_URL; +} +version = import.meta.env.SHOPRO_VERSION; +console.log(`[商城 ${version}]`); + +export const apiPath = import.meta.env.SHOPRO_API_PATH; +export const staticUrl = import.meta.env.SHOPRO_STATIC_URL; + +export default { + baseUrl, + apiPath, + staticUrl, +}; diff --git a/sheep/config/zIndex.js b/sheep/config/zIndex.js new file mode 100644 index 0000000..6652d48 --- /dev/null +++ b/sheep/config/zIndex.js @@ -0,0 +1,20 @@ +// uniapp在H5中各API的z-index值如下: +/** + * actionsheet: 999 + * modal: 999 + * navigate: 998 + * tabbar: 998 + * toast: 999 + */ + +export default { + toast: 10090, + noNetwork: 10080, + popup: 10075, // popup包含popup,actionsheet,keyboard,picker的值 + mask: 10070, + navbar: 980, + topTips: 975, + sticky: 970, + indexListSticky: 965, + popover: 960, +}; diff --git a/sheep/helper/digit.js b/sheep/helper/digit.js new file mode 100644 index 0000000..be50b32 --- /dev/null +++ b/sheep/helper/digit.js @@ -0,0 +1,168 @@ +let _boundaryCheckingState = true; // 是否进行越界检查的全局开关 + +/** + * 把错误的数据转正 + * @private + * @example strip(0.09999999999999998)=0.1 + */ +function strip(num, precision = 15) { + return +parseFloat(Number(num).toPrecision(precision)); +} + +/** + * Return digits length of a number + * @private + * @param {*number} num Input number + */ +function digitLength(num) { + // Get digit length of e + const eSplit = num.toString().split(/[eE]/); + const len = (eSplit[0].split('.')[1] || '').length - +(eSplit[1] || 0); + return len > 0 ? len : 0; +} + +/** + * 把小数转成整数,如果是小数则放大成整数 + * @private + * @param {*number} num 输入数 + */ +function float2Fixed(num) { + if (num.toString().indexOf('e') === -1) { + return Number(num.toString().replace('.', '')); + } + const dLen = digitLength(num); + return dLen > 0 ? strip(Number(num) * Math.pow(10, dLen)) : Number(num); +} + +/** + * 检测数字是否越界,如果越界给出提示 + * @private + * @param {*number} num 输入数 + */ +function checkBoundary(num) { + if (_boundaryCheckingState) { + if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) { + console.warn(`${num} 超出了精度限制,结果可能不正确`); + } + } +} + +/** + * 把递归操作扁平迭代化 + * @param {number[]} arr 要操作的数字数组 + * @param {function} operation 迭代操作 + * @private + */ +function iteratorOperation(arr, operation) { + const [num1, num2, ...others] = arr; + let res = operation(num1, num2); + + others.forEach((num) => { + res = operation(res, num); + }); + + return res; +} + +/** + * 高精度乘法 + * @export + */ +export function times(...nums) { + if (nums.length > 2) { + return iteratorOperation(nums, times); + } + + const [num1, num2] = nums; + const num1Changed = float2Fixed(num1); + const num2Changed = float2Fixed(num2); + const baseNum = digitLength(num1) + digitLength(num2); + const leftValue = num1Changed * num2Changed; + + checkBoundary(leftValue); + + return leftValue / Math.pow(10, baseNum); +} + +/** + * 高精度加法 + * @export + */ +export function plus(...nums) { + if (nums.length > 2) { + return iteratorOperation(nums, plus); + } + + const [num1, num2] = nums; + // 取最大的小数位 + const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2))); + // 把小数都转为整数然后再计算 + return (times(num1, baseNum) + times(num2, baseNum)) / baseNum; +} + +/** + * 高精度减法 + * @export + */ +export function minus(...nums) { + if (nums.length > 2) { + return iteratorOperation(nums, minus); + } + + const [num1, num2] = nums; + const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2))); + return (times(num1, baseNum) - times(num2, baseNum)) / baseNum; +} + +/** + * 高精度除法 + * @export + */ +export function divide(...nums) { + if (nums.length > 2) { + return iteratorOperation(nums, divide); + } + + const [num1, num2] = nums; + const num1Changed = float2Fixed(num1); + const num2Changed = float2Fixed(num2); + checkBoundary(num1Changed); + checkBoundary(num2Changed); + // 重要,这里必须用strip进行修正 + return times( + num1Changed / num2Changed, + strip(Math.pow(10, digitLength(num2) - digitLength(num1))), + ); +} + +/** + * 四舍五入 + * @export + */ +export function round(num, ratio) { + const base = Math.pow(10, ratio); + let result = divide(Math.round(Math.abs(times(num, base))), base); + if (num < 0 && result !== 0) { + result = times(result, -1); + } + // 位数不足则补0 + return result; +} + +/** + * 是否进行边界检查,默认开启 + * @param flag 标记开关,true 为开启,false 为关闭,默认为 true + * @export + */ +export function enableBoundaryChecking(flag = true) { + _boundaryCheckingState = flag; +} + +export default { + times, + plus, + minus, + divide, + round, + enableBoundaryChecking, +}; diff --git a/sheep/helper/index.js b/sheep/helper/index.js new file mode 100644 index 0000000..38e6afe --- /dev/null +++ b/sheep/helper/index.js @@ -0,0 +1,708 @@ +import test from './test.js'; +import { round } from './digit.js'; +/** + * @description 如果value小于min,取min;如果value大于max,取max + * @param {number} min + * @param {number} max + * @param {number} value + */ +function range(min = 0, max = 0, value = 0) { + return Math.max(min, Math.min(max, Number(value))); +} + +/** + * @description 用于获取用户传递值的px值 如果用户传递了"xxpx"或者"xxrpx",取出其数值部分,如果是"xxxrpx"还需要用过uni.upx2px进行转换 + * @param {number|string} value 用户传递值的px值 + * @param {boolean} unit + * @returns {number|string} + */ +export function getPx(value, unit = false) { + if (test.number(value)) { + return unit ? `${value}px` : Number(value); + } + // 如果带有rpx,先取出其数值部分,再转为px值 + if (/(rpx|upx)$/.test(value)) { + return unit ? `${uni.upx2px(parseInt(value))}px` : Number(uni.upx2px(parseInt(value))); + } + return unit ? `${parseInt(value)}px` : parseInt(value); +} + +/** + * @description 进行延时,以达到可以简写代码的目的 + * @param {number} value 堵塞时间 单位ms 毫秒 + * @returns {Promise} 返回promise + */ +export function sleep(value = 30) { + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, value); + }); +} +/** + * @description 运行期判断平台 + * @returns {string} 返回所在平台(小写) + * @link 运行期判断平台 https://uniapp.dcloud.io/frame?id=判断平台 + */ +export function os() { + return uni.getSystemInfoSync().platform.toLowerCase(); +} +/** + * @description 获取系统信息同步接口 + * @link 获取系统信息同步接口 https://uniapp.dcloud.io/api/system/info?id=getsysteminfosync + */ +export function sys() { + return uni.getSystemInfoSync(); +} + +/** + * @description 取一个区间数 + * @param {Number} min 最小值 + * @param {Number} max 最大值 + */ +function random(min, max) { + if (min >= 0 && max > 0 && max >= min) { + const gab = max - min + 1; + return Math.floor(Math.random() * gab + min); + } + return 0; +} + +/** + * @param {Number} len uuid的长度 + * @param {Boolean} firstU 将返回的首字母置为"u" + * @param {Nubmer} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制 + */ +export function guid(len = 32, firstU = true, radix = null) { + const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); + const uuid = []; + radix = radix || chars.length; + + if (len) { + // 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位 + for (let i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * radix)]; + } else { + let r; + // rfc4122标准要求返回的uuid中,某些位为固定的字符 + uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; + uuid[14] = '4'; + + for (let i = 0; i < 36; i++) { + if (!uuid[i]) { + r = 0 | (Math.random() * 16); + uuid[i] = chars[i == 19 ? (r & 0x3) | 0x8 : r]; + } + } + } + // 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class + if (firstU) { + uuid.shift(); + return `u${uuid.join('')}`; + } + return uuid.join(''); +} + +/** +* @description 获取父组件的参数,因为支付宝小程序不支持provide/inject的写法 + this.$parent在非H5中,可以准确获取到父组件,但是在H5中,需要多次this.$parent.$parent.xxx + 这里默认值等于undefined有它的含义,因为最顶层元素(组件)的$parent就是undefined,意味着不传name + 值(默认为undefined),就是查找最顶层的$parent +* @param {string|undefined} name 父组件的参数名 +*/ +export function $parent(name = undefined) { + let parent = this.$parent; + // 通过while历遍,这里主要是为了H5需要多层解析的问题 + while (parent) { + // 父组件 + if (parent.$options && parent.$options.name !== name) { + // 如果组件的name不相等,继续上一级寻找 + parent = parent.$parent; + } else { + return parent; + } + } + return false; +} + +/** + * @description 样式转换 + * 对象转字符串,或者字符串转对象 + * @param {object | string} customStyle 需要转换的目标 + * @param {String} target 转换的目的,object-转为对象,string-转为字符串 + * @returns {object|string} + */ +export function addStyle(customStyle, target = 'object') { + // 字符串转字符串,对象转对象情形,直接返回 + if ( + test.empty(customStyle) || + (typeof customStyle === 'object' && target === 'object') || + (target === 'string' && typeof customStyle === 'string') + ) { + return customStyle; + } + // 字符串转对象 + if (target === 'object') { + // 去除字符串样式中的两端空格(中间的空格不能去掉,比如padding: 20px 0如果去掉了就错了),空格是无用的 + customStyle = trim(customStyle); + // 根据";"将字符串转为数组形式 + const styleArray = customStyle.split(';'); + const style = {}; + // 历遍数组,拼接成对象 + for (let i = 0; i < styleArray.length; i++) { + // 'font-size:20px;color:red;',如此最后字符串有";"的话,会导致styleArray最后一个元素为空字符串,这里需要过滤 + if (styleArray[i]) { + const item = styleArray[i].split(':'); + style[trim(item[0])] = trim(item[1]); + } + } + return style; + } + // 这里为对象转字符串形式 + let string = ''; + for (const i in customStyle) { + // 驼峰转为中划线的形式,否则css内联样式,无法识别驼峰样式属性名 + const key = i.replace(/([A-Z])/g, '-$1').toLowerCase(); + string += `${key}:${customStyle[i]};`; + } + // 去除两端空格 + return trim(string); +} + +/** + * @description 添加单位,如果有rpx,upx,%,px等单位结尾或者值为auto,直接返回,否则加上px单位结尾 + * @param {string|number} value 需要添加单位的值 + * @param {string} unit 添加的单位名 比如px + */ +export function addUnit(value = 'auto', unit = 'px') { + value = String(value); + return test.number(value) ? `${value}${unit}` : value; +} + +/** + * @description 深度克隆 + * @param {object} obj 需要深度克隆的对象 + * @returns {*} 克隆后的对象或者原值(不是对象) + */ +function deepClone(obj) { + // 对常见的“非”值,直接返回原来值 + if ([null, undefined, NaN, false].includes(obj)) return obj; + if (typeof obj !== 'object' && typeof obj !== 'function') { + // 原始类型直接返回 + return obj; + } + const o = test.array(obj) ? [] : {}; + for (const i in obj) { + if (obj.hasOwnProperty(i)) { + o[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]; + } + } + return o; +} + +/** + * @description JS对象深度合并 + * @param {object} target 需要拷贝的对象 + * @param {object} source 拷贝的来源对象 + * @returns {object|boolean} 深度合并后的对象或者false(入参有不是对象) + */ +export function deepMerge(target = {}, source = {}) { + target = deepClone(target); + if (typeof target !== 'object' || typeof source !== 'object') return false; + for (const prop in source) { + if (!source.hasOwnProperty(prop)) continue; + if (prop in target) { + if (typeof target[prop] !== 'object') { + target[prop] = source[prop]; + } else if (typeof source[prop] !== 'object') { + target[prop] = source[prop]; + } else if (target[prop].concat && source[prop].concat) { + target[prop] = target[prop].concat(source[prop]); + } else { + target[prop] = deepMerge(target[prop], source[prop]); + } + } else { + target[prop] = source[prop]; + } + } + return target; +} + +/** + * @description error提示 + * @param {*} err 错误内容 + */ +function error(err) { + // 开发环境才提示,生产环境不会提示 + if (process.env.NODE_ENV === 'development') { + console.error(`SheepJS:${err}`); + } +} + +/** + * @description 打乱数组 + * @param {array} array 需要打乱的数组 + * @returns {array} 打乱后的数组 + */ +function randomArray(array = []) { + // 原理是sort排序,Math.random()产生0<= x < 1之间的数,会导致x-0.05大于或者小于0 + return array.sort(() => Math.random() - 0.5); +} + +// padStart 的 polyfill,因为某些机型或情况,还无法支持es7的padStart,比如电脑版的微信小程序 +// 所以这里做一个兼容polyfill的兼容处理 +if (!String.prototype.padStart) { + // 为了方便表示这里 fillString 用了ES6 的默认参数,不影响理解 + String.prototype.padStart = function (maxLength, fillString = ' ') { + if (Object.prototype.toString.call(fillString) !== '[object String]') { + throw new TypeError('fillString must be String'); + } + const str = this; + // 返回 String(str) 这里是为了使返回的值是字符串字面量,在控制台中更符合直觉 + if (str.length >= maxLength) return String(str); + + const fillLength = maxLength - str.length; + let times = Math.ceil(fillLength / fillString.length); + while ((times >>= 1)) { + fillString += fillString; + if (times === 1) { + fillString += fillString; + } + } + return fillString.slice(0, fillLength) + str; + }; +} + +/** + * @description 格式化时间 + * @param {String|Number} dateTime 需要格式化的时间戳 + * @param {String} fmt 格式化规则 yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合 默认yyyy-mm-dd + * @returns {string} 返回格式化后的字符串 + */ +function timeFormat(dateTime = null, formatStr = 'yyyy-mm-dd') { + let date; + // 若传入时间为假值,则取当前时间 + if (!dateTime) { + date = new Date(); + } + // 若为unix秒时间戳,则转为毫秒时间戳(逻辑有点奇怪,但不敢改,以保证历史兼容) + else if (/^\d{10}$/.test(dateTime?.toString().trim())) { + date = new Date(dateTime * 1000); + } + // 若用户传入字符串格式时间戳,new Date无法解析,需做兼容 + else if (typeof dateTime === 'string' && /^\d+$/.test(dateTime.trim())) { + date = new Date(Number(dateTime)); + } + // 其他都认为符合 RFC 2822 规范 + else { + // 处理平台性差异,在Safari/Webkit中,new Date仅支持/作为分割符的字符串时间 + date = new Date(typeof dateTime === 'string' ? dateTime.replace(/-/g, '/') : dateTime); + } + + const timeSource = { + y: date.getFullYear().toString(), // 年 + m: (date.getMonth() + 1).toString().padStart(2, '0'), // 月 + d: date.getDate().toString().padStart(2, '0'), // 日 + h: date.getHours().toString().padStart(2, '0'), // 时 + M: date.getMinutes().toString().padStart(2, '0'), // 分 + s: date.getSeconds().toString().padStart(2, '0'), // 秒 + // 有其他格式化字符需求可以继续添加,必须转化成字符串 + }; + + for (const key in timeSource) { + const [ret] = new RegExp(`${key}+`).exec(formatStr) || []; + if (ret) { + // 年可能只需展示两位 + const beginIndex = key === 'y' && ret.length === 2 ? 2 : 0; + formatStr = formatStr.replace(ret, timeSource[key].slice(beginIndex)); + } + } + + return formatStr; +} + +/** + * @description 时间戳转为多久之前 + * @param {String|Number} timestamp 时间戳 + * @param {String|Boolean} format + * 格式化规则如果为时间格式字符串,超出一定时间范围,返回固定的时间格式; + * 如果为布尔值false,无论什么时间,都返回多久以前的格式 + * @returns {string} 转化后的内容 + */ +function timeFrom(timestamp = null, format = 'yyyy-mm-dd') { + if (timestamp == null) timestamp = Number(new Date()); + timestamp = parseInt(timestamp); + // 判断用户输入的时间戳是秒还是毫秒,一般前端js获取的时间戳是毫秒(13位),后端传过来的为秒(10位) + if (timestamp.toString().length == 10) timestamp *= 1000; + let timer = new Date().getTime() - timestamp; + timer = parseInt(timer / 1000); + // 如果小于5分钟,则返回"刚刚",其他以此类推 + let tips = ''; + switch (true) { + case timer < 300: + tips = '刚刚'; + break; + case timer >= 300 && timer < 3600: + tips = `${parseInt(timer / 60)}分钟前`; + break; + case timer >= 3600 && timer < 86400: + tips = `${parseInt(timer / 3600)}小时前`; + break; + case timer >= 86400 && timer < 2592000: + tips = `${parseInt(timer / 86400)}天前`; + break; + default: + // 如果format为false,则无论什么时间戳,都显示xx之前 + if (format === false) { + if (timer >= 2592000 && timer < 365 * 86400) { + tips = `${parseInt(timer / (86400 * 30))}个月前`; + } else { + tips = `${parseInt(timer / (86400 * 365))}年前`; + } + } else { + tips = timeFormat(timestamp, format); + } + } + return tips; +} + +/** + * @description 去除空格 + * @param String str 需要去除空格的字符串 + * @param String pos both(左右)|left|right|all 默认both + */ +function trim(str, pos = 'both') { + str = String(str); + if (pos == 'both') { + return str.replace(/^\s+|\s+$/g, ''); + } + if (pos == 'left') { + return str.replace(/^\s*/, ''); + } + if (pos == 'right') { + return str.replace(/(\s*$)/g, ''); + } + if (pos == 'all') { + return str.replace(/\s+/g, ''); + } + return str; +} + +/** + * @description 对象转url参数 + * @param {object} data,对象 + * @param {Boolean} isPrefix,是否自动加上"?" + * @param {string} arrayFormat 规则 indices|brackets|repeat|comma + */ +function queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') { + const prefix = isPrefix ? '?' : ''; + const _result = []; + if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) + arrayFormat = 'brackets'; + for (const key in data) { + const value = data[key]; + // 去掉为空的参数 + if (['', undefined, null].indexOf(value) >= 0) { + continue; + } + // 如果值为数组,另行处理 + if (value.constructor === Array) { + // e.g. {ids: [1, 2, 3]} + switch (arrayFormat) { + case 'indices': + // 结果: ids[0]=1&ids[1]=2&ids[2]=3 + for (let i = 0; i < value.length; i++) { + _result.push(`${key}[${i}]=${value[i]}`); + } + break; + case 'brackets': + // 结果: ids[]=1&ids[]=2&ids[]=3 + value.forEach((_value) => { + _result.push(`${key}[]=${_value}`); + }); + break; + case 'repeat': + // 结果: ids=1&ids=2&ids=3 + value.forEach((_value) => { + _result.push(`${key}=${_value}`); + }); + break; + case 'comma': + // 结果: ids=1,2,3 + let commaStr = ''; + value.forEach((_value) => { + commaStr += (commaStr ? ',' : '') + _value; + }); + _result.push(`${key}=${commaStr}`); + break; + default: + value.forEach((_value) => { + _result.push(`${key}[]=${_value}`); + }); + } + } else { + _result.push(`${key}=${value}`); + } + } + return _result.length ? prefix + _result.join('&') : ''; +} + +/** + * 显示消息提示框 + * @param {String} title 提示的内容,长度与 icon 取值有关。 + * @param {Number} duration 提示的延迟时间,单位毫秒,默认:2000 + */ +function toast(title, duration = 2000) { + uni.showToast({ + title: String(title), + icon: 'none', + duration, + }); +} + +/** + * @description 根据主题type值,获取对应的图标 + * @param {String} type 主题名称,primary|info|error|warning|success + * @param {boolean} fill 是否使用fill填充实体的图标 + */ +function type2icon(type = 'success', fill = false) { + // 如果非预置值,默认为success + if (['primary', 'info', 'error', 'warning', 'success'].indexOf(type) == -1) type = 'success'; + let iconName = ''; + // 目前(2019-12-12),info和primary使用同一个图标 + switch (type) { + case 'primary': + iconName = 'info-circle'; + break; + case 'info': + iconName = 'info-circle'; + break; + case 'error': + iconName = 'close-circle'; + break; + case 'warning': + iconName = 'error-circle'; + break; + case 'success': + iconName = 'checkmark-circle'; + break; + default: + iconName = 'checkmark-circle'; + } + // 是否是实体类型,加上-fill,在icon组件库中,实体的类名是后面加-fill的 + if (fill) iconName += '-fill'; + return iconName; +} + +/** + * @description 数字格式化 + * @param {number|string} number 要格式化的数字 + * @param {number} decimals 保留几位小数 + * @param {string} decimalPoint 小数点符号 + * @param {string} thousandsSeparator 千分位符号 + * @returns {string} 格式化后的数字 + */ +function priceFormat(number, decimals = 0, decimalPoint = '.', thousandsSeparator = ',') { + number = `${number}`.replace(/[^0-9+-Ee.]/g, ''); + const n = !isFinite(+number) ? 0 : +number; + const prec = !isFinite(+decimals) ? 0 : Math.abs(decimals); + const sep = typeof thousandsSeparator === 'undefined' ? ',' : thousandsSeparator; + const dec = typeof decimalPoint === 'undefined' ? '.' : decimalPoint; + let s = ''; + + s = (prec ? round(n, prec) + '' : `${Math.round(n)}`).split('.'); + const re = /(-?\d+)(\d{3})/; + while (re.test(s[0])) { + s[0] = s[0].replace(re, `$1${sep}$2`); + } + + if ((s[1] || '').length < prec) { + s[1] = s[1] || ''; + s[1] += new Array(prec - s[1].length + 1).join('0'); + } + return s.join(dec); +} + +/** + * @description 获取duration值 + * 如果带有ms或者s直接返回,如果大于一定值,认为是ms单位,小于一定值,认为是s单位 + * 比如以30位阈值,那么300大于30,可以理解为用户想要的是300ms,而不是想花300s去执行一个动画 + * @param {String|number} value 比如: "1s"|"100ms"|1|100 + * @param {boolean} unit 提示: 如果是false 默认返回number + * @return {string|number} + */ +function getDuration(value, unit = true) { + const valueNum = parseInt(value); + if (unit) { + if (/s$/.test(value)) return value; + return value > 30 ? `${value}ms` : `${value}s`; + } + if (/ms$/.test(value)) return valueNum; + if (/s$/.test(value)) return valueNum > 30 ? valueNum : valueNum * 1000; + return valueNum; +} + +/** + * @description 日期的月或日补零操作 + * @param {String} value 需要补零的值 + */ +function padZero(value) { + return `00${value}`.slice(-2); +} + +/** + * @description 获取某个对象下的属性,用于通过类似'a.b.c'的形式去获取一个对象的的属性的形式 + * @param {object} obj 对象 + * @param {string} key 需要获取的属性字段 + * @returns {*} + */ +function getProperty(obj, key) { + if (!obj) { + return; + } + if (typeof key !== 'string' || key === '') { + return ''; + } + if (key.indexOf('.') !== -1) { + const keys = key.split('.'); + let firstObj = obj[keys[0]] || {}; + + for (let i = 1; i < keys.length; i++) { + if (firstObj) { + firstObj = firstObj[keys[i]]; + } + } + return firstObj; + } + return obj[key]; +} + +/** + * @description 设置对象的属性值,如果'a.b.c'的形式进行设置 + * @param {object} obj 对象 + * @param {string} key 需要设置的属性 + * @param {string} value 设置的值 + */ +function setProperty(obj, key, value) { + if (!obj) { + return; + } + // 递归赋值 + const inFn = function (_obj, keys, v) { + // 最后一个属性key + if (keys.length === 1) { + _obj[keys[0]] = v; + return; + } + // 0~length-1个key + while (keys.length > 1) { + const k = keys[0]; + if (!_obj[k] || typeof _obj[k] !== 'object') { + _obj[k] = {}; + } + const key = keys.shift(); + // 自调用判断是否存在属性,不存在则自动创建对象 + inFn(_obj[k], keys, v); + } + }; + + if (typeof key !== 'string' || key === '') { + } else if (key.indexOf('.') !== -1) { + // 支持多层级赋值操作 + const keys = key.split('.'); + inFn(obj, keys, value); + } else { + obj[key] = value; + } +} + +/** + * @description 获取当前页面路径 + */ +function page() { + const pages = getCurrentPages(); + // 某些特殊情况下(比如页面进行redirectTo时的一些时机),pages可能为空数组 + return `/${pages[pages.length - 1]?.route ?? ''}`; +} + +/** + * @description 获取当前路由栈实例数组 + */ +function pages() { + const pages = getCurrentPages(); + return pages; +} + +/** + * 获取H5-真实根地址 兼容hash+history模式 + */ +export function getRootUrl() { + let url = ''; + // #ifdef H5 + url = location.origin + '/'; + + if (location.hash !== '') { + url += '#/'; + } + // #endif + return url; +} + +/** + * copyText 多端复制文本 + */ +export function copyText(text) { + // #ifndef H5 + uni.setClipboardData({ + data: text, + success: function () { + toast('复制成功!'); + }, + fail: function () { + toast('复制失败!'); + }, + }); + // #endif + // #ifdef H5 + var createInput = document.createElement('textarea'); + createInput.value = text; + document.body.appendChild(createInput); + createInput.select(); + document.execCommand('Copy'); + createInput.className = 'createInput'; + createInput.style.display = 'none'; + toast('复制成功'); + // #endif +} + +export default { + range, + getPx, + sleep, + os, + sys, + random, + guid, + $parent, + addStyle, + addUnit, + deepClone, + deepMerge, + error, + randomArray, + timeFormat, + timeFrom, + trim, + queryParams, + toast, + type2icon, + priceFormat, + getDuration, + padZero, + getProperty, + setProperty, + page, + pages, + test, + getRootUrl, + copyText, +}; diff --git a/sheep/helper/test.js b/sheep/helper/test.js new file mode 100644 index 0000000..ca550a1 --- /dev/null +++ b/sheep/helper/test.js @@ -0,0 +1,285 @@ +/** + * 验证电子邮箱格式 + */ +function email(value) { + return /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/.test(value); +} + +/** + * 验证手机格式 + */ +function mobile(value) { + return /^1[23456789]\d{9}$/.test(value); +} + +/** + * 验证URL格式 + */ +function url(value) { + return /^((https|http|ftp|rtsp|mms):\/\/)(([0-9a-zA-Z_!~*'().&=+$%-]+: )?[0-9a-zA-Z_!~*'().&=+$%-]+@)?(([0-9]{1,3}.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z].[a-zA-Z]{2,6})(:[0-9]{1,4})?((\/?)|(\/[0-9a-zA-Z_!~*'().;?:@&=+$,%#-]+)+\/?)$/.test( + value, + ); +} + +/** + * 验证日期格式 + */ +function date(value) { + if (!value) return false; + // 判断是否数值或者字符串数值(意味着为时间戳),转为数值,否则new Date无法识别字符串时间戳 + if (number(value)) value = +value; + return !/Invalid|NaN/.test(new Date(value).toString()); +} + +/** + * 验证ISO类型的日期格式 + */ +function dateISO(value) { + return /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value); +} + +/** + * 验证十进制数字 + */ +function number(value) { + return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value); +} + +/** + * 验证字符串 + */ +function string(value) { + return typeof value === 'string'; +} + +/** + * 验证整数 + */ +function digits(value) { + return /^\d+$/.test(value); +} + +/** + * 验证身份证号码 + */ +function idCard(value) { + return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(value); +} + +/** + * 是否车牌号 + */ +function carNo(value) { + // 新能源车牌 + const xreg = + /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/; + // 旧车牌 + const creg = + /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/; + if (value.length === 7) { + return creg.test(value); + } + if (value.length === 8) { + return xreg.test(value); + } + return false; +} + +/** + * 金额,只允许2位小数 + */ +function amount(value) { + // 金额,只允许保留两位小数 + return /^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0\.\d{1,2}$/.test(value); +} + +/** + * 中文 + */ +function chinese(value) { + const reg = /^[\u4e00-\u9fa5]+$/gi; + return reg.test(value); +} + +/** + * 只能输入字母 + */ +function letter(value) { + return /^[a-zA-Z]*$/.test(value); +} + +/** + * 只能是字母或者数字 + */ +function enOrNum(value) { + // 英文或者数字 + const reg = /^[0-9a-zA-Z]*$/g; + return reg.test(value); +} + +/** + * 验证是否包含某个值 + */ +function contains(value, param) { + return value.indexOf(param) >= 0; +} + +/** + * 验证一个值范围[min, max] + */ +function range(value, param) { + return value >= param[0] && value <= param[1]; +} + +/** + * 验证一个长度范围[min, max] + */ +function rangeLength(value, param) { + return value.length >= param[0] && value.length <= param[1]; +} + +/** + * 是否固定电话 + */ +function landline(value) { + const reg = /^\d{3,4}-\d{7,8}(-\d{3,4})?$/; + return reg.test(value); +} + +/** + * 判断是否为空 + */ +function empty(value) { + switch (typeof value) { + case 'undefined': + return true; + case 'string': + if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0) return true; + break; + case 'boolean': + if (!value) return true; + break; + case 'number': + if (value === 0 || isNaN(value)) return true; + break; + case 'object': + if (value === null || value.length === 0) return true; + for (const i in value) { + return false; + } + return true; + } + return false; +} + +/** + * 是否json字符串 + */ +function jsonString(value) { + if (typeof value === 'string') { + try { + const obj = JSON.parse(value); + if (typeof obj === 'object' && obj) { + return true; + } + return false; + } catch (e) { + return false; + } + } + return false; +} + +/** + * 是否数组 + */ +function array(value) { + if (typeof Array.isArray === 'function') { + return Array.isArray(value); + } + return Object.prototype.toString.call(value) === '[object Array]'; +} + +/** + * 是否对象 + */ +function object(value) { + return Object.prototype.toString.call(value) === '[object Object]'; +} + +/** + * 是否短信验证码 + */ +function code(value, len = 6) { + return new RegExp(`^\\d{${len}}$`).test(value); +} + +/** + * 是否函数方法 + * @param {Object} value + */ +function func(value) { + return typeof value === 'function'; +} + +/** + * 是否promise对象 + * @param {Object} value + */ +function promise(value) { + return object(value) && func(value.then) && func(value.catch); +} + +/** 是否图片格式 + * @param {Object} value + */ +function image(value) { + const newValue = value.split('?')[0]; + const IMAGE_REGEXP = /\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i; + return IMAGE_REGEXP.test(newValue); +} + +/** + * 是否视频格式 + * @param {Object} value + */ +function video(value) { + const VIDEO_REGEXP = /\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|m3u8)/i; + return VIDEO_REGEXP.test(value); +} + +/** + * 是否为正则对象 + * @param {Object} + * @return {Boolean} + */ +function regExp(o) { + return o && Object.prototype.toString.call(o) === '[object RegExp]'; +} + +export default { + email, + mobile, + url, + date, + dateISO, + number, + digits, + idCard, + carNo, + amount, + chinese, + letter, + enOrNum, + contains, + range, + rangeLength, + empty, + isEmpty: empty, + isNumber: number, + jsonString, + landline, + object, + array, + code, +}; diff --git a/sheep/helper/throttle.js b/sheep/helper/throttle.js new file mode 100644 index 0000000..c318127 --- /dev/null +++ b/sheep/helper/throttle.js @@ -0,0 +1,31 @@ +let timer; +let flag; +/** + * 节流原理:在一定时间内,只能触发一次 + * + * @param {Function} func 要执行的回调函数 + * @param {Number} wait 延时的时间 + * @param {Boolean} immediate 是否立即执行 + * @return null + */ +function throttle(func, wait = 500, immediate = true) { + if (immediate) { + if (!flag) { + flag = true; + // 如果是立即执行,则在wait毫秒内开始时执行 + typeof func === 'function' && func(); + timer = setTimeout(() => { + flag = false; + }, wait); + } else { + } + } else if (!flag) { + flag = true; + // 如果是非立即执行,则在wait毫秒内的结束处执行 + timer = setTimeout(() => { + flag = false; + typeof func === 'function' && func(); + }, wait); + } +} +export default throttle; diff --git a/sheep/helper/tools.js b/sheep/helper/tools.js new file mode 100644 index 0000000..49efcec --- /dev/null +++ b/sheep/helper/tools.js @@ -0,0 +1,67 @@ +import router from '@/sheep/router'; +export default { + /** + * 打电话 + * @param {String} phoneNumber - 数字字符串 + */ + callPhone(phoneNumber = '') { + let num = phoneNumber.toString(); + uni.makePhoneCall({ + phoneNumber: num, + fail(err) { + console.log('makePhoneCall出错', err); + }, + }); + }, + + /** + * 微信头像 + * @param {String} url -图片地址 + */ + checkMPUrl(url) { + // #ifdef MP + if ( + url.substring(0, 4) === 'http' && + url.substring(0, 5) !== 'https' && + url.substring(0, 12) !== 'http://store' && + url.substring(0, 10) !== 'http://tmp' && + url.substring(0, 10) !== 'http://usr' + ) { + url = 'https' + url.substring(4, url.length); + } + // #endif + return url; + }, + + /** + * getUuid 生成唯一id + */ + getUuid(len = 32, firstU = true, radix = null) { + const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); + const uuid = []; + radix = radix || chars.length; + + if (len) { + // 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位 + for (let i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * radix)]; + } else { + let r; + // rfc4122标准要求返回的uuid中,某些位为固定的字符 + uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; + uuid[14] = '4'; + + for (let i = 0; i < 36; i++) { + if (!uuid[i]) { + r = 0 | (Math.random() * 16); + uuid[i] = chars[i == 19 ? (r & 0x3) | 0x8 : r]; + } + } + } + // 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class + if (firstU) { + uuid.shift(); + return `u${uuid.join('')}`; + } + return uuid.join(''); + }, +}; diff --git a/sheep/helper/utils.js b/sheep/helper/utils.js new file mode 100644 index 0000000..bcb5d25 --- /dev/null +++ b/sheep/helper/utils.js @@ -0,0 +1,168 @@ +export function isArray(value) { + if (typeof Array.isArray === 'function') { + return Array.isArray(value); + } else { + return Object.prototype.toString.call(value) === '[object Array]'; + } +} + +export function isObject(value) { + return Object.prototype.toString.call(value) === '[object Object]'; +} + +export function isNumber(value) { + return !isNaN(Number(value)); +} + +export function isFunction(value) { + return typeof value == 'function'; +} + +export function isString(value) { + return typeof value == 'string'; +} + +export function isEmpty(value) { + if (isArray(value)) { + return value.length === 0; + } + + if (isObject(value)) { + return Object.keys(value).length === 0; + } + + return value === '' || value === undefined || value === null; +} + +export function isBoolean(value) { + return typeof value === 'boolean'; +} + +export function last(data) { + if (isArray(data) || isString(data)) { + return data[data.length - 1]; + } +} + +export function cloneDeep(obj) { + const d = isArray(obj) ? obj : {}; + + if (isObject(obj)) { + for (const key in obj) { + if (obj[key]) { + if (obj[key] && typeof obj[key] === 'object') { + d[key] = cloneDeep(obj[key]); + } else { + d[key] = obj[key]; + } + } + } + } + + return d; +} + +export function clone(obj) { + return Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); +} + +export function deepMerge(a, b) { + let k; + for (k in b) { + a[k] = a[k] && a[k].toString() === '[object Object]' ? deepMerge(a[k], b[k]) : (a[k] = b[k]); + } + return a; +} + +export function contains(parent, node) { + while (node && (node = node.parentNode)) if (node === parent) return true; + return false; +} + +export function orderBy(list, key) { + return list.sort((a, b) => a[key] - b[key]); +} + +export function deepTree(list) { + const newList = []; + const map = {}; + + list.forEach((e) => (map[e.id] = e)); + + list.forEach((e) => { + const parent = map[e.parentId]; + + if (parent) { + (parent.children || (parent.children = [])).push(e); + } else { + newList.push(e); + } + }); + + const fn = (list) => { + list.map((e) => { + if (e.children instanceof Array) { + e.children = orderBy(e.children, 'orderNum'); + + fn(e.children); + } + }); + }; + + fn(newList); + + return orderBy(newList, 'orderNum'); +} + +export function revDeepTree(list = []) { + const d = []; + let id = 0; + + const deep = (list, parentId) => { + list.forEach((e) => { + if (!e.id) { + e.id = id++; + } + + e.parentId = parentId; + + d.push(e); + + if (e.children && isArray(e.children)) { + deep(e.children, e.id); + } + }); + }; + + deep(list || [], null); + + return d; +} + +export function basename(path) { + let index = path.lastIndexOf('/'); + index = index > -1 ? index : path.lastIndexOf('\\'); + if (index < 0) { + return path; + } + return path.substring(index + 1); +} + +export function isWxBrowser() { + const ua = navigator.userAgent.toLowerCase(); + if (ua.match(/MicroMessenger/i) == 'micromessenger') { + return true; + } else { + return false; + } +} + +/** + * @description 如果value小于min,取min;如果value大于max,取max + * @param {number} min + * @param {number} max + * @param {number} value + */ +export function range(min = 0, max = 0, value = 0) { + return Math.max(min, Math.min(max, Number(value))); +} diff --git a/sheep/hooks/useApp.js b/sheep/hooks/useApp.js new file mode 100644 index 0000000..e69de29 diff --git a/sheep/hooks/useGoods.js b/sheep/hooks/useGoods.js new file mode 100644 index 0000000..5627447 --- /dev/null +++ b/sheep/hooks/useGoods.js @@ -0,0 +1,389 @@ +import { ref } from 'vue'; +import dayjs from 'dayjs'; +import $url from '@/sheep/url'; +import { formatDate } from '@/sheep/util'; + +/** + * 格式化销量 + * @param {'exact' | string} type 格式类型:exact=精确值,其它=大致数量 + * @param {number} num 销量 + * @return {string} 格式化后的销量字符串 + */ +export function formatSales(type, num) { + let prefix = type !== 'exact' && num < 10 ? '销量' : '已售'; + return formatNum(prefix, type, num) +} + +/** + * 格式化兑换量 + * @param {'exact' | string} type 格式类型:exact=精确值,其它=大致数量 + * @param {number} num 销量 + * @return {string} 格式化后的销量字符串 + */ +export function formatExchange(type, num) { + return formatNum('已兑换', type, num) +} + + +/** + * 格式化库存 + * @param {'exact' | any} type 格式类型:exact=精确值,其它=大致数量 + * @param {number} num 销量 + * @return {string} 格式化后的销量字符串 + */ +export function formatStock(type, num) { + return formatNum('库存', type, num) +} + +/** + * 格式化数字 + * @param {string} prefix 前缀 + * @param {'exact' | string} type 格式类型:exact=精确值,其它=大致数量 + * @param {number} num 销量 + * @return {string} 格式化后的销量字符串 + */ +export function formatNum(prefix, type, num) { + num = (num || 0); + // 情况一:精确数值 + if (type === 'exact') { + return prefix + num; + } + // 情况二:小于等于 10 + if (num < 10) { + return `${prefix}≤10`; + } + // 情况三:大于 10,除第一位外,其它位都显示为0 + // 例如:100 - 199 显示为 100+ + // 9000 - 9999 显示为 9000+ + const numStr = num.toString(); + const first = numStr[0]; + const other = '0'.repeat(numStr.length - 1); + return `${prefix}${first}${other}+`; +} + +// 格式化价格 +export function formatPrice(e) { + return e.length === 1 ? e[0] : e.join('~'); +} + +// 视频格式后缀列表 +const VIDEO_SUFFIX_LIST = ['.avi', '.mp4'] + +/** + * 转换商品轮播的链接列表:根据链接的后缀,判断是视频链接还是图片链接 + * + * @param {string[]} urlList 链接列表 + * @return {{src: string, type: 'video' | 'image' }[]} 转换后的链接列表 + */ +export function formatGoodsSwiper(urlList) { + return urlList?.filter(url => url).map((url, key) => { + const isVideo = VIDEO_SUFFIX_LIST.some(suffix => url.includes(suffix)); + const type = isVideo ? 'video' : 'image' + const src = $url.cdn(url); + return { type, src } + }) || []; +} + +/** + * 格式化订单状态的颜色 + * + * @param order 订单 + * @return {string} 颜色的 class 名称 + */ +export function formatOrderColor(order) { + if (order.status === 0) { + return 'info-color'; + } + if (order.status === 10 + || order.status === 20 + || (order.status === 30 && !order.commentStatus)) { + return 'warning-color'; + } + if (order.status === 30 && order.commentStatus) { + return 'success-color'; + } + return 'danger-color'; +} + +/** + * 格式化订单状态 + * + * @param order 订单 + */ +export function formatOrderStatus(order) { + if (order.status === 0) { + return '待付款'; + } + if (order.status === 10 && order.deliveryType === 1) { + return '待发货'; + } + if (order.status === 10 && order.deliveryType === 2) { + return '待核销'; + } + if (order.status === 20) { + return '待收货'; + } + if (order.status === 30 && !order.commentStatus) { + return '待评价'; + } + if (order.status === 30 && order.commentStatus) { + return '已完成'; + } + return '已关闭'; +} + +/** + * 格式化订单状态的描述 + * + * @param order 订单 + */ +export function formatOrderStatusDescription(order) { + if (order.status === 0) { + return `请在 ${ formatDate(order.payExpireTime) } 前完成支付`; + } + if (order.status === 10) { + return '商家未发货,请耐心等待'; + } + if (order.status === 20) { + return '商家已发货,请耐心等待'; + } + if (order.status === 30 && !order.commentStatus) { + return '已收货,快去评价一下吧'; + } + if (order.status === 30 && order.commentStatus) { + return '交易完成,感谢您的支持'; + } + return '交易关闭'; +} + +/** + * 处理订单的 button 操作按钮数组 + * + * @param order 订单 + */ +export function handleOrderButtons(order) { + order.buttons = [] + if (order.type === 3) { // 查看拼团 + order.buttons.push('combination'); + } + if (order.status === 20) { // 确认收货 + order.buttons.push('confirm'); + } + if (order.logisticsId > 0) { // 查看物流 + order.buttons.push('express'); + } + if (order.status === 0) { // 取消订单 / 发起支付 + order.buttons.push('cancel'); + order.buttons.push('pay'); + } + if (order.status === 30 && !order.commentStatus) { // 发起评价 + order.buttons.push('comment'); + } + if (order.status === 40) { // 删除订单 + order.buttons.push('delete'); + } +} + +/** + * 格式化售后状态 + * + * @param afterSale 售后 + */ +export function formatAfterSaleStatus(afterSale) { + if (afterSale.status === 10) { + return '申请售后'; + } + if (afterSale.status === 20) { + return '商品待退货'; + } + if (afterSale.status === 30) { + return '商家待收货'; + } + if (afterSale.status === 40) { + return '等待退款'; + } + if (afterSale.status === 50) { + return '退款成功'; + } + if (afterSale.status === 61) { + return '买家取消'; + } + if (afterSale.status === 62) { + return '商家拒绝'; + } + if (afterSale.status === 63) { + return '商家拒收货'; + } + return '未知状态'; +} + +/** + * 格式化售后状态的描述 + * + * @param afterSale 售后 + */ +export function formatAfterSaleStatusDescription(afterSale) { + if (afterSale.status === 10) { + return '退款申请待商家处理'; + } + if (afterSale.status === 20) { + return '请退货并填写物流信息'; + } + if (afterSale.status === 30) { + return '退货退款申请待商家处理'; + } + if (afterSale.status === 40) { + return '等待退款'; + } + if (afterSale.status === 50) { + return '退款成功'; + } + if (afterSale.status === 61) { + return '退款关闭'; + } + if (afterSale.status === 62) { + return `商家不同意退款申请,拒绝原因:${afterSale.auditReason}`; + } + if (afterSale.status === 63) { + return `商家拒绝收货,不同意退款,拒绝原因:${afterSale.auditReason}`; + } + return '未知状态'; +} + +/** + * 处理售后的 button 操作按钮数组 + * + * @param afterSale 售后 + */ +export function handleAfterSaleButtons(afterSale) { + afterSale.buttons = []; + if ([10, 20, 30].includes(afterSale.status)) { // 取消订单 + afterSale.buttons.push('cancel'); + } + if (afterSale.status === 20) { // 退货信息 + afterSale.buttons.push('delivery'); + } +} + +/** + * 倒计时 + * @param toTime 截止时间 + * @param fromTime 起始时间,默认当前时间 + * @return {{s: string, ms: number, h: string, m: string}} 持续时间 + */ +export function useDurationTime(toTime, fromTime = '') { + toTime = getDayjsTime(toTime); + if (fromTime === '') { + fromTime = dayjs(); + } + let duration = ref(toTime - fromTime); + if (duration.value > 0) { + setTimeout(() => { + if (duration.value > 0) { + duration.value -= 1000; + } + }, 1000); + } + + let durationTime = dayjs.duration(duration.value); + return { + h: (durationTime.months() * 30 * 24 + durationTime.days() * 24 + durationTime.hours()) + .toString() + .padStart(2, '0'), + m: durationTime.minutes().toString().padStart(2, '0'), + s: durationTime.seconds().toString().padStart(2, '0'), + ms: durationTime.$ms, + }; +} + +/** + * 转换为 Dayjs + * @param {any} time 时间 + * @return {dayjs.Dayjs} + */ +function getDayjsTime(time) { + time = time.toString(); + if (time.indexOf('-') > 0) { + // 'date' + return dayjs(time); + } + if (time.length > 10) { + // 'timestamp' + return dayjs(parseInt(time)); + } + if (time.length === 10) { + // 'unixTime' + return dayjs.unix(parseInt(time)); + } +} + +/** + * 将分转成元 + * + * @param price 分,例如说 100 分 + * @returns {string} 元,例如说 1.00 元 + */ +export function fen2yuan(price) { + return (price / 100.0).toFixed(2) +} + +/** + * 从商品 SKU 数组中,转换出商品属性的数组 + * + * 类似结构:[{ + * id: // 属性的编号 + * name: // 属性的名字 + * values: [{ + * id: // 属性值的编号 + * name: // 属性值的名字 + * }] + * }] + * + * @param skus 商品 SKU 数组 + */ +export function convertProductPropertyList(skus) { + let result = []; + for (const sku of skus) { + if (!sku.properties) { + continue + } + for (const property of sku.properties) { + // ① 先处理属性 + let resultProperty = result.find(item => item.id === property.propertyId) + if (!resultProperty) { + resultProperty = { + id: property.propertyId, + name: property.propertyName, + values: [] + } + result.push(resultProperty) + } + // ② 再处理属性值 + let resultValue = resultProperty.values.find(item => item.id === property.valueId) + if (!resultValue) { + resultProperty.values.push({ + id: property.valueId, + name: property.valueName + }) + } + } + } + return result; +} + +/** + * 格式化满减送活动的规则 + * + * @param activity 活动信息 + * @param rule 优惠规格 + * @returns {string} 规格字符串 + */ +export function formatRewardActivityRule(activity, rule) { + if (activity.conditionType === 10) { + return `满 ${fen2yuan(rule.limit)} 元减 ${fen2yuan(rule.discountPrice)} 元`; + } + if (activity.conditionType === 20) { + return `满 ${rule.limit} 件减 ${fen2yuan(rule.discountPrice)} 元`; + } + return ''; +} diff --git a/sheep/hooks/useModal.js b/sheep/hooks/useModal.js new file mode 100644 index 0000000..2c22b73 --- /dev/null +++ b/sheep/hooks/useModal.js @@ -0,0 +1,140 @@ +import $store from '@/sheep/store'; +import $helper from '@/sheep/helper'; +import dayjs from 'dayjs'; +import { ref } from 'vue'; +import test from '@/sheep/helper/test.js'; +import AuthUtil from '@/api/member/auth'; + +// 打开授权弹框 +export function showAuthModal(type = 'smsLogin') { + const modal = $store('modal'); + if (modal.auth !== '') { + closeAuthModal(); + setTimeout(() => { + modal.$patch((state) => { + state.auth = type; + }); + }, 100); + } else { + modal.$patch((state) => { + state.auth = type; + }); + } +} + +// 关闭授权弹框 +export function closeAuthModal() { + $store('modal').$patch((state) => { + state.auth = ''; + }); +} + +// 打开分享弹框 +export function showShareModal() { + $store('modal').$patch((state) => { + state.share = true; + }); +} + +// 关闭分享弹框 +export function closeShareModal() { + $store('modal').$patch((state) => { + state.share = false; + }); +} + +// 打开快捷菜单 +export function showMenuTools() { + $store('modal').$patch((state) => { + state.menu = true; + }); +} + +// 关闭快捷菜单 +export function closeMenuTools() { + $store('modal').$patch((state) => { + state.menu = false; + }); +} + +// 发送短信验证码 60秒 +export function getSmsCode(event, mobile) { + const modalStore = $store('modal'); + const lastSendTimer = modalStore.lastTimer[event]; + if (typeof lastSendTimer === 'undefined') { + $helper.toast('短信发送事件错误'); + return; + } + + const duration = dayjs().unix() - lastSendTimer; + const canSend = duration >= 60; + if (!canSend) { + $helper.toast('请稍后再试'); + return; + } + // 只有 mobile 非空时才校验。因为部分场景(修改密码),不需要输入手机 + if (mobile && !test.mobile(mobile)) { + $helper.toast('手机号码格式不正确'); + return; + } + + // 发送验证码 + 更新上次发送验证码时间 + let scene = -1; + switch (event) { + case 'resetPassword': + scene = 4; + break; + case 'changePassword': + scene = 3; + break; + case 'changeMobile': + scene = 2; + break; + case 'smsLogin': + scene = 1; + break; + } + AuthUtil.sendSmsCode(mobile, scene).then((res) => { + if (res.code === 0) { + modalStore.$patch((state) => { + state.lastTimer[event] = dayjs().unix(); + }); + } + }); +} + +// 获取短信验证码倒计时 -- 60秒 +export function getSmsTimer(event, mobile = '') { + const modalStore = $store('modal'); + const lastSendTimer = modalStore.lastTimer[event]; + + if (typeof lastSendTimer === 'undefined') { + $helper.toast('短信发送事件错误'); + return; + } + + const duration = ref(dayjs().unix() - lastSendTimer - 60); + const canSend = duration.value >= 0; + + if (canSend) { + return '获取验证码'; + } + + if (!canSend) { + setTimeout(() => { + duration.value++; + }, 1000); + return -duration.value.toString() + ' 秒'; + } +} + +// 记录广告弹框历史 +export function saveAdvHistory(adv) { + const modal = $store('modal'); + + modal.$patch((state) => { + if (!state.advHistory.includes(adv.imgUrl)) { + state.advHistory.push(adv.imgUrl); + } + }); +} diff --git a/sheep/index.js b/sheep/index.js new file mode 100644 index 0000000..9af8f08 --- /dev/null +++ b/sheep/index.js @@ -0,0 +1,52 @@ +import $url from '@/sheep/url'; +import $router from '@/sheep/router'; +import $platform from '@/sheep/platform'; +import $helper from '@/sheep/helper'; +import zIndex from '@/sheep/config/zIndex.js'; +import $store from '@/sheep/store'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; +import duration from 'dayjs/plugin/duration'; +import 'dayjs/locale/zh-cn'; + +dayjs.locale('zh-cn'); +dayjs.extend(relativeTime); +dayjs.extend(duration); + +const sheep = { + $store, + $url, + $router, + $platform, + $helper, + $zIndex: zIndex, +}; + +// 加载Shopro底层依赖 +export async function ShoproInit() { + // 应用初始化 + await $store('app').init(); + + // 平台初始化加载(各平台provider提供不同的加载流程) + $platform.load(); + + if (process.env.NODE_ENV === 'development') { + ShoproDebug(); + } +} + +// 开发模式 +function ShoproDebug() { + // 开发环境引入vconsole调试 + // #ifdef H5 + // import("vconsole").then(vconsole => { + // new vconsole.default(); + // }); + // #endif + + // TODO 芋艿:可以打印路由 + // 同步前端页面到后端 + // console.log(ROUTES) +} + +export default sheep; diff --git a/sheep/libs/mplive-manifest-plugin.js b/sheep/libs/mplive-manifest-plugin.js new file mode 100644 index 0000000..d1df9bf --- /dev/null +++ b/sheep/libs/mplive-manifest-plugin.js @@ -0,0 +1,32 @@ +const fs = require('fs'); + +const manifestPath = process.env.UNI_INPUT_DIR + '/manifest.json'; + +let Manifest = fs.readFileSync(manifestPath, { + encoding: 'utf-8' +}); + +function mpliveMainfestPlugin(isOpen) { + if (process.env.UNI_PLATFORM !== 'mp-weixin') return; + + const manifestData = JSON.parse(Manifest) + + if (isOpen === '0') { + delete manifestData['mp-weixin'].plugins['live-player-plugin']; + } + + if (isOpen === '1') { + manifestData['mp-weixin'].plugins['live-player-plugin'] = { + "version": "1.3.5", + "provider": "wx2b03c6e691cd7370" + } + } + + Manifest = JSON.stringify(manifestData, null, 2) + + fs.writeFileSync(manifestPath, Manifest, { + "flag": "w" + }) +} + +export default mpliveMainfestPlugin diff --git a/sheep/libs/permission.js b/sheep/libs/permission.js new file mode 100644 index 0000000..59f9413 --- /dev/null +++ b/sheep/libs/permission.js @@ -0,0 +1,246 @@ +/// null = 未请求,1 = 已允许,0 = 拒绝|受限, 2 = 系统未开启 + +var isIOS; + +function album() { + var result = 0; + var PHPhotoLibrary = plus.ios.import('PHPhotoLibrary'); + var authStatus = PHPhotoLibrary.authorizationStatus(); + if (authStatus === 0) { + result = null; + } else if (authStatus == 3) { + result = 1; + } else { + result = 0; + } + plus.ios.deleteObject(PHPhotoLibrary); + return result; +} + +function camera() { + var result = 0; + var AVCaptureDevice = plus.ios.import('AVCaptureDevice'); + var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide'); + if (authStatus === 0) { + result = null; + } else if (authStatus == 3) { + result = 1; + } else { + result = 0; + } + plus.ios.deleteObject(AVCaptureDevice); + return result; +} + +function location() { + var result = 0; + var cllocationManger = plus.ios.import('CLLocationManager'); + var enable = cllocationManger.locationServicesEnabled(); + var status = cllocationManger.authorizationStatus(); + if (!enable) { + result = 2; + } else if (status === 0) { + result = null; + } else if (status === 3 || status === 4) { + result = 1; + } else { + result = 0; + } + plus.ios.deleteObject(cllocationManger); + return result; +} + +function push() { + var result = 0; + var UIApplication = plus.ios.import('UIApplication'); + var app = UIApplication.sharedApplication(); + var enabledTypes = 0; + if (app.currentUserNotificationSettings) { + var settings = app.currentUserNotificationSettings(); + enabledTypes = settings.plusGetAttribute('types'); + if (enabledTypes == 0) { + result = 0; + console.log('推送权限没有开启'); + } else { + result = 1; + console.log('已经开启推送功能!'); + } + plus.ios.deleteObject(settings); + } else { + enabledTypes = app.enabledRemoteNotificationTypes(); + if (enabledTypes == 0) { + result = 3; + console.log('推送权限没有开启!'); + } else { + result = 4; + console.log('已经开启推送功能!'); + } + } + plus.ios.deleteObject(app); + plus.ios.deleteObject(UIApplication); + return result; +} + +function contact() { + var result = 0; + var CNContactStore = plus.ios.import('CNContactStore'); + var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0); + if (cnAuthStatus === 0) { + result = null; + } else if (cnAuthStatus == 3) { + result = 1; + } else { + result = 0; + } + plus.ios.deleteObject(CNContactStore); + return result; +} + +function record() { + var result = null; + var avaudiosession = plus.ios.import('AVAudioSession'); + var avaudio = avaudiosession.sharedInstance(); + var status = avaudio.recordPermission(); + console.log('permissionStatus:' + status); + if (status === 1970168948) { + result = null; + } else if (status === 1735552628) { + result = 1; + } else { + result = 0; + } + plus.ios.deleteObject(avaudiosession); + return result; +} + +function calendar() { + var result = null; + var EKEventStore = plus.ios.import('EKEventStore'); + var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(0); + if (ekAuthStatus == 3) { + result = 1; + console.log('日历权限已经开启'); + } else { + console.log('日历权限没有开启'); + } + plus.ios.deleteObject(EKEventStore); + return result; +} + +function memo() { + var result = null; + var EKEventStore = plus.ios.import('EKEventStore'); + var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(1); + if (ekAuthStatus == 3) { + result = 1; + console.log('备忘录权限已经开启'); + } else { + console.log('备忘录权限没有开启'); + } + plus.ios.deleteObject(EKEventStore); + return result; +} + +function requestIOS(permissionID) { + return new Promise((resolve, reject) => { + switch (permissionID) { + case 'push': + resolve(push()); + break; + case 'location': + resolve(location()); + break; + case 'record': + resolve(record()); + break; + case 'camera': + resolve(camera()); + break; + case 'album': + resolve(album()); + break; + case 'contact': + resolve(contact()); + break; + case 'calendar': + resolve(calendar()); + break; + case 'memo': + resolve(memo()); + break; + default: + resolve(0); + break; + } + }); +} + +function requestAndroid(permissionID) { + return new Promise((resolve, reject) => { + plus.android.requestPermissions( + [permissionID], + function (resultObj) { + var result = 0; + for (var i = 0; i < resultObj.granted.length; i++) { + var grantedPermission = resultObj.granted[i]; + console.log('已获取的权限:' + grantedPermission); + result = 1; + } + for (var i = 0; i < resultObj.deniedPresent.length; i++) { + var deniedPresentPermission = resultObj.deniedPresent[i]; + console.log('拒绝本次申请的权限:' + deniedPresentPermission); + result = 0; + } + for (var i = 0; i < resultObj.deniedAlways.length; i++) { + var deniedAlwaysPermission = resultObj.deniedAlways[i]; + console.log('永久拒绝申请的权限:' + deniedAlwaysPermission); + result = -1; + } + resolve(result); + }, + function (error) { + console.log('result error: ' + error.message); + resolve({ + code: error.code, + message: error.message, + }); + }, + ); + }); +} + +function gotoAppPermissionSetting() { + if (permission.isIOS) { + var UIApplication = plus.ios.import('UIApplication'); + var application2 = UIApplication.sharedApplication(); + var NSURL2 = plus.ios.import('NSURL'); + var setting2 = NSURL2.URLWithString('app-settings:'); + application2.openURL(setting2); + plus.ios.deleteObject(setting2); + plus.ios.deleteObject(NSURL2); + plus.ios.deleteObject(application2); + } else { + var Intent = plus.android.importClass('android.content.Intent'); + var Settings = plus.android.importClass('android.provider.Settings'); + var Uri = plus.android.importClass('android.net.Uri'); + var mainActivity = plus.android.runtimeMainActivity(); + var intent = new Intent(); + intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + var uri = Uri.fromParts('package', mainActivity.getPackageName(), null); + intent.setData(uri); + mainActivity.startActivity(intent); + } +} + +const permission = { + get isIOS() { + return typeof isIOS === 'boolean' + ? isIOS + : (isIOS = uni.getSystemInfoSync().platform === 'ios'); + }, + requestIOS: requestIOS, + requestAndroid: requestAndroid, + gotoAppSetting: gotoAppPermissionSetting, +}; + +export default permission; diff --git a/sheep/libs/sdk-h5-weixin.js b/sheep/libs/sdk-h5-weixin.js new file mode 100644 index 0000000..a1ba002 --- /dev/null +++ b/sheep/libs/sdk-h5-weixin.js @@ -0,0 +1,182 @@ +/** + * 本模块封装微信浏览器下的一些方法。 + * 更多微信网页开发sdk方法,详见:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html + */ + +import jweixin, { ready } from 'weixin-js-sdk'; +import $helper from '@/sheep/helper'; +import AuthUtil from '@/api/member/auth'; + +let configSuccess = false; + +export default { + // 判断是否在微信中 + isWechat() { + const ua = window.navigator.userAgent.toLowerCase(); + // noinspection EqualityComparisonWithCoercionJS + return ua.match(/micromessenger/i) == 'micromessenger'; + }, + + isReady(api) { + jweixin.ready(api); + }, + + // 初始化 JSSDK + async init(callback) { + if (!this.isWechat()) { + $helper.toast('请使用微信网页浏览器打开'); + return; + } + + // 调用后端接口,获得 JSSDK 初始化所需的签名 + const url = location.href.split('#')[0]; + const { code, data } = await AuthUtil.createWeixinMpJsapiSignature(url); + if (code === 0) { + jweixin.config({ + debug: false, + appId: data.appId, + timestamp: data.timestamp, + nonceStr: data.nonceStr, + signature: data.signature, + jsApiList: ['chooseWXPay'], // TODO 芋艿:后续可以设置更多权限; + openTagList: data.openTagList + }); + } + + // 监听结果 + configSuccess = true; + jweixin.error((err) => { + configSuccess = false; + console.error('微信 JSSDK 初始化失败', err); + // $helper.toast('微信JSSDK:' + err.errMsg); + }); + jweixin.ready(() => { + if (configSuccess) { + console.log('微信 JSSDK 初始化成功'); + } + }) + + // 回调 + if (callback) { + callback(data); + } + }, + + //在需要定位页面调用 TODO 芋艿:未测试 + getLocation(callback) { + this.isReady(() => { + jweixin.getLocation({ + type: 'gcj02', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02' + success: function (res) { + callback(res); + }, + fail: function (res) { + console.log('%c微信H5sdk,getLocation失败:', 'color:green;background:yellow'); + }, + }); + }); + }, + + //获取微信收货地址 TODO 芋艿:未测试 + openAddress(callback) { + this.isReady(() => { + jweixin.openAddress({ + success: function (res) { + callback.success && callback.success(res); + }, + fail: function (err) { + callback.error && callback.error(err); + console.log('%c微信H5sdk,openAddress失败:', 'color:green;background:yellow'); + }, + complete: function (res) {}, + }); + }); + }, + + // 微信扫码 TODO 芋艿:未测试 + scanQRCode(callback) { + this.isReady(() => { + jweixin.scanQRCode({ + needResult: 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果, + scanType: ['qrCode', 'barCode'], // 可以指定扫二维码还是一维码,默认二者都有 + success: function (res) { + callback(res); + }, + fail: function (res) { + console.log('%c微信H5sdk,scanQRCode失败:', 'color:green;background:yellow'); + }, + }); + }); + }, + + // 更新微信分享信息 TODO 芋艿:未测试 + updateShareInfo(data, callback = null) { + this.isReady(() => { + const shareData = { + title: data.title, + desc: data.desc, + link: data.link, + imgUrl: data.image, + success: function (res) { + if (callback) { + callback(res); + } + // 分享后的一些操作,比如分享统计等等 + }, + cancel: function (res) {}, + }; + + // 新版 分享聊天api + jweixin.updateAppMessageShareData(shareData); + // 新版 分享到朋友圈api + jweixin.updateTimelineShareData(shareData); + }); + }, + + // 打开坐标位置 TODO 芋艿:未测试 + openLocation(data, callback) { + this.isReady(() => { + jweixin.openLocation({ + //根据传入的坐标打开地图 + latitude: data.latitude, + longitude: data.longitude, + }); + }); + }, + + // 选择图片 TODO 芋艿:未测试 + chooseImage(callback) { + this.isReady(() => { + jweixin.chooseImage({ + count: 1, + sizeType: ['compressed'], + sourceType: ['album'], + success: function (rs) { + callback(rs); + }, + }); + }); + }, + + // 微信支付 + wxpay(data, callback) { + this.isReady(() => { + jweixin.chooseWXPay({ + timestamp: data.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符 + nonceStr: data.nonceStr, // 支付签名随机串,不长于 32 位 + package: data.packageValue, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*) + signType: data.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5' + paySign: data.paySign, // 支付签名 + success: function (res) { + callback.success && callback.success(res); + }, + fail: function (err) { + callback.fail && callback.fail(err); + }, + cancel: function (err) { + callback.cancel && callback.cancel(err); + }, + }); + }); + }, +}; diff --git a/sheep/platform/index.js b/sheep/platform/index.js new file mode 100644 index 0000000..36a54fc --- /dev/null +++ b/sheep/platform/index.js @@ -0,0 +1,175 @@ +/** + * Shopro 第三方平台功能聚合 + * @version 1.0.3 + * @author lidongtony + * @param {String} name - 厂商+平台名称 + * @param {String} provider - 厂商 + * @param {String} platform - 平台名称 + * @param {String} os - 系统型号 + * @param {Object} device - 设备信息 + */ + +import { isEmpty } from 'lodash'; +// #ifdef H5 +import { isWxBrowser } from '@/sheep/helper/utils'; +// #endif +import wechat from './provider/wechat/index.js'; +import apple from './provider/apple'; +import share from './share'; +import Pay from './pay'; + +const device = uni.getSystemInfoSync(); + +const os = device.platform; + +let name = ''; +let provider = ''; +let platform = ''; +let isWechatInstalled = true; + +// #ifdef H5 +if (isWxBrowser()) { + name = 'WechatOfficialAccount'; + provider = 'wechat'; + platform = 'officialAccount'; +} else { + name = 'H5'; + platform = 'h5'; +} +// #endif + +// #ifdef APP-PLUS +name = 'App'; +platform = 'openPlatform'; +// 检查微信客户端是否安装,否则AppleStore会因此拒绝上架 +if (os === 'ios') { + isWechatInstalled = plus.ios.import('WXApi').isWXAppInstalled(); +} +// #endif + +// #ifdef MP-WEIXIN +name = 'WechatMiniProgram'; +platform = 'miniProgram'; +provider = 'wechat'; +// #endif + +if (isEmpty(name)) { + uni.showToast({ + title: '暂不支持该平台', + icon: 'none', + }); +} + +// 加载当前平台前置行为 +const load = () => { + if (provider === 'wechat') { + wechat.load(); + } +}; + +// 使用厂商独占sdk name = 'wechat' | 'alipay' | 'apple' +const useProvider = (_provider = '') => { + if (_provider === '') _provider = provider; + if (_provider === 'wechat') return wechat; + if (_provider === 'apple') return apple; +}; + +// 支付服务转发 +const pay = (payment, orderType, orderSN) => { + return new Pay(payment, orderType, orderSN); +}; + +/** + * 检查更新 (只检查小程序和App) + * @param {Boolean} silence - 静默检查 + */ +const checkUpdate = (silence = false) => { + let canUpdate; + // #ifdef MP-WEIXIN + useProvider().checkUpdate(silence); + // #endif + + // #ifdef APP-PLUS + // TODO: 热更新 + // #endif +}; + +/** + * 检查网络 + * @param {Boolean} silence - 静默检查 + */ +async function checkNetwork() { + const networkStatus = await uni.getNetworkType(); + if (networkStatus.networkType == 'none') { + return Promise.resolve(false); + } + return Promise.resolve(true); +} + +// 获取小程序胶囊信息 +const getCapsule = () => { + // #ifdef MP + let capsule = uni.getMenuButtonBoundingClientRect(); + if (!capsule) { + capsule = { + bottom: 56, + height: 32, + left: 278, + right: 365, + top: 24, + width: 87, + }; + } + return capsule; + // #endif + + // #ifndef MP + return { + bottom: 56, + height: 32, + left: 278, + right: 365, + top: 24, + width: 87, + }; + // #endif +}; + +const capsule = getCapsule(); + +// 标题栏高度 +const getNavBar = () => { + return device.statusBarHeight + 44; +}; +const navbar = getNavBar(); + +function getLandingPage() { + let page = ''; + // #ifdef H5 + page = location.href.split('?')[0]; + // #endif + return page; +} + +// 设置ios+公众号网页落地页 解决微信sdk签名问题 +const landingPage = getLandingPage(); + +const _platform = { + name, + device, + os, + provider, + platform, + useProvider, + checkUpdate, + checkNetwork, + pay, + share, + load, + capsule, + navbar, + landingPage, + isWechatInstalled, +}; + +export default _platform; diff --git a/sheep/platform/pay.js b/sheep/platform/pay.js new file mode 100644 index 0000000..a12bb48 --- /dev/null +++ b/sheep/platform/pay.js @@ -0,0 +1,351 @@ +import sheep from '@/sheep'; +// #ifdef H5 +import $wxsdk from '@/sheep/libs/sdk-h5-weixin'; +// #endif +import { getRootUrl } from '@/sheep/helper'; +import PayOrderApi from '@/api/pay/order'; + +/** + * 支付 + * + * @param {String} payment = ['wechat','alipay','wallet','mock'] - 支付方式 + * @param {String} orderType = ['goods','recharge','groupon'] - 订单类型 + * @param {String} id - 订单号 + */ + +export default class SheepPay { + constructor(payment, orderType, id) { + this.payment = payment; + this.id = id; + this.orderType = orderType; + this.payAction(); + } + + payAction() { + const payAction = { + WechatOfficialAccount: { + wechat: () => { + this.wechatOfficialAccountPay(); + }, + alipay: () => { + this.redirectPay(); // 现在公众号可以直接跳转支付宝页面 + }, + wallet: () => { + this.walletPay(); + }, + mock: () => { + this.mockPay(); + } + }, + WechatMiniProgram: { + wechat: () => { + this.wechatMiniProgramPay(); + }, + alipay: () => { + this.copyPayLink(); + }, + wallet: () => { + this.walletPay(); + }, + mock: () => { + this.mockPay(); + } + }, + App: { + wechat: () => { + this.wechatAppPay(); + }, + alipay: () => { + this.alipay(); + }, + wallet: () => { + this.walletPay(); + }, + mock: () => { + this.mockPay(); + } + }, + H5: { + wechat: () => { + this.wechatWapPay(); + }, + alipay: () => { + this.redirectPay(); + }, + wallet: () => { + this.walletPay(); + }, + mock: () => { + this.mockPay(); + } + }, + }; + return payAction[sheep.$platform.name][this.payment](); + } + + // 预支付 + prepay(channel) { + return new Promise(async (resolve, reject) => { + let data = { + id: this.id, + channelCode: channel, + channelExtras: {} + }; + // 特殊逻辑:微信公众号、小程序支付时,必须传入 openid + if (['wx_pub', 'wx_lite'].includes(channel)) { + const openid = await sheep.$platform.useProvider('wechat').getOpenid(); + // 如果获取不到 openid,微信无法发起支付,此时需要引导 + if (!openid) { + this.bindWeixin(); + return; + } + data.channelExtras.openid = openid; + } + // 发起预支付 API 调用 + PayOrderApi.submitOrder(data).then((res) => { + // 成功时 + res.code === 0 && resolve(res); + // 失败时 + if (res.code !== 0 && res.msg.indexOf('无效的openid') >= 0) { + // 特殊逻辑:微信公众号、小程序支付时,必须传入 openid 不正确的情况 + if (res.msg.indexOf('无效的openid') >= 0 // 获取的 openid 不正确时,或者随便输入了个 openid + || res.msg.indexOf('下单账号与支付账号不一致') >= 0) { // https://developers.weixin.qq.com/community/develop/doc/00008c53c347804beec82aed051c00 + this.bindWeixin(); + } + } + }); + }); + } + // #ifdef H5 + // 微信公众号 JSSDK 支付 + async wechatOfficialAccountPay() { + let { code, data } = await this.prepay('wx_pub'); + if (code !== 0) { + return; + } + const payConfig = JSON.parse(data.displayContent); + $wxsdk.wxpay(payConfig, { + success: () => { + this.payResult('success'); + }, + cancel: () => { + sheep.$helper.toast('支付已手动取消'); + }, + fail: (error) => { + if (error.errMsg.indexOf('chooseWXPay:没有此SDK或暂不支持此SDK模拟') >= 0) { + sheep.$helper.toast('发起微信支付失败,原因:可能是微信开发者工具不支持,建议使用微信打开网页后支付'); + return + } + this.payResult('fail'); + }, + }); + } + + // 浏览器微信 H5 支付 TODO 芋艿:待接入 + async wechatWapPay() { + const { error, data } = await this.prepay(); + if (error === 0) { + const redirect_url = `${getRootUrl()}pages/pay/result?id=${this.id}&payment=${this.payment}&orderType=${this.orderType}`; + location.href = `${data.pay_data.h5_url}&redirect_url=${encodeURIComponent(redirect_url)}`; + } + } + + // 支付链接 TODO 芋艿:待接入 + async redirectPay() { + let { error, data } = await this.prepay(); + if (error === 0) { + const redirect_url = `${getRootUrl()}pages/pay/result?id=${this.id}&payment=${this.payment}&orderType=${this.orderType}`; + location.href = data.pay_data + encodeURIComponent(redirect_url); + } + } + + // #endif + + // 微信小程序支付 + async wechatMiniProgramPay() { + // let that = this; + let { code, data } = await this.prepay('wx_lite'); + if (code !== 0) { + return; + } + // 调用微信小程序支付 + const payConfig = JSON.parse(data.displayContent); + uni.requestPayment({ + provider: 'wxpay', + timeStamp: payConfig.timeStamp, + nonceStr: payConfig.nonceStr, + package: payConfig.packageValue, + signType: payConfig.signType, + paySign: payConfig.paySign, + success: (res) => { + this.payResult('success'); + }, + fail: (err) => { + if (err.errMsg === 'requestPayment:fail cancel') { + sheep.$helper.toast('支付已手动取消'); + } else { + this.payResult('fail'); + } + }, + }); + } + + // 余额支付 + async walletPay() { + const { code } = await this.prepay('wallet'); + code === 0 && this.payResult('success'); + } + + // 模拟支付 + async mockPay() { + const { code } = await this.prepay('mock'); + code === 0 && this.payResult('success'); + } + + // 支付宝复制链接支付 TODO 芋艿:待接入 + async copyPayLink() { + let that = this; + let { error, data } = await this.prepay(); + if (error === 0) { + // 引入showModal 点击确认 复制链接; + uni.showModal({ + title: '支付宝支付', + content: '复制链接到外部浏览器', + confirmText: '复制链接', + success: (res) => { + if (res.confirm) { + sheep.$helper.copyText(data.pay_data); + } + }, + }); + } + } + + // 支付宝支付 TODO 芋艿:待接入 + async alipay() { + let that = this; + const { error, data } = await this.prepay(); + if (error === 0) { + uni.requestPayment({ + provider: 'alipay', + orderInfo: data.pay_data, //支付宝订单数据 + success: (res) => { + that.payResult('success'); + }, + fail: (err) => { + if (err.errMsg === 'requestPayment:fail [paymentAlipay:62001]user cancel') { + sheep.$helper.toast('支付已手动取消'); + } else { + that.payResult('fail'); + } + }, + }); + } + } + + // 微信支付 TODO 芋艿:待接入 + async wechatAppPay() { + let that = this; + let { error, data } = await this.prepay(); + if (error === 0) { + uni.requestPayment({ + provider: 'wxpay', + orderInfo: data.pay_data, //微信订单数据(官方说是string。实测为object) + success: (res) => { + that.payResult('success'); + }, + fail: (err) => { + err.errMsg !== 'requestPayment:fail cancel' && that.payResult('fail'); + }, + }); + } + } + + // 支付结果跳转,success:成功,fail:失败 + payResult(resultType) { + sheep.$router.redirect('/pages/pay/result', { + id: this.id, + orderType: this.orderType, + payState: resultType + }); + } + + // 引导绑定微信 + bindWeixin() { + uni.showModal({ + title: '微信支付', + content: '请先绑定微信再使用微信支付', + success: function (res) { + if (res.confirm) { + sheep.$platform.useProvider('wechat').bind(); + } + }, + }); + } + +} + +export function getPayMethods(channels) { + const payMethods = [ + { + icon: '/static/img/shop/pay/wechat.png', + title: '微信支付', + value: 'wechat', + disabled: true, + }, + { + icon: '/static/img/shop/pay/alipay.png', + title: '支付宝支付', + value: 'alipay', + disabled: true, + }, + { + icon: '/static/img/shop/pay/wallet.png', + title: '余额支付', + value: 'wallet', + disabled: true, + }, + { + icon: '/static/img/shop/pay/apple.png', + title: 'Apple Pay', + value: 'apple', + disabled: true, + }, + { + icon: '/static/img/shop/pay/wallet.png', + title: '模拟支付', + value: 'mock', + disabled: true, + } + ]; + const platform = sheep.$platform.name + + // 1. 处理【微信支付】 + const wechatMethod = payMethods[0]; + if ((platform === 'WechatOfficialAccount' && channels.includes('wx_pub')) + || (platform === 'WechatMiniProgram' && channels.includes('wx_lite')) + || (platform === 'App' && channels.includes('wx_app'))) { + wechatMethod.disabled = false; + } + wechatMethod.disabled = false; // TODO 芋艿:临时测试 + + // 2. 处理【支付宝支付】 + const alipayMethod = payMethods[1]; + if ((platform === 'WechatOfficialAccount' && channels.includes('alipay_wap')) + || platform === 'WechatMiniProgram' && channels.includes('alipay_wap') + || platform === 'App' && channels.includes('alipay_app')) { + alipayMethod.disabled = false; + } + // 3. 处理【余额支付】 + const walletMethod = payMethods[2]; + if (channels.includes('wallet')) { + walletMethod.disabled = false; + } + // 4. 处理【苹果支付】TODO 芋艿:未来接入 + // 5. 处理【模拟支付】 + const mockMethod = payMethods[4]; + if (channels.includes('mock')) { + mockMethod.disabled = false; + } + return payMethods; +} \ No newline at end of file diff --git a/sheep/platform/provider/apple/app.js b/sheep/platform/provider/apple/app.js new file mode 100644 index 0000000..405bb55 --- /dev/null +++ b/sheep/platform/provider/apple/app.js @@ -0,0 +1,36 @@ +// import third from '@/api/third'; +// TODO 芋艿:等后面搞 App 再弄 + +const login = () => { + return new Promise(async (resolve, reject) => { + const loginRes = await uni.login({ + provider: 'apple', + success: () => { + uni.getUserInfo({ + provider: 'apple', + success: async (res) => { + if (res.errMsg === 'getUserInfo:ok') { + const payload = res.userInfo; + const { error } = await third.apple.login({ + payload, + shareInfo: uni.getStorageSync('shareLog') || {}, + }); + if (error === 0) { + resolve(true); + } else { + resolve(false); + } + } + }, + }); + }, + fail: (err) => { + resolve(false); + }, + }); + }); +}; + +export default { + login, +}; diff --git a/sheep/platform/provider/apple/index.js b/sheep/platform/provider/apple/index.js new file mode 100644 index 0000000..388c093 --- /dev/null +++ b/sheep/platform/provider/apple/index.js @@ -0,0 +1,9 @@ +// #ifdef APP-PLUS +import service from './app'; +// #endif + +let apple = {}; +if (typeof service !== 'undefined') { + apple = service; +} +export default apple; diff --git a/sheep/platform/provider/wechat/index.js b/sheep/platform/provider/wechat/index.js new file mode 100644 index 0000000..3bb2c7f --- /dev/null +++ b/sheep/platform/provider/wechat/index.js @@ -0,0 +1,15 @@ +// #ifdef H5 +import service from './officialAccount'; +// #endif + +// #ifdef MP-WEIXIN +import service from './miniProgram'; +// #endif + +// #ifdef APP-PLUS +import service from './openPlatform'; +// #endif + +const wechat = service; + +export default wechat; diff --git a/sheep/platform/provider/wechat/miniProgram.js b/sheep/platform/provider/wechat/miniProgram.js new file mode 100644 index 0000000..8bbfb5f --- /dev/null +++ b/sheep/platform/provider/wechat/miniProgram.js @@ -0,0 +1,205 @@ +import third from '@/api/migration/third' +import AuthUtil from '@/api/member/auth'; +import SocialApi from '@/api/member/social'; +import UserApi from '@/api/member/user'; + +const socialType = 34; // 社交类型 - 微信小程序 + +let subscribeEventList = []; + +// 加载微信小程序 +function load() { + checkUpdate(); + getSubscribeTemplate(); +} + +// 微信小程序静默授权登陆 +const login = async () => { + return new Promise(async (resolve, reject) => { + // 1. 获得微信 code + const codeResult = await uni.login(); + if (codeResult.errMsg !== 'login:ok') { + return resolve(false); + } + + // 2. 社交登录 + const loginResult = await AuthUtil.socialLogin(socialType, codeResult.code, 'default'); + if (loginResult.code === 0) { + setOpenid(loginResult.data.openid); + return resolve(true); + } else { + return resolve(false); + } + }); +}; + +// 微信小程序手机号授权登陆 +const mobileLogin = async (e) => { + return new Promise(async (resolve, reject) => { + if (e.errMsg !== 'getPhoneNumber:ok') { + return resolve(false); + } + + // 1. 获得微信 code + const codeResult = await uni.login(); + if (codeResult.errMsg !== 'login:ok') { + return resolve(false); + } + + // 2. 一键登录 + const loginResult = await AuthUtil.weixinMiniAppLogin(e.code, codeResult.code, 'default'); + if (loginResult.code === 0) { + setOpenid(loginResult.data.openid); + return resolve(true); + } else { + return resolve(false); + } + // TODO 芋艿:shareInfo: uni.getStorageSync('shareLog') || {}, + }); +}; + +// 微信小程序绑定 +const bind = () => { + return new Promise(async (resolve, reject) => { + // 1. 获得微信 code + const codeResult = await uni.login(); + if (codeResult.errMsg !== 'login:ok') { + return resolve(false); + } + + // 2. 绑定账号 + const bindResult = await SocialApi.socialBind(socialType, codeResult.code, 'default'); + if (bindResult.code === 0) { + setOpenid(bindResult.data); + return resolve(true); + } else { + return resolve(false); + } + }); +}; + +// 微信小程序解除绑定 +const unbind = async (openid) => { + const { code } = await SocialApi.socialUnbind(socialType, openid); + return code === 0; +}; + +// 绑定用户手机号 +const bindUserPhoneNumber = (e) => { + return new Promise(async (resolve, reject) => { + const { code } = await UserApi.updateUserMobileByWeixin(e.code); + if (code === 0) { + resolve(true); + } + resolve(false); + }); +}; + +// 设置 openid 到本地存储,目前只有 pay 支付时会使用 +function setOpenid(openid) { + uni.setStorageSync('openid', openid); +} + +// 获得 openid +async function getOpenid(force = false) { + let openid = uni.getStorageSync('openid'); + if (!openid && force) { + const info = await getInfo(); + if (info && info.openid) { + openid = info.openid; + setOpenid(openid); + } + } + return openid; +} + +// 获得社交信息 +async function getInfo() { + const { code, data } = await SocialApi.getSocialUser(socialType); + if (code !== 0) { + return undefined; + } + return data; +} + +// ========== 非登录相关的逻辑 ========== + +// 小程序更新 +const checkUpdate = async (silence = true) => { + if (uni.canIUse('getUpdateManager')) { + const updateManager = uni.getUpdateManager(); + updateManager.onCheckForUpdate(function (res) { + // 请求完新版本信息的回调 + if (res.hasUpdate) { + updateManager.onUpdateReady(function () { + uni.showModal({ + title: '更新提示', + content: '新版本已经准备好,是否重启应用?', + success: function (res) { + if (res.confirm) { + // 新的版本已经下载好,调用 applyUpdate 应用新版本并重启 + updateManager.applyUpdate(); + } + }, + }); + }); + updateManager.onUpdateFailed(function () { + // 新的版本下载失败 + // uni.showModal({ + // title: '已经有新版本了哟~', + // content: '新版本已经上线啦,请您删除当前小程序,重新搜索打开~', + // }); + }); + } else { + if (!silence) { + uni.showModal({ + title: '当前为最新版本', + showCancel: false, + }); + } + } + }); + } +}; + +// 获取订阅消息模板 +async function getSubscribeTemplate() { + const { error, data } = await third.wechat.subscribeTemplate(); + if (error === 0) { + subscribeEventList = data; + } +} + +// 订阅消息 +function subscribeMessage(event) { + let tmplIds = []; + if (typeof event === 'string') { + tmplIds.push(subscribeEventList[event]); + } + if (typeof event === 'object') { + event.forEach((item) => { + if (typeof subscribeEventList[item] !== 'undefined') tmplIds.push(subscribeEventList[item]); + }); + } + if (tmplIds.length === 0) return; + + uni.requestSubscribeMessage({ + tmplIds, + fail: (err) => { + console.log(err); + }, + }); +} + +export default { + load, + login, + bind, + unbind, + bindUserPhoneNumber, + mobileLogin, + getInfo, + getOpenid, + subscribeMessage, + checkUpdate +}; diff --git a/sheep/platform/provider/wechat/officialAccount.js b/sheep/platform/provider/wechat/officialAccount.js new file mode 100644 index 0000000..c9c0759 --- /dev/null +++ b/sheep/platform/provider/wechat/officialAccount.js @@ -0,0 +1,107 @@ +import $wxsdk from '@/sheep/libs/sdk-h5-weixin'; +import { getRootUrl } from '@/sheep/helper'; +import AuthUtil from '@/api/member/auth'; +import SocialApi from '@/api/member/social'; + +const socialType = 31; // 社交类型 - 微信公众号 + +// 加载微信公众号JSSDK +async function load() { + $wxsdk.init(); +} + +// 微信公众号登陆 +async function login(code = '', state = '') { + // 情况一:没有 code 时,去获取 code + if (!code) { + const loginUrl = await getLoginUrl(); + if (loginUrl) { + uni.setStorageSync('returnUrl', location.href); + window.location = loginUrl; + } + // 情况二:有 code 时,使用 code 去自动登录 + } else { + // 解密 code 发起登陆 + const loginResult = await AuthUtil.socialLogin(socialType, code, state); + if (loginResult.code === 0) { + // TODO 芋艿:shareLog + setOpenid(loginResult.data.openid); + return loginResult; + } + } + return false; +} + +// 微信公众号绑定 +async function bind(code = '', state = '') { + // 情况一:没有 code 时,去获取 code + if (code === '') { + const loginUrl = await getLoginUrl('bind'); + if (loginUrl) { + uni.setStorageSync('returnUrl', location.href); + window.location = loginUrl; + } + } else { + // 情况二:有 code 时,使用 code 去自动绑定 + const loginResult = await SocialApi.socialBind(socialType, code, state); + if (loginResult.code === 0) { + setOpenid(loginResult.data); + return loginResult; + } + } + return false; +} + +// 微信公众号解除绑定 +const unbind = async (openid) => { + const { code } = await SocialApi.socialUnbind(socialType, openid); + return code === 0; +}; + +// 获取公众号登陆地址 +async function getLoginUrl(event = 'login') { + const page = getRootUrl() + 'pages/index/login' + + '?event=' + event; // event 目的,区分是 login 还是 bind + const { code, data } = await AuthUtil.socialAuthRedirect(socialType, page); + if (code !== 0) { + return undefined; + } + return data; +} + +// 设置 openid 到本地存储,目前只有 pay 支付时会使用 +function setOpenid(openid) { + uni.setStorageSync('openid', openid); +} + +// 获得 openid +async function getOpenid(force = false) { + let openid = uni.getStorageSync('openid'); + if (!openid && force) { + const info = await getInfo(); + if (info && info.openid) { + openid = info.openid; + setOpenid(openid); + } + } + return openid; +} + +// 获得社交信息 +async function getInfo() { + const { code, data } = await SocialApi.getSocialUser(socialType); + if (code !== 0) { + return undefined; + } + return data; +} + +export default { + load, + login, + bind, + unbind, + getInfo, + getOpenid, + jssdk: $wxsdk, +}; diff --git a/sheep/platform/provider/wechat/openPlatform.js b/sheep/platform/provider/wechat/openPlatform.js new file mode 100644 index 0000000..5bdbd3f --- /dev/null +++ b/sheep/platform/provider/wechat/openPlatform.js @@ -0,0 +1,61 @@ +// 登录 +// import third from '@/api/third'; +import SocialApi from '@/api/member/social'; + +// TODO 芋艿:等后面搞 App 再弄 +const socialType = 32; // 社交类型 - 微信开放平台 + +const load = async () => {}; + +// 微信开放平台移动应用授权登陆 +const login = () => { + return new Promise(async (resolve, reject) => { + const loginRes = await uni.login({ + provider: 'weixin', + onlyAuthorize: true, + }); + debugger + if (loginRes.errMsg == 'login:ok') { + const res = await third.wechat.login({ + platform: 'openPlatform', + shareInfo: uni.getStorageSync('shareLog') || {}, + payload: encodeURIComponent( + JSON.stringify({ + code: loginRes.code, + }), + ), + }); + + if (res.error === 0) { + resolve(true); + } + } else { + uni.showToast({ + icon: 'none', + title: loginRes.errMsg, + }); + } + resolve(false); + }); +}; + +// 微信 App 解除绑定 +const unbind = async (openid) => { + const { code } = await SocialApi.socialUnbind(socialType, openid); + return code === 0; +}; + +// 获得社交信息 +async function getInfo() { + const { code, data } = await SocialApi.getSocialUser(socialType); + if (code !== 0) { + return undefined; + } + return data; +} + +export default { + load, + login, + getInfo +}; diff --git a/sheep/platform/share.js b/sheep/platform/share.js new file mode 100644 index 0000000..471c587 --- /dev/null +++ b/sheep/platform/share.js @@ -0,0 +1,187 @@ +import $store from '@/sheep/store'; +import $platform from '@/sheep/platform'; +import $router from '@/sheep/router'; +import $url from '@/sheep/url'; +// #ifdef H5 +import $wxsdk from '@/sheep/libs/sdk-h5-weixin'; +// #endif + +// 设置分享的平台渠道: 1=H5,2=微信公众号网页,3=微信小程序,4=App,...按需扩展 +const platformMap = ['H5', 'WechatOfficialAccount', 'WechatMiniProgram', 'App']; + +// 设置分享方式: 1=直接转发,2=海报,3=复制链接,...按需扩展 +const fromMap = ['forward', 'poster', 'link']; + +// TODO 芋艿:分享的接入 +// 设置分享信息参数 +const getShareInfo = ( + scene = { + title: '', // 自定义分享标题 + desc: '', // 自定义描述 + image: '', // 自定义分享图片 + params: {}, // 自定义分享参数 + }, + poster = { + // 自定义海报数据 + type: 'user', + }, +) => { + const shareInfo = { + title: '', // 分享标题 + desc: '', // 描述 + image: '', // 分享图片 + path: '', // 分享页面+参数 + link: '', // 分享Url+参数 + query: '', // 分享参数 + poster, // 海报所需数据 + }; + + const app = $store('app'); + const shareConfig = app.platform.share; + + // 自动拼接分享用户参数 + const query = buildSpmQuery(scene.params); + shareInfo.query = query; + + // 配置分享链接地址 + shareInfo.link = buildSpmLink(query, shareConfig.linkAddress); + // 配置页面地址带参数 + shareInfo.path = buildSpmPath(query); + + // 配置转发参数 + if (shareConfig.methods.includes('forward')) { + // TODO puhui999: forward 这块有点问题 + if (shareConfig.forwardInfo.title === '' || shareConfig.forwardInfo.image === '') { + console.log('请在平台设置中配置转发信息'); + } + // 设置自定义分享信息 + shareInfo.title = scene.title || shareConfig.forwardInfo.title; + shareInfo.image = $url.cdn(scene.image || shareConfig.forwardInfo.image); + shareInfo.desc = scene.desc || shareConfig.forwardInfo.subtitle; + shareInfo.path = buildSpmPath(scene.path, query); + } + + return shareInfo; +}; + +// 构造spm分享参数 +const buildSpmQuery = (params) => { + const user = $store('user'); + let shareId = '0'; // 设置分享者用户ID + if (typeof params.shareId === 'undefined') { + if (user.isLogin) { + shareId = user.userInfo.id; + } + } + let page = '1'; // 页面类型: 1=首页(默认),2=商品,3=拼团商品,4=秒杀商品,5=邀请参团...按需扩展 + if (typeof params.page !== 'undefined') { + page = params.page; + } + let query = '0'; // 设置页面ID: 如商品ID、拼团ID等 + if (typeof params.query !== 'undefined') { + query = params.query; + } + let platform = platformMap.indexOf($platform.name) + 1; + let from = '1'; + if (typeof params.from !== 'undefined') { + from = platformMap.indexOf(params.from) + 1; + } + //spmParams = ... 可按需扩展 + return `spm=${shareId}.${page}.${query}.${platform}.${from}`; +}; + +// 构造页面分享参数: 所有的分享都先到首页进行 spm 参数解析 +const buildSpmPath = (query) => { + // 默认是主页,页面 page,例如 pages/index/index,根路径前不要填加 /, + // 不能携带参数(参数请放在scene字段里),如果不填写这个字段,默认跳主页面。scancode_time为系统保留参数,不允许配置 + return `pages/index/index`; +}; + +// 构造分享链接 +const buildSpmLink = (query, linkAddress = '') => { + return `${linkAddress}?${query}`; +}; + +// 解析Spm +const decryptSpm = (spm) => { + const user = $store('user'); + let shareParamsArray = spm.split('.'); + let shareParams = { + spm, + shareId: 0, + page: '', + query: {}, + platform: '', + from: '', + }; + let query; + shareParams.shareId = shareParamsArray[0]; + switch (shareParamsArray[1]) { + case '1': + // 默认首页不跳转 + shareParams.page = '/pages/index/index'; + break; + case '2': + // 普通商品 + shareParams.page = '/pages/goods/index'; + shareParams.query = { + id: shareParamsArray[2], + }; + break; + case '3': + // 拼团商品 + shareParams.page = '/pages/goods/groupon'; + query = shareParamsArray[2].split(','); + shareParams.query = { + id: query[0], + activity_id: query[1], // TODO 芋艿:接入分享后,应该统一成 id 参数 + }; + break; + case '4': + // 秒杀商品 + shareParams.page = '/pages/goods/seckill'; + query = shareParamsArray[2].split(','); + shareParams.query = { + id: query[1], + }; + break; + case '5': + // 参与拼团 + shareParams.page = '/pages/activity/groupon/detail'; + shareParams.query = { + id: shareParamsArray[2], + }; + break; + } + shareParams.platform = platformMap[shareParamsArray[3] - 1]; + shareParams.from = fromMap[shareParamsArray[4] - 1]; + if (shareParams.shareId != 0) { + // 已登录 立即添加分享记录 + if (user.isLogin) { + user.addShareLog(shareParams); + } else { + // 未登录 待用户登录后添加分享记录 + uni.setStorageSync('shareLog', shareParams); + } + } + + if (shareParams.page !== '/pages/index/index') { + $router.go(shareParams.page, shareParams.query); + } + return shareParams; +}; + +// 更新公众号分享sdk +const updateShareInfo = (shareInfo) => { + // #ifdef H5 + if ($platform.name === 'WechatOfficialAccount') { + $wxsdk.updateShareInfo(shareInfo); + } + // #endif +}; + +export default { + getShareInfo, + updateShareInfo, + decryptSpm, +}; diff --git a/sheep/request/index.js b/sheep/request/index.js new file mode 100644 index 0000000..c4ae4c4 --- /dev/null +++ b/sheep/request/index.js @@ -0,0 +1,302 @@ +/** + * Shopro-request + * @description api模块管理,loading配置,请求拦截,错误处理 + */ + +import Request from 'luch-request'; +import { baseUrl, apiPath } from '@/sheep/config'; +import $store from '@/sheep/store'; +import $platform from '@/sheep/platform'; +import { + showAuthModal +} from '@/sheep/hooks/useModal'; +import AuthUtil from '@/api/member/auth'; + +const options = { + // 显示操作成功消息 默认不显示 + showSuccess: false, + // 成功提醒 默认使用后端返回值 + successMsg: '', + // 显示失败消息 默认显示 + showError: true, + // 失败提醒 默认使用后端返回信息 + errorMsg: '', + // 显示请求时loading模态框 默认显示 + showLoading: true, + // loading提醒文字 + loadingMsg: '加载中', + // 需要授权才能请求 默认放开 + auth: false, + // ... +}; + +// Loading全局实例 +let LoadingInstance = { + target: null, + count: 0, +}; + +/** + * 关闭loading + */ +function closeLoading() { + if (LoadingInstance.count > 0) LoadingInstance.count--; + if (LoadingInstance.count === 0) uni.hideLoading(); +} + +/** + * @description 请求基础配置 可直接使用访问自定义请求 + */ +const http = new Request({ + baseURL: baseUrl + apiPath, + timeout: 8000, + method: 'GET', + header: { + Accept: 'text/json', + 'Content-Type': 'application/json;charset=UTF-8', + platform: $platform.name, + }, + // #ifdef APP-PLUS + sslVerify: false, + // #endif + // #ifdef H5 + // 跨域请求时是否携带凭证(cookies)仅H5支持(HBuilderX 2.6.15+) + withCredentials: false, + // #endif + custom: options, +}); + +/** + * @description 请求拦截器 + */ +http.interceptors.request.use( + (config) => { + // 自定义处理【auth 授权】:必须登录的接口,则跳出 AuthModal 登录弹窗 + if (config.custom.auth && !$store('user').isLogin) { + showAuthModal(); + return Promise.reject(); + } + + // 自定义处理【loading 加载中】:如果需要显示 loading,则显示 loading + if (config.custom.showLoading) { + LoadingInstance.count++; + LoadingInstance.count === 1 && + uni.showLoading({ + title: config.custom.loadingMsg, + mask: true, + fail: () => { + uni.hideLoading(); + }, + }); + } + + // 增加 token 令牌、terminal 终端、tenant 租户的请求头 + const token = getAccessToken(); + if (token) { + config.header['Authorization'] = token; + } + // TODO 芋艿:特殊处理 + config.header['Accept'] = '*/*' + config.header['tenant-id'] = '1'; + config.header['terminal'] = '20'; + // config.header['Authorization'] = 'Bearer test247'; + return config; + }, + (error) => { + return Promise.reject(error); + }, +); + +/** + * @description 响应拦截器 + */ +http.interceptors.response.use( + (response) => { + // 约定:如果是 /auth/ 下的 URL 地址,并且返回了 accessToken 说明是登录相关的接口,则自动设置登陆令牌 + if (response.config.url.indexOf('/member/auth/') >= 0 && response.data?.data?.accessToken) { + $store('user').setToken(response.data.data.accessToken, response.data.data.refreshToken); + } + + // 自定处理【loading 加载中】:如果需要显示 loading,则关闭 loading + response.config.custom.showLoading && closeLoading(); + + // 自定义处理【error 错误提示】:如果需要显示错误提示,则显示错误提示 + if (response.data.code !== 0) { + // 特殊:如果 401 错误码,则跳转到登录页 or 刷新令牌 + if (response.data.code === 401) { + return refreshToken(response.config); + } + + // 错误提示 + if (response.config.custom.showError) { + uni.showToast({ + title: response.data.msg || '服务器开小差啦,请稍后再试~', + icon: 'none', + mask: true, + }); + } + } + + // 自定义处理【showSuccess 成功提示】:如果需要显示成功提示,则显示成功提示 + if (response.config.custom.showSuccess + && response.config.custom.successMsg !== '' + && response.data.code === 0) { + uni.showToast({ + title: response.config.custom.successMsg, + icon: 'none', + }); + } + + // 返回结果:包括 code + data + msg + return Promise.resolve(response.data); + }, + (error) => { + const userStore = $store('user'); + const isLogin = userStore.isLogin; + let errorMessage = '网络请求出错'; + if (error !== undefined) { + switch (error.statusCode) { + case 400: + errorMessage = '请求错误'; + break; + case 401: + errorMessage = isLogin ? '您的登陆已过期' : '请先登录'; + // 正常情况下,后端不会返回 401 错误,所以这里不处理 handleAuthorized + break; + case 403: + errorMessage = '拒绝访问'; + break; + case 404: + errorMessage = '请求出错'; + break; + case 408: + errorMessage = '请求超时'; + break; + case 429: + errorMessage = '请求频繁, 请稍后再访问'; + break; + case 500: + errorMessage = '服务器开小差啦,请稍后再试~'; + break; + case 501: + errorMessage = '服务未实现'; + break; + case 502: + errorMessage = '网络错误'; + break; + case 503: + errorMessage = '服务不可用'; + break; + case 504: + errorMessage = '网络超时'; + break; + case 505: + errorMessage = 'HTTP 版本不受支持'; + break; + } + if (error.errMsg.includes('timeout')) errorMessage = '请求超时'; + // #ifdef H5 + if (error.errMsg.includes('Network')) + errorMessage = window.navigator.onLine ? '服务器异常' : '请检查您的网络连接'; + // #endif + } + + if (error && error.config) { + if (error.config.custom.showError === false) { + uni.showToast({ + title: error.data?.msg || errorMessage, + icon: 'none', + mask: true, + }); + } + error.config.custom.showLoading && closeLoading(); + } + + return false; + }, +); + +// Axios 无感知刷新令牌,参考 https://www.dashingdog.cn/article/11 与 https://segmentfault.com/a/1190000020210980 实现 +let requestList = [] // 请求队列 +let isRefreshToken = false // 是否正在刷新中 +const refreshToken = async (config) => { + // 如果当前已经是 refresh-token 的 URL 地址,并且还是 401 错误,说明是刷新令牌失败了,直接返回 Promise.reject(error) + if (config.url.indexOf('/member/auth/refresh-token') >= 0) { + return Promise.reject('error') + } + + // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了 + if (!isRefreshToken) { + isRefreshToken = true + // 1. 如果获取不到刷新令牌,则只能执行登出操作 + const refreshToken = getRefreshToken() + if (!refreshToken) { + return handleAuthorized() + } + // 2. 进行刷新访问令牌 + try { + const refreshTokenResult = await AuthUtil.refreshToken(refreshToken); + if (refreshTokenResult.code !== 0) { + // 如果刷新不成功,直接抛出 e 触发 2.2 的逻辑 + // noinspection ExceptionCaughtLocallyJS + throw new Error('刷新令牌失败'); + } + // 2.1 刷新成功,则回放队列的请求 + 当前请求 + config.header.Authorization = 'Bearer ' + getAccessToken() + requestList.forEach((cb) => { + cb() + }) + requestList = [] + return request(config) + } catch (e) { + // 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。 + // 2.2 刷新失败,只回放队列的请求 + requestList.forEach((cb) => { + cb() + }) + // 提示是否要登出。即不回放当前请求!不然会形成递归 + return handleAuthorized() + } finally { + requestList = [] + isRefreshToken = false + } + } else { + // 添加到队列,等待刷新获取到新的令牌 + return new Promise((resolve) => { + requestList.push(() => { + config.header.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改 + resolve(request(config)) + }) + }) + } +} + +/** + * 处理 401 未登录的错误 + */ +const handleAuthorized = () => { + const userStore = $store('user'); + userStore.logout(true); + showAuthModal(); + // 登录超时 + return Promise.reject({ + code: 401, + msg: userStore.isLogin ? '您的登陆已过期' : '请先登录' + }) +} + +/** 获得访问令牌 */ +const getAccessToken = () => { + return uni.getStorageSync('token'); +} + +/** 获得刷新令牌 */ +const getRefreshToken = () => { + return uni.getStorageSync('refresh-token'); +} + +const request = (config) => { + return http.middleware(config); +}; + +export default request; \ No newline at end of file diff --git a/sheep/router/index.js b/sheep/router/index.js new file mode 100644 index 0000000..86796e5 --- /dev/null +++ b/sheep/router/index.js @@ -0,0 +1,185 @@ +import $store from '@/sheep/store'; +import { showAuthModal, showShareModal } from '@/sheep/hooks/useModal'; +import { isNumber, isString, isEmpty, startsWith, isObject, isNil, clone } from 'lodash'; +import throttle from '@/sheep/helper/throttle'; + +const _go = ( + path, + params = {}, + options = { + redirect: false, + }, +) => { + let page = ''; // 跳转页面 + let query = ''; // 页面参数 + let url = ''; // 跳转页面完整路径 + + if (isString(path)) { + // 判断跳转类型是 path | 还是http + if (startsWith(path, 'http')) { + // #ifdef H5 + window.location = path; + return; + // #endif + // #ifndef H5 + page = `/pages/public/webview`; + query = `url=${encodeURIComponent(path)}`; + // #endif + } else if (startsWith(path, 'action:')) { + handleAction(path); + return; + } else { + [page, query] = path.split('?'); + } + if (!isEmpty(params)) { + let query2 = paramsToQuery(params); + if (isEmpty(query)) { + query = query2; + } else { + query += '&' + query2; + } + } + } + + if (isObject(path)) { + page = path.url; + if (!isNil(path.params)) { + query = paramsToQuery(path.params); + } + } + + const nextRoute = ROUTES_MAP[page]; + + // 未找到指定跳转页面 + // mark: 跳转404页 + if (!nextRoute) { + console.log(`%c跳转路径参数错误<${page || 'EMPTY'}>`, 'color:red;background:yellow'); + return; + } + + // 页面登录拦截 + if (nextRoute.meta?.auth && !$store('user').isLogin) { + showAuthModal(); + return; + } + + url = page; + if (!isEmpty(query)) { + url += `?${query}`; + } + + // 跳转底部导航 + if (TABBAR.includes(page)) { + uni.switchTab({ + url, + }); + return; + } + + // 使用redirect跳转 + if (options.redirect) { + uni.redirectTo({ + url, + }); + return; + } + + uni.navigateTo({ + url, + }); +}; + +// 限流 防止重复点击跳转 +function go(...args) { + throttle(() => { + _go(...args); + }); +} + +function paramsToQuery(params) { + if (isEmpty(params)) { + return ''; + } + // return new URLSearchParams(Object.entries(params)).toString(); + let query = []; + for (let key in params) { + query.push(key + '=' + params[key]); + } + + return query.join('&'); +} + +function back() { + // #ifdef H5 + history.back(); + // #endif + + // #ifndef H5 + uni.navigateBack(); + // #endif +} + +function redirect(path, params = {}) { + go(path, params, { + redirect: true, + }); +} + +// 检测是否有浏览器历史 +function hasHistory() { + // #ifndef H5 + const pages = getCurrentPages(); + if (pages.length > 1) { + return true; + } + return false; + // #endif + + // #ifdef H5 + return !!history.state.back; + // #endif +} + +function getCurrentRoute(field = '') { + let currentPage = getCurrentPage(); + // #ifdef MP + currentPage.$page['route'] = currentPage.route; + currentPage.$page['options'] = currentPage.options; + // #endif + if (field !== '') { + return currentPage.$page[field]; + } else { + return currentPage.$page; + } +} + +function getCurrentPage() { + let pages = getCurrentPages(); + return pages[pages.length - 1]; +} + +function handleAction(path) { + const action = path.split(':'); + switch (action[1]) { + case 'showShareModal': + showShareModal(); + break; + } +} + +function error(errCode, errMsg = '') { + redirect('/pages/public/error', { + errCode, + errMsg, + }); +} + +export default { + go, + back, + hasHistory, + redirect, + getCurrentPage, + getCurrentRoute, + error, +}; diff --git a/sheep/router/utils/strip-json-comments.js b/sheep/router/utils/strip-json-comments.js new file mode 100644 index 0000000..5995992 --- /dev/null +++ b/sheep/router/utils/strip-json-comments.js @@ -0,0 +1,79 @@ +const singleComment = Symbol('singleComment'); +const multiComment = Symbol('multiComment'); + +const stripWithoutWhitespace = () => ''; +const stripWithWhitespace = (string, start, end) => string.slice(start, end).replace(/\S/g, ' '); + +const isEscaped = (jsonString, quotePosition) => { + let index = quotePosition - 1; + let backslashCount = 0; + + while (jsonString[index] === '\\') { + index -= 1; + backslashCount += 1; + } + + return Boolean(backslashCount % 2); +}; + +export default function stripJsonComments(jsonString, { whitespace = true } = {}) { + if (typeof jsonString !== 'string') { + throw new TypeError( + `Expected argument \`jsonString\` to be a \`string\`, got \`${typeof jsonString}\``, + ); + } + + const strip = whitespace ? stripWithWhitespace : stripWithoutWhitespace; + + let isInsideString = false; + let isInsideComment = false; + let offset = 0; + let result = ''; + + for (let index = 0; index < jsonString.length; index++) { + const currentCharacter = jsonString[index]; + const nextCharacter = jsonString[index + 1]; + + if (!isInsideComment && currentCharacter === '"') { + const escaped = isEscaped(jsonString, index); + if (!escaped) { + isInsideString = !isInsideString; + } + } + + if (isInsideString) { + continue; + } + + if (!isInsideComment && currentCharacter + nextCharacter === '//') { + result += jsonString.slice(offset, index); + offset = index; + isInsideComment = singleComment; + index++; + } else if (isInsideComment === singleComment && currentCharacter + nextCharacter === '\r\n') { + index++; + isInsideComment = false; + result += strip(jsonString, offset, index); + offset = index; + continue; + } else if (isInsideComment === singleComment && currentCharacter === '\n') { + isInsideComment = false; + result += strip(jsonString, offset, index); + offset = index; + } else if (!isInsideComment && currentCharacter + nextCharacter === '/*') { + result += jsonString.slice(offset, index); + offset = index; + isInsideComment = multiComment; + index++; + continue; + } else if (isInsideComment === multiComment && currentCharacter + nextCharacter === '*/') { + index++; + isInsideComment = false; + result += strip(jsonString, offset, index + 1); + offset = index + 1; + continue; + } + } + + return result + (isInsideComment ? strip(jsonString.slice(offset)) : jsonString.slice(offset)); +} diff --git a/sheep/router/utils/uni-read-pages-v3.js b/sheep/router/utils/uni-read-pages-v3.js new file mode 100644 index 0000000..303f10a --- /dev/null +++ b/sheep/router/utils/uni-read-pages-v3.js @@ -0,0 +1,103 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { + value: true, +}); +const fs = require('fs'); +import stripJsonComments from './strip-json-comments'; +import { isArray, isEmpty } from 'lodash'; + +class TransformPages { + constructor({ includes, pagesJsonDir }) { + this.includes = includes; + this.uniPagesJSON = JSON.parse(stripJsonComments(fs.readFileSync(pagesJsonDir, 'utf-8'))); + this.routes = this.getPagesRoutes().concat(this.getSubPackagesRoutes()); + this.tabbar = this.getTabbarRoutes(); + this.routesMap = this.transformPathToKey(this.routes); + } + /** + * 通过读取pages.json文件 生成直接可用的routes + */ + getPagesRoutes(pages = this.uniPagesJSON.pages, rootPath = null) { + let routes = []; + for (let i = 0; i < pages.length; i++) { + const item = pages[i]; + let route = {}; + for (let j = 0; j < this.includes.length; j++) { + const key = this.includes[j]; + let value = item[key]; + if (key === 'path') { + value = rootPath ? `/${rootPath}/${value}` : `/${value}`; + } + if (key === 'aliasPath' && i == 0 && rootPath == null) { + route[key] = route[key] || '/'; + } else if (value !== undefined) { + route[key] = value; + } + } + routes.push(route); + } + return routes; + } + /** + * 解析小程序分包路径 + */ + getSubPackagesRoutes() { + if (!(this.uniPagesJSON && this.uniPagesJSON.subPackages)) { + return []; + } + const subPackages = this.uniPagesJSON.subPackages; + let routes = []; + for (let i = 0; i < subPackages.length; i++) { + const subPages = subPackages[i].pages; + const root = subPackages[i].root; + const subRoutes = this.getPagesRoutes(subPages, root); + routes = routes.concat(subRoutes); + } + return routes; + } + + getTabbarRoutes() { + if (!(this.uniPagesJSON && this.uniPagesJSON.tabBar && this.uniPagesJSON.tabBar.list)) { + return []; + } + const tabbar = this.uniPagesJSON.tabBar.list; + let tabbarMap = []; + tabbar.forEach((bar) => { + tabbarMap.push('/' + bar.pagePath); + }); + return tabbarMap; + } + + transformPathToKey(list) { + if (!isArray(list) || isEmpty(list)) { + return []; + } + let map = {}; + list.forEach((i) => { + map[i.path] = i; + }); + return map; + } +} + +function uniReadPagesV3Plugin({ pagesJsonDir, includes }) { + let defaultIncludes = ['path', 'aliasPath', 'name']; + includes = [...defaultIncludes, ...includes]; + let pages = new TransformPages({ + pagesJsonDir, + includes, + }); + return { + name: 'uni-read-pages-v3', + config(config) { + return { + define: { + ROUTES: pages.routes, + ROUTES_MAP: pages.routesMap, + TABBAR: pages.tabbar, + }, + }; + }, + }; +} +exports.default = uniReadPagesV3Plugin; diff --git a/sheep/scss/_main.scss b/sheep/scss/_main.scss new file mode 100644 index 0000000..999513a --- /dev/null +++ b/sheep/scss/_main.scss @@ -0,0 +1,354 @@ +body { + color: var(--text-a); + background-color: var(--ui-BG-1) !important; + font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', + sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; +} + +/* ================== + 初始化 + ==================== */ +.ui-link { + cursor: pointer; +} +navigator { + display: inline-flex; +} +navigator.navigator-hover { + background-color: inherit; + transform: translate(1rpx, 1rpx); + // opacity: 1; +} + +/* ================== + 辅助类 + ==================== */ +.none { + display: none !important; +} +.inline { + display: inline !important; +} +.inline-block { + display: inline-block !important; +} +.block { + display: block !important; +} +.touch-none { + pointer-events: none; +} +.touch-all { + pointer-events: all; +} +.flex { + display: flex !important; +} +.inline-flex { + display: inline-flex !important; +} +.w-100 { + width: 100%; +} +/* -- 浮动 -- */ +.cf::after, +.cf::before { + content: ''; + display: table; +} +.cf::after { + clear: both; +} +.fl { + float: left; +} +.fr { + float: right; +} +.position-center { + @include position-center; +} +.position-relative { + position: relative; +} +/* -- 工具类 -- */ +@function negativify-map($map) { + $result: (); + @each $key, $value in $map { + @if $key != 0 { + $result: map-merge($result, ('n' + $key: (-$value))); + } + } + @return $result; +} + +$utilities: () !default; +$utilities: map-merge( + ( + 'margin': ( + responsive: true, + property: margin, + class: m, + values: + map-merge( + $spacers, + ( + auto: auto, + ) + ), + ), + 'margin-x': ( + property: margin-right margin-left, + class: mx, + values: + map-merge( + $spacers, + ( + auto: auto, + ) + ), + ), + 'margin-y': ( + property: margin-top margin-bottom, + class: my, + values: + map-merge( + $spacers, + ( + auto: auto, + ) + ), + ), + 'margin-top': ( + property: margin-top, + class: mt, + values: + map-merge( + $spacers, + ( + auto: auto, + ) + ), + ), + 'margin-right': ( + property: margin-right, + class: mr, + values: + map-merge( + $spacers, + ( + auto: auto, + ) + ), + ), + 'margin-bottom': ( + property: margin-bottom, + class: mb, + values: + map-merge( + $spacers, + ( + auto: auto, + ) + ), + ), + 'margin-left': ( + property: margin-left, + class: ml, + values: + map-merge( + $spacers, + ( + auto: auto, + ) + ), + ), + 'padding': ( + responsive: true, + property: padding, + class: p, + values: $spacers, + ), + 'padding-x': ( + property: padding-right padding-left, + class: px, + values: $spacers, + ), + 'padding-y': ( + property: padding-top padding-bottom, + class: py, + values: $spacers, + ), + 'padding-top': ( + property: padding-top, + class: pt, + values: $spacers, + ), + 'padding-right': ( + property: padding-right, + class: pr, + values: $spacers, + ), + 'padding-bottom': ( + property: padding-bottom, + class: pb, + values: $spacers, + ), + 'padding-left': ( + property: padding-left, + class: pl, + values: $spacers, + ), + 'font-weight': ( + property: font-weight, + class: text, + values: ( + light: $font-weight-light, + lighter: $font-weight-lighter, + normal: $font-weight-normal, + bold: $font-weight-bold, + bolder: $font-weight-bolder, + ), + ), + 'text-align': ( + property: text-align, + class: text, + values: left right center, + ), + 'font-color': ( + property: color, + class: text, + values: + map-merge( + $colors, + map-merge( + $grays, + map-merge( + $darks, + ( + 'reset': inherit, + ) + ) + ) + ), + ), + 'line-height': ( + property: line-height, + class: lh, + values: ( + 1: 1, + sm: $line-height-sm, + base: $line-height-base, + lg: $line-height-lg, + ), + ), + 'white-space': ( + property: white-space, + class: text, + values: ( + nowrap: nowrap, + ), + ), + 'radius': ( + property: border-radius, + class: radius, + values: ( + null: $radius, + sm: $radius-sm, + lg: $radius-lg, + 0: 0, + ), + ), + 'round': ( + property: border-radius, + class: round, + values: ( + null: $round-pill, + circle: 50%, + ), + ), + 'radius-top': ( + property: border-top-left-radius border-top-right-radius, + class: radius-top, + values: ( + null: $radius, + ), + ), + 'radius-right': ( + property: border-top-right-radius border-bottom-right-radius, + class: radius-right, + values: ( + null: $radius, + ), + ), + 'radius-bottom': ( + property: border-bottom-right-radius border-bottom-left-radius, + class: radius-bottom, + values: ( + null: $radius, + ), + ), + 'radius-left': ( + property: border-bottom-left-radius border-top-left-radius, + class: radius-left, + values: ( + null: $radius, + ), + ), + 'radius-lr': ( + property: border-top-left-radius border-bottom-right-radius, + class: radius-lr, + values: ( + null: $radius, + ), + ), + 'radius-lrs': ( + property: border-top-right-radius border-bottom-left-radius, + class: radius-lr, + values: ( + null: 0, + ), + ), + 'radius-rl': ( + property: border-top-right-radius border-bottom-left-radius, + class: radius-rl, + values: ( + null: $radius, + ), + ), + 'radius-rls': ( + property: border-top-left-radius border-bottom-right-radius, + class: radius-rl, + values: ( + null: 0, + ), + ), + ), + $utilities +); +@each $key, $utility in $utilities { + @if type-of($utility) == 'map' { + $values: map-get($utility, values); + @if type-of($values) == 'string' or type-of(nth($values, 1)) != 'list' { + $values: zip($values, $values); + } + @each $key, $value in $values { + $properties: map-get($utility, property); + @if type-of($properties) == 'string' { + $properties: append((), $properties); + } + $property-class: if( + map-has-key($utility, class), + map-get($utility, class), + nth($properties, 1) + ); + $property-class: if($property-class == null, '', $property-class); + $property-class-modifier: if($key, if($property-class == '', '', '-') + $key, ''); + .#{$property-class + $property-class-modifier} { + @each $property in $properties { + #{$property}: $value !important; + } + } + } + } +} diff --git a/sheep/scss/_mixins.scss b/sheep/scss/_mixins.scss new file mode 100644 index 0000000..299f7b1 --- /dev/null +++ b/sheep/scss/_mixins.scss @@ -0,0 +1,61 @@ +@mixin bg-square { + background: { + color: #fff; + image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%), + linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%); + size: 40rpx 40rpx; + position: 0 0, 20rpx 20rpx; + } +} + +@mixin flex($direction: row) { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: $direction; +} +@mixin flex-bar { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; +} +@mixin flex-center { + display: flex; + align-items: center; + justify-content: center; +} + +@mixin arrow { + content: ''; + height: 0; + width: 0; + position: absolute; +} +@mixin arrow-top { + @include arrow; + // border-color: transparent transparent $ui-BG; + border-style: none solid solid; + border-width: 0 20rpx 20rpx; +} + +@mixin arrow-right { + @include arrow; + // border-color: transparent $ui-BG transparent; + border-style: solid solid solid none; + border-width: 20rpx 20rpx 20rpx 0; +} +@mixin position-center { + position: absolute !important; + top: 0; + right: 0; + bottom: 0; + left: 0; + margin: auto; +} + +@mixin blur { + -webkit-backdrop-filter: blur(20px); + backdrop-filter: blur(20px); + color: var(--ui-TC); +} diff --git a/sheep/scss/_tools.scss b/sheep/scss/_tools.scss new file mode 100644 index 0000000..e1fb636 --- /dev/null +++ b/sheep/scss/_tools.scss @@ -0,0 +1,286 @@ +/* ================== + 常用工具 + ==================== */ + +.ss-bg-opactity-block { + background-color: rgba(#000, 0.2); + color: #fff; +} + +/* ================== + flex布局 + ==================== */ + +.ss-flex { + display: flex; + flex-direction: row; + align-items: center; +} + +.ss-flex-1 { + flex: 1; +} + +.ss-flex-col { + display: flex; + flex-direction: column; +} + +.ss-flex-wrap { + flex-wrap: wrap; +} + +.ss-flex-nowrap { + flex-wrap: nowrap; +} + +.ss-col-center { + align-items: center; +} + +.ss-col-top { + align-items: flex-start; +} + +.ss-col-bottom { + align-items: flex-end; +} + +.ss-col-stretch { + align-items: stretch; +} + +.ss-row-center { + justify-content: center; +} + +.ss-row-left { + justify-content: flex-start; +} + +.ss-row-right { + justify-content: flex-end; +} + +.ss-row-between { + justify-content: space-between; +} + +.ss-row-around { + justify-content: space-around; +} + +.ss-self-start { + align-self: flex-start; +} + +.ss-self-end { + align-self: flex-end; +} + +.ss-self-center { + align-self: center; +} +.ss-h-100 { + height: 100%; +} +.ss-w-100 { + width: 100%; +} + +/* ================== + + margin padding: 内外边距 + + ==================== */ +@for $i from 0 through 100 { + // 只要双数和能被5除尽的数 + @if $i % 2==0 or $i % 5==0 { + // 得出:u-margin-30或者u-m-30 + .ss-margin-#{$i}, + .ss-m-#{$i} { + margin: $i + rpx; + } + .ss-m-x-#{$i} { + margin-left: $i + rpx; + margin-right: $i + rpx; + } + .ss-m-y-#{$i} { + margin-top: $i + rpx; + margin-bottom: $i + rpx; + } + + // 得出:u-padding-30或者u-p-30 + .ss-padding-#{$i}, + .ss-p-#{$i} { + padding: $i + rpx; + } + .ss-p-x-#{$i} { + padding-left: $i + rpx; + padding-right: $i + rpx; + } + .ss-p-y-#{$i} { + padding-top: $i + rpx; + padding-bottom: $i + rpx; + } + + @each $short, $long in l left, t top, r right, b bottom { + // 缩写版,结果如: u-m-l-30 + // 定义外边距 + .ss-m-#{$short}-#{$i} { + margin-#{$long}: $i + rpx; + } + + // 定义内边距 + .ss-p-#{$short}-#{$i} { + padding-#{$long}: $i + rpx; + } + + // 完整版,结果如:u-margin-left-30 + // 定义外边距 + .ss-margin-#{$long}-#{$i} { + margin-#{$long}: $i + rpx; + } + + // 定义内边距 + .ss-padding-#{$long}-#{$i} { + padding-#{$long}: $i + rpx; + } + } + } +} + +/* ================== + + radius + + ==================== */ +@for $i from 0 through 100 { + // 只要双数和能被5除尽的数 + @if $i % 2==0 or $i % 5==0 { + .ss-radius-#{$i}, + .ss-r-#{$i} { + border-radius: $i + rpx; + } + + .ss-r-t-#{$i} { + border-top-left-radius: $i + rpx; + border-top-right-radius: $i + rpx; + } + + .ss-r-b-#{$i} { + border-bottom-left-radius: $i + rpx; + border-bottom-right-radius: $i + rpx; + } + + @each $short, $long in tl 'top-left', tr 'top-right', bl 'bottom-right', br 'bottom-right' { + // 定义外边距 + .ss-r-#{$short}-#{$i} { + border-#{$long}-radius: $i + rpx; + } + + // 定义内边距 + .ss-radius-#{$long}-#{$i} { + border-#{$long}-radius: $i + rpx; + } + } + } +} + +/* ================== + + 溢出省略号 + @param {Number} 行数 + + ==================== */ +@mixin ellipsis($rowCount: 1) { + // @if $rowCount <=1 { + // overflow: hidden; + // text-overflow: ellipsis; + // white-space: nowrap; + // } @else { + // min-width: 0; + // overflow: hidden; + // text-overflow: ellipsis; + // display: -webkit-box; + // -webkit-line-clamp: $rowCount; + // -webkit-box-orient: vertical; + // } + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: $rowCount; + -webkit-box-orient: vertical; +} + +@for $i from 1 through 6 { + .ss-line-#{$i} { + @include ellipsis($i); + } +} + +/* ================== + hover + ==================== */ +.ss-hover-class { + background-color: $gray-c; + opacity: 0.6; +} +.ss-hover-btn { + transform: translate(1px, 1px); +} + +/* ================== + 底部安全区域 + ==================== */ + +.ss-safe-bottom { + padding-bottom: 0; + padding-bottom: calc(constant(safe-area-inset-bottom) / 5 * 3); + padding-bottom: calc(env(safe-area-inset-bottom) / 5 * 3); +} + +/* ================== + + 字体大小 + + ==================== */ + +@for $i from 20 through 50 { + .ss-font-#{$i} { + font-size: $i + rpx; + } +} + +/* ================== + 按钮 + ==================== */ +.ss-reset-button { + padding: 0; + margin: 0; + font-size: inherit; + background-color: transparent; + color: inherit; + position: relative; + border: 0rpx; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + align-items: center; + justify-content: center; + box-sizing: border-box; + text-align: center; + text-decoration: none; + white-space: nowrap; + vertical-align: baseline; + transform: translate(0, 0); +} +.ss-reset-button.button-hover { + transform: translate(1px, 1px); + background: none; +} + +.ss-reset-button::after { + border: none; +} diff --git a/sheep/scss/_var.scss b/sheep/scss/_var.scss new file mode 100644 index 0000000..b0d88c8 --- /dev/null +++ b/sheep/scss/_var.scss @@ -0,0 +1,162 @@ +@import './mixins'; + +//颜色 ,渐变背景60% +$yellow: #ffc300; //ss-黄 +$orange: #ff6000; //ss-橘 +$red: #ff3000; //ss-红 +$pink: #e03997; +$mauve: #b745cb; +$purple: #652abf; //rgba(101, 42, 191, 1); // ss-紫 +$blue: #0081ff; +$cyan: #37c0fe; +$green: #2aae67; //ss-绿 +$olive: #8dc63f; +$grey: #8799a3; +$brown: #a5673f; +$black: #484848; //ss-黑 +$golden: #e9b461; //ss-金 + +$colors: (); +$colors: map-merge( + ( + 'yellow': $yellow, + 'orange': $orange, + 'red': $red, + 'pink': $pink, + 'mauve': $mauve, + 'purple': $purple, + 'violet': $purple, + 'blue': $blue, + 'cyan': $cyan, + 'green': $green, + 'olive': $olive, + 'grey': $grey, + 'brown': $brown, + 'black': $black, + 'golden': $golden, + ), + $colors +); + +//灰度 +$bg-page: #f6f6f6; +$white: #ffffff; +$gray-f: #f8f9fa; +$gray-e: #eeeeee; +$gray-d: #dddddd; +$gray-c: #cccccc; +$gray-b: #bbbbbb; +$gray-a: #aaaaaa; +$dark-9: #999999; +$dark-8: #888888; +$dark-7: #777777; +$dark-6: #666666; +$dark-5: #555555; +$dark-4: #484848; //ss-黑 +$dark-3: #333333; +$dark-2: #222222; +$dark-1: #111111; +$black: #000000; + +$grays: (); +$grays: map-merge( + ( + 'white': $white, + 'gray-f': $gray-f, + 'gray-e': $gray-e, + 'gray-d': $gray-d, + 'gray-c': $gray-c, + 'gray-b': $gray-b, + 'gray-a': $gray-a, + 'gray': $gray-a, + ), + $grays +); + +$darks: (); +$darks: map-merge( + ( + 'dark-9': $dark-9, + 'dark-8': $dark-8, + 'dark-7': $dark-7, + 'dark-6': $dark-6, + 'dark-5': $dark-5, + 'dark-4': $dark-4, + 'dark-3': $dark-3, + 'dark-2': $dark-2, + 'dark-1': $dark-1, + 'black': $black, + ), + $darks +); + +// 边框 +$border-width: 1rpx !default; // 边框大小 +$border-color: $gray-d !default; // 边框颜色 + +// 圆角 +$radius: 10rpx !default; // 默认圆角大小 +$radius-lg: 40rpx !default; // 大圆角 +$radius-sm: 6rpx !default; // 小圆角 +$round-pill: 1000rpx !default; // 半圆 + +// 动画过渡 +$transition-base: all 0.2s ease-in-out !default; // 默认过渡 +$transition-base-out: all 0.04s ease-in-out !default; // 进场过渡 +$transition-fade: opacity 0.15s linear !default; // 透明过渡 +$transition-collapse: height 0.35s ease !default; // 收缩过渡 + +// 间距 +$spacer: 20rpx !default; +$spacers: () !default; +$spacers: map-merge( + ( + 0: 0, + 1: $spacer * 0.25, + 2: $spacer * 0.5, + 3: $spacer, + 4: $spacer * 1.5, + 5: $spacer * 3, + 6: $spacer * 5, + ), + $spacers +); +// 字形 +$font-weight-lighter: lighter !default; +$font-weight-light: 300 !default; +$font-weight-normal: 400 !default; +$font-weight-bold: 700 !default; +$font-weight-bolder: 900 !default; +$fontsize: () !default; +$fontsize: map-merge( + ( + xs: 20, + sm: 24, + df: 28, + lg: 32, + xl: 36, + xxl: 44, + sl: 80, + xsl: 120, + ), + $fontsize +); +// 段落 +$line-height-base: 1.5 !default; +$line-height-lg: 2 !default; +$line-height-sm: 1.25 !default; +// 图标 +$iconsize: () !default; +$iconsize: map-merge( + ( + xs: 0.5, + sm: 0.75, + df: 1, + lg: 1.25, + xl: 1.5, + xxl: 2, + sl: 6, + xsl: 10, + ), + $iconsize +); diff --git a/sheep/scss/font/OPPOSANS-M-subfont.ttf b/sheep/scss/font/OPPOSANS-M-subfont.ttf new file mode 100644 index 0000000..88ff835 Binary files /dev/null and b/sheep/scss/font/OPPOSANS-M-subfont.ttf differ diff --git a/sheep/scss/icon/_coloricon.scss b/sheep/scss/icon/_coloricon.scss new file mode 100644 index 0000000..f391ca4 --- /dev/null +++ b/sheep/scss/icon/_coloricon.scss @@ -0,0 +1,1340 @@ +@font-face { + font-family: 'coloricon'; + src: url('data:application/x-font-woff2;charset=utf-8;base64,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') + format('woff2'); + /* #ifdef MP-ALIPAY */ + src: url('//at.alicdn.com/t/font_1656945_d66u4pxvlq6.woff2') format('woff2'), + url('//at.alicdn.com/t/font_1656945_d66u4pxvlq6.woff') format('woff'), + url('//at.alicdn.com/t/font_1656945_d66u4pxvlq6.ttf') format('truetype'); + /* #endif */ + font-weight: normal; + font-style: normal; +} +[class*='cicon-'] { + font-family: 'coloricon'; + display: inline-block; +} + +.cicon-Aa:before { + content: '\e7a1'; +} +.cicon-accounts:before { + content: '\e681'; +} +.cicon-accounts-o:before { + content: '\e686'; +} +.cicon-add:before { + content: '\e6e4'; +} +.cicon-add-round:before { + content: '\e717'; +} +.cicon-add-round-o:before { + content: '\e718'; +} +.cicon-alarm:before { + content: '\e61e'; +} +.cicon-album:before { + content: '\e759'; +} +.cicon-alipay:before { + content: '\e6e1'; +} +.cicon-android:before { + content: '\e6e2'; +} +.cicon-angle:before { + content: '\e605'; +} +.cicon-apple:before { + content: '\e8e7'; +} +.cicon-apps:before { + content: '\e737'; +} +.cicon-archive:before { + content: '\e7ae'; +} +.cicon-archive-o:before { + content: '\e7ad'; +} +.cicon-arrow:before { + content: '\e608'; +} +.cicon-at-line:before { + content: '\e75c'; +} +.cicon-avatar:before { + content: '\e663'; +} +.cicon-avatar-o:before { + content: '\e665'; +} +.cicon-avatars:before { + content: '\e67e'; +} +.cicon-avatars-o:before { + content: '\e680'; +} +.cicon-back:before { + content: '\e600'; +} +.cicon-backspace:before { + content: '\e6a9'; +} +.cicon-backup:before { + content: '\e61f'; +} +.cicon-backup-restore:before { + content: '\e62d'; +} +.cicon-barcode:before { + content: '\e71f'; +} +.cicon-book:before { + content: '\e6a2'; +} +.cicon-bookmark:before { + content: '\e6a3'; +} +.cicon-bookmark-o:before { + content: '\e697'; +} +.cicon-bookmarks:before { + content: '\e6a6'; +} +.cicon-box:before { + content: '\e714'; +} +.cicon-box-block:before { + content: '\e6ac'; +} +.cicon-box-right:before { + content: '\e6a0'; +} +.cicon-brand:before { + content: '\e726'; +} +.cicon-brand-o:before { + content: '\e727'; +} +.cicon-building:before { + content: '\e6c3'; +} +.cicon-building-o:before { + content: '\e6c7'; +} +.cicon-camera:before { + content: '\e6fa'; +} +.cicon-camera-add:before { + content: '\e736'; +} +.cicon-camera-add-o:before { + content: '\e735'; +} +.cicon-camera-lens:before { + content: '\e68f'; +} +.cicon-camera-lens-o:before { + content: '\e68e'; +} +.cicon-camera-o:before { + content: '\e6fb'; +} +.cicon-camera-rotate:before { + content: '\e71e'; +} +.cicon-card:before { + content: '\e744'; +} +.cicon-cardboard:before { + content: '\e7a9'; +} +.cicon-cardboard-o:before { + content: '\e7aa'; +} +.cicon-cardboard-off-o:before { + content: '\e7af'; +} +.cicon-cart:before { + content: '\e70b'; +} +.cicon-cart-o:before { + content: '\e708'; +} +.cicon-chat:before { + content: '\e739'; +} +.cicon-chat-bubble:before { + content: '\e69b'; +} +.cicon-chat-bubble-o:before { + content: '\e6a7'; +} +.cicon-chat-list:before { + content: '\e69d'; +} +.cicon-chat-list-o:before { + content: '\e6aa'; +} +.cicon-chat-o:before { + content: '\e73c'; +} +.cicon-chat-smile:before { + content: '\e779'; +} +.cicon-chat-smile-o:before { + content: '\e78e'; +} +.cicon-chat-smiles:before { + content: '\e76b'; +} +.cicon-chat-smiles-o:before { + content: '\e74a'; +} +.cicon-check:before { + content: '\e69f'; +} +.cicon-checkbox:before { + content: '\e713'; +} +.cicon-checkbox-o:before { + content: '\e715'; +} +.cicon-check-round:before { + content: '\e6f1'; +} +.cicon-check-round-o:before { + content: '\e6f2'; +} +.cicon-choiceness:before { + content: '\e728'; +} +.cicon-choiceness-o:before { + content: '\e729'; +} +.cicon-chrome:before { + content: '\e6e3'; +} +.cicon-circle:before { + content: '\e7b0'; +} +.cicon-circle-o:before { + content: '\e7b1'; +} +.cicon-close:before { + content: '\e6ed'; +} +.cicon-close-round:before { + content: '\e6f3'; +} +.cicon-close-round-o:before { + content: '\e6f4'; +} +.cicon-clothes:before { + content: '\e72a'; +} +.cicon-clothes-o:before { + content: '\e72b'; +} +.cicon-cloud:before { + content: '\e64e'; +} +.cicon-cloud-done:before { + content: '\e641'; +} +.cicon-cloud-download:before { + content: '\e647'; +} +.cicon-cloud-o:before { + content: '\e646'; +} +.cicon-cloud-off:before { + content: '\e64b'; +} +.cicon-cloud-upload:before { + content: '\e687'; +} +.cicon-code-box:before { + content: '\e7c3'; +} +.cicon-coin:before { + content: '\e78a'; +} +.cicon-coin-o:before { + content: '\e79d'; +} +.cicon-comment:before { + content: '\e738'; +} +.cicon-comment-o:before { + content: '\e70e'; +} +.cicon-community:before { + content: '\e742'; +} +.cicon-community-o:before { + content: '\e743'; +} +.cicon-countdown:before { + content: '\e722'; +} +.cicon-countdown-o:before { + content: '\e723'; +} +.cicon-creative:before { + content: '\e72c'; +} +.cicon-creative-o:before { + content: '\e72d'; +} +.cicon-crop:before { + content: '\e6d9'; +} +.cicon-crown:before { + content: '\e776'; +} +.cicon-crown-o:before { + content: '\e777'; +} +.cicon-cut:before { + content: '\e74b'; +} +.cicon-DarkMode:before { + content: '\e7c4'; +} +.cicon-dashboard:before { + content: '\e62e'; +} +.cicon-delete:before { + content: '\e6bd'; +} +.cicon-delete-close:before { + content: '\e6ae'; +} +.cicon-delete-line:before { + content: '\e707'; +} +.cicon-delete-line-o:before { + content: '\e709'; +} +.cicon-delete-o:before { + content: '\e69a'; +} +.cicon-deliver:before { + content: '\e7f7'; +} +.cicon-deliver-o:before { + content: '\e6ff'; +} +.cicon-demo:before { + content: '\e916'; +} +.cicon-discover:before { + content: '\e70c'; +} +.cicon-discover-o:before { + content: '\e702'; +} +.cicon-discuss-fill:before { + content: '\e790'; +} +.cicon-discuss-line:before { + content: '\e78f'; +} +.cicon-dollar:before { + content: '\e79f'; +} +.cicon-dollar-o:before { + content: '\e79e'; +} +.cicon-done:before { + content: '\e633'; +} +.cicon-done-all:before { + content: '\e62a'; +} +.cicon-douyin:before { + content: '\e6e7'; +} +.cicon-drop-down:before { + content: '\e61c'; +} +.cicon-drop-up:before { + content: '\e61d'; +} +.cicon-eject:before { + content: '\e63a'; +} +.cicon-ellipse:before { + content: '\e74c'; +} +.cicon-emoji:before { + content: '\e78d'; +} +.cicon-emoji-o:before { + content: '\e6ee'; +} +.cicon-equalizer:before { + content: '\e802'; +} +.cicon-eraser:before { + content: '\e770'; +} +.cicon-eraser-o:before { + content: '\e772'; +} +.cicon-evaluate:before { + content: '\e7f0'; +} +.cicon-evaluate-o:before { + content: '\e700'; +} +.cicon-event-close:before { + content: '\e6a5'; +} +.cicon-event-done:before { + content: '\e6b2'; +} +.cicon-event-list:before { + content: '\e6b8'; +} +.cicon-explore:before { + content: '\e628'; +} +.cicon-explore-line:before { + content: '\e719'; +} +.cicon-explore-line-o:before { + content: '\e710'; +} +.cicon-explore-o:before { + content: '\e626'; +} +.cicon-extension:before { + content: '\e620'; +} +.cicon-extension-o:before { + content: '\e63f'; +} +.cicon-eye:before { + content: '\e740'; +} +.cicon-eye-favor:before { + content: '\e7b4'; +} +.cicon-eye-favor-o:before { + content: '\e7b5'; +} +.cicon-eye-o:before { + content: '\e741'; +} +.cicon-eye-off:before { + content: '\e7b3'; +} +.cicon-eye-off-o:before { + content: '\e7b2'; +} +.cicon-facebook:before { + content: '\e6ea'; +} +.cicon-favorite:before { + content: '\e623'; +} +.cicon-favorite-o:before { + content: '\e621'; +} +.cicon-female:before { + content: '\e72f'; +} +.cicon-file:before { + content: '\e857'; +} +.cicon-file-copy:before { + content: '\e85c'; +} +.cicon-file-copy-o:before { + content: '\e7bc'; +} +.cicon-file-o:before { + content: '\e7bb'; +} +.cicon-file-text:before { + content: '\e858'; +} +.cicon-file-text-o:before { + content: '\e7b9'; +} +.cicon-filter:before { + content: '\e6ec'; +} +.cicon-fingerprint:before { + content: '\e63b'; +} +.cicon-first-page:before { + content: '\e60c'; +} +.cicon-flag:before { + content: '\e64d'; +} +.cicon-flag-o:before { + content: '\e64c'; +} +.cicon-flash-close:before { + content: '\e73b'; +} +.cicon-flash-off:before { + content: '\e6d5'; +} +.cicon-flash-on:before { + content: '\e6dc'; +} +.cicon-flash-open:before { + content: '\e74f'; +} +.cicon-folder:before { + content: '\e6a1'; +} +.cicon-folder-add:before { + content: '\e6b4'; +} +.cicon-folder-o:before { + content: '\e6b0'; +} +.cicon-folder-special:before { + content: '\e65c'; +} +.cicon-forward:before { + content: '\e601'; +} +.cicon-fullscreen:before { + content: '\e915'; +} +.cicon-fullscreen-exit:before { + content: '\e914'; +} +.cicon-game:before { + content: '\e6c0'; +} +.cicon-game-o:before { + content: '\e6d1'; +} +.cicon-git-commit:before { + content: '\e7be'; +} +.cicon-git-commit-o:before { + content: '\e7bd'; +} +.cicon-github:before { + content: '\e6e9'; +} +.cicon-github-circle:before { + content: '\ead8'; +} +.cicon-goods:before { + content: '\e778'; +} +.cicon-goodsnew:before { + content: '\e7bf'; +} +.cicon-goodsnew-o:before { + content: '\e7c0'; +} +.cicon-goods-o:before { + content: '\e70f'; +} +.cicon-GooglePlaylogo:before { + content: '\e6e5'; +} +.cicon-grid:before { + content: '\e6ce'; +} +.cicon-grid-o:before { + content: '\e6cc'; +} +.cicon-group:before { + content: '\e7f5'; +} +.cicon-group-o:before { + content: '\e753'; +} +.cicon-guanli:before { + content: '\e750'; +} +.cicon-headset:before { + content: '\e6a4'; +} +.cicon-headset-mic:before { + content: '\e6b1'; +} +.cicon-help:before { + content: '\e66b'; +} +.cicon-help-o:before { + content: '\e65e'; +} +.cicon-home:before { + content: '\e70d'; +} +.cicon-home-2:before { + content: '\e6fd'; +} +.cicon-home-2-o:before { + content: '\e6cf'; +} +.cicon-home-3:before { + content: '\e6fc'; +} +.cicon-home-3-o:before { + content: '\e6e0'; +} +.cicon-home-4:before { + content: '\e732'; +} +.cicon-home-4-o:before { + content: '\e6e6'; +} +.cicon-home-community:before { + content: '\e799'; +} +.cicon-home-dot:before { + content: '\e794'; +} +.cicon-home-dot-o:before { + content: '\e797'; +} +.cicon-home-line:before { + content: '\e793'; +} +.cicon-home-line-o:before { + content: '\e792'; +} +.cicon-home-o:before { + content: '\e70a'; +} +.cicon-home-sm:before { + content: '\e798'; +} +.cicon-home-smile:before { + content: '\e79c'; +} +.cicon-home-smile-o:before { + content: '\e7a0'; +} +.cicon-home-smline:before { + content: '\e791'; +} +.cicon-home-smline-o:before { + content: '\e731'; +} +.cicon-home-sm-o:before { + content: '\e79b'; +} +.cicon-hotel:before { + content: '\e7a8'; +} +.cicon-hotel-o:before { + content: '\e7a3'; +} +.cicon-huohu:before { + content: '\e72e'; +} +.cicon-IE:before { + content: '\e922'; +} +.cicon-image-text:before { + content: '\e781'; +} +.cicon-image-text-o:before { + content: '\e758'; +} +.cicon-import-export:before { + content: '\e615'; +} +.cicon-info:before { + content: '\e6ef'; +} +.cicon-info-o:before { + content: '\e705'; +} +.cicon-input:before { + content: '\e75f'; +} +.cicon-input-o:before { + content: '\e6c8'; +} +.cicon-keyboard:before { + content: '\e6b6'; +} +.cicon-kinds:before { + content: '\e748'; +} +.cicon-last-page:before { + content: '\e60d'; +} +.cicon-layout:before { + content: '\e7e8'; +} +.cicon-layout-o:before { + content: '\e7e7'; +} +.cicon-LightMode:before { + content: '\e7ba'; +} +.cicon-link:before { + content: '\e6ab'; +} +.cicon-link-off:before { + content: '\e6b9'; +} +.cicon-loader-fill:before { + content: '\e76d'; +} +.cicon-loading:before { + content: '\e746'; +} +.cicon-loading1:before { + content: '\e749'; +} +.cicon-loading2:before { + content: '\e7f1'; +} +.cicon-location-off:before { + content: '\e671'; +} +.cicon-location-off-o:before { + content: '\e66d'; +} +.cicon-location-on:before { + content: '\e65f'; +} +.cicon-location-on-o:before { + content: '\e661'; +} +.cicon-lock:before { + content: '\e6ad'; +} +.cicon-lock-o:before { + content: '\e6b3'; +} +.cicon-lock-open:before { + content: '\e6ba'; +} +.cicon-logout:before { + content: '\e76e'; +} +.cicon-loop:before { + content: '\e616'; +} +.cicon-magic:before { + content: '\e6b7'; +} +.cicon-magic-o:before { + content: '\e6c2'; +} +.cicon-mail:before { + content: '\e6be'; +} +.cicon-mail-o:before { + content: '\e6bc'; +} +.cicon-male:before { + content: '\e730'; +} +.cicon-mic:before { + content: '\e656'; +} +.cicon-mic-none:before { + content: '\e642'; +} +.cicon-mic-off:before { + content: '\e652'; +} +.cicon-miniprogram:before { + content: '\e7d6'; +} +.cicon-mobile:before { + content: '\e854'; +} +.cicon-mobile-o:before { + content: '\e7b6'; +} +.cicon-moneybag:before { + content: '\e7ce'; +} +.cicon-moneybag-o:before { + content: '\e7d1'; +} +.cicon-more:before { + content: '\e688'; +} +.cicon-more-tag:before { + content: '\e672'; +} +.cicon-move:before { + content: '\e768'; +} +.cicon-move-round:before { + content: '\e602'; +} +.cicon-move-round-o:before { + content: '\e603'; +} +.cicon-music:before { + content: '\e795'; +} +.cicon-music-off:before { + content: '\e796'; +} +.cicon-my:before { + content: '\e78c'; +} +.cicon-my-o:before { + content: '\e78b'; +} +.cicon-near-me:before { + content: '\e654'; +} +.cicon-near-me-o:before { + content: '\e649'; +} +.cicon-not:before { + content: '\e667'; +} +.cicon-notice:before { + content: '\e666'; +} +.cicon-notice-active:before { + content: '\e66f'; +} +.cicon-notice-active-o:before { + content: '\e65d'; +} +.cicon-notice-o:before { + content: '\e664'; +} +.cicon-notice-off:before { + content: '\e6b5'; +} +.cicon-notice-off-o:before { + content: '\e6bb'; +} +.cicon-numcode:before { + content: '\e755'; +} +.cicon-order:before { + content: '\e786'; +} +.cicon-order-o:before { + content: '\e7b8'; +} +.cicon-paint:before { + content: '\e75d'; +} +.cicon-paint-o:before { + content: '\e75a'; +} +.cicon-palette:before { + content: '\e696'; +} +.cicon-palette-o:before { + content: '\e691'; +} +.cicon-pause:before { + content: '\e669'; +} +.cicon-pause-circle:before { + content: '\e678'; +} +.cicon-person:before { + content: '\e679'; +} +.cicon-person-add:before { + content: '\e668'; +} +.cicon-person-add-o:before { + content: '\e66a'; +} +.cicon-person-o:before { + content: '\e67d'; +} +.cicon-person-pin-circle:before { + content: '\e66c'; +} +.cicon-person-pin-circle-o:before { + content: '\e670'; +} +.cicon-phone:before { + content: '\e6f0'; +} +.cicon-phone-call:before { + content: '\e6d7'; +} +.cicon-pic:before { + content: '\e756'; +} +.cicon-pic-o:before { + content: '\e69e'; +} +.cicon-pin-drop:before { + content: '\e648'; +} +.cicon-pin-drop-o:before { + content: '\e655'; +} +.cicon-place:before { + content: '\e651'; +} +.cicon-place-o:before { + content: '\e650'; +} +.cicon-play-arrow:before { + content: '\e66e'; +} +.cicon-play-circle:before { + content: '\e674'; +} +.cicon-play-circle-o:before { + content: '\e67f'; +} +.cicon-popover:before { + content: '\e74e'; +} +.cicon-popover-o:before { + content: '\e757'; +} +.cicon-present:before { + content: '\e73a'; +} +.cicon-present-o:before { + content: '\e711'; +} +.cicon-progress:before { + content: '\e784'; +} +.cicon-qq:before { + content: '\e7d9'; +} +.cicon-qr-code-fill:before { + content: '\e767'; +} +.cicon-qr-code-line:before { + content: '\e75e'; +} +.cicon-quill:before { + content: '\e760'; +} +.cicon-quill-o:before { + content: '\e761'; +} +.cicon-radio:before { + content: '\e6d4'; +} +.cicon-radiobox:before { + content: '\e763'; +} +.cicon-radiobox-o:before { + content: '\e75b'; +} +.cicon-recharge:before { + content: '\e71c'; +} +.cicon-recharge-o:before { + content: '\e71d'; +} +.cicon-record:before { + content: '\e7a4'; +} +.cicon-record-o:before { + content: '\e7a6'; +} +.cicon-redo:before { + content: '\e612'; +} +.cicon-redpacket:before { + content: '\e7d3'; +} +.cicon-redpacket-o:before { + content: '\e71a'; +} +.cicon-refresh:before { + content: '\e611'; +} +.cicon-repair:before { + content: '\e73f'; +} +.cicon-repair-o:before { + content: '\e73e'; +} +.cicon-repeat:before { + content: '\e617'; +} +.cicon-replay:before { + content: '\e619'; +} +.cicon-reply:before { + content: '\e618'; +} +.cicon-reply-all:before { + content: '\e614'; +} +.cicon-road-map:before { + content: '\e769'; +} +.cicon-road-map-o:before { + content: '\e76a'; +} +.cicon-round:before { + content: '\e716'; +} +.cicon-round-angle:before { + content: '\e6f5'; +} +.cicon-round-angle-o:before { + content: '\e6f6'; +} +.cicon-round-arrow-line:before { + content: '\e734'; +} +.cicon-round-box:before { + content: '\e604'; +} +.cicon-safe:before { + content: '\e77f'; +} +.cicon-safe-check:before { + content: '\e875'; +} +.cicon-safe-check-o:before { + content: '\e876'; +} +.cicon-safe-flash:before { + content: '\e783'; +} +.cicon-safe-flash-o:before { + content: '\e775'; +} +.cicon-safe-key:before { + content: '\e76c'; +} +.cicon-safe-key-o:before { + content: '\e766'; +} +.cicon-safe-o:before { + content: '\e77e'; +} +.cicon-save:before { + content: '\e677'; +} +.cicon-save-o:before { + content: '\e684'; +} +.cicon-scan:before { + content: '\e703'; +} +.cicon-scissors:before { + content: '\e762'; +} +.cicon-search:before { + content: '\e6f7'; +} +.cicon-search-line:before { + content: '\e771'; +} +.cicon-searchlist:before { + content: '\e720'; +} +.cicon-search-o:before { + content: '\e782'; +} +.cicon-search-sm:before { + content: '\e631'; +} +.cicon-service:before { + content: '\e73d'; +} +.cicon-service-fill:before { + content: '\e704'; +} +.cicon-service-o:before { + content: '\e721'; +} +.cicon-set:before { + content: '\e773'; +} +.cicon-set-list:before { + content: '\e76f'; +} +.cicon-set-o:before { + content: '\e774'; +} +.cicon-settings:before { + content: '\e77a'; +} +.cicon-settings-o:before { + content: '\e780'; +} +.cicon-share:before { + content: '\e6c5'; +} +.cicon-share-line-o:before { + content: '\e74d'; +} +.cicon-shengji:before { + content: '\e747'; +} +.cicon-shopping-cart:before { + content: '\e685'; +} +.cicon-shopping-cart-o:before { + content: '\e676'; +} +.cicon-show:before { + content: '\e785'; +} +.cicon-show-o:before { + content: '\e787'; +} +.cicon-shuffle:before { + content: '\e61a'; +} +.cicon-sip:before { + content: '\e764'; +} +.cicon-sip-o:before { + content: '\e765'; +} +.cicon-skip-next:before { + content: '\e6dd'; +} +.cicon-skip-previous:before { + content: '\e6d6'; +} +.cicon-slack:before { + content: '\e87b'; +} +.cicon-slack-square:before { + content: '\e891'; +} +.cicon-sort:before { + content: '\e6bf'; +} +.cicon-sort-order:before { + content: '\e6fe'; +} +.cicon-sound:before { + content: '\e77b'; +} +.cicon-sponsor:before { + content: '\e77c'; +} +.cicon-sponsor-o:before { + content: '\e77d'; +} +.cicon-star:before { + content: '\e683'; +} +.cicon-star-half:before { + content: '\e67c'; +} +.cicon-star-o:before { + content: '\e67b'; +} +.cicon-stock:before { + content: '\e789'; +} +.cicon-stop:before { + content: '\e6db'; +} +.cicon-store:before { + content: '\e7ac'; +} +.cicon-store-0:before { + content: '\e7ab'; +} +.cicon-store-2:before { + content: '\e7a7'; +} +.cicon-store-2-o:before { + content: '\e7a5'; +} +.cicon-sub-left:before { + content: '\e60b'; +} +.cicon-sub-right:before { + content: '\e60f'; +} +.cicon-subtitles:before { + content: '\e6da'; +} +.cicon-subtitles-o:before { + content: '\e6d8'; +} +.cicon-sync-alt:before { + content: '\e613'; +} +.cicon-tag:before { + content: '\e751'; +} +.cicon-tag-o:before { + content: '\e752'; +} +.cicon-taobao:before { + content: '\e712'; +} +.cicon-terminal:before { + content: '\e7c1'; +} +.cicon-terminal-o:before { + content: '\e7c2'; +} +.cicon-thumb-down:before { + content: '\e6c1'; +} +.cicon-thumb-down-o:before { + content: '\e6c9'; +} +.cicon-thumb-up:before { + content: '\e6c6'; +} +.cicon-thumb-up-line:before { + content: '\e71b'; +} +.cicon-thumb-up-line-o:before { + content: '\e6eb'; +} +.cicon-thumb-up-o:before { + content: '\e6cb'; +} +.cicon-ticket:before { + content: '\e800'; +} +.cicon-ticket-o:before { + content: '\e701'; +} +.cicon-time:before { + content: '\e6f8'; +} +.cicon-time-o:before { + content: '\e6f9'; +} +.cicon-timer:before { + content: '\e69c'; +} +.cicon-title:before { + content: '\e82f'; +} +.cicon-titles:before { + content: '\e745'; +} +.cicon-toggle:before { + content: '\e706'; +} +.cicon-toggle-o:before { + content: '\e733'; +} +.cicon-topbar:before { + content: '\e788'; +} +.cicon-translate:before { + content: '\e79a'; +} +.cicon-tree:before { + content: '\e659'; +} +.cicon-Tt:before { + content: '\e7a2'; +} +.cicon-twiter:before { + content: '\e6e8'; +} +.cicon-cicon-community-o:before { + content: '\e6df'; +} +.cicon-undo:before { + content: '\e61b'; +} +.cicon-unfold-less:before { + content: '\e60e'; +} +.cicon-unfold-more:before { + content: '\e609'; +} +.cicon-upstage:before { + content: '\e724'; +} +.cicon-upstage-o:before { + content: '\e725'; +} +.cicon-view-agenda:before { + content: '\e639'; +} +.cicon-view-array:before { + content: '\e636'; +} +.cicon-view-carousel:before { + content: '\e638'; +} +.cicon-view-column:before { + content: '\e632'; +} +.cicon-view-day:before { + content: '\e627'; +} +.cicon-view-headline:before { + content: '\e62b'; +} +.cicon-view-list:before { + content: '\e63c'; +} +.cicon-view-module:before { + content: '\e629'; +} +.cicon-view-quilt:before { + content: '\e630'; +} +.cicon-volume:before { + content: '\e6c4'; +} +.cicon-volume-off:before { + content: '\e6cd'; +} +.cicon-warn:before { + content: '\e662'; +} +.cicon-warn-o:before { + content: '\e675'; +} +.cicon-wechat-pay:before { + content: '\e7e6'; +} +.cicon-weibo-fill:before { + content: '\e7e4'; +} +.cicon-weibo-o:before { + content: '\e7e3'; +} +.cicon-weixin:before { + content: '\e6de'; +} +.cicon-whatshot:before { + content: '\e6ca'; +} +.cicon-whatshot-o:before { + content: '\e6d0'; +} +.cicon-wifi:before { + content: '\e6d2'; +} +.cicon-wifi-off:before { + content: '\e6d3'; +} +.cicon-yamaxun:before { + content: '\e7b7'; +} +.cicon-zuoji:before { + content: '\e754'; +} diff --git a/sheep/scss/icon/_icon.scss b/sheep/scss/icon/_icon.scss new file mode 100644 index 0000000..f277fc8 --- /dev/null +++ b/sheep/scss/icon/_icon.scss @@ -0,0 +1,181 @@ +@font-face { + font-family: 'colorui'; /* Project id 2620914 */ + src: url('data:application/x-font-woff2;charset=utf-8;base64,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') + format('woff2'); + /* #ifdef MP-ALIPAY */ + src: url('//at.alicdn.com/t/font_2620914_57y9q5zpbel.woff?t=1624238023908') format('woff'), + url('//at.alicdn.com/t/font_2620914_57y9q5zpbel.ttf?t=1624238023908') format('truetype'); + /* #endif */ +} + +[class*='_icon-'] { + font-family: 'colorui' !important; + display: inline-block; +} +@font-face { + font-family: 'ui-num'; + src: url('data:application/x-font-ttf;base64,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') + format('woff2'); + font-weight: normal; + font-style: normal; +} + +._icon-checkbox:before { + content: '\e713'; +} + +._icon-box:before { + content: '\e714'; +} + +._icon-checkbox-o:before { + content: '\e715'; +} + +._icon-round:before { + content: '\e716'; +} + +._icon-home-o:before { + content: '\e70a'; +} + +._icon-home:before { + content: '\e70d'; +} + +._icon-edit:before { + content: '\e649'; +} + +._icon-close:before { + content: '\e6ed'; +} + +._icon-check-round:before { + content: '\e6f1'; +} + +._icon-check-round-o:before { + content: '\e6f2'; +} + +._icon-close-round:before { + content: '\e6f3'; +} + +._icon-close-round-o:before { + content: '\e6f4'; +} + +._icon-waiting:before { + content: '\e6f8'; +} + +._icon-waiting-o:before { + content: '\e6f9'; +} + +._icon-warn:before { + content: '\e662'; +} + +._icon-warn-o:before { + content: '\e675'; +} + +._icon-more:before { + content: '\e688'; +} + +._icon-delete:before { + content: '\e707'; +} + +._icon-delete-o:before { + content: '\e709'; +} + +._icon-add-round:before { + content: '\e717'; +} + +._icon-add-round-o:before { + content: '\e718'; +} + +._icon-add:before { + content: '\e6e4'; +} + +._icon-info:before { + content: '\e6ef'; +} + +._icon-info-o:before { + content: '\e705'; +} + +._icon-move:before { + content: '\e768'; +} + +._icon-title:before { + content: '\e82f'; +} + +._icon-titles:before { + content: '\e745'; +} + +._icon-loading:before { + content: '\e746'; +} + +._icon-copy-o:before { + content: '\e7bc'; +} + +._icon-copy:before { + content: '\e85c'; +} + +._icon-loader:before { + content: '\e76d'; +} + +._icon-search:before { + content: '\e782'; +} + +._icon-back:before { + content: '\e600'; +} + +._icon-forward:before { + content: '\e601'; +} + +._icon-arrow:before { + content: '\e608'; +} + +._icon-drop-down:before { + content: '\e61c'; +} + +._icon-drop-up:before { + content: '\e61d'; +} + +._icon-check:before { + content: '\e69f'; +} + +._icon-move-round:before { + content: '\e602'; +} + +._icon-move-round-o:before { + content: '\e603'; +} diff --git a/sheep/scss/icon/_sheepicon.scss b/sheep/scss/icon/_sheepicon.scss new file mode 100644 index 0000000..cf7ea08 --- /dev/null +++ b/sheep/scss/icon/_sheepicon.scss @@ -0,0 +1,94 @@ +@font-face { + font-family: 'sheepicon'; + src: url('data:application/x-font-woff2;charset=utf-8;base64,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') + format('woff2'); +} + +[class*='sicon-'] { + font-family: 'sheepicon'; + display: inline-block; +} + +.sicon-edit:before { + content: '\e711'; +} + +.sicon-basic:before { + content: '\e712'; +} + +.sicon-home:before { + content: '\e70c'; +} + +.sicon-more:before { + content: '\e707'; +} + +.sicon-check-line:before { + content: '\e708'; +} + +.sicon-transport:before { + content: '\e709'; +} + +.sicon-goods-card:before { + content: '\e70a'; +} + +.sicon-collect:before { + content: '\e70b'; +} + +.sicon-warning-line:before { + content: '\e70d'; +} + +.sicon-score1:before { + content: '\e70e'; +} + +.sicon-score2:before { + content: '\e70f'; +} + +.sicon-goods-list:before { + content: '\e710'; +} + +.sicon-back:before { + content: '\e706'; +} + +.sicon-unchecked:before { + content: '\e703'; +} + +.sicon-warning-outline:before { + content: '\e6ff'; +} + +.sicon-question-outline:before { + content: '\e700'; +} + +.sicon-circlecheck:before { + content: '\e701'; +} + +.sicon-circleclose:before { + content: '\e702'; +} + +.sicon-delivery:before { + content: '\e6fd'; +} + +.sicon-orders:before { + content: '\e6fe'; +} + +.sicon-qrcode:before { + content: '\e6f9'; +} diff --git a/sheep/scss/icon/_style.scss b/sheep/scss/icon/_style.scss new file mode 100644 index 0000000..a2c4dc8 --- /dev/null +++ b/sheep/scss/icon/_style.scss @@ -0,0 +1,43 @@ +@import './icon'; //核心图标库 +@import './coloricon'; //扩展图标库 +@import './sheepicon'; +.icon-spin { + animation: icon-spin 2s infinite linear; +} + +.icon-pulse { + animation: icon-spin 1s infinite steps(8); +} + +@keyframes icon-spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(359deg); + } +} +.icon-90 { + transform: rotate(90deg); +} +.icon-180 { + transform: rotate(180deg); +} +.icon-270 { + transform: rotate(270deg); +} +.icon-x { + transform: scale(-1, 1); +} +.icon-y { + transform: scale(1, -1); +} +.icon-fw { + width: calc(18em / 14); + text-align: center; +} +@each $class, $value in $iconsize { + .icon-#{$class} { + transform: scale(#{$value}); + } +} diff --git a/sheep/scss/index.scss b/sheep/scss/index.scss new file mode 100644 index 0000000..c841956 --- /dev/null +++ b/sheep/scss/index.scss @@ -0,0 +1,27 @@ +@import './tools'; +@import './ui'; + +/* 字体文件 */ +@font-face { + font-family: OPPOSANS; + src: url('~@/sheep/scss/font/OPPOSANS-M-subfont.ttf'); +} +.font-OPPOSANS { + font-family: OPPOSANS; +} +page { + -webkit-overflow-scrolling: touch; // 解决ios滑动不流畅 + height: 100%; + width: 100%; + // font-family: OPPOSANS; + word-break: break-all; //英文文本不换行 + white-space: normal; + background-color: $bg-page; + color: $dark-3; +} +::-webkit-scrollbar { + width: 0; + height: 0; + color: transparent; + display: none; +} diff --git a/sheep/scss/style/_avatar.scss b/sheep/scss/style/_avatar.scss new file mode 100644 index 0000000..e69de29 diff --git a/sheep/scss/style/_background.scss b/sheep/scss/style/_background.scss new file mode 100644 index 0000000..775f37f --- /dev/null +++ b/sheep/scss/style/_background.scss @@ -0,0 +1,204 @@ +/* ================== + 背景 + ==================== */ +/* -- 基础色 -- */ +@each $color, $value in map-merge($colors, $darks) { + .bg-#{$color} { + background-color: $value !important; + @if $color == 'yellow' { + color: #333333 !important; + } @else { + color: #ffffff !important; + } + } +} + +/* -- 浅色 -- */ +@each $color, $value in $colors { + .bg-#{$color}-light { + background-image: linear-gradient(45deg, white, mix(white, $value, 85%)) !important; + color: $value !important; + } + + .bg-#{$color}-thin { + background-color: rgba($value, var(--ui-BG-opacity)) !important; + color: $value !important; + } +} + +/* -- 渐变色 -- */ + +@each $color, $value in $colors { + @each $colorsub, $valuesub in $colors { + @if $color != $colorsub { + .bg-#{$color}-#{$colorsub} { + // background-color: $value !important; + background-image: linear-gradient(130deg, $value, $valuesub) !important; + color: #ffffff !important; + } + } + } +} +.bg-yellow-gradient { + background-image: linear-gradient(45deg, #f5fe00, #ff6600) !important; + color: $dark-3 !important; +} +.bg-orange-gradient { + background-image: linear-gradient(90deg, #ff6000, #fe832a) !important; + color: $white !important; +} +.bg-red-gradient { + background-image: linear-gradient(45deg, #f33a41, #ed0586) !important; + color: $white !important; +} +.bg-pink-gradient { + background-image: linear-gradient(45deg, #fea894, #ff1047) !important; + color: $white !important; +} +.bg-mauve-gradient { + background-image: linear-gradient(45deg, #c01f95, #7115cc) !important; + color: $white !important; +} +.bg-purple-gradient { + background-image: linear-gradient(45deg, #9829ea, #5908fb) !important; + color: $white !important; +} +.bg-blue-gradient { + background-image: linear-gradient(45deg, #00b8f9, #0166eb) !important; + color: $white !important; +} +.bg-cyan-gradient { + background-image: linear-gradient(45deg, #06edfe, #48b2fe) !important; + color: $white !important; +} +.bg-green-gradient { + background-image: linear-gradient(45deg, #3ab54a, #8cc63f) !important; + color: $white !important; +} +.bg-olive-gradient { + background-image: linear-gradient(45deg, #90e630, #39d266) !important; + color: $white !important; +} +.bg-grey-gradient { + background-image: linear-gradient(45deg, #9aadb9, #354855) !important; + color: $white !important; +} +.bg-brown-gradient { + background-image: linear-gradient(45deg, #ca6f2e, #cb1413) !important; + color: $white !important; +} + +@each $color, $value in $grays { + .bg-#{$color} { + background-color: $value !important; + color: #333333 !important; + } +} + +.bg-square { + @include bg-square; +} +.bg-none { + background: transparent !important; + color: inherit !important; +} + +[class*='bg-mask'] { + position: relative; + //background: transparent !important; + color: #ffffff !important; + > view, + > text { + position: relative; + z-index: 1; + color: #ffffff; + } + &::before { + content: ''; + border-radius: inherit; + width: 100%; + height: 100%; + @include position-center; + background-color: rgba(0, 0, 0, 0.4); + z-index: 0; + } + @at-root .bg-mask-80::before { + background: rgba(0, 0, 0, 0.8) !important; + } + @at-root .bg-mask-50::before { + background: rgba(0, 0, 0, 0.5) !important; + } + @at-root .bg-mask-20::before { + background: rgba(0, 0, 0, 0.2) !important; + } + @at-root .bg-mask-top::before { + background-color: rgba(0, 0, 0, 0); + background-image: linear-gradient(rgba(0, 0, 0, 1), rgba(0, 0, 0, 0.618), rgba(0, 0, 0, 0.01)); + } + @at-root .bg-white-top { + background-color: rgba(0, 0, 0, 0); + background-image: linear-gradient(rgba(255, 255, 255, 1), rgba(255, 255, 255, 0.3)); + } + @at-root .bg-mask-bottom::before { + background-color: rgba(0, 0, 0, 0); + background-image: linear-gradient(rgba(0, 0, 0, 0.01), rgba(0, 0, 0, 0.618), rgba(0, 0, 0, 1)); + } +} +.bg-img { + background-size: cover; + background-position: center; + background-repeat: no-repeat; +} + +[class*='bg-blur'] { + position: relative; + > view, + > text { + position: relative; + z-index: 1; + } + &::before { + content: ''; + width: 100%; + height: 100%; + @include position-center; + border-radius: inherit; + transform-origin: 0 0; + pointer-events: none; + box-sizing: border-box; + } +} +@supports (-webkit-backdrop-filter: blur(20px)) or (backdrop-filter: blur(20px)) { + .bg-blur::before { + @include blur; + background-color: var(--ui-Blur-1); + } + .bg-blur-1::before { + @include blur; + background-color: var(--ui-Blur-2); + } + .bg-blur-2::before { + @include blur; + background-color: var(--ui-Blur-3); + } +} +@supports not (backdrop-filter: blur(5px)) { + .bg-blur { + color: var(--ui-TC); + &::before { + background-color: var(--ui-BG); + } + } + .bg-blur-1 { + color: var(--ui-TC); + &::before { + background-color: var(--ui-BG-1); + } + } + .bg-blur-2 { + color: var(--ui-TC); + &::before { + background-color: var(--ui-BG-2); + } + } +} diff --git a/sheep/scss/style/_border.scss b/sheep/scss/style/_border.scss new file mode 100644 index 0000000..4ef1d54 --- /dev/null +++ b/sheep/scss/style/_border.scss @@ -0,0 +1,140 @@ +/* ================== + 边框 + ==================== */ +/* -- 实线 -- */ +.border { + overflow: initial !important; + @at-root [class*='border'], + [class*='dashed'] { + position: relative; + &.dline { + --ui-Border: var(--ui-BG-3); + } + &::after { + content: ' '; + width: 200%; + height: 200%; + position: absolute; + z-index: 0; + top: 0; + left: 0; + transform: scale(0.5); + transform-origin: 0 0; + pointer-events: none; + box-sizing: border-box; + border-radius: inherit; + } + &.radius::after { + border-radius: calc(#{$radius} * 2); + } + &.round::after { + border-radius: #{$round-pill}; + } + } + &::after { + border: 1px solid var(--ui-Border); + } + &s::after { + border: 4rpx solid var(--ui-Border); + } + &ss::after { + border: 8rpx solid var(--ui-Border); + } + @each $value in (top, right, bottom, left) { + &-#{$value}::after { + border-#{$value}: 1px solid var(--ui-Border); + } + &s-#{$value}::after { + border-#{$value}: 4rpx solid var(--ui-Border); + } + &ss-#{$value}::after { + border-#{$value}: 8rpx solid var(--ui-Border); + } + } +} +/* -- 虚线 -- */ +.dashed { + &::after { + border: 4rpx dashed var(--ui-Border); + } + &s::after { + border: 6rpx dashed var(--ui-Border); + } + @each $value in (top, right, bottom, left) { + &-#{$value}::after { + border-#{$value}: 4rpx dashed var(--ui-Border); + } + &s-#{$value}::after { + border-#{$value}: 6rpx dashed var(--ui-Border); + } + } +} +@each $color, $value in map-merge($colors, map-merge($darks, $grays)) { + .border-#{$color}::after, + .border-#{$color}[class*='-shine']::before { + border-color: $value !important; + } +} +@each $value in (a, b, c, d, e) { + .main-#{$value}-border::after, + .main-#{$value}-border[class*='-shine']::before { + border-color: var(--main-#{$value}) !important; + } +} +.dashed-shine, +.dasheds-shine { + position: relative; + overflow: hidden; + &::after, + &::before { + border-style: dashed; + border-color: var(--ui-Border); + animation: shineafter 1s infinite linear; + width: calc(200% + 40px); + height: 200%; + border-width: 2px 0; + } + &::before { + content: ' '; + position: absolute; + transform: scale(0.5); + transform-origin: 0 0; + pointer-events: none; + box-sizing: border-box; + animation: shinebefore 1s infinite linear; + width: 200%; + height: calc(200% + 40px); + border-width: 0 2px; + } +} +.dasheds-shine { + &::after, + &::before { + border-width: 4px 0; + } + &::before { + border-width: 0 4px; + } +} + +@keyframes shineafter { + 0% { + top: 0; + left: -22px; + } + 100% { + top: 0px; + left: 0px; + } +} + +@keyframes shinebefore { + 0% { + top: -22px; + left: 0; + } + 100% { + top: 0px; + left: 0px; + } +} diff --git a/sheep/scss/style/_button.scss b/sheep/scss/style/_button.scss new file mode 100644 index 0000000..7069345 --- /dev/null +++ b/sheep/scss/style/_button.scss @@ -0,0 +1,87 @@ +.ui-btn-box { + display: inline-block; +} +.ui-btn { + position: relative; + border: 0rpx; + display: inline-block; + align-items: center; + justify-content: center; + box-sizing: border-box; + padding: 0.7857em 1.5em 0.7857em; + font-size: 28rpx; + line-height: 1em; + text-align: center; + text-decoration: none; + overflow: visible; + margin: 0 0.25em 0 0; + transform: translate(0rpx, 0rpx); + border-radius: $radius; + white-space: nowrap; + color: var(--text-a); + background-color: var(--ui-BG); + vertical-align: baseline; + &:first-child:last-child { + margin: 0; + } + &:not([class*='round'])::after { + border-radius: calc(#{$radius} * 2); + } + &:not([class*='border'])::after { + // content: ' '; + // width: 200%; + // height: 200%; + // display: block; + // position: absolute; + // z-index: 0; + // top: 0; + // left: 0; + // transform: scale(0.5); + // transform-origin: 0 0; + // pointer-events: none; + // box-sizing: border-box; + display: none; + } + &.round::after { + border-radius: #{$round-pill}; + } + &.icon { + padding: 0.8em 0.8em; + } + + &.sm { + font-size: 24rpx; + } + + &.lg { + font-size: 32rpx; + } + + &.xl { + font-size: 36rpx; + } + + &.block { + width: 100%; + display: block; + font-size: 32rpx; + } + + &[disabled] { + opacity: 0.6; + } + + &.none-style { + background-color: transparent !important; + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + display: flex; + } +} + +.ui-btn:not(.icon) [class*='icon-'] { + margin: 0 0.25em; +} diff --git a/sheep/scss/style/_card.scss b/sheep/scss/style/_card.scss new file mode 100644 index 0000000..17aa6b3 --- /dev/null +++ b/sheep/scss/style/_card.scss @@ -0,0 +1,353 @@ +/* ================== + 卡片 + ==================== */ + +.ui-cards { + display: block; + overflow: hidden; + & .ui-btn.badge { + top: 0; + right: 0; + font-size: 24rpx; + padding: 0rpx 15rpx; + height: 40rpx; + } + &.no-card > .ui-item { + margin: 0rpx; + border-radius: 0rpx; + } + & > .ui-item { + display: block; + overflow: hidden; + border-radius: 10rpx; + margin: 30rpx; + } + & > .ui-item.shadow-blur { + overflow: initial; + } + .grid.grid-square { + margin-bottom: -20rpx; + } + &.article { + display: block; + & > .ui-item { + padding: 30rpx; + background-color: var(--box-bg); + display: flex; + align-items: flex-start; + } + & > .time { + padding: 30rpx 0 0 30rpx; + } + & > .ui-item .title { + font-size: 30rpx; + font-weight: 900; + color: #333333; + } + & > .ui-item .content { + flex: 1; + } + & > .ui-item > image { + width: 240rpx; + height: 6.4em; + margin-left: 20rpx; + border-radius: 6rpx; + } + & > .ui-item .content .desc { + font-size: 12px; + color: var(--text-c); + } + & > .ui-item .content .text-content { + font-size: 28rpx; + color: #888; + } + } + &.case { + .image { + position: relative; + image { + width: 100%; + display: block; + } + .ui-tag { + position: absolute; + right: 0; + top: 0; + } + .ui-bar { + position: absolute; + bottom: 0; + width: 100%; + background-color: transparent; + padding: 0rpx 30rpx; + } + .bg-black { + position: absolute; + bottom: 0; + width: 100%; + background-color: rgba(0, 0, 0, 0.6); + } + } + &.no-card .image { + margin: 30rpx 30rpx 0; + overflow: hidden; + border-radius: 10rpx; + } + } + &.dynamic { + display: block; + & > .ui-item { + display: block; + overflow: hidden; + & > .text-content { + padding: 0 30rpx 0; + font-size: 30rpx; + margin-bottom: 20rpx; + } + & .square-img { + width: 100%; + height: 200rpx; + border-radius: 6rpx; + } + & .only-img { + width: 100%; + height: 320rpx; + border-radius: 6rpx; + } + } + } + &.goods { + display: block; + & > .ui-item { + padding: 30rpx; + display: flex; + position: relative; + background-color: var(--ui-BG); + & + .ui-item { + border-top: 1rpx solid #eeeeee; + } + .content { + width: 410rpx; + padding: 0rpx; + } + .title { + font-size: 30rpx; + font-weight: 900; + color: #333333; + line-height: 1.4; + height: 1.4em; + overflow: hidden; + } + } + &.col-goods.col-twice { + display: flex; + flex-wrap: wrap; + padding-bottom: 30rpx; + & > .ui-item { + width: calc(50% - 30rpx); + margin: 20rpx 20rpx 0rpx 20rpx; + .content { + padding: 20rpx; + } + } + & > .ui-item:nth-child(2n) { + margin-left: 0rpx; + } + } + &.col-goods > .ui-item { + padding: 0rpx; + display: block; + border: 0px; + .content { + width: 100%; + padding: 30rpx; + } + } + &.no-card > .ui-item .content { + width: 470rpx; + padding: 0rpx; + } + &.no-card > .ui-item .title, + &.col-goods > .ui-item .title { + height: 3em; + overflow: hidden; + } + & > .ui-item .text-linecut-2 { + -webkit-line-clamp: 1; + } + &.no-card > .ui-item .text-linecut-2, + &.col-goods > .ui-item .text-linecut-2 { + -webkit-line-clamp: 2; + line-height: 1.6em; + height: 3.2em; + } + & > .ui-item > image { + width: 200rpx; + height: 200rpx; + margin-right: 20rpx; + border-radius: 6rpx; + } + &.no-card > .ui-item > image { + width: 220rpx; + height: 170rpx; + } + &.col-goods > .ui-item > image { + width: 100%; + height: 340rpx; + border-bottom-left-radius: 0rpx; + border-bottom-right-radius: 0rpx; + display: block; + } + &.col-goods.col-twice > .ui-item > image { + height: 236rpx; + } + } + &.loan { + display: block; + & > .ui-item { + padding: 30rpx 0 30rpx 30rpx; + display: flex; + position: relative; + background-color: var(--box-bg); + + .content { + width: 450rpx; + padding: 0rpx; + .tag-list { + width: 450rpx; + display: flex; + flex-wrap: wrap; + font-size: 12px; + margin-top: 18rpx; + } + } + .action { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + } + } + } + &.houses { + display: block; + & > .ui-item { + padding: 20rpx; + display: flex; + position: relative; + background-color: var(--box-bg); + .image { + width: 230rpx; + height: 180rpx; + margin-right: 20rpx; + border-radius: 6rpx; + } + .content { + width: 400rpx; + padding: 0rpx; + .tag-list { + width: 400rpx; + display: flex; + flex-wrap: wrap; + font-size: 12px; + margin-top: 10rpx; + .ui-item { + height: 20px; + line-height: 20px; + } + } + } + .action { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + } + } + } + + &.product { + display: flex; + flex-wrap: wrap; + padding-bottom: 30rpx; + & > .ui-item { + width: calc(100% - 15rpx); + margin: 20rpx 20rpx 0rpx 20rpx; + background-color: var(--box-bg); + position: relative; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + // display: flex; + // flex-wrap: wrap; + .content { + padding: 20rpx; + // width: calc(100% - 345rpx); + .text-cut { + font-size: 16px; + } + } + .image { + width: 100%; + height: 240rpx; + border-radius: 6rpx 0 0 6rpx; + display: block; + } + .ui-progress-tag { + width: 4em; + text-align: right; + font-size: 12px; + } + .border-top { + width: 100%; + } + .ui-tag { + position: absolute; + top: 0; + left: 0; + border-radius: 6rpx 0 6rpx 0; + } + } + // & > .ui-item:nth-child(2n) { + // margin-left: 0rpx; + // } + } + &.shop { + display: flex; + flex-wrap: wrap; + padding-bottom: 30rpx; + & > .ui-item { + width: calc(50% - 30rpx); + margin: 20rpx 20rpx 0rpx 20rpx; + background-color: var(--box-bg); + padding: 20rpx; + .content { + margin-top: 15rpx; + } + .image { + width: 100%; + height: 285rpx; + border-radius: 6rpx; + display: block; + } + } + & > .ui-item:nth-child(2n) { + margin-left: 0rpx; + } + } + + &.orders .ui-item { + margin-top: 30rpx; + .address-box { + padding: 15rpx; + margin: 0 30rpx 30rpx; + border: 1px solid; + border-color: var(--main-a); + border-radius: 10px; + position: relative; + .ui-form-group { + min-height: 10px; + } + } + } +} diff --git a/sheep/scss/style/_code.scss b/sheep/scss/style/_code.scss new file mode 100644 index 0000000..5221c88 --- /dev/null +++ b/sheep/scss/style/_code.scss @@ -0,0 +1,55 @@ +.ui-code { + font-family: Monaco, Menlo, Consolas, 'Courier New'; + font-size: 90%; + position: relative; + z-index: 1; + color: var(--ui-TC); + .ui-rich-text { + display: inline-block; + } + + &.code { + display: inline-block; + padding: 0 10rpx; + margin: 0 10rpx; + border-radius: $radius-sm; + line-height: 1.6; + vertical-align: baseline; + } + + &.pre { + display: block; + margin: 1em 0; + line-height: 1.6; + &.hasTitle { + margin: 3.2em 0 1em; + } + // border-radius: $radius-sm; + .ui-code-title { + position: absolute; + top: -2.2em; + color: var(--ui-TC-2); + left: 0; + } + .ui-rich-text { + padding: 40rpx; + white-space: pre-wrap; + word-break: break-all; + word-wrap: break-word; + } + .ui-scroll-view { + &.ui-scroll { + max-height: 500px; + white-space: pre; + } + } + .ui-copy-btn { + position: absolute; + z-index: 2; + top: 0; + right: 0; + padding: 0.8em; + border-radius: 0 $radius-sm 0 $radius-sm; + } + } +} diff --git a/sheep/scss/style/_flex.scss b/sheep/scss/style/_flex.scss new file mode 100644 index 0000000..1daa45b --- /dev/null +++ b/sheep/scss/style/_flex.scss @@ -0,0 +1,79 @@ +/* ================== + 弹性布局 + ==================== */ +.flex { + display: flex !important; + &-sub { + flex: 1 !important; + } + &-twice { + flex: 2 !important; + } + &-treble { + flex: 3 !important; + } + &-column { + flex-direction: column !important; + } + &-row { + flex-direction: row !important; + } + &-column-reverse { + flex-direction: column-reverse !important; + } + &-row-reverse { + flex-direction: row-reverse !important; + } + &-wrap { + flex-wrap: wrap !important; + } + &-center { + @include flex-center; + } + &-bar { + @include flex-bar; + } +} +.basis { + @each $class, $value in (xs: 20%, sm: 40%, df: 50%, lg: 60%, xl: 80%) { + &-#{$class} { + flex-basis: $value !important; + } + } +} +.align { + @each $class, + $value + in (start: flex-start, end: flex-end, center: center, stretch: stretch, baseline: baseline) + { + &-#{$class} { + align-items: $value !important; + } + } +} +.self { + @each $class, + $value + in (start: flex-start, end: flex-end, center: center, stretch: stretch, baseline: baseline) + { + &-#{$class} { + align-self: $value !important; + } + } +} +.justify { + @each $class, + $value + in ( + start: flex-start, + end: flex-end, + center: center, + between: space-between, + around: space-around + ) + { + &-#{$class} { + justify-content: $value !important; + } + } +} diff --git a/sheep/scss/style/_form.scss b/sheep/scss/style/_form.scss new file mode 100644 index 0000000..91d3eb3 --- /dev/null +++ b/sheep/scss/style/_form.scss @@ -0,0 +1,121 @@ +/* ================== + 表单 + ==================== */ +.ui-form-item { + padding: 1rpx 24rpx; + display: flex; + align-items: center; + min-height: 100rpx; + justify-content: space-between; + .title { + text-align: justify; + padding-right: 30rpx; + font-size: 30rpx; + position: relative; + height: 60rpx; + line-height: 60rpx; + } + .content { + flex: 1; + } + input, + ui-input { + flex: 1; + font-size: 30rpx; + color: #555; + padding-right: 20rpx; + } + text[class*='icon-'] { + font-size: 36rpx; + padding: 0; + box-sizing: border-box; + } + textarea { + margin: 32rpx 0 30rpx; + height: 4.6em; + width: 100%; + line-height: 1.2em; + flex: 1; + font-size: 28rpx; + padding: 0; + } + picker, + .arrow { + flex: 1; + padding-right: 40rpx; + overflow: hidden; + position: relative; + } + picker .picker, + .arrow > view { + line-height: 100rpx; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + width: 100%; + } + picker::after, + .arrow::after { + font-family: 'ui'; + display: block; + content: '\e605'; + position: absolute; + font-size: 34rpx; + color: #8799a3; + line-height: 100rpx; + width: 60rpx; + text-align: center; + top: 0; + bottom: 0; + right: -20rpx; + margin: auto; + } + textarea[disabled], + textarea[disabled] .placeholder { + color: transparent; + } + &.align-start .title { + height: 1em; + margin-top: 32rpx; + line-height: 1em; + } + .grid-square { + > view { + background-color: #f8f8f8; + border-radius: 12rpx; + .mask { + background-color: rgba(0, 0, 0, 0.6); + position: absolute; + font-size: 20rpx; + color: #ffffff; + width: 100%; + bottom: 0; + text-align: center; + padding: 6rpx 0; + &.red-mask { + background-color: rgba(255, 80, 80, 0.6); + } + } + [class*='icon'] { + position: absolute; + width: 100%; + height: 100%; + display: flex; + align-items: center; + transform: scale(1.5); + justify-content: center; + } + .text-gray { + position: absolute; + width: 100%; + font-size: 24rpx; + text-align: center; + bottom: 20rpx; + } + } + } +} +.disabled { + opacity: 0.6; + cursor: not-allowed !important; +} diff --git a/sheep/scss/style/_grid.scss b/sheep/scss/style/_grid.scss new file mode 100644 index 0000000..b1b5230 --- /dev/null +++ b/sheep/scss/style/_grid.scss @@ -0,0 +1,103 @@ +/* ================== + 栅栏 + ==================== */ +@use 'sass:math'; + +@mixin make_col($screen) { + @for $i from 1 through 12 { + .ui-col-#{$screen}-#{$i} { + width: calc(100% / 12 * #{$i}); + } + .ui-cols-#{$screen}-#{$i} .ui-item { + width: calc(100% / #{$i}); + } + } +} +.ui-container { + box-sizing: border-box; + margin-left: auto; + margin-right: auto; + padding-left: 30rpx; + padding-right: 30rpx; + width: 100%; + max-width: 1440px; + &-fluid { + max-width: 100%; + padding-left: 0; + padding-right: 0; + } +} +.ui-grid { + display: flex; + flex-wrap: wrap; + &.multi-column { + display: block; + column-count: 2; + column-width: 0px; + column-gap: 0px; + > .ui-item { + break-inside: avoid; + padding: 0.001em; + } + } + &.grid-square { + overflow: hidden; + > .ui-item { + margin-right: 20rpx; + margin-bottom: 20rpx; + position: relative; + overflow: hidden; + } + @for $i from 1 through 12 { + &.ui-cols-#{$i} > .ui-item { + padding-bottom: calc((100% - #{20rpx * ($i - 1)}) / #{$i}); + height: 0; + width: calc((100% - #{20rpx * ($i - 1)}) / #{$i}); + } + } + @for $i from 1 through 12 { + &.ui-cols-#{$i} > .ui-item:nth-child(#{$i}n) { + margin-right: 0; + } + } + } +} +@for $i from 1 through 12 { + .ui-cols-#{$i} .ui-item { + width: calc(100% / #{$i}); + } +} +@for $i from 1 through 12 { + .ui-col-#{$i} { + width: calc(100% / 12 * #{$i}); + } +} +// 小屏 +@media screen and (min-width: 0px) { + @include make_col('xs'); +} + +// 小屏 +@media screen and (min-width: 320px) { + @include make_col('sm'); +} + +// 中屏 +@media screen and (min-width: 768px) { + @include make_col('md'); +} + +// 普通屏 +@media screen and (min-width: 1025px) { + @include make_col('lg'); +} + +// 大屏 +@media screen and (min-width: 1440px) { + @include make_col('xl'); +} + +// 超大屏 +@media screen and (min-width: 1920px) { + @include make_col('xxl'); +} diff --git a/sheep/scss/style/_markdown.scss b/sheep/scss/style/_markdown.scss new file mode 100644 index 0000000..37b023d --- /dev/null +++ b/sheep/scss/style/_markdown.scss @@ -0,0 +1,62 @@ +.cu-markdown { + position: relative; + z-index: 1; + &.selectable { + cursor: auto; + user-select: text; + } + inline { + display: inline-block; + } + + .list { + .list-item { + line-height: 1.8; + .list { + margin-left: 1.28571em; + .ui-title { + transform: scale(0.6); + &:before { + content: '\e716'; + } + } + } + } + .list-item-p { + position: relative; + padding-left: 1.5em; + .list-item-t { + display: block; + width: 1.3em; + text-align: center; + position: absolute; + left: 0; + } + } + } + .md-table + .md-table { + margin-top: 30rpx; + } +} + +.paragraph { + margin: 0 0 40rpx; + line-height: 1.8; +} + +.blockquote { + @extend .paragraph; + padding: 20rpx 30rpx; + border-left-style: solid; + border-left-width: 10rpx; + border-color: var(--ui-Border); + background: none repeat scroll 0 0 rgba(102, 128, 153, 0.05); + + .paragraph { + margin-bottom: 30rpx; + } + + .paragraph:last-child { + margin-bottom: 0; + } +} diff --git a/sheep/scss/style/_menu.scss b/sheep/scss/style/_menu.scss new file mode 100644 index 0000000..a4a8282 --- /dev/null +++ b/sheep/scss/style/_menu.scss @@ -0,0 +1,54 @@ +.ui-menu { + background-color: var(--ui-BG); +} + +.ui-menu-item { + position: relative; + @include flex-bar; + min-height: 4em; + padding: 0 30rpx; + .ui-menu-item-icon { + width: 1.7em; + margin-right: 0.3em; + position: relative; + display: flex; + align-items: center; + justify-content: center; + transform: scale(1.3); + } + .ui-menu-item-icon .ui-menu-item-image { + width: 1.2em; + height: 1.2em; + display: inline-block; + } + .ui-menu-item-content { + flex: 1; + position: relative; + @include flex-bar; + } + .ui-menu-item-arrow { + width: 1.6em; + text-align: center; + color: var(--ui-TC-3); + } + &::after { + content: ' '; + width: calc(200% - 120rpx); + left: 30rpx; + position: absolute; + top: 0; + box-sizing: border-box; + height: 200%; + border-top: 1px solid var(--ui-Border); + border-radius: inherit; + transform: scale(1); + transform-origin: 0 0; + pointer-events: none; + } + &.first-item::after { + display: none; + } + &:first-child::after { + display: none; + } +} diff --git a/sheep/scss/style/_shadow.scss b/sheep/scss/style/_shadow.scss new file mode 100644 index 0000000..27cb3f6 --- /dev/null +++ b/sheep/scss/style/_shadow.scss @@ -0,0 +1,90 @@ +/* ================== + 阴影 + ==================== */ + +.shadow { + box-shadow: var(--ui-Shadow); + &-sm { + box-shadow: var(--ui-Shadow-sm); + } + &-lg { + box-shadow: var(--ui-Shadow-lg); + } + &-inset { + box-shadow: var(--ui-Shadow-inset); + } + @each $color, $value in $colors { + @at-root .shadow-#{$color} { + box-shadow: 0 0.5em 1em rgba($value, var(--ui-Shadow-opacity)); + } + &-sm.shadow-#{$color} { + box-shadow: 0 0.125em 0.25em rgba($value, var(--ui-Shadow-opacity)); + } + &-lg.shadow-#{$color} { + box-shadow: 0 1em 3em rgba($value, var(--ui-Shadow-opacity-lg)); + } + } + + &-warp { + position: relative; + } + &-warp:before, + &-warp:after { + position: absolute; + content: ''; + bottom: -10rpx; + left: 20rpx; + width: calc(50% - #{40rpx}); + height: 30rpx; + transform: skew(0deg, -6deg); + transform-origin: 50% 50%; + background-color: rgba(0, 0, 0, var(--ui-Shadow-opacity)); + filter: blur(20rpx); + z-index: -1; + opacity: 0.5; + } + &-warp:after { + right: 20rpx; + left: auto; + transform: skew(0deg, 6deg); + } + &-blur { + position: relative; + } + &-blur::before { + content: ''; + display: block; + background: inherit; + filter: blur(20rpx); + position: absolute; + width: 100%; + height: 100%; + top: 0.5em; + left: 0.5em; + z-index: -1; + opacity: var(--ui-Shadow-opacity-lg); + transform-origin: 0 0; + border-radius: inherit; + transform: scale(1, 1); + } +} +.drop-shadow { + filter: drop-shadow(0 0 30rpx rgba(0, 0, 0, 0.1)); + &-sm { + filter: drop-shadow(0 4rpx 4rpx rgba(0, 0, 0, 0.06)); + } + &-lg { + filter: drop-shadow(0 30rpx 60rpx rgba(0, 0, 0, 0.2)); + } + @each $color, $value in $colors { + @at-root .drop-shadow-#{$color} { + filter: drop-shadow(0 15rpx 15rpx rgba(darken($value, 10%), 0.3)); + } + &-sm.drop-shadow-#{$color} { + filter: drop-shadow(0 4rpx 4rpx rgba(darken($value, 10%), 0.3)); + } + &-lg.drop-shadow-#{$color} { + filter: drop-shadow(0 50rpx 100rpx rgba(darken($value, 10%), 0.2)); + } + } +} diff --git a/sheep/scss/style/_table.scss b/sheep/scss/style/_table.scss new file mode 100644 index 0000000..ad5effa --- /dev/null +++ b/sheep/scss/style/_table.scss @@ -0,0 +1,133 @@ +.ui-table { + background-color: var(--ui-BG); + max-width: 100%; + display: table; + &.table-full { + width: 100%; + } + &.table-radius { + border-radius: $radius; + .ui-table-header { + .ui-table-tr { + border-top-left-radius: $radius; + border-top-right-radius: $radius; + } + .ui-table-th { + &:first-child { + border-top-left-radius: $radius; + } + &:last-child { + border-top-right-radius: $radius; + } + } + } + } + .ui-table-header { + display: table-header-group; + .ui-table-th { + font-weight: bold; + border-bottom: 1px solid var(--ui-Border); + white-space: nowrap; + + padding: 1em 0.8em; + } + } + + .ui-table-tr { + display: table-row; + z-index: 1; + } + + .ui-table-body { + display: table-row-group; + position: relative; + .ui-table-tr:hover { + background-color: var(--ui-BG-1) !important; + } + .ui-table-loading { + min-height: 300px; + position: absolute !important; + width: 100%; + height: 100%; + z-index: 2; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid var(--ui-Border); + } + } + + .ui-table-td, + .ui-table-th { + display: table-cell; + text-align: unset; + padding: 0.5em 0.8em; + // font-size: 90%; + vertical-align: middle; + } +} + +.ui-table.table-border { + &, + & .ui-table-td, + & .ui-table-th { + position: relative; + &::after { + content: ' '; + width: 200%; + height: 200%; + position: absolute; + top: 0; + left: 0; + border-radius: inherit; + transform: scale(0.5); + transform-origin: 0 0; + pointer-events: none; + box-sizing: border-box; + border: 1px solid var(--ui-Border); + z-index: 1; + } + } + .ui-table-td, + .ui-table-th { + &::after { + border-width: 1px 1px 0 0; + } + &:last-child::after { + border-right: none; + } + } +} +.ui-table.table-radius { + &::after { + border-radius: calc(#{$radius} * 2); + } + & .ui-table-tr .ui-table-th:first-child { + border-top-left-radius: calc(#{$radius} * 2); + } + & .ui-table-tr .ui-table-th:last-child { + border-top-right-radius: calc(#{$radius} * 2); + } + & .ui-table-tr:last-child .ui-table-td:first-child { + border-bottom-left-radius: #{$radius}; + } + & .ui-table-tr:last-child .ui-table-td:last-child { + border-bottom-right-radius: #{$radius}; + } +} +.ui-table.table-striped > .ui-table-body > .ui-table-tr:nth-child(2n + 1), +.ui-table.table-striped > .ui-table-body > .ui-table-tr:nth-child(2n + 1) { + background-color: var(--ui-BG-1); +} + +.table-responsive { + width: inherit; + height: 100%; + max-width: 100%; + overflow: hidden; + box-sizing: border-box; + .table-responsive-box { + position: relative; + overflow: hidden; + } +} diff --git a/sheep/scss/style/_tag.scss b/sheep/scss/style/_tag.scss new file mode 100644 index 0000000..e69de29 diff --git a/sheep/scss/style/_text.scss b/sheep/scss/style/_text.scss new file mode 100644 index 0000000..8249022 --- /dev/null +++ b/sheep/scss/style/_text.scss @@ -0,0 +1,104 @@ +/* ================== + 文本 + ==================== */ +@use 'sass:math'; +.font-0 { + font-size: 24rpx; + --textSize: -4rpx; +} +.font-1 { + font-size: 28rpx; + --textSize: 0rpx; +} +.font-2 { + font-size: 32rpx; + --textSize: 4rpx; +} +.font-3 { + font-size: 36rpx; + --textSize: 8rpx; +} +.font-4 { + font-size: 40rpx; + --textSize: 12rpx; +} +.text { + @each $class, $value in $fontsize { + &-#{$class}, + &-#{math.div($value ,2)} { + font-size: calc(#{$value}rpx + var(--textSize)) !important; + } + } + &-cut { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + @at-root [class*='text-linecut'] { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + overflow: hidden; + word-break: break-all; + } + @for $i from 2 through 10 { + &-linecut-#{$i} { + -webkit-line-clamp: #{$i}; + } + } + &-justify { + text-align: justify; + } + &-justify-line { + text-align: justify; + line-height: 0.5em; + margin-top: 0.5em; + &::after { + content: '.'; + display: inline-block; + width: 100%; + } + } + + &-Abc { + text-transform: Capitalize !important; + } + &-ABC { + text-transform: Uppercase !important; + } + &-abc { + text-transform: Lowercase !important; + } + &-del, + &-line { + text-decoration: line-through !important; + } + &-bottomline { + text-decoration: underline !important; + } + &-italic { + font-style: italic !important; + } + &-style-none { + text-decoration: none !important; + } + &-break { + word-break: break-word !important; + overflow-wrap: break-word !important; + } + &-reset { + color: inherit !important; + } + &-price::before { + content: '¥'; + font-size: 80%; + margin-right: 4rpx; + } + &-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; + } +} diff --git a/sheep/scss/theme/_dark.scss b/sheep/scss/theme/_dark.scss new file mode 100644 index 0000000..8caad17 --- /dev/null +++ b/sheep/scss/theme/_dark.scss @@ -0,0 +1,39 @@ +// 核心主题样式文件 +@mixin theme-dark { + // 背景色 + --ui-BG: #393939; + --ui-BG-1: #333333; + --ui-BG-2: #2c2c2c; + --ui-BG-3: #292929; + --ui-BG-4: #222222; + + // 文本色 + --ui-TC: #ffffff; + --ui-TC-1: #d4d4d4; + --ui-TC-2: #919191; + --ui-TC-3: #6a6a6a; + --ui-TC-4: #474747; + + // 模糊 + --ui-Blur: rgba(38, 38, 38, 0.98); + --ui-Blur-1: rgba(38, 38, 38, 0.75); + --ui-Blur-2: rgba(38, 38, 38, 0.25); + --ui-Blur-3: rgba(38, 38, 38, 0.05); + + // 边框 + --ui-Border: rgba(119, 119, 119, 0.25); + --ui-Outline: rgba(255, 255, 255, 0.1); + --ui-Line: rgba(119, 119, 119, 0.25); + + // 透明与阴影 + --ui-Shadow: 0 0.5em 1em rgba(0, 0, 0, 0.45); + --ui-Shadow-sm: 0 0.125em 0.25em rgba(0, 0, 0, 0.475); + --ui-Shadow-lg: 0 1em 3em rgba(0, 0, 0, 0.475); + --ui-Shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.475); + + --ui-Shadow-opacity: 0.55; + --ui-Shadow-opacity-sm: 0.175; + --ui-Shadow-opacity-lg: 0.75; + + --ui-BG-opacity: 0.1; +} diff --git a/sheep/scss/theme/_light.scss b/sheep/scss/theme/_light.scss new file mode 100644 index 0000000..af5f245 --- /dev/null +++ b/sheep/scss/theme/_light.scss @@ -0,0 +1,39 @@ +// 核心主题样式文件 +@mixin theme-light { + // 背景色 + --ui-BG: #ffffff; + --ui-BG-1: #f6f6f6; + --ui-BG-2: #f1f1f1; + --ui-BG-3: #e8e8e8; + --ui-BG-4: #e0e0e0; + + // 文本色 + --ui-TC: #303030; + --ui-TC-1: #525252; + --ui-TC-2: #777777; + --ui-TC-3: #9e9e9e; + --ui-TC-4: #c6c6c6; + + // 模糊 + --ui-Blur: rgba(255, 255, 255, 0.98); + --ui-Blur-1: rgba(255, 255, 255, 0.75); + --ui-Blur-2: rgba(255, 255, 255, 0.25); + --ui-Blur-3: rgba(255, 255, 255, 0.05); + + // 边框 + --ui-Border: rgba(119, 119, 119, 0.25); + --ui-Outline: rgba(0, 0, 0, 0.1); + --ui-Line: rgba(119, 119, 119, 0.25); + + // 透明与阴影 + --ui-Shadow: 0 0.5em 1em rgba(0, 0, 0, 0.15); + --ui-Shadow-sm: 0 0.125em 0.25em rgba(0, 0, 0, 0.075); + --ui-Shadow-lg: 0 1em 3em rgba(0, 0, 0, 0.175); + --ui-Shadow-inset: inset 0 0.1em 0.2em rgba(0, 0, 0, 0.075); + + --ui-Shadow-opacity: 0.45; + --ui-Shadow-opacity-sm: 0.075; + --ui-Shadow-opacity-lg: 0.65; + + --ui-BG-opacity: 0.1; +} diff --git a/sheep/scss/theme/_style.scss b/sheep/scss/theme/_style.scss new file mode 100644 index 0000000..1eef587 --- /dev/null +++ b/sheep/scss/theme/_style.scss @@ -0,0 +1,68 @@ +@import './light'; //浅蓝主题 +@import './dark'; //深蓝主题 +// 多主题 +.theme-light { + @include theme-light; +} +.theme-dark { + @include theme-dark; +} +.theme-auto { + @include theme-light; +} +@media (prefers-color-scheme: dark) { + .theme-auto { + @include theme-dark; + } +} + +@each $value in ('', '-1', '-2', '-3', '-4') { + // 背景色 + 文字色 : 白色 + 默认色; + .ui-BG#{$value} { + background-color: var(--ui-BG#{$value}) !important; + color: var(--ui-TC); + } + // 文字颜色 + .ui-TC#{$value} { + color: var(--ui-TC#{$value}) !important; + } + // 主题色背景 + .ui-BG-Main#{$value} { + background-color: var(--ui-BG-Main#{$value}) !important; + color: var(--ui-BG-Main-TC) !important; + } + // 主题色渐变,横向 + .ui-BG-Main-Gradient { + background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient)) !important; + color: var(--ui-BG-Main-TC) !important; + } + // 主题色文字 + .ui-TC-Main#{$value} { + color: var(--ui-BG-Main#{$value}) !important; + } + // 主题色阴影 + .ui-Shadow-Main { + box-shadow: var(--ui-Main-box-shadow) !important; + } + .ui-BG-Main-light { + background: var(----ui-BG-Main-light) !important; + color: var(--ui-BG-Main#{$value}) !important; + } +} + +@each $color, $value in $colors { + .main-#{$color} { + --ui-BG-Main: #{$value}; + --ui-BG-Main-tag: #{rgba($value, 0.05)}; + --ui-BG-Main-gradient: #{rgba($value, 0.6)}; + --ui-BG-Main-light: #{rgba($value, 0.2)}; + --ui-BG-Main-opacity-1: #{rgba($value, 0.1)}; + --ui-BG-Main-opacity-4: #{rgba($value, 0.4)}; + --ui-Main-box-shadow: 0 0.2em 0.5em #{rgba($value, var(--ui-Shadow-opacity))}; + --ui-BG-Main-1: #{mix(rgba(255, 255, 255, 0.7), desaturate($value, 20%), 10%)}; + --ui-BG-Main-2: #{mix(rgba(255, 255, 255, 0.6), desaturate($value, 40%), 20%)}; + --ui-BG-Main-3: #{mix(rgba(119, 119, 119, 0.2), desaturate($value, 40%), 40%)}; + --ui-BG-Main-4: #{mix(rgba(119, 119, 119, 0.1), desaturate($value, 40%), 60%)}; + --ui-BG-Main-TC: #ffffff !important; + } +} diff --git a/sheep/scss/ui.scss b/sheep/scss/ui.scss new file mode 100644 index 0000000..b9b7381 --- /dev/null +++ b/sheep/scss/ui.scss @@ -0,0 +1,19 @@ +@import './theme/style'; //系统主题 +@import './main'; //主样式* + +@import './style/background'; //背景 +@import './style/grid'; //列 +@import './style/flex'; //布局 +@import './style/border'; //边框 +@import './style/text'; //文本 +@import './style/shadow'; //阴影 +@import './icon/style'; //图标 +@import './style/tag'; //标签 +@import './style/button'; //按钮 +@import './style/avatar'; //头像 +@import './style/table'; //表格 +@import './style/code'; //代码片段 +@import './style/form'; //表单 +@import './style/menu'; //表单 +@import './style/markdown'; //表单 +@import './style/card'; //表单 diff --git a/sheep/store/app.js b/sheep/store/app.js new file mode 100644 index 0000000..65ddb8e --- /dev/null +++ b/sheep/store/app.js @@ -0,0 +1,139 @@ +import DiyApi from '@/api/promotion/diy'; +import { defineStore } from 'pinia'; +import $platform from '@/sheep/platform'; +import $router from '@/sheep/router'; +import user from './user'; +import sys from './sys'; + +const app = defineStore({ + id: 'app', + state: () => ({ + info: { + // 应用信息 + name: '', // 商城名称 + logo: '', // logo + version: '', // 版本号 + copyright: '', // 版权信息 I + copytime: '', // 版权信息 II + + cdnurl: '', // 云存储域名 + filesystem: '', // 云存储平台 + }, + platform: { + share: { + methods: [], // 支持的分享方式 + forwardInfo: {}, // 默认转发信息 + posterInfo: {}, // 海报信息 + linkAddress: '', // 复制链接地址 + }, + bind_mobile: 0, // 登陆后绑定手机号提醒 (弱提醒,可手动关闭) + }, + chat: {}, + template: { + // 店铺装修模板 + basic: {}, // 基本信息 + home: { + // 首页模板 + style: {}, + data: [], + }, + user: { + // 个人中心模板 + style: {}, + data: [], + }, + }, + shareInfo: {}, // 全局分享信息 + has_wechat_trade_managed: 0 // 小程序发货信息管理 0 没有 || 1 有 + }), + actions: { + // 获取Shopro应用配置和模板 + async init(templateId = null) { + // 检查网络 + const networkStatus = await $platform.checkNetwork(); + if (!networkStatus) { + $router.error('NetworkError'); + } + + // 加载装修配置 + await adaptTemplate(this.template, templateId) + + // TODO 芋艿:未来支持管理后台可配;对应 https://api.shopro.sheepjs.com/shop/api/init + if (true) { + this.info = { + name: '商城', + logo: 'https://static.iocoder.cn/ruoyi-vue-pro-logo.png', + version: '1.1.13', + copyright: '全部开源,个人与企业可 100% 免费使用', + copytime: 'Copyright© 2018-2024', + + cdnurl: 'https://file.sheepjs.com', // 云存储域名 + filesystem: 'qcloud', // 云存储平台 + }; + this.platform = { + share: { + methods: ["poster", "link"], + linkAddress: "https://shopro.sheepjs.com/#/", + posterInfo: { + "user_bg": "/static/img/shop/config/user-poster-bg.png", + "goods_bg": "/static/img/shop/config/goods-poster-bg.png", + "groupon_bg": "/static/img/shop/config/groupon-poster-bg.png" + } + }, + bind_mobile: 0 + }; + this.chat = { + chat_domain: "https://api.shopro.sheepjs.com/chat", + room_id: "admin" + } + this.has_wechat_trade_managed = 0; + + // 加载主题 + const sysStore = sys(); + sysStore.setTheme(); + + // 模拟用户登录 + const userStore = user(); + if (userStore.isLogin) { + userStore.loginAfter(); + } + return Promise.resolve(true); + } else { + $router.error('InitError', res.msg || '加载失败'); + } + }, + }, + persist: { + enabled: true, + strategies: [ + { + key: 'app-store', + }, + ], + }, +}); + +// todo: @owen 先做数据适配,后期重构 +const adaptTemplate = async (appTemplate, templateId) => { + const { data: diyTemplate } = templateId + // 查询指定模板,一般是预览时使用 + ? await DiyApi.getDiyTemplate(templateId) + : await DiyApi.getUsedDiyTemplate(); + // 模板不存在 + if (!diyTemplate) { + $router.error('TemplateError'); + return + } + + const tabBar = diyTemplate?.property?.tabBar; + if (tabBar) { + appTemplate.basic.tabbar = tabBar + if (tabBar?.theme) { + appTemplate.basic.theme = tabBar?.theme; + } + } + appTemplate.home = diyTemplate?.home; + appTemplate.user = diyTemplate?.user; +} + +export default app; diff --git a/sheep/store/cart.js b/sheep/store/cart.js new file mode 100644 index 0000000..0994b8e --- /dev/null +++ b/sheep/store/cart.js @@ -0,0 +1,106 @@ +import { defineStore } from 'pinia'; +import CartApi from '@/api/trade/cart'; + +const cart = defineStore({ + id: 'cart', + state: () => ({ + list: [], // 购物车列表 + selectedIds: [], // 已选列表 + isAllSelected: false, // 是否全选 + totalPriceSelected: 0, // 选中项总金额 + }), + actions: { + // 获取购物车列表 + async getList() { + const { data, code } = await CartApi.getCartList(); + if (code === 0) { + this.list = data.validList; + + // 计算各种关联属性 + this.selectedIds = []; + this.isAllSelected = true; + this.totalPriceSelected = 0; + this.list.forEach((item) => { + if (item.selected) { + this.selectedIds.push(item.id); + this.totalPriceSelected += item.count * item.sku.price; + } else { + this.isAllSelected = false; + } + }); + } + }, + + // 添加购物车 + async add(goodsInfo) { + // 添加购物项 + const { code } = await CartApi.addCart({ + skuId: goodsInfo.id, + count: goodsInfo.goods_num, + }); + // 刷新购物车列表 + if (code === 0) { + await this.getList(); + } + }, + + // 更新购物车 + async update(goodsInfo) { + const { code } = await CartApi.updateCartCount({ + id: goodsInfo.goods_id, + count: goodsInfo.goods_num, + }); + if (code === 0) { + await this.getList(); + } + }, + + // 移除购物车 + async delete(ids) { + const { code } = await CartApi.deleteCart(ids.join(',')); + if (code === 0) { + await this.getList(); + } + }, + + // 单选购物车商品 + async selectSingle(goodsId) { + const { code } = await CartApi.updateCartSelected({ + ids: [goodsId], + selected: !this.selectedIds.includes(goodsId), // 取反 + }); + if (code === 0) { + await this.getList(); + } + }, + + // 全选购物车商品 + async selectAll(flag) { + const { code } = await CartApi.updateCartSelected({ + ids: this.list.map((item) => item.id), + selected: flag + }); + if (code === 0) { + await this.getList(); + } + }, + + // 清空购物车。注意,仅用于用户退出时,重置数据 + emptyList() { + this.list = []; + this.selectedIds = []; + this.isAllSelected = true; + this.totalPriceSelected = 0; + }, + }, + persist: { + enabled: true, + strategies: [ + { + key: 'cart-store', + }, + ], + }, +}); + +export default cart; diff --git a/sheep/store/index.js b/sheep/store/index.js new file mode 100644 index 0000000..3d06698 --- /dev/null +++ b/sheep/store/index.js @@ -0,0 +1,20 @@ +import { createPinia } from 'pinia'; +import piniaPersist from 'pinia-plugin-persist-uni'; + +// 自动注入所有pinia模块 +const files = import.meta.glob('./*.js', { eager: true }); +const modules = {}; +Object.keys(files).forEach((key) => { + modules[key.replace(/(.*\/)*([^.]+).*/gi, '$2')] = files[key].default; +}); + +export const setupPinia = (app) => { + const pinia = createPinia(); + pinia.use(piniaPersist); + + app.use(pinia); +}; + +export default (name) => { + return modules[name](); +}; diff --git a/sheep/store/modal.js b/sheep/store/modal.js new file mode 100644 index 0000000..bde9e0a --- /dev/null +++ b/sheep/store/modal.js @@ -0,0 +1,29 @@ +import { defineStore } from 'pinia'; + +const modal = defineStore({ + id: 'modal', + state: () => ({ + auth: '', // 授权弹框 accountLogin|smsLogin|resetPassword|changeMobile|changePassword|changeUsername + share: false, // 分享弹框 + menu: false, // 快捷菜单弹框 + advHistory: [], // 广告弹框记录 + lastTimer: { + // 短信验证码计时器,为了防止刷新请求做了持久化 + smsLogin: 0, + changeMobile: 0, + resetPassword: 0, + changePassword: 0, + } + }), + persist: { + enabled: true, + strategies: [ + { + key: 'modal-store', + paths: ['lastTimer', 'advHistory'], + }, + ], + }, +}); + +export default modal; diff --git a/sheep/store/sys.js b/sheep/store/sys.js new file mode 100644 index 0000000..f7151e0 --- /dev/null +++ b/sheep/store/sys.js @@ -0,0 +1,32 @@ +import { defineStore } from 'pinia'; +import app from './app'; + +const sys = defineStore({ + id: 'sys', + state: () => ({ + theme: '', // 主题, + mode: 'light', // 明亮模式、暗黑模式(暂未支持) + modeAuto: false, // 跟随系统 + fontSize: 1, // 设置默认字号等级(0-4) + }), + getters: {}, + actions: { + setTheme(theme = '') { + if (theme === '') { + this.theme = app().template?.basic.theme || 'orange'; + } else { + this.theme = theme; + } + }, + }, + persist: { + enabled: true, + strategies: [ + { + key: 'sys-store', + }, + ], + }, +}); + +export default sys; diff --git a/sheep/store/user.js b/sheep/store/user.js new file mode 100644 index 0000000..c79c98a --- /dev/null +++ b/sheep/store/user.js @@ -0,0 +1,186 @@ +import { + defineStore +} from 'pinia'; +import $share from '@/sheep/platform/share'; +import { + isEmpty, + cloneDeep, + clone +} from 'lodash'; +import cart from './cart'; +import app from './app'; +import { + showAuthModal +} from '@/sheep/hooks/useModal'; +import UserApi from '@/api/member/user'; +import PayWalletApi from '@/api/pay/wallet'; +import OrderApi from '@/api/trade/order'; +import CouponApi from '@/api/promotion/coupon'; + +// 默认用户信息 +const defaultUserInfo = { + avatar: '', // 头像 + nickname: '', // 昵称 + gender: 0, // 性别 + mobile: '', // 手机号 + point: 0, // 积分 +}; + +// 默认钱包信息 +const defaultUserWallet = { + balance: 0, // 余额 +} + +// 默认订单、优惠券等其他资产信息 +const defaultNumData = { + unusedCouponCount: 0, + orderCount: { + allCount: 0, + unpaidCount: 0, + undeliveredCount: 0, + deliveredCount: 0, + uncommentedCount: 0, + afterSaleCount: 0, + }, +}; + +const user = defineStore({ + id: 'user', + state: () => ({ + userInfo: clone(defaultUserInfo), // 用户信息 + userWallet: clone(defaultUserWallet), // 用户钱包信息 + isLogin: !!uni.getStorageSync('token'), // 登录状态 + numData: cloneDeep(defaultNumData), // 用户其他数据 + lastUpdateTime: 0, // 上次更新时间 + }), + + actions: { + // 获取用户信息 + async getInfo() { + const { code, data } = await UserApi.getUserInfo(); + if (code !== 0) { + return; + } + this.userInfo = data; + return Promise.resolve(data); + }, + + // 获得用户钱包 + async getWallet() { + const { code, data } = await PayWalletApi.getPayWallet(); + if (code !== 0) { + return; + } + this.userWallet = data; + }, + + // 获取订单、优惠券等其他资产信息 + getNumData() { + OrderApi.getOrderCount().then(res => { + if (res.code === 0) { + this.numData.orderCount = res.data; + } + }); + CouponApi.getUnusedCouponCount().then(res => { + if (res.code === 0) { + this.numData.unusedCouponCount = res.data; + } + }); + }, + + // 添加分享记录 + // TODO 芋艿:整理下; + async addShareLog(params) { + const { + error + } = await userApi.addShareLog(params); + if (error === 0) uni.removeStorageSync('shareLog'); + }, + + // 设置 token + setToken(token = '', refreshToken = '') { + if (token === '') { + this.isLogin = false; + uni.removeStorageSync('token'); + uni.removeStorageSync('refresh-token') + } else { + this.isLogin = true; + uni.setStorageSync('token', token); + uni.setStorageSync('refresh-token', refreshToken); + this.loginAfter(); + } + return this.isLogin; + }, + + // 更新用户相关信息 (手动限流,5 秒之内不刷新) + async updateUserData() { + if (!this.isLogin) { + this.resetUserData(); + return; + } + // 防抖,5 秒之内不刷新 + const nowTime = new Date().getTime(); + if (this.lastUpdateTime + 5000 > nowTime) { + return; + } + this.lastUpdateTime = nowTime; + + // 获取最新信息 + await this.getInfo(); + this.getWallet(); + this.getNumData(); + return this.userInfo; + }, + + // 重置用户默认数据 + resetUserData() { + // 清空 token + this.setToken(); + // 清空用户相关的缓存 + this.userInfo = clone(defaultUserInfo); + this.userWallet = clone(defaultUserWallet); + this.numData = cloneDeep(defaultNumData); + // 清空购物车的缓存 + cart().emptyList(); + }, + + // 登录后,加载各种信息 + // TODO 芋艿:整理下; + async loginAfter() { + await this.updateUserData(); + + // 加载购物车 + cart().getList(); + // 登录后设置全局分享参数 + $share.getShareInfo(); + + // 提醒绑定手机号 + if (app().platform.bind_mobile && !this.userInfo.mobile) { + showAuthModal('changeMobile'); + } + + // 添加分享记录 + // TODO 芋艿:整理下; + const shareLog = uni.getStorageSync('shareLog'); + if (!isEmpty(shareLog)) { + this.addShareLog({ + ...shareLog, + }); + } + }, + + // 登出系统 + async logout() { + this.resetUserData(); + return !this.isLogin; + } + }, + persist: { + enabled: true, + strategies: [{ + key: 'user-store', + }, ], + }, +}); + +export default user; diff --git a/sheep/ui/su-coupon/su-coupon.vue b/sheep/ui/su-coupon/su-coupon.vue new file mode 100644 index 0000000..472d17f --- /dev/null +++ b/sheep/ui/su-coupon/su-coupon.vue @@ -0,0 +1,319 @@ + + + + + diff --git a/sheep/ui/su-data-checkbox/su-data-checkbox.vue b/sheep/ui/su-data-checkbox/su-data-checkbox.vue new file mode 100644 index 0000000..537ead5 --- /dev/null +++ b/sheep/ui/su-data-checkbox/su-data-checkbox.vue @@ -0,0 +1,894 @@ + + + + + diff --git a/sheep/ui/su-dialog/su-dialog.vue b/sheep/ui/su-dialog/su-dialog.vue new file mode 100644 index 0000000..b53e9ce --- /dev/null +++ b/sheep/ui/su-dialog/su-dialog.vue @@ -0,0 +1,269 @@ + + + + + diff --git a/sheep/ui/su-fixed/su-fixed.vue b/sheep/ui/su-fixed/su-fixed.vue new file mode 100644 index 0000000..e2a9808 --- /dev/null +++ b/sheep/ui/su-fixed/su-fixed.vue @@ -0,0 +1,217 @@ + + + + + diff --git a/sheep/ui/su-image/su-image.vue b/sheep/ui/su-image/su-image.vue new file mode 100644 index 0000000..35f8410 --- /dev/null +++ b/sheep/ui/su-image/su-image.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/sheep/ui/su-inner-navbar/su-inner-navbar.vue b/sheep/ui/su-inner-navbar/su-inner-navbar.vue new file mode 100644 index 0000000..9fc102d --- /dev/null +++ b/sheep/ui/su-inner-navbar/su-inner-navbar.vue @@ -0,0 +1,365 @@ + + + + + diff --git a/sheep/ui/su-navbar/su-navbar.vue b/sheep/ui/su-navbar/su-navbar.vue new file mode 100644 index 0000000..af96f24 --- /dev/null +++ b/sheep/ui/su-navbar/su-navbar.vue @@ -0,0 +1,483 @@ + + + + + + diff --git a/sheep/ui/su-notice-bar/su-notice-bar.vue b/sheep/ui/su-notice-bar/su-notice-bar.vue new file mode 100644 index 0000000..fc5075a --- /dev/null +++ b/sheep/ui/su-notice-bar/su-notice-bar.vue @@ -0,0 +1,473 @@ + + + + + + diff --git a/sheep/ui/su-number-box/su-number-box.vue b/sheep/ui/su-number-box/su-number-box.vue new file mode 100644 index 0000000..6b662cd --- /dev/null +++ b/sheep/ui/su-number-box/su-number-box.vue @@ -0,0 +1,225 @@ + + + diff --git a/sheep/ui/su-popover/su-popover.vue b/sheep/ui/su-popover/su-popover.vue new file mode 100644 index 0000000..adff127 --- /dev/null +++ b/sheep/ui/su-popover/su-popover.vue @@ -0,0 +1,314 @@ + + + + + diff --git a/sheep/ui/su-popup/keypress.js b/sheep/ui/su-popup/keypress.js new file mode 100644 index 0000000..6141c4c --- /dev/null +++ b/sheep/ui/su-popup/keypress.js @@ -0,0 +1,45 @@ +// #ifdef H5 +export default { + name: 'Keypress', + props: { + disable: { + type: Boolean, + default: false, + }, + }, + mounted() { + const keyNames = { + esc: ['Esc', 'Escape'], + tab: 'Tab', + enter: 'Enter', + space: [' ', 'Spacebar'], + up: ['Up', 'ArrowUp'], + left: ['Left', 'ArrowLeft'], + right: ['Right', 'ArrowRight'], + down: ['Down', 'ArrowDown'], + delete: ['Backspace', 'Delete', 'Del'], + }; + const listener = ($event) => { + if (this.disable) { + return; + } + const keyName = Object.keys(keyNames).find((key) => { + const keyName = $event.key; + const value = keyNames[key]; + return value === keyName || (Array.isArray(value) && value.includes(keyName)); + }); + if (keyName) { + // 避免和其他按键事件冲突 + setTimeout(() => { + this.$emit(keyName, {}); + }, 0); + } + }; + document.addEventListener('keyup', listener); + // this.$once('hook:beforeDestroy', () => { + // document.removeEventListener('keyup', listener) + // }) + }, + render: () => {}, +}; +// #endif diff --git a/sheep/ui/su-popup/su-popup.vue b/sheep/ui/su-popup/su-popup.vue new file mode 100644 index 0000000..b55b007 --- /dev/null +++ b/sheep/ui/su-popup/su-popup.vue @@ -0,0 +1,589 @@ + + + + diff --git a/sheep/ui/su-progress/su-progress.vue b/sheep/ui/su-progress/su-progress.vue new file mode 100644 index 0000000..4612705 --- /dev/null +++ b/sheep/ui/su-progress/su-progress.vue @@ -0,0 +1,203 @@ + + + + + diff --git a/sheep/ui/su-radio/su-radio.vue b/sheep/ui/su-radio/su-radio.vue new file mode 100644 index 0000000..6fc4f74 --- /dev/null +++ b/sheep/ui/su-radio/su-radio.vue @@ -0,0 +1,301 @@ + + + + + diff --git a/sheep/ui/su-region-picker/su-region-picker.vue b/sheep/ui/su-region-picker/su-region-picker.vue new file mode 100644 index 0000000..958fd11 --- /dev/null +++ b/sheep/ui/su-region-picker/su-region-picker.vue @@ -0,0 +1,247 @@ + + + + + + diff --git a/sheep/ui/su-status-bar/su-status-bar.vue b/sheep/ui/su-status-bar/su-status-bar.vue new file mode 100644 index 0000000..9af07f9 --- /dev/null +++ b/sheep/ui/su-status-bar/su-status-bar.vue @@ -0,0 +1,16 @@ + + + + + diff --git a/sheep/ui/su-sticky/su-sticky.vue b/sheep/ui/su-sticky/su-sticky.vue new file mode 100644 index 0000000..a8831a2 --- /dev/null +++ b/sheep/ui/su-sticky/su-sticky.vue @@ -0,0 +1,264 @@ + + + + + diff --git a/sheep/ui/su-subline/su-subline.vue b/sheep/ui/su-subline/su-subline.vue new file mode 100644 index 0000000..c11d176 --- /dev/null +++ b/sheep/ui/su-subline/su-subline.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/sheep/ui/su-swiper/su-swiper.vue b/sheep/ui/su-swiper/su-swiper.vue new file mode 100644 index 0000000..cff1c4b --- /dev/null +++ b/sheep/ui/su-swiper/su-swiper.vue @@ -0,0 +1,502 @@ + + + + + diff --git a/sheep/ui/su-switch/su-switch.vue b/sheep/ui/su-switch/su-switch.vue new file mode 100644 index 0000000..d63e800 --- /dev/null +++ b/sheep/ui/su-switch/su-switch.vue @@ -0,0 +1,100 @@ + + + + + + diff --git a/sheep/ui/su-tab-item/su-tab-item.vue b/sheep/ui/su-tab-item/su-tab-item.vue new file mode 100644 index 0000000..615df2f --- /dev/null +++ b/sheep/ui/su-tab-item/su-tab-item.vue @@ -0,0 +1,169 @@ + + + + + + + diff --git a/sheep/ui/su-tab/su-tab.vue b/sheep/ui/su-tab/su-tab.vue new file mode 100644 index 0000000..17a7983 --- /dev/null +++ b/sheep/ui/su-tab/su-tab.vue @@ -0,0 +1,474 @@ + + + + + + + diff --git a/sheep/ui/su-tabbar-item/su-tabbar-item.vue b/sheep/ui/su-tabbar-item/su-tabbar-item.vue new file mode 100644 index 0000000..cc55ac1 --- /dev/null +++ b/sheep/ui/su-tabbar-item/su-tabbar-item.vue @@ -0,0 +1,234 @@ + + + + + + diff --git a/sheep/ui/su-tabbar/su-tabbar.vue b/sheep/ui/su-tabbar/su-tabbar.vue new file mode 100644 index 0000000..92e0352 --- /dev/null +++ b/sheep/ui/su-tabbar/su-tabbar.vue @@ -0,0 +1,227 @@ + + + + + + diff --git a/sheep/ui/su-tabs-item/props.js b/sheep/ui/su-tabs-item/props.js new file mode 100644 index 0000000..3908c36 --- /dev/null +++ b/sheep/ui/su-tabs-item/props.js @@ -0,0 +1,3 @@ +export default { + props: {}, +}; diff --git a/sheep/ui/su-tabs-item/su-tabs-item.vue b/sheep/ui/su-tabs-item/su-tabs-item.vue new file mode 100644 index 0000000..7139684 --- /dev/null +++ b/sheep/ui/su-tabs-item/su-tabs-item.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/sheep/ui/su-tabs/su-tabs.vue b/sheep/ui/su-tabs/su-tabs.vue new file mode 100644 index 0000000..05d7dcb --- /dev/null +++ b/sheep/ui/su-tabs/su-tabs.vue @@ -0,0 +1,434 @@ + + + + + diff --git a/sheep/ui/su-time-line/su-time-line.vue b/sheep/ui/su-time-line/su-time-line.vue new file mode 100644 index 0000000..9c3f8c1 --- /dev/null +++ b/sheep/ui/su-time-line/su-time-line.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/sheep/ui/su-timeline-item/su-timeline-item.vue b/sheep/ui/su-timeline-item/su-timeline-item.vue new file mode 100644 index 0000000..ddf7a0b --- /dev/null +++ b/sheep/ui/su-timeline-item/su-timeline-item.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/sheep/ui/su-toolbar/su-toolbar.vue b/sheep/ui/su-toolbar/su-toolbar.vue new file mode 100644 index 0000000..17351ec --- /dev/null +++ b/sheep/ui/su-toolbar/su-toolbar.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/sheep/ui/su-video/su-video.vue b/sheep/ui/su-video/su-video.vue new file mode 100644 index 0000000..a75a79c --- /dev/null +++ b/sheep/ui/su-video/su-video.vue @@ -0,0 +1,199 @@ + + + diff --git a/sheep/url/index.js b/sheep/url/index.js new file mode 100644 index 0000000..d216f83 --- /dev/null +++ b/sheep/url/index.js @@ -0,0 +1,199 @@ +import $store from '@/sheep/store'; +import { staticUrl } from '@/sheep/config'; + +const cdn = (url = '', cdnurl = '') => { + if (!url) return ''; + if (url.indexOf('http') === 0) { + return url; + } + if (cdnurl === '') { + cdnurl = $store('app').info.cdnurl; + } + return cdnurl + url; +}; +export default { + // 添加cdn域名前缀 + cdn, + // 对象存储自动剪裁缩略图 + thumb: (url = '', params) => { + url = cdn(url); + return append_thumbnail_params(url, params); + }, + // 静态资源地址 + static: (url = '', staticurl = '') => { + if (staticurl === '') { + staticurl = staticUrl; + } + if (staticurl !== 'local') { + url = cdn(url, staticurl); + } + return url; + }, + // css背景图片地址 + css: (url = '', staticurl = '') => { + if (staticurl === '') { + staticurl = staticUrl; + } + if (staticurl !== 'local') { + url = cdn(url, staticurl); + } + // #ifdef APP-PLUS + if (staticurl === 'local') { + url = plus.io.convertLocalFileSystemURL(url); + } + // #endif + return `url(${url})`; + }, +}; + +/** + * 追加对象存储自动裁剪/压缩参数 + * + * @return string + */ +function append_thumbnail_params(url, params) { + const filesystem = $store('app').info.filesystem; + if (filesystem === 'public') { + return url; + } + let width = params.width || '200'; // 宽度 + let height = params.height || '200'; // 高度 + let mode = params.mode || 'lfit'; // 缩放模式 + let quality = params.quality || 90; // 压缩质量 + let gravity = params.gravity || 'center'; // 剪裁质量 + let suffix = ''; + let crop_str = ''; + let quality_str = ''; + let size = width + 'x' + height; + switch (filesystem) { + case 'aliyun': + // 裁剪 + if (!gravity && gravity != 'center') { + // 指定了裁剪区域 + mode = 'mfit'; + crop_str = '/crop,g_' + gravityFormatMap('aliyun', gravity) + ',w_' + width + ',h_' + height; + } + + // 质量压缩 + if (quality > 0 && quality < 100) { + quality_str = '/quality,q_' + quality; + } + + // 缩放参数 + suffix = 'x-oss-process=image/resize,m_' + mode + ',w_' + width + ',h_' + height; + + // 拼接裁剪和质量压缩 + suffix += crop_str + quality_str; + break; + case 'qcloud': + let mode_str = 'thumbnail'; + if (mode == 'fill' || (!gravity && gravity != 'center')) { + // 指定了裁剪区域 + mode_str = 'crop'; + mode = 'fill'; + crop_str = '/gravity/' + gravityFormatMap('qcloud', gravity); + } + + // 质量压缩 + if (quality > 0 && quality < 100) { + quality_str = '/rquality/' + quality; + } + + switch (mode) { + case 'lfit': + size = '' + size + '>'; + break; + case 'mfit': + size = '!' + size + 'r'; + case 'fill': + break; + case 'pad': + size = size + '/pad/1'; + break; + case 'fixed': + size = size + '!'; + break; + } + + suffix = 'imageMogr2/' + mode_str + '/' + size + crop_str + quality_str; + break; + case 'qiniu': + if (mode == 'fill' || (!gravity && gravity != 'center')) { + // 指定了裁剪区域,全部转为 mfit + mode = 'mfit'; + crop_str = '/gravity/' + gravityFormatMap('qiniu', gravity) + '/crop/' + size; + } + // 质量压缩 + if (quality > 0 && quality < 100) { + quality_str = '/quality/' + quality; + } + + switch (mode) { + case 'lfit': + case 'pad': // 七牛不支持在缩放之后,尺寸不足时,填充背景色,所以这里和 lfit 模式一样 + size = size + '>'; + break; + case 'mfit': + size = '!' + size + 'r'; + break; + case 'fill': + // 会被转为 mfit + break; + case 'fixed': + size = size + '!'; + break; + } + + suffix = 'imageMogr2/thumbnail/' + size + crop_str + quality_str; + break; + } + return url + '?' + suffix; +} + +/** + * 裁剪区域格式转换 + * + * @param string $type aliyun|qcloud|qiniu + * @param string $gravity 统一的裁剪区域字符 + * + * @return string + */ +function gravityFormatMap(type, gravity) { + let gravityFormat = { + aliyun: { + north_west: 'nw', // 左上 + north: 'north', // 中上 + north_east: 'ne', // 右上 + west: 'west', // 左中 + center: 'center', // 中部 + east: 'east', // 右中 + south_west: 'sw', // 左下 + south: 'south', // 中下 + south_east: 'se', // 右下 + }, + qcloud: { + northwest: 'nw', // 左上 + north: 'north', // 中上 + northeast: 'ne', // 右上 + west: 'west', // 左中 + center: 'center', // 中部 + east: 'east', // 右中 + southwest: 'sw', // 左下 + south: 'south', // 中下 + southeast: 'se', // 右下 + }, + qiniu: { + NorthWest: 'nw', // 左上 + North: 'north', // 中上 + NorthEast: 'ne', // 右上 + West: 'west', // 左中 + Center: 'center', // 中部 + East: 'east', // 右中 + SouthWest: 'sw', // 左下 + South: 'south', // 中下 + SouthEast: 'se', // 右下 + }, + }; + + return gravityFormat[type][gravity]; +} diff --git a/sheep/util/const.js b/sheep/util/const.js new file mode 100644 index 0000000..e5eba0c --- /dev/null +++ b/sheep/util/const.js @@ -0,0 +1,68 @@ +// ========== MALL - 营销模块 ========== + +import dayjs from "dayjs"; + +/** + * 优惠类型枚举 + */ +export const PromotionDiscountTypeEnum = { + PRICE: { + type: 1, + name: '满减' + }, + PERCENT: { + type: 2, + name: '折扣' + } +} + +/** + * 优惠劵模板的有限期类型的枚举 + */ +export const CouponTemplateValidityTypeEnum = { + DATE: { + type: 1, + name: '固定日期可用' + }, + TERM: { + type: 2, + name: '领取之后可用' + } +} + +/** + * 营销的商品范围枚举 + */ +export const PromotionProductScopeEnum = { + ALL: { + scope: 1, + name: '通用劵' + }, + SPU: { + scope: 2, + name: '商品劵' + }, + CATEGORY: { + scope: 3, + name: '品类劵' + } +} + + +// 时间段的状态枚举 +export const TimeStatusEnum = { + WAIT_START: '即将开始', + STARTED: '进行中', + END: '已结束', +} + +export const getTimeStatusEnum = (startTime, endTime) => { + const now = dayjs(); + if (now.isBefore(startTime)) { + return TimeStatusEnum.WAIT_START; + } else if (now.isAfter(endTime)) { + return TimeStatusEnum.END; + } else { + return TimeStatusEnum.STARTED; + } +} diff --git a/sheep/util/index.js b/sheep/util/index.js new file mode 100644 index 0000000..9b355e8 --- /dev/null +++ b/sheep/util/index.js @@ -0,0 +1,115 @@ +import dayjs from "dayjs"; + +/** + * 将一个整数转换为分数保留两位小数 + * @param {number | string | undefined} num 整数 + * @return {number} 分数 + */ +export const formatToFraction = (num) => { + if (typeof num === 'undefined') return 0 + const parsedNumber = typeof num === 'string' ? parseFloat(num) : num + return parseFloat((parsedNumber / 100).toFixed(2)) +} + +/** + * 将一个数转换为 1.00 这样 + * 数据呈现的时候使用 + * + * @param {number | string | undefined} num 整数 + * @return {string} 分数 + */ +export const floatToFixed2 = (num) => { + let str = '0.00' + if (typeof num === 'undefined') { + return str + } + const f = formatToFraction(num) + const decimalPart = f.toString().split('.')[1] + const len = decimalPart ? decimalPart.length : 0 + switch (len) { + case 0: + str = f.toString() + '.00' + break + case 1: + str = f.toString() + '.0' + break + case 2: + str = f.toString() + break + } + return str +} + +/** + * 将一个分数转换为整数 + * + * @param {number | string | undefined} num 分数 + * @return {number} 整数 + */ +export const convertToInteger = (num) => { + if (typeof num === 'undefined') return 0 + const parsedNumber = typeof num === 'string' ? parseFloat(num) : num + // TODO 分转元后还有小数则四舍五入 + return Math.round(parsedNumber * 100) +} + +/** + * 时间日期转换 + * @param {dayjs.ConfigType} date 当前时间,new Date() 格式 + * @param {string} format 需要转换的时间格式字符串 + * @description format 字符串随意,如 `YYYY-mm、YYYY-mm-dd` + * @description format 季度:"YYYY-mm-dd HH:MM:SS QQQQ" + * @description format 星期:"YYYY-mm-dd HH:MM:SS WWW" + * @description format 几周:"YYYY-mm-dd HH:MM:SS ZZZ" + * @description format 季度 + 星期 + 几周:"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ" + * @returns {string} 返回拼接后的时间字符串 + */ +export function formatDate(date, format) { + // 日期不存在,则返回空 + if (!date) { + return '' + } + // 日期存在,则进行格式化 + if (format === undefined) { + format = 'YYYY-MM-DD HH:mm:ss' + } + return dayjs(date).format(format) +} + +/** + * 构造树型结构数据 + * + * @param {*} data 数据源 + * @param {*} id id字段 默认 'id' + * @param {*} parentId 父节点字段 默认 'parentId' + * @param {*} children 孩子节点字段 默认 'children' + * @param {*} rootId 根Id 默认 0 + */ +export function handleTree(data, id = 'id', parentId = 'parentId', children = 'children', rootId = 0) { + // 对源数据深度克隆 + const cloneData = JSON.parse(JSON.stringify(data)) + // 循环所有项 + const treeData = cloneData.filter(father => { + let branchArr = cloneData.filter(child => { + //返回每一项的子级数组 + return father[id] === child[parentId] + }); + branchArr.length > 0 ? father.children = branchArr : ''; + //返回第一层 + return father[parentId] === rootId; + }); + return treeData !== '' ? treeData : data; +} + +/** + * 重置分页对象 + * + * TODO 芋艿:需要处理其它页面 + * + * @param pagination 分页对象 + */ +export function resetPagination(pagination) { + pagination.list = []; + pagination.total = 0; + pagination.pageNo = 1; +} \ No newline at end of file diff --git a/sheep/validate/form.js b/sheep/validate/form.js new file mode 100644 index 0000000..07b5725 --- /dev/null +++ b/sheep/validate/form.js @@ -0,0 +1,164 @@ +/** + * Validate v1.0.0 通用验证 + * @description 项目中用到的表单验证规则 + */ +import test from '@/sheep/helper/test.js'; + +// 手机号 +export const mobile = { + rules: [ + { + required: true, + errorMessage: '请输入手机号', + }, + { + validateFunction: function (rule, value, data, callback) { + if (!test.mobile(value)) { + callback('手机号码格式不正确'); + } + return true; + }, + }, + ], +}; + +// 密码 +export const password = { + rules: [ + { + required: true, + errorMessage: '请输入密码', + }, + { + validateFunction: function (rule, value, data, callback) { + if (!/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]+\S{5,12}$/.test(value)) { + callback('需包含字母和数字,长度在6-12之间'); + } + return true; + }, + }, + ], +}; + +// 短信验证码 +export const code = { + rules: [ + { + required: true, + errorMessage: '请输入验证码', + }, + ], +}; + +// 真实姓名 +export const realName = { + rules: [ + { + required: true, + errorMessage: '请输入姓名', + }, + { + validateFunction: function (rule, value, data, callback) { + if (!test.chinese(value)) { + callback('请输入汉字'); + } + return true; + }, + }, + ], +}; + +export const taxName = { + rules: [ + { + required: true, + errorMessage: '请输入发票抬头名称', + }, + { + validateFunction: function (rule, value, data, callback) { + if (!test.chinese(value)) { + callback('请输入汉字'); + } + return true; + }, + }, + ], +}; + +// 税号 +export const taxNo = { + rules: [ + { + required: true, + errorMessage: '请输入税号', + }, + ], +}; + +// 开户行 +export const bankName = { + rules: [ + { + required: true, + errorMessage: '请输入开户行', + }, + { + validateFunction: function (rule, value, data, callback) { + if (!test.chinese(value)) { + callback('请输入汉字'); + } + return true; + }, + }, + ], +}; +// 银行卡号 +export const bankCode = { + rules: [ + { + required: true, + errorMessage: '请输入银行卡号', + }, + { + validateFunction: function (rule, value, data, callback) { + if (!test.number(value)) { + callback('请输入正确账号'); + } + return true; + }, + }, + ], +}; + +// 支付宝账号 +export const alipayAccount = { + rules: [ + { + required: true, + errorMessage: '请输入支付宝账号', + }, + { + validateFunction: function (rule, value, data, callback) { + let isEmail = test.email(value); + let isMobile = test.mobile(value); + + if (!isEmail && !isMobile) { + callback('请输入正确账号'); + } + return true; + }, + }, + ], +}; + +export default { + mobile, + alipayAccount, + bankCode, + bankName, + realName, + password, + code, + taxNo, + taxName, +}; diff --git a/static/activity-left.png b/static/activity-left.png new file mode 100644 index 0000000..62e3b83 Binary files /dev/null and b/static/activity-left.png differ diff --git a/static/activity-right.png b/static/activity-right.png new file mode 100644 index 0000000..e18a96c Binary files /dev/null and b/static/activity-right.png differ diff --git a/static/cart-empty.png b/static/cart-empty.png new file mode 100644 index 0000000..355d182 Binary files /dev/null and b/static/cart-empty.png differ diff --git a/static/collect-empty.png b/static/collect-empty.png new file mode 100644 index 0000000..300bd21 Binary files /dev/null and b/static/collect-empty.png differ diff --git a/static/comment-empty.png b/static/comment-empty.png new file mode 100644 index 0000000..810a3b1 Binary files /dev/null and b/static/comment-empty.png differ diff --git a/static/coupon-empty.png b/static/coupon-empty.png new file mode 100644 index 0000000..9e14d9d Binary files /dev/null and b/static/coupon-empty.png differ diff --git a/static/data-empty.png b/static/data-empty.png new file mode 100644 index 0000000..a682ff7 Binary files /dev/null and b/static/data-empty.png differ diff --git a/static/goods-empty.png b/static/goods-empty.png new file mode 100644 index 0000000..bd42eca Binary files /dev/null and b/static/goods-empty.png differ diff --git a/static/images/search.png b/static/images/search.png new file mode 100644 index 0000000..157be9d Binary files /dev/null and b/static/images/search.png differ diff --git a/static/images/sort1.png b/static/images/sort1.png new file mode 100644 index 0000000..2a88241 Binary files /dev/null and b/static/images/sort1.png differ diff --git a/static/images/sort2.png b/static/images/sort2.png new file mode 100644 index 0000000..74faf73 Binary files /dev/null and b/static/images/sort2.png differ diff --git a/static/images/sort3.png b/static/images/sort3.png new file mode 100644 index 0000000..c3ce7d4 Binary files /dev/null and b/static/images/sort3.png differ diff --git a/static/internet-empty.png b/static/internet-empty.png new file mode 100644 index 0000000..55ac2f6 Binary files /dev/null and b/static/internet-empty.png differ diff --git a/static/logo.png b/static/logo.png new file mode 100644 index 0000000..b5771e2 Binary files /dev/null and b/static/logo.png differ diff --git a/static/order-empty.png b/static/order-empty.png new file mode 100644 index 0000000..a7b46fe Binary files /dev/null and b/static/order-empty.png differ diff --git a/static/soldout-empty.png b/static/soldout-empty.png new file mode 100644 index 0000000..1761466 Binary files /dev/null and b/static/soldout-empty.png differ diff --git a/uni.promisify.adaptor.js b/uni.promisify.adaptor.js new file mode 100644 index 0000000..47fbce1 --- /dev/null +++ b/uni.promisify.adaptor.js @@ -0,0 +1,10 @@ +uni.addInterceptor({ + returnValue (res) { + if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) { + return res; + } + return new Promise((resolve, reject) => { + res.then((res) => res[0] ? reject(res[0]) : resolve(res[1])); + }); + }, +}); \ No newline at end of file diff --git a/uni.scss b/uni.scss new file mode 100644 index 0000000..eadc5cd --- /dev/null +++ b/uni.scss @@ -0,0 +1,76 @@ +/** + * 这里是uni-app内置的常用样式变量 + * + * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 + * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App + * + */ +@import '@/sheep/scss/_var.scss'; +/** + * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 + * + * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 + */ + +/* 颜色变量 */ + +/* 行为相关颜色 */ +$uni-color-primary: #007aff; +$uni-color-success: #4cd964; +$uni-color-warning: #f0ad4e; +$uni-color-error: #dd524d; + +/* 文字基本颜色 */ +$uni-text-color:#333;//基本色 +$uni-text-color-inverse:#fff;//反色 +$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 +$uni-text-color-placeholder: #808080; +$uni-text-color-disable:#c0c0c0; + +/* 背景颜色 */ +$uni-bg-color:#ffffff; +$uni-bg-color-grey:#f8f8f8; +$uni-bg-color-hover:#f1f1f1;//点击状态颜色 +$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 + +/* 边框颜色 */ +$uni-border-color:#e5e5e5; + +/* 尺寸变量 */ + +/* 文字尺寸 */ +$uni-font-size-sm:12px; +$uni-font-size-base:14px; +$uni-font-size-lg:16; + +/* 图片尺寸 */ +$uni-img-size-sm:20px; +$uni-img-size-base:26px; +$uni-img-size-lg:40px; + +/* Border Radius */ +$uni-border-radius-sm: 2px; +$uni-border-radius-base: 3px; +$uni-border-radius-lg: 6px; +$uni-border-radius-circle: 50%; + +/* 水平间距 */ +$uni-spacing-row-sm: 5px; +$uni-spacing-row-base: 10px; +$uni-spacing-row-lg: 15px; + +/* 垂直间距 */ +$uni-spacing-col-sm: 4px; +$uni-spacing-col-base: 8px; +$uni-spacing-col-lg: 12px; + +/* 透明度 */ +$uni-opacity-disabled: 0.3; // 组件禁用态的透明度 + +/* 文章场景相关 */ +$uni-color-title: #2C405A; // 文章标题颜色 +$uni-font-size-title:20px; +$uni-color-subtitle: #555555; // 二级标题颜色 +$uni-font-size-subtitle:26px; +$uni-color-paragraph: #3F536E; // 文章段落颜色 +$uni-font-size-paragraph:15px; diff --git a/uni_modules/mp-html/README.md b/uni_modules/mp-html/README.md new file mode 100644 index 0000000..7626289 --- /dev/null +++ b/uni_modules/mp-html/README.md @@ -0,0 +1,194 @@ +## 为减小组件包的大小,默认组件包中不包含编辑、latex 公式等扩展功能,需要使用扩展功能的请参考下方的 插件扩展 栏的说明 + +## 功能介绍 +- 全端支持(含 `v3、NVUE`) +- 支持丰富的标签(包括 `table`、`video`、`svg` 等) +- 支持丰富的事件效果(自动预览图片、链接处理等) +- 支持设置占位图(加载中、出错时、预览时) +- 支持锚点跳转、长按复制等丰富功能 +- 支持大部分 *html* 实体 +- 丰富的插件(关键词搜索、内容编辑、`latex` 公式等) +- 效率高、容错性强且轻量化 + +查看 [功能介绍](https://jin-yufeng.gitee.io/mp-html/#/overview/feature) 了解更多 + +## 使用方法 +- `uni_modules` 方式 + 1. 点击右上角的 `使用 HBuilder X 导入插件` 按钮直接导入项目或点击 `下载插件 ZIP` 按钮下载插件包并解压到项目的 `uni_modules/mp-html` 目录下 + 2. 在需要使用页面的 `(n)vue` 文件中添加 + ```html + + + ``` + ```javascript + export default { + data() { + return { + html: '
Hello World!
' + } + } + } + ``` + 3. 需要更新版本时在 `HBuilder X` 中右键 `uni_modules/mp-html` 目录选择 `从插件市场更新` 即可 + +- 源码方式 + 1. 从 [github](https://github.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 或 [gitee](https://gitee.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 下载源码 + 插件市场的 **非 uni_modules 版本** 无法更新,不建议从插件市场获取 + 2. 在需要使用页面的 `(n)vue` 文件中添加 + ```html + + ``` + ```javascript + import mpHtml from '@/components/mp-html/mp-html' + export default { + // HBuilderX 2.5.5+ 可以通过 easycom 自动引入 + components: { + mpHtml + }, + data() { + return { + html: '
Hello World!
' + } + } + } + ``` + +- npm 方式 + 1. 在项目根目录下执行 + ```bash + npm install mp-html + ``` + 2. 在需要使用页面的 `(n)vue` 文件中添加 + ```html + + ``` + ```javascript + import mpHtml from 'mp-html/dist/uni-app/components/mp-html/mp-html' + export default { + // 不可省略 + components: { + mpHtml + }, + data() { + return { + html: '
Hello World!
' + } + } + } + ``` + 3. 需要更新版本时执行以下命令即可 + ```bash + npm update mp-html + ``` + + 使用 *cli* 方式运行的项目,通过 *npm* 方式引入时,需要在 *vue.config.js* 中配置 *transpileDependencies*,详情可见 [#330](https://github.com/jin-yufeng/mp-html/issues/330#issuecomment-913617687) + 如果在 **nvue** 中使用还要将 `dist/uni-app/static` 目录下的内容拷贝到项目的 `static` 目录下,否则无法运行 + +查看 [快速开始](https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart) 了解更多 + +## 组件属性 + +| 属性 | 类型 | 默认值 | 说明 | +|:---:|:---:|:---:|---| +| container-style | String | | 容器的样式([2.1.0+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v210)) | +| content | String | | 用于渲染的 html 字符串 | +| copy-link | Boolean | true | 是否允许外部链接被点击时自动复制 | +| domain | String | | 主域名(用于链接拼接) | +| error-img | String | | 图片出错时的占位图链接 | +| lazy-load | Boolean | false | 是否开启图片懒加载 | +| loading-img | String | | 图片加载过程中的占位图链接 | +| pause-video | Boolean | true | 是否在播放一个视频时自动暂停其他视频 | +| preview-img | Boolean | true | 是否允许图片被点击时自动预览 | +| scroll-table | Boolean | false | 是否给每个表格添加一个滚动层使其能单独横向滚动 | +| selectable | Boolean | false | 是否开启文本长按复制 | +| set-title | Boolean | true | 是否将 title 标签的内容设置到页面标题 | +| show-img-menu | Boolean | true | 是否允许图片被长按时显示菜单 | +| tag-style | Object | | 设置标签的默认样式 | +| use-anchor | Boolean | false | 是否使用锚点链接 | + +查看 [属性](https://jin-yufeng.gitee.io/mp-html/#/basic/prop) 了解更多 + +## 组件事件 + +| 名称 | 触发时机 | +|:---:|---| +| load | dom 树加载完毕时 | +| ready | 图片加载完毕时 | +| error | 发生渲染错误时 | +| imgtap | 图片被点击时 | +| linktap | 链接被点击时 | +| play | 音视频播放时 | + +查看 [事件](https://jin-yufeng.gitee.io/mp-html/#/basic/event) 了解更多 + +## api +组件实例上提供了一些 `api` 方法可供调用 + +| 名称 | 作用 | +|:---:|---| +| in | 将锚点跳转的范围限定在一个 scroll-view 内 | +| navigateTo | 锚点跳转 | +| getText | 获取文本内容 | +| getRect | 获取富文本内容的位置和大小 | +| setContent | 设置富文本内容 | +| imgList | 获取所有图片的数组 | +| pauseMedia | 暂停播放音视频([2.2.2+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v222)) | +| setPlaybackRate | 设置音视频播放速率([2.4.0+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v240)) | + +查看 [api](https://jin-yufeng.gitee.io/mp-html/#/advanced/api) 了解更多 + +## 插件扩展 +除基本功能外,本组件还提供了丰富的扩展,可按照需要选用 + +| 名称 | 作用 | +|:---:|---| +| audio | 音乐播放器 | +| editable | 富文本 **编辑**([示例项目](https://mp-html.oss-cn-hangzhou.aliyuncs.com/editable.zip)) | +| emoji | 解析 emoji | +| highlight | 代码块高亮显示 | +| markdown | 渲染 markdown | +| search | 关键词搜索 | +| style | 匹配 style 标签中的样式 | +| txv-video | 使用腾讯视频 | +| img-cache | 图片缓存 by [@PentaTea](https://github.com/PentaTea) | +| latex | 渲染 latex 公式 by [@Zeng-J](https://github.com/Zeng-J) | + +从插件市场导入的包中 **不含有** 扩展插件,使用插件需通过微信小程序 `富文本插件` 获取或参考以下方法进行打包: +1. 获取完整组件包 + ```bash + npm install mp-html + ``` +2. 编辑 `tools/config.js` 中的 `plugins` 项,选择需要的插件 +3. 生成新的组件包 + 在 `node_modules/mp-html` 目录下执行 + ```bash + npm install + npm run build:uni-app + ``` +4. 拷贝 `dist/uni-app` 中的内容到项目根目录 + +查看 [插件](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin) 了解更多 + +## 关于 nvue +`nvue` 使用原生渲染,不支持部分 `css` 样式,为实现和 `html` 相同的效果,组件内部通过 `web-view` 进行渲染,性能上差于原生,根据 `weex` 官方建议,`web` 标签仅应用在非常规的降级场景。因此,如果通过原生的方式(如 `richtext`)能够满足需要,则不建议使用本组件,如果有较多的富文本内容,则可以直接使用 `vue` 页面 +由于渲染方式与其他端不同,有以下限制: +1. 不支持 `lazy-load` 属性 +2. 视频不支持全屏播放 +3. 如果在 `flex-direction: row` 的容器中使用,需要给组件设置宽度或设置 `flex: 1` 占满剩余宽度 + +纯 `nvue` 模式下,[此问题](https://ask.dcloud.net.cn/question/119678) 修复前,不支持通过 `uni_modules` 引入,需要本地引入(将 [dist/uni-app](https://github.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 中的内容拷贝到项目根目录下) + +## 立即体验 +![富文本插件](https://mp-html.oss-cn-hangzhou.aliyuncs.com/qrcode.jpg) + +## 问题反馈 +遇到问题时,请先查阅 [常见问题](https://jin-yufeng.gitee.io/mp-html/#/question/faq) 和 [issue](https://github.com/jin-yufeng/mp-html/issues) 中是否已有相同的问题 +可通过 [issue](https://github.com/jin-yufeng/mp-html/issues/new/choose) 、插件问答或发送邮件到 [mp_html@126.com](mailto:mp_html@126.com) 提问,不建议在评论区提问(不方便回复) +提问请严格按照 [issue 模板](https://github.com/jin-yufeng/mp-html/issues/new/choose) ,描述清楚使用环境、`html` 内容或可复现的 `demo` 项目以及复现方式,对于 **描述不清**、**无法复现** 或重复的问题将不予回复 + +欢迎加入 `QQ` 交流群: +群1(已满):`699734691` +群2(已满):`778239129` +群3:`960265313` + +查看 [问题反馈](https://jin-yufeng.gitee.io/mp-html/#/question/feedback) 了解更多 diff --git a/uni_modules/mp-html/changelog.md b/uni_modules/mp-html/changelog.md new file mode 100644 index 0000000..6fd03eb --- /dev/null +++ b/uni_modules/mp-html/changelog.md @@ -0,0 +1,150 @@ +## v2.5.0(2024-04-22) +1. `U` `play` 事件增加返回 `src` 等信息 [详细](https://github.com/jin-yufeng/mp-html/issues/526) +2. `U` `preview-img` 属性支持设置为 `all` 开启 `base64` 图片预览 [详细](https://github.com/jin-yufeng/mp-html/issues/536) +3. `U` `editable` 插件增加简易模式(点击文字直接编辑) +4. `U` `latex` 插件支持块级公式 [详细](https://github.com/jin-yufeng/mp-html/issues/582) +5. `F` 修复了表格部分情况下背景丢失的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/587) +6. `F` 修复了部分 `svg` 无法显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/591) +7. `F` 修复了 `h5` 和 `app` 端部分情况下样式无法识别的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/518) +8. `F` 修复了 `latex` 插件部分情况下显示不正确的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/580) +9. `F` 修复了 `editable` 插件表格无法删除的问题 +10. `F` 修复了 `editable` 插件 `vue3` `h5` 端点击图片报错的问题 +11. `F` 修复了 `editable` 插件点击表格没有菜单栏的问题 +## v2.4.3(2024-01-21) +1. `A` 增加 [card](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#card) 插件 [详细](https://github.com/jin-yufeng/mp-html/pull/533) by [@whoooami](https://github.com/whoooami) +2. `F` 修复了 `svg` 中包含 `foreignobject` 可能不显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/523) +3. `F` 修复了合并单元格的表格部分情况下显示不正确的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/561) +4. `F` 修复了 `img` 标签设置 `object-fit` 无效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/567) +5. `F` 修复了 `latex` 插件公式会换行的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/540) +6. `F` 修复了 `editable` 和 `audio` 插件共用时点击 `audio` 无法编辑的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/529) by [@whoooami](https://github.com/whoooami) +7. `F` 修复了微信小程序部分情况下图片会报错 `replace of undefined` 的问题 +8. `F` 修复了快手小程序图片不显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/571) +## v2.4.2(2023-05-14) +1. `A` `editable` 插件支持修改文字颜色 [详细](https://github.com/jin-yufeng/mp-html/issues/254) +2. `F` 修复了 `svg` 中有 `style` 不生效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/505) +3. `F` 修复了使用旧版编译器可能报错 `Bad attr nodes` 的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/472) +4. `F` 修复了 `app` 端可能出现无法读取 `lazyLoad` 的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/513) +5. `F` 修复了 `editable` 插件在点击换图时未拼接 `domain` 的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/497) by [@TwoKe945](https://github.com/TwoKe945) +6. `F` 修复了 `latex` 插件部分情况下不显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/515) +7. `F` 修复了 `editable` 插件点击音视频时其他标签框不消失的问题 +## v2.4.1(2022-12-25) +1. `F` 修复了没有图片时 `ready` 事件可能不触发的问题 +2. `F` 修复了加载过程中可能出现 `Root label not found` 错误的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/470) +3. `F` 修复了 `audio` 插件退出页面可能会报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/457) +4. `F` 修复了 `vue3` 运行到 `app` 在 `HBuilder X 3.6.10` 以上报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/480) +5. `F` 修复了 `nvue` 端链接中包含 `%22` 时可能无法显示的问题 +6. `F` 修复了 `vue3` 使用 `highlight` 插件可能报错的问题 +## v2.4.0(2022-08-27) +1. `A` 增加了 [setPlaybackRate](https://jin-yufeng.gitee.io/mp-html/#/advanced/api#setPlaybackRate) 的 `api`,可以设置音视频的播放速率 [详细](https://github.com/jin-yufeng/mp-html/issues/452) +2. `A` 示例小程序代码开源 [详细](https://github.com/jin-yufeng/mp-html-demo) +3. `U` 优化 `ready` 事件触发时机,未设置懒加载的情况下基本可以准确触发 [详细](https://github.com/jin-yufeng/mp-html/issues/195) +4. `U` `highlight` 插件在编辑状态下不进行高亮处理,便于编辑 +5. `F` 修复了 `flex` 布局下图片大小可能不正确的问题 +6. `F` 修复了 `selectable` 属性没有设置 `force` 也可能出现渲染异常的问题 +7. `F` 修复了表格中的图片大小可能不正确的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/448) +8. `F` 修复了含有合并单元格的表格可能无法设置竖直对齐的问题 +9. `F` 修复了 `editable` 插件在 `scroll-view` 中使用时工具条位置可能不正确的问题 +10. `F` 修复了 `vue3` 使用 [search](advanced/plugin#search) 插件可能导致错误换行的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/449) +## v2.3.2(2022-08-13) +1. `A` 增加 [latex](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#latex) 插件,可以渲染数学公式 [详细](https://github.com/jin-yufeng/mp-html/pull/447) by [@Zeng-J](https://github.com/Zeng-J) +2. `U` 优化根节点下有很多标签的长内容渲染速度 +3. `U` `highlight` 插件适配 `lang-xxx` 格式 +4. `F` 修复了 `table` 标签设置 `border` 属性后可能无法修改边框样式的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/439) by [@zouxingjie](https://github.com/zouxingjie) +5. `F` 修复了 `editable` 插件输入连续空格无效的问题 +6. `F` 修复了 `vue3` 图片设置 `inline` 会报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/438) +7. `F` 修复了 `vue3` 使用 `table` 可能报错的问题 +## v2.3.1(2022-05-20) +1. `U` `app` 端支持使用本地图片 +2. `U` 优化了微信小程序 `selectable` 属性在 `ios` 端的处理 [详细](https://jin-yufeng.gitee.io/mp-html/#/basic/prop#selectable) +3. `F` 修复了 `editable` 插件不在顶部时 `tooltip` 位置可能错误的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/430) +4. `F` 修复了 `vue3` 运行到微信小程序可能报错丢失内容的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/414) +5. `F` 修复了 `vue3` 部分标签可能被错误换行的问题 +6. `F` 修复了 `editable` 插件 `app` 端插入视频无法预览的问题 +## v2.3.0(2022-04-01) +1. `A` 增加了 `play` 事件,音视频播放时触发,可用于与页面其他音视频进行互斥播放 [详细](basic/event#play) +2. `U` `show-img-menu` 属性支持控制预览时是否长按弹出菜单 +3. `U` 优化 `wxs` 处理,提高渲染性能 [详细](https://developers.weixin.qq.com/community/develop/article/doc/0006cc2b204740f601bd43fa25a413) +4. `U` `video` 标签支持 `object-fit` 属性 +5. `U` 增加支持一些常用实体编码 [详细](https://github.com/jin-yufeng/mp-html/issues/418) +6. `F` 修复了图片仅设置高度可能不显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/410) +7. `F` 修复了 `video` 标签高度设置为 `auto` 不显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/411) +8. `F` 修复了使用 `grid` 布局时可能样式错误的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/413) +9. `F` 修复了含有合并单元格的表格部分情况下显示异常的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/417) +10. `F` 修复了 `editable` 插件连续插入内容时顺序不正确的问题 +11. `F` 修复了 `uni-app` 包 `vue3` 使用 `audio` 插件报错的问题 +12. `F` 修复了 `uni-app` 包 `highlight` 插件使用自定义的 `prism.min.js` 报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/416) +## v2.2.2(2022-02-26) +1. `A` 增加了 [pauseMedia](https://jin-yufeng.gitee.io/mp-html/#/advanced/api#pauseMedia) 的 `api`,可用于暂停播放音视频 [详细](https://github.com/jin-yufeng/mp-html/issues/317) +2. `U` 优化了长内容的加载速度 +3. `U` 适配 `vue3` [#389](https://github.com/jin-yufeng/mp-html/issues/389)、[#398](https://github.com/jin-yufeng/mp-html/pull/398) by [@zhouhuafei](https://github.com/zhouhuafei)、[#400](https://github.com/jin-yufeng/mp-html/issues/400) +4. `F` 修复了小程序端图片高度设置为百分比时可能不显示的问题 +5. `F` 修复了 `highlight` 插件部分情况下可能显示不完整的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/403) +## v2.2.1(2021-12-24) +1. `A` `editable` 插件增加上下移动标签功能 +2. `U` `editable` 插件支持在文本中间光标处插入内容 +3. `F` 修复了 `nvue` 端设置 `margin` 后可能导致高度不正确的问题 +4. `F` 修复了 `highlight` 插件使用压缩版的 `prism.css` 可能导致背景失效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/367) +5. `F` 修复了编辑状态下使用 `emoji` 插件内容为空时可能报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/371) +6. `F` 修复了使用 `editable` 插件后将 `selectable` 属性设置为 `force` 不生效的问题 +## v2.2.0(2021-10-12) +1. `A` 增加 `customElements` 配置项,便于添加自定义功能性标签 [详细](https://github.com/jin-yufeng/mp-html/issues/350) +2. `A` `editable` 插件增加切换音视频自动播放状态的功能 [详细](https://github.com/jin-yufeng/mp-html/pull/341) by [@leeseett](https://github.com/leeseett) +3. `A` `editable` 插件删除媒体标签时触发 `remove` 事件,便于删除已上传的文件 +4. `U` `editable` 插件 `insertImg` 方法支持同时插入多张图片 [详细](https://github.com/jin-yufeng/mp-html/issues/342) +5. `U` `editable` 插入图片和音视频时支持拼接 `domian` 主域名 +6. `F` 修复了内部链接参数中包含 `://` 时被认为是外部链接的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/356) +7. `F` 修复了部分 `svg` 标签名或属性名大小写不正确时不生效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/351) +8. `F` 修复了 `nvue` 页面运行到非 `app` 平台时可能样式错误的问题 +## v2.1.5(2021-08-13) +1. `A` 增加支持标签的 `dir` 属性 +2. `F` 修复了 `ruby` 标签文字与拼音没有居中对齐的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/325) +3. `F` 修复了音视频标签内有 `a` 标签时可能无法播放的问题 +4. `F` 修复了 `externStyle` 中的 `class` 名包含下划线或数字时可能失效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/326) +5. `F` 修复了 `h5` 端引入 `externStyle` 可能不生效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/326) +## v2.1.4(2021-07-14) +1. `F` 修复了 `rt` 标签无法设置样式的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/318) +2. `F` 修复了表格中有单元格同时合并行和列时可能显示不正确的问题 +3. `F` 修复了 `app` 端无法关闭图片长按菜单的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/322) +4. `F` 修复了 `editable` 插件只能添加图片链接不能修改的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/312) by [@leeseett](https://github.com/leeseett) +## v2.1.3(2021-06-12) +1. `A` `editable` 插件增加 `insertTable` 方法 +2. `U` `editable` 插件支持编辑表格中的空白单元格 [详细](https://github.com/jin-yufeng/mp-html/issues/310) +3. `F` 修复了 `externStyle` 中使用伪类可能失效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/298) +4. `F` 修复了多个组件同时使用时 `tag-style` 属性时可能互相影响的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/305) by [@woodguoyu](https://github.com/woodguoyu) +5. `F` 修复了包含 `linearGradient` 的 `svg` 可能无法显示的问题 +6. `F` 修复了编译到头条小程序时可能报错的问题 +7. `F` 修复了 `nvue` 端不触发 `click` 事件的问题 +8. `F` 修复了 `editable` 插件尾部插入时无法撤销的问题 +9. `F` 修复了 `editable` 插件的 `insertHtml` 方法只能在末尾插入的问题 +10. `F` 修复了 `editable` 插件插入音频不显示的问题 +## v2.1.2(2021-04-24) +1. `A` 增加了 [img-cache](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#img-cache) 插件,可以在 `app` 端缓存图片 [详细](https://github.com/jin-yufeng/mp-html/issues/292) by [@PentaTea](https://github.com/PentaTea) +2. `U` 支持通过 `container-style` 属性设置 `white-space` 来保留连续空格和换行符 [详细](https://jin-yufeng.gitee.io/mp-html/#/question/faq#space) +3. `U` 代码风格符合 [standard](https://standardjs.com) 标准 +4. `U` `editable` 插件编辑状态下支持预览视频 [详细](https://github.com/jin-yufeng/mp-html/issues/286) +5. `F` 修复了 `svg` 标签内嵌 `svg` 时无法显示的问题 +6. `F` 修复了编译到支付宝和头条小程序时部分区域不可复制的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/291) +## v2.1.1(2021-04-09) +1. 修复了对 `p` 标签设置 `tag-style` 可能不生效的问题 +2. 修复了 `svg` 标签中的文本无法显示的问题 +3. 修复了使用 `editable` 插件编辑表格时可能报错的问题 +4. 修复了使用 `highlight` 插件运行到头条小程序时可能没有样式的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/280) +5. 修复了使用 `editable` 插件 `editable` 属性为 `false` 时会报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/284) +6. 修复了 `style` 插件连续子选择器失效的问题 +7. 修复了 `editable` 插件无法修改图片和字体大小的问题 +## v2.1.0.2(2021-03-21) +修复了 `nvue` 端使用可能报错的问题 +## v2.1.0(2021-03-20) +1. `A` 增加了 [container-style](https://jin-yufeng.gitee.io/mp-html/#/basic/prop#container-style) 属性 [详细](https://gitee.com/jin-yufeng/mp-html/pulls/1) +2. `A` 增加支持 `strike` 标签 +3. `A` `editable` 插件增加 `placeholder` 属性 [详细](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#editable) +4. `A` `editable` 插件增加 `insertHtml` 方法 [详细](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#editable) +5. `U` 外部样式支持标签名选择器 [详细](https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart#setting) +6. `F` 修复了 `nvue` 端部分情况下可能不显示的问题 +## v2.0.5(2021-03-12) +1. `U` [linktap](https://jin-yufeng.gitee.io/mp-html/#/basic/event#linktap) 事件增加返回内部文本内容 `innerText` [详细](https://github.com/jin-yufeng/mp-html/issues/271) +2. `U` [selectable](https://jin-yufeng.gitee.io/mp-html/#/basic/prop#selectable) 属性设置为 `force` 时能够在微信 `iOS` 端生效(文本块会变成 `inline-block`) [详细](https://github.com/jin-yufeng/mp-html/issues/267) +3. `F` 修复了部分情况下竖向无法滚动的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/182) +4. `F` 修复了多次修改富文本数据时部分内容可能不显示的问题 +5. `F` 修复了 [腾讯视频](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#txv-video) 插件可能无法播放的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/265) +6. `F` 修复了 [highlight](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#highlight) 插件没有设置高亮语言时没有应用默认样式的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/276) by [@fuzui](https://github.com/fuzui) diff --git a/uni_modules/mp-html/components/mp-html/mp-html.vue b/uni_modules/mp-html/components/mp-html/mp-html.vue new file mode 100644 index 0000000..5d1d36d --- /dev/null +++ b/uni_modules/mp-html/components/mp-html/mp-html.vue @@ -0,0 +1,498 @@ + + + + + diff --git a/uni_modules/mp-html/components/mp-html/node/node.vue b/uni_modules/mp-html/components/mp-html/node/node.vue new file mode 100644 index 0000000..09252fd --- /dev/null +++ b/uni_modules/mp-html/components/mp-html/node/node.vue @@ -0,0 +1,587 @@ +