@ -0,0 +1,4 @@ |
||||
# 请求域名 |
||||
NUXT_API_URL=http://127.0.0.1:8081 |
||||
# 接口默认前缀 |
||||
NUXT_API_PREFIX=/api/ |
@ -0,0 +1,4 @@ |
||||
# 请求域名 |
||||
NUXT_API_URL=http://127.0.0.1:8081 |
||||
# 接口默认前缀 |
||||
NUXT_API_PREFIX=/api/ |
@ -0,0 +1,46 @@ |
||||
/* eslint-env node */ |
||||
module.exports = { |
||||
root: true, |
||||
extends: [ |
||||
'plugin:nuxt/recommended', |
||||
'plugin:vue/vue3-essential', |
||||
'eslint:recommended', |
||||
'@vue/eslint-config-typescript/recommended', |
||||
'@vue/eslint-config-prettier', |
||||
], |
||||
parserOptions: { |
||||
ecmaVersion: 'latest', |
||||
parser: '@typescript-eslint/parser', |
||||
sourceType: 'module', |
||||
}, |
||||
plugins: ['@typescript-eslint'], |
||||
rules: { |
||||
'prettier/prettier': [ |
||||
'warn', |
||||
{ |
||||
semi: false, |
||||
singleQuote: true, |
||||
printWidth: 80, |
||||
proseWrap: 'preserve', |
||||
bracketSameLine: false, |
||||
endOfLine: 'auto', |
||||
tabWidth: 2, |
||||
useTabs: false, |
||||
trailingComma: 'none', |
||||
}, |
||||
], |
||||
'no-useless-escape': 'off', |
||||
'vue/multi-word-component-names': 'off', |
||||
'@typescript-eslint/no-explicit-any': 'off', |
||||
'@typescript-eslint/ban-ts-comment': 'off', |
||||
'no-undef': 'off', |
||||
'vue/prefer-import-from-vue': 'off', |
||||
'no-prototype-builtins': 'off', |
||||
'prefer-spread': 'off', |
||||
'@typescript-eslint/no-non-null-assertion': 'off', |
||||
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off', |
||||
}, |
||||
globals: { |
||||
module: 'readonly', |
||||
}, |
||||
} |
@ -0,0 +1,315 @@ |
||||
### Node template |
||||
# Logs |
||||
logs |
||||
*.log |
||||
npm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
||||
lerna-debug.log* |
||||
.pnpm-debug.log* |
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html) |
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json |
||||
|
||||
# Runtime data |
||||
pids |
||||
*.pid |
||||
*.seed |
||||
*.pid.lock |
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover |
||||
lib-cov |
||||
|
||||
# Coverage directory used by tools like istanbul |
||||
coverage |
||||
*.lcov |
||||
|
||||
# nyc test coverage |
||||
.nyc_output |
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) |
||||
.grunt |
||||
|
||||
# Bower dependency directory (https://bower.io/) |
||||
bower_components |
||||
|
||||
# node-waf configuration |
||||
.lock-wscript |
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html) |
||||
build/Release |
||||
|
||||
# Dependency directories |
||||
node_modules/ |
||||
jspm_packages/ |
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/) |
||||
web_modules/ |
||||
|
||||
# TypeScript cache |
||||
*.tsbuildinfo |
||||
|
||||
# Optional npm cache directory |
||||
.npm |
||||
|
||||
# Optional eslint cache |
||||
.eslintcache |
||||
|
||||
# Optional stylelint cache |
||||
.stylelintcache |
||||
|
||||
# Microbundle cache |
||||
.rpt2_cache/ |
||||
.rts2_cache_cjs/ |
||||
.rts2_cache_es/ |
||||
.rts2_cache_umd/ |
||||
|
||||
# Optional REPL history |
||||
.node_repl_history |
||||
|
||||
# Output of 'npm pack' |
||||
*.tgz |
||||
|
||||
# Yarn Integrity file |
||||
.yarn-integrity |
||||
|
||||
# dotenv environment variable files |
||||
.env |
||||
.env.development.local |
||||
.env.test.local |
||||
.env.production.local |
||||
.env.local |
||||
|
||||
# parcel-bundler cache (https://parceljs.org/) |
||||
.cache |
||||
.parcel-cache |
||||
|
||||
# Next.js build output |
||||
.next |
||||
out |
||||
|
||||
# Nuxt.js build / generate output |
||||
.nuxt |
||||
dist |
||||
|
||||
# Gatsby files |
||||
.cache/ |
||||
# Comment in the public line in if your project uses Gatsby and not Next.js |
||||
# https://nextjs.org/blog/next-9-1#public-directory-support |
||||
# public |
||||
|
||||
# vuepress build output |
||||
.vuepress/dist |
||||
|
||||
# vuepress v2.x temp and cache directory |
||||
.temp |
||||
.cache |
||||
|
||||
# Docusaurus cache and generated files |
||||
.docusaurus |
||||
|
||||
# Serverless directories |
||||
.serverless/ |
||||
|
||||
# FuseBox cache |
||||
.fusebox/ |
||||
|
||||
# DynamoDB Local files |
||||
.dynamodb/ |
||||
|
||||
# TernJS port file |
||||
.tern-port |
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions |
||||
.vscode-test |
||||
|
||||
# yarn v2 |
||||
.yarn/cache |
||||
.yarn/unplugged |
||||
.yarn/build-state.yml |
||||
.yarn/install-state.gz |
||||
.pnp.* |
||||
|
||||
### Vue template |
||||
# gitignore template for Vue.js projects |
||||
# |
||||
# Recommended template: Node.gitignore |
||||
|
||||
# TODO: where does this rule come from? |
||||
docs/_book |
||||
|
||||
# TODO: where does this rule come from? |
||||
test/ |
||||
|
||||
!/package-lock.json |
||||
/.idea/inspectionProfiles/profiles_settings.xml |
||||
/.idea/inspectionProfiles/Project_Default.xml |
||||
/.idea/.gitignore |
||||
/.idea/mer-pc.iml |
||||
/.idea/modules.xml |
||||
/.idea/prettier.xml |
||||
/.idea/vcs.xml |
||||
/.output/public/_nuxt/_id_.4f65f3f3.js |
||||
/.output/public/_nuxt/_id_.bb2eedb2.css |
||||
/.output/public/_nuxt/address_list.4e42d913.js |
||||
/.output/public/_nuxt/address_list.9c72fa93.css |
||||
/.output/public/_nuxt/agreement_rules.58031173.js |
||||
/.output/public/_nuxt/application_record.3aef8ea2.css |
||||
/.output/public/_nuxt/application_record.297caf13.js |
||||
/.output/public/_nuxt/asyncData.92e418ba.js |
||||
/.output/public/_nuxt/browsing_history.9e883ee3.js |
||||
/.output/public/_nuxt/checkbox.6cb61408.js |
||||
/.output/public/_nuxt/checkbox.56c0bd3a.css |
||||
/.output/public/_nuxt/collect_merchant.62c9e8e8.css |
||||
/.output/public/_nuxt/collect_merchant.143f8976.js |
||||
/.output/public/_nuxt/collect_products.59e73173.js |
||||
/.output/public/_nuxt/collect_products.962f748c.css |
||||
/.output/public/_nuxt/debounce.1a973c23.js |
||||
/.output/public/_nuxt/default.65c1aefd.css |
||||
/.output/public/_nuxt/default.68238643.js |
||||
/.output/public/_nuxt/defaultData.4ed993c7.js |
||||
/.output/public/_nuxt/defaultOrder.23384ad8.js |
||||
/.output/public/_nuxt/dialog.80e101db.js |
||||
/.output/public/_nuxt/dialog.c614b8ee.css |
||||
/.output/public/_nuxt/divider.07810808.css |
||||
/.output/public/_nuxt/divider.e4702176.js |
||||
/.output/public/_nuxt/dropdown.0b00051c.js |
||||
/.output/public/_nuxt/entry.808fc290.js |
||||
/.output/public/_nuxt/entry.c680c8a7.css |
||||
/.output/public/_nuxt/error-404.2a4421e5.js |
||||
/.output/public/_nuxt/error-404.7fc72018.css |
||||
/.output/public/_nuxt/error-500.c5df6088.css |
||||
/.output/public/_nuxt/error-500.d12ed7e7.js |
||||
/.output/public/_nuxt/error-component.80535782.js |
||||
/.output/public/_nuxt/goodsApi.3255d570.js |
||||
/.output/public/_nuxt/helper.301f26b7.js |
||||
/.output/public/_nuxt/image-viewer.1408de4f.js |
||||
/.output/public/_nuxt/image-viewer.bcc58336.css |
||||
/.output/public/_nuxt/index.35be60ef.css |
||||
/.output/public/_nuxt/index.efa0dc50.js |
||||
/.output/public/_nuxt/index.f92107a4.js |
||||
/.output/public/_nuxt/isEqual.a24691d3.js |
||||
/.output/public/_nuxt/merchant_settled.53fdfcac.css |
||||
/.output/public/_nuxt/merchant_settled.579702ed.js |
||||
/.output/public/_nuxt/merchant_street.9b2895af.js |
||||
/.output/public/_nuxt/merchantApi.da67b3ce.js |
||||
/.output/public/_nuxt/nuxt-link.24dc26bd.js |
||||
/.output/public/_nuxt/order_confirm.1ecbc8e2.css |
||||
/.output/public/_nuxt/order_confirm.d9bec777.js |
||||
/.output/public/_nuxt/order_details.9ba6bff6.css |
||||
/.output/public/_nuxt/order_details.ee22973b.js |
||||
/.output/public/_nuxt/order_payment.0ca0ace3.css |
||||
/.output/public/_nuxt/order_payment.3a6dc8c2.js |
||||
/.output/public/_nuxt/orderApi.40e90da4.js |
||||
/.output/public/_nuxt/orderProduct.vue.50f40c00.js |
||||
/.output/public/_nuxt/pageHeader.vue.dbf6b680.js |
||||
/.output/public/_nuxt/pagination.0af840e7.css |
||||
/.output/public/_nuxt/pagination.75fc347e.js |
||||
/.output/public/_nuxt/popper.6172019c.css |
||||
/.output/public/_nuxt/popper.b231ab57.js |
||||
/.output/public/_nuxt/select.6b7a2b87.css |
||||
/.output/public/_nuxt/select.c7b6ad97.js |
||||
/.output/public/_nuxt/shopping_cart.85b6814b.js |
||||
/.output/public/_nuxt/shopping_cart.c61fa8ef.css |
||||
/.output/public/_nuxt/swiper-vue.5f03663a.js |
||||
/.output/public/_nuxt/swiper-vue.f31e28c4.css |
||||
/.output/public/_nuxt/tips.0d8f84ae.js |
||||
/.output/public/_nuxt/tjchenggong.445bd1d9.png |
||||
/.output/public/_nuxt/useOrder.87a22d3f.js |
||||
/.output/public/_nuxt/user_balance.2f911ad7.js |
||||
/.output/public/_nuxt/user_balance.2406564a.css |
||||
/.output/public/_nuxt/user_coupon.86c5c4f0.css |
||||
/.output/public/_nuxt/user_coupon.f79479c7.js |
||||
/.output/public/_nuxt/user_integral.ad973a6e.js |
||||
/.output/public/_nuxt/user_integral.d45a347c.css |
||||
/.output/public/_nuxt/userApi.5cd08da7.js |
||||
/.output/public/_nuxt/userProductList.1c0ca74a.css |
||||
/.output/public/_nuxt/userProductList.361dd686.js |
||||
/.output/public/_nuxt/users.ddd09eb2.css |
||||
/.output/public/_nuxt/users.f9f7b18e.js |
||||
/.output/public/_nuxt/Verify.35438517.css |
||||
/.output/public/_nuxt/Verify.b7c4aaa1.js |
||||
/.output/public/css/animate.min.css |
||||
/.output/public/css/fullPage.css |
||||
/.output/public/fonts/iconfont.css |
||||
/.output/public/fonts/iconfont.js |
||||
/.output/public/fonts/iconfont.svg |
||||
/.output/public/fonts/iconfont.ttf |
||||
/.output/public/fonts/iconfont.woff |
||||
/.output/public/fonts/iconfont.woff2 |
||||
/.output/public/images/approved.png |
||||
/.output/public/images/audit-failed.png |
||||
/.output/public/images/collection.png |
||||
/.output/public/images/creationCenter.png |
||||
/.output/public/images/crmeb.png |
||||
/.output/public/images/details.png |
||||
/.output/public/images/none-001.png |
||||
/.output/public/images/portrait.png |
||||
/.output/public/images/ranking.png |
||||
/.output/public/images/read.png |
||||
/.output/public/images/thumbsUp.png |
||||
/.output/public/images/under-review.png |
||||
/.output/public/js/web-office-sdk-v1.1.19.es.js |
||||
/.output/public/tinymce/langs/zh-Hans.js |
||||
/.output/public/tinymce/skins/content/dark/content.css |
||||
/.output/public/tinymce/skins/content/dark/content.min.css |
||||
/.output/public/tinymce/skins/content/default/content.css |
||||
/.output/public/tinymce/skins/content/default/content.min.css |
||||
/.output/public/tinymce/skins/content/document/content.css |
||||
/.output/public/tinymce/skins/content/document/content.min.css |
||||
/.output/public/tinymce/skins/content/tinymce-5/content.css |
||||
/.output/public/tinymce/skins/content/tinymce-5/content.min.css |
||||
/.output/public/tinymce/skins/content/tinymce-5-dark/content.css |
||||
/.output/public/tinymce/skins/content/tinymce-5-dark/content.min.css |
||||
/.output/public/tinymce/skins/content/writer/content.css |
||||
/.output/public/tinymce/skins/content/writer/content.min.css |
||||
/.output/public/tinymce/skins/ui/oxide/content.css |
||||
/.output/public/tinymce/skins/ui/oxide/content.inline.css |
||||
/.output/public/tinymce/skins/ui/oxide/content.inline.min.css |
||||
/.output/public/tinymce/skins/ui/oxide/content.min.css |
||||
/.output/public/tinymce/skins/ui/oxide/skin.css |
||||
/.output/public/tinymce/skins/ui/oxide/skin.min.css |
||||
/.output/public/tinymce/skins/ui/oxide/skin.shadowdom.css |
||||
/.output/public/tinymce/skins/ui/oxide/skin.shadowdom.min.css |
||||
/.output/public/tinymce/skins/ui/oxide-dark/content.css |
||||
/.output/public/tinymce/skins/ui/oxide-dark/content.inline.css |
||||
/.output/public/tinymce/skins/ui/oxide-dark/content.inline.min.css |
||||
/.output/public/tinymce/skins/ui/oxide-dark/content.min.css |
||||
/.output/public/tinymce/skins/ui/oxide-dark/skin.css |
||||
/.output/public/tinymce/skins/ui/oxide-dark/skin.min.css |
||||
/.output/public/tinymce/skins/ui/oxide-dark/skin.shadowdom.css |
||||
/.output/public/tinymce/skins/ui/oxide-dark/skin.shadowdom.min.css |
||||
/.output/public/tinymce/skins/ui/tinymce-5/content.css |
||||
/.output/public/tinymce/skins/ui/tinymce-5/content.inline.css |
||||
/.output/public/tinymce/skins/ui/tinymce-5/content.inline.min.css |
||||
/.output/public/tinymce/skins/ui/tinymce-5/content.min.css |
||||
/.output/public/tinymce/skins/ui/tinymce-5/skin.css |
||||
/.output/public/tinymce/skins/ui/tinymce-5/skin.min.css |
||||
/.output/public/tinymce/skins/ui/tinymce-5/skin.shadowdom.css |
||||
/.output/public/tinymce/skins/ui/tinymce-5/skin.shadowdom.min.css |
||||
/.output/public/tinymce/skins/ui/tinymce-5-dark/content.css |
||||
/.output/public/tinymce/skins/ui/tinymce-5-dark/content.inline.css |
||||
/.output/public/tinymce/skins/ui/tinymce-5-dark/content.inline.min.css |
||||
/.output/public/tinymce/skins/ui/tinymce-5-dark/content.min.css |
||||
/.output/public/tinymce/skins/ui/tinymce-5-dark/skin.css |
||||
/.output/public/tinymce/skins/ui/tinymce-5-dark/skin.min.css |
||||
/.output/public/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.css |
||||
/.output/public/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.min.css |
||||
/.output/public/CNAME |
||||
/.output/public/favicon.ico |
||||
/.output/server/chunks/app/client.manifest.mjs |
||||
/.output/server/chunks/app/client.manifest.mjs.map |
||||
/.output/server/chunks/app/styles.mjs |
||||
/.output/server/chunks/app/styles.mjs.map |
||||
/.output/server/chunks/handlers/renderer.mjs |
||||
/.output/server/chunks/handlers/renderer.mjs.map |
||||
/.output/server/chunks/nitro/node-server.mjs |
||||
/.output/server/chunks/nitro/node-server.mjs.map |
||||
/.output/server/chunks/rollup/_virtual_head-static.mjs |
||||
/.output/server/chunks/rollup/_virtual_head-static.mjs.map |
||||
/.output/server/chunks/error-500.mjs |
||||
/.output/server/chunks/error-500.mjs.map |
||||
/.output/server/index.mjs |
||||
/.output/server/index.mjs.map |
||||
/.output/server/package.json |
||||
/.output/nitro.json |
@ -0,0 +1,3 @@ |
||||
shamefully-hoist=true |
||||
strict-peer-dependencies=false |
||||
shell-emulator=true |
@ -0,0 +1,47 @@ |
||||
/** .prettierrc.js |
||||
* 在VSCode中安装prettier插件 打开插件配置填写`.prettierrc.js` 将本文件作为其代码格式化规范 |
||||
* 在本文件中修改格式化规则,不会同时触发改变ESLint代码检查,所以每次修改本文件需要重启VSCode,ESLint检查才能同步代码格式化 |
||||
* 需要相应的代码格式化规范请自行查阅配置,下面为默认项目配置 |
||||
*/ |
||||
module.exports = { |
||||
// 一行最多多少个字符
|
||||
printWidth: 150, |
||||
// 指定每个缩进级别的空格数
|
||||
tabWidth: 2, |
||||
// 使用制表符而不是空格缩进行
|
||||
useTabs: true, |
||||
// 在语句末尾是否需要分号
|
||||
semi: true, |
||||
// 是否使用单引号
|
||||
singleQuote: true, |
||||
// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
|
||||
quoteProps: 'as-needed', |
||||
// 在JSX中使用单引号而不是双引号
|
||||
jsxSingleQuote: false, |
||||
// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
|
||||
trailingComma: 'es5', |
||||
// 在对象文字中的括号之间打印空格
|
||||
bracketSpacing: true, |
||||
// jsx 标签的反尖括号需要换行
|
||||
jsxBracketSameLine: false, |
||||
// 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
|
||||
arrowParens: 'always', |
||||
// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
|
||||
rangeStart: 0, |
||||
rangeEnd: Infinity, |
||||
// 指定要使用的解析器,不需要写文件开头的 @prettier
|
||||
requirePragma: false, |
||||
// 不需要自动在文件开头插入 @prettier
|
||||
insertPragma: false, |
||||
// 使用默认的折行标准 always\never\preserve
|
||||
proseWrap: 'preserve', |
||||
// 指定HTML文件的全局空格敏感度 css\strict\ignore
|
||||
htmlWhitespaceSensitivity: 'css', |
||||
// Vue文件脚本和样式标签缩进
|
||||
vueIndentScriptAndStyle: false, |
||||
//在 windows 操作系统中换行符通常是回车 (CR) 加换行分隔符 (LF),也就是回车换行(CRLF),
|
||||
//然而在 Linux 和 Unix 中只使用简单的换行分隔符 (LF)。
|
||||
//对应的控制字符为 "\n" (LF) 和 "\r\n"(CRLF)。auto意为保持现有的行尾
|
||||
// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
|
||||
endOfLine: 'auto', |
||||
}; |
@ -0,0 +1,19 @@ |
||||
{ |
||||
"printWidth": 120, |
||||
"tabWidth": 2, |
||||
"useTabs": false, |
||||
"semi": false, |
||||
"singleQuote": true, |
||||
"quoteProps": "as-needed", |
||||
"jsxSingleQuote": false, |
||||
"trailingComma": "all", |
||||
"bracketSpacing": true, |
||||
"arrowParens": "always", |
||||
"rangeStart": 0, |
||||
"requirePragma": false, |
||||
"insertPragma": false, |
||||
"proseWrap": "preserve", |
||||
"htmlWhitespaceSensitivity": "css", |
||||
"endOfLine": "lf" |
||||
|
||||
} |
@ -0,0 +1,21 @@ |
||||
MIT License |
||||
|
||||
Copyright (c) 2021 Element Plus |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
@ -0,0 +1,38 @@ |
||||
# mer_pc |
||||
|
||||
#### Description |
||||
多商户PC商城 |
||||
|
||||
``` |
||||
|
||||
## 开发 |
||||
|
||||
```bash |
||||
# node版本 |
||||
node版本建议使用v16.18以上 |
||||
|
||||
# 进入项目目录 |
||||
cd ## |
||||
|
||||
# 安装依赖 |
||||
npm install |
||||
|
||||
# 建议不要直接使用 cnpm 安装依赖,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题 |
||||
npm install --registry=https://registry.npm.taobao.org |
||||
|
||||
# 启动服务 |
||||
npm run dev |
||||
``` |
||||
|
||||
浏览器访问 http://localhost:9527 |
||||
|
||||
## 发布 |
||||
|
||||
```bash |
||||
# 构建测试环境 |
||||
npm run build |
||||
|
||||
# 代码格式检查并自动修复 |
||||
npm run prettier |
||||
``` |
||||
|
@ -0,0 +1,38 @@ |
||||
# mer_pc |
||||
|
||||
#### 介绍 |
||||
多商户PC商城 |
||||
|
||||
``` |
||||
|
||||
## 开发 |
||||
|
||||
```bash |
||||
# node版本 |
||||
node版本建议使用v16.18以上 |
||||
|
||||
# 进入项目目录 |
||||
cd ## |
||||
|
||||
# 安装依赖 |
||||
npm install |
||||
|
||||
# 建议不要直接使用 cnpm 安装依赖,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题 |
||||
npm install --registry=https://registry.npm.taobao.org |
||||
|
||||
# 启动服务 |
||||
npm run dev |
||||
``` |
||||
|
||||
浏览器访问 http://localhost:9527 |
||||
|
||||
## 发布 |
||||
|
||||
```bash |
||||
# 构建测试环境 |
||||
npm run build |
||||
|
||||
# 代码格式检查并自动修复 |
||||
npm run prettier |
||||
``` |
||||
|
@ -0,0 +1,8 @@ |
||||
<template> |
||||
<!-- 路由出口 --> |
||||
<NuxtLayout> |
||||
<NuxtPage></NuxtPage> |
||||
</NuxtLayout> |
||||
</template> |
||||
<script setup> |
||||
</script> |
@ -0,0 +1,36 @@ |
||||
@font-face { |
||||
font-family: 'oppoSans-R'; |
||||
src: url('./OPPOSans-R.ttf') format('truetype'); |
||||
} |
||||
@font-face { |
||||
font-family: 'oppoSans-M'; |
||||
src: url('./OPPOSans-M.ttf') format('truetype'); |
||||
} |
||||
@font-face { |
||||
font-family: 'dinProSemiBold'; |
||||
src: url('./D-DIN-PRO-600-SemiBold.ttf'); |
||||
} |
||||
@font-face { |
||||
font-family: 'dinProRegular'; |
||||
src: url('./D-DIN-PRO-400-Regular.ttf'); |
||||
} |
||||
@font-face { |
||||
font-family: 'alimama'; |
||||
src: url('alimamashuheiti.ttf') format('truetype'); |
||||
} |
||||
.dinProSemiBold{ |
||||
font-family: 'dinProSemiBold'; |
||||
} |
||||
.dinProRegular{ |
||||
font-family: 'dinProRegular'; |
||||
} |
||||
.alimama{ |
||||
font-family: 'alimama'; |
||||
} |
||||
.oppoSans-M{ |
||||
font-family: 'oppoSans-M'; |
||||
font-weight: 500; |
||||
} |
||||
.oppoSans-R{ |
||||
font-family: 'oppoSans-R'; |
||||
} |
@ -0,0 +1,218 @@ |
||||
@font-face { |
||||
font-family: "iconfont"; /* Project id 4247807 */ |
||||
src: url('//at.alicdn.com/t/c/font_4247807_9t2habcu5gg.woff2?t=1701833017633') format('woff2'), |
||||
url('//at.alicdn.com/t/c/font_4247807_9t2habcu5gg.woff?t=1701833017633') format('woff'), |
||||
url('//at.alicdn.com/t/c/font_4247807_9t2habcu5gg.ttf?t=1701833017633') format('truetype'); |
||||
} |
||||
|
||||
.iconfont { |
||||
font-family: "iconfont" !important; |
||||
font-size: 16px; |
||||
font-style: normal; |
||||
-webkit-font-smoothing: antialiased; |
||||
-moz-osx-font-smoothing: grayscale; |
||||
} |
||||
|
||||
.icon-lianxikefu:before { |
||||
content: "\e6f6"; |
||||
} |
||||
|
||||
.icon-yishoucang:before { |
||||
content: "\e6f4"; |
||||
} |
||||
|
||||
.icon-zhiding:before { |
||||
content: "\e6f5"; |
||||
} |
||||
|
||||
.icon-shenqingjilu:before { |
||||
content: "\e6f3"; |
||||
} |
||||
|
||||
.icon-shijian:before { |
||||
content: "\e6f2"; |
||||
} |
||||
|
||||
.icon-di:before { |
||||
content: "\e6db"; |
||||
} |
||||
|
||||
.icon-gao:before { |
||||
content: "\e6f1"; |
||||
} |
||||
|
||||
.icon-weitongguo1:before { |
||||
content: "\e6f0"; |
||||
} |
||||
|
||||
.icon-gaodi:before { |
||||
content: "\e6ee"; |
||||
} |
||||
|
||||
.icon-baozhang:before { |
||||
content: "\e6ef"; |
||||
} |
||||
|
||||
.icon-shenhezhong:before { |
||||
content: "\e6df"; |
||||
} |
||||
|
||||
.icon-shangchuan:before { |
||||
content: "\e6e0"; |
||||
} |
||||
|
||||
.icon-sousuo:before { |
||||
content: "\e6e1"; |
||||
} |
||||
|
||||
.icon-shanchu:before { |
||||
content: "\e6cd"; |
||||
} |
||||
|
||||
.icon-shoujishangcheng:before { |
||||
content: "\e6e2"; |
||||
} |
||||
|
||||
.icon-shangchengshouye:before { |
||||
content: "\e6ce"; |
||||
} |
||||
|
||||
.icon-kuaidi:before { |
||||
content: "\e6e3"; |
||||
} |
||||
|
||||
.icon-shangpinfenlei:before { |
||||
content: "\e6e4"; |
||||
} |
||||
|
||||
.icon-guanzhudianpu:before { |
||||
content: "\e6cc"; |
||||
} |
||||
|
||||
.icon-liulan:before { |
||||
content: "\e6e5"; |
||||
} |
||||
|
||||
.icon-jia:before { |
||||
content: "\e6e6"; |
||||
} |
||||
|
||||
.icon-jian:before { |
||||
content: "\e6e7"; |
||||
} |
||||
|
||||
.icon-guanzhu:before { |
||||
content: "\e6e8"; |
||||
} |
||||
|
||||
.icon-gouwuche:before { |
||||
content: "\e6e9"; |
||||
} |
||||
|
||||
.icon-dingdan:before { |
||||
content: "\e6ea"; |
||||
} |
||||
|
||||
.icon-baobeishoucang:before { |
||||
content: "\e6eb"; |
||||
} |
||||
|
||||
.icon-gengduo:before { |
||||
content: "\e6ec"; |
||||
} |
||||
|
||||
.icon-bianji:before { |
||||
content: "\e6ed"; |
||||
} |
||||
|
||||
.icon-shoucang:before { |
||||
content: "\e6d2"; |
||||
} |
||||
|
||||
.icon-zuji:before { |
||||
content: "\e6d3"; |
||||
} |
||||
|
||||
.icon-youhuiquan:before { |
||||
content: "\e6d1"; |
||||
} |
||||
|
||||
.icon-xiala:before { |
||||
content: "\e6d0"; |
||||
} |
||||
|
||||
.icon-zuo1:before { |
||||
content: "\e6d4"; |
||||
} |
||||
|
||||
.icon-yitongguo:before { |
||||
content: "\e6d5"; |
||||
} |
||||
|
||||
.icon-zhifubaozhifu:before { |
||||
content: "\e6d7"; |
||||
} |
||||
|
||||
.icon-xiaoxi:before { |
||||
content: "\e6d8"; |
||||
} |
||||
|
||||
.icon-yuezhifu:before { |
||||
content: "\e6d9"; |
||||
} |
||||
|
||||
.icon-you1:before { |
||||
content: "\e6da"; |
||||
} |
||||
|
||||
.icon-wode:before { |
||||
content: "\e6dc"; |
||||
} |
||||
|
||||
.icon-xingji:before { |
||||
content: "\e6dd"; |
||||
} |
||||
|
||||
.icon-weixinzhifu:before { |
||||
content: "\e6de"; |
||||
} |
||||
|
||||
.icon-xuanzhong:before { |
||||
content: "\e6ca"; |
||||
} |
||||
|
||||
.icon-weixuanzhong:before { |
||||
content: "\e6cb"; |
||||
} |
||||
|
||||
.icon-zuo:before { |
||||
content: "\e6c9"; |
||||
} |
||||
|
||||
.icon-you:before { |
||||
content: "\e6c8"; |
||||
} |
||||
|
||||
.icon-yanzhengma:before { |
||||
content: "\e6c5"; |
||||
} |
||||
|
||||
.icon-shoujihao:before { |
||||
content: "\e6c6"; |
||||
} |
||||
|
||||
.icon-mima:before { |
||||
content: "\e6c7"; |
||||
} |
||||
|
||||
.icon-danchuangguanbi:before { |
||||
content: "\e6c1"; |
||||
} |
||||
|
||||
.icon-erweima:before { |
||||
content: "\e6c3"; |
||||
} |
||||
|
||||
.icon-zhanghaodenglu:before { |
||||
content: "\e6c4"; |
||||
} |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 668 B |
After Width: | Height: | Size: 586 B |
After Width: | Height: | Size: 502 B |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 167 B |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 882 B |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 440 B |
After Width: | Height: | Size: 448 B |
After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 747 B |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 838 B |
After Width: | Height: | Size: 705 B |
After Width: | Height: | Size: 74 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 578 B |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 410 B |
After Width: | Height: | Size: 796 B |
After Width: | Height: | Size: 563 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 551 B |
After Width: | Height: | Size: 407 B |
@ -0,0 +1,66 @@ |
||||
/* 单选框和多选框 */ |
||||
.checkbox-wrapper { |
||||
position: relative; |
||||
input { |
||||
display: none; |
||||
} |
||||
.icon { |
||||
position: relative; |
||||
left: 0px; |
||||
top: 7px; |
||||
display: inline-block; |
||||
width: 13px; |
||||
height: 13px; |
||||
border: 1px solid #cccccc; |
||||
border-radius: 50%; |
||||
-webkit-transform: translate(0, -50%); |
||||
-moz-transform: translate(0, -50%); |
||||
-o-transform: translate(0, -50%); |
||||
-ms-transform: translate(0, -50%); |
||||
transform: translate(0, -50%); |
||||
} |
||||
input:checked + .icon { |
||||
background-color: #e93323; |
||||
border-color: #e93323; |
||||
background-image: url("../images/enter.png"); |
||||
-webkit-background-size: 10px 8px; |
||||
-moz-background-size: 10px 8px; |
||||
background-size: 10px 8px; |
||||
background-repeat: no-repeat; |
||||
background-position: center center; |
||||
} |
||||
} |
||||
|
||||
|
||||
.Checkbox { |
||||
position: absolute; |
||||
visibility: hidden; |
||||
outline: none; |
||||
background: #fff; |
||||
} |
||||
.Checkbox+label { |
||||
position:absolute; |
||||
width: 16px; |
||||
height: 16px; |
||||
border: 1px solid #9B9B9B; |
||||
border-radius: 50%; |
||||
background-color:#fff; |
||||
left: 11px; |
||||
top: 50%; |
||||
margin-top: -8px; |
||||
} |
||||
.Checkbox:checked+label:after { |
||||
content: ""; |
||||
position: absolute; |
||||
left: 3px; |
||||
top:3px; |
||||
width: 6px; |
||||
height: 3px; |
||||
border: 2px solid #9B9B9B; |
||||
border-top-color: transparent; |
||||
border-right-color: transparent; |
||||
transform: rotate(-45deg); |
||||
-ms-transform: rotate(-45deg); |
||||
-moz-transform: rotate(-45deg); |
||||
-webkit-transform: rotate(-45deg); |
||||
} |
@ -0,0 +1,26 @@ |
||||
$-colors: ( |
||||
'primary': ( |
||||
'base': #E93323, |
||||
), |
||||
'success': ( |
||||
'base': #67c23a, |
||||
), |
||||
'warning': ( |
||||
'base': #e6a23c, |
||||
), |
||||
'danger': ( |
||||
'base': #f56c6c, |
||||
), |
||||
'error': ( |
||||
'base': #E93323, |
||||
), |
||||
'info': ( |
||||
'base': #909399, |
||||
), |
||||
); |
||||
|
||||
@forward 'element-plus/theme-chalk/src/common/var.scss' with ( |
||||
$colors: $-colors |
||||
); |
||||
|
||||
@use './dark.scss'; |
@ -0,0 +1,430 @@ |
||||
|
||||
* { |
||||
margin: 0; |
||||
padding: 0; |
||||
font-family: 'oppoSans-M'; |
||||
} |
||||
html { |
||||
background: #f5f5f5; |
||||
} |
||||
|
||||
.page-enter-active, |
||||
.page-leave-active { |
||||
transition: all 0.4s; |
||||
} |
||||
.page-enter-from, |
||||
.page-leave-to { |
||||
opacity: 0; |
||||
filter: blur(1rem); |
||||
} |
||||
|
||||
|
||||
.el-dropdown-link{ |
||||
border: none; |
||||
outline: none; |
||||
} |
||||
//标签 |
||||
.labelClass{ |
||||
font-size: 12px; |
||||
line-height: 1; |
||||
padding: 2px 4px; |
||||
border-radius: 2px; |
||||
} |
||||
.verticalClass{ |
||||
writing-mode: vertical-rl; |
||||
text-orientation: upright; |
||||
white-space: nowrap; |
||||
letter-spacing: 0.2em; |
||||
display: flex; |
||||
align-items: center; |
||||
} |
||||
.acea-row { |
||||
display: -webkit-box; |
||||
display: -moz-box; |
||||
display: -webkit-flex; |
||||
display: -ms-flexbox; |
||||
display: flex; |
||||
-webkit-box-lines: multiple; |
||||
-moz-box-lines: multiple; |
||||
-o-box-lines: multiple; |
||||
-webkit-flex-wrap: wrap; |
||||
-ms-flex-wrap: wrap; |
||||
flex-wrap: wrap; |
||||
} |
||||
.min_wrapper_1200 { |
||||
width: 1200px; |
||||
margin: 0 auto; |
||||
height: 100%; |
||||
} |
||||
.acea-row.row-middle { |
||||
-webkit-box-align: center; |
||||
-moz-box-align: center; |
||||
-o-box-align: center; |
||||
-ms-flex-align: center; |
||||
-webkit-align-items: center; |
||||
align-items: center; |
||||
} |
||||
|
||||
.acea-row.row-top { |
||||
-webkit-box-align: start; |
||||
-moz-box-align: start; |
||||
-o-box-align: start; |
||||
-ms-flex-align: start; |
||||
-webkit-align-items: flex-start; |
||||
align-items: flex-start; |
||||
} |
||||
|
||||
.acea-row.row-bottom { |
||||
-webkit-box-align: end; |
||||
-moz-box-align: end; |
||||
-o-box-align: end; |
||||
-ms-flex-align: end; |
||||
-webkit-align-items: flex-end; |
||||
align-items: flex-end; |
||||
} |
||||
|
||||
.acea-row.row-center { |
||||
-webkit-box-pack: center; |
||||
-moz-box-pack: center; |
||||
-o-box-pack: center; |
||||
-ms-flex-pack: center; |
||||
-webkit-justify-content: center; |
||||
justify-content: center; |
||||
} |
||||
|
||||
.acea-row.row-right { |
||||
-webkit-box-pack: end; |
||||
-moz-box-pack: end; |
||||
-o-box-pack: end; |
||||
-ms-flex-pack: end; |
||||
-webkit-justify-content: flex-end; |
||||
justify-content: flex-end; |
||||
} |
||||
|
||||
.acea-row.row-left { |
||||
-webkit-box-pack: start; |
||||
-moz-box-pack: start; |
||||
-o-box-pack: start; |
||||
-ms-flex-pack: start; |
||||
-webkit-justify-content: flex-start; |
||||
justify-content: flex-start; |
||||
} |
||||
|
||||
.acea-row.row-between { |
||||
-webkit-box-pack: justify; |
||||
-moz-box-pack: justify; |
||||
-o-box-pack: justify; |
||||
-ms-flex-pack: justify; |
||||
-webkit-justify-content: space-between; |
||||
justify-content: space-between; |
||||
} |
||||
|
||||
.acea-row.row-around { |
||||
justify-content: space-around; |
||||
-webkit-justify-content: space-around; |
||||
} |
||||
|
||||
.acea-row.row-column-around { |
||||
-webkit-flex-direction: column; |
||||
-ms-flex-direction: column; |
||||
flex-direction: column; |
||||
justify-content: space-around; |
||||
-webkit-justify-content: space-around; |
||||
} |
||||
|
||||
.acea-row.row-column { |
||||
-webkit-box-orient: vertical; |
||||
-moz-box-orient: vertical; |
||||
-o-box-orient: vertical; |
||||
-webkit-flex-direction: column; |
||||
-ms-flex-direction: column; |
||||
flex-direction: column; |
||||
} |
||||
|
||||
.acea-row.row-column-between { |
||||
-webkit-box-orient: vertical; |
||||
-moz-box-orient: vertical; |
||||
-o-box-orient: vertical; |
||||
-webkit-flex-direction: column; |
||||
-ms-flex-direction: column; |
||||
flex-direction: column; |
||||
-webkit-box-pack: justify; |
||||
-moz-box-pack: justify; |
||||
-o-box-pack: justify; |
||||
-ms-flex-pack: justify; |
||||
-webkit-justify-content: space-between; |
||||
justify-content: space-between; |
||||
} |
||||
|
||||
.acea-row.row-center-wrapper { |
||||
-webkit-box-align: center; |
||||
-moz-box-align: center; |
||||
-o-box-align: center; |
||||
-ms-flex-align: center; |
||||
-webkit-align-items: center; |
||||
align-items: center; |
||||
-webkit-box-pack: center; |
||||
-moz-box-pack: center; |
||||
-o-box-pack: center; |
||||
-ms-flex-pack: center; |
||||
-webkit-justify-content: center; |
||||
justify-content: center; |
||||
} |
||||
|
||||
.acea-row.row-between-wrapper { |
||||
-webkit-box-align: center; |
||||
-moz-box-align: center; |
||||
-o-box-align: center; |
||||
-ms-flex-align: center; |
||||
-webkit-align-items: center; |
||||
align-items: center; |
||||
-webkit-box-pack: justify; |
||||
-moz-box-pack: justify; |
||||
-o-box-pack: justify; |
||||
-ms-flex-pack: justify; |
||||
-webkit-justify-content: space-between; |
||||
justify-content: space-between; |
||||
} |
||||
.wrapper_1200 { |
||||
width: 1200px; |
||||
margin: 0 auto; |
||||
} |
||||
.router-link-active { |
||||
text-decoration: none; //去除默认样式 |
||||
color: orange; //高亮的颜色 |
||||
} |
||||
a { |
||||
text-decoration: none; |
||||
color: grey; |
||||
} |
||||
div { |
||||
box-sizing: border-box; |
||||
} |
||||
.pad020 { |
||||
padding: 0 20px; |
||||
} |
||||
|
||||
::-webkit-input-placeholder { |
||||
color: #cccccc !important; |
||||
} |
||||
::-moz-placeholder { |
||||
color: #cccccc !important; |
||||
} |
||||
:-ms-input-placeholder { |
||||
color: #cccccc !important; |
||||
} |
||||
|
||||
.font14 { |
||||
font-size: 14px; |
||||
color: #666666; |
||||
} |
||||
.font12 { |
||||
font-size: 12px; |
||||
} |
||||
.fonts14 { |
||||
font-size: 14px; |
||||
} |
||||
.font20 { |
||||
font-size: 20px; |
||||
} |
||||
.fonts16 { |
||||
font-size: 16px; |
||||
} |
||||
.fontsweight { |
||||
font-weight: 600 !important; |
||||
} |
||||
.fontColor333 { |
||||
color: #333333; |
||||
} |
||||
.fontColor6 { |
||||
color: #666666; |
||||
} |
||||
.font18 { |
||||
font-size: 18px; |
||||
color: #333333; |
||||
font-weight: 500 !important; |
||||
} |
||||
.mbtom24 { |
||||
margin-bottom: 24px; |
||||
} |
||||
.mbtom30 { |
||||
margin-bottom: 30px; |
||||
} |
||||
.cursors { |
||||
cursor: pointer; |
||||
} |
||||
.mbtom20 { |
||||
margin-bottom: 20px; |
||||
} |
||||
.pad30 { |
||||
padding: 30px; |
||||
} |
||||
.bg-color { |
||||
background: #e93323; |
||||
} |
||||
.font-color { |
||||
color: #FF4E8D !important; |
||||
} |
||||
|
||||
.borRadius { |
||||
border-radius: 16px 16px 16px 16px !important; |
||||
} |
||||
.line1 { |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
white-space: nowrap; |
||||
} |
||||
|
||||
.line2 { |
||||
word-break: break-all; |
||||
display: -webkit-box; |
||||
-webkit-line-clamp: 2; |
||||
-webkit-box-orient: vertical; |
||||
overflow: hidden; |
||||
/* height: 84rpx; */ |
||||
} |
||||
|
||||
//分页样式 |
||||
.page-item { |
||||
margin: 0 auto; |
||||
} |
||||
|
||||
.textarea { |
||||
outline: none; |
||||
resize: none; |
||||
border-radius: 8px 8px 8px 8px; |
||||
opacity: 1; |
||||
border: 1px solid #cccccc; |
||||
padding: 15px; |
||||
font-size: 14px; |
||||
box-sizing: border-box; |
||||
} |
||||
.borderSolRed { |
||||
border: 1px solid #E93323; |
||||
} |
||||
.borderSol { |
||||
border: 1px solid #cccccc; |
||||
} |
||||
.borderSol-eee { |
||||
border: 1px solid #EEEEEE; |
||||
} |
||||
.borderSolE9 { |
||||
border: 1px solid #E93323 !important; |
||||
} |
||||
.borderBotDas { |
||||
border-bottom: 1px dashed #eeeeee; |
||||
} |
||||
.borderBotSol { |
||||
border-bottom: 1px solid #eeeeee; |
||||
} |
||||
.page-item{ |
||||
--el-color-primary:#E93323; |
||||
} |
||||
.image-slot{ |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
font-size: 14px; |
||||
background: var(--el-fill-color-light); |
||||
color: var(--el-text-color-placeholder); |
||||
vertical-align: middle; |
||||
} |
||||
//搜索框 |
||||
.seachInput{ |
||||
:deep(.el-input__wrapper) { |
||||
border: none !important; |
||||
box-shadow: none !important; |
||||
} |
||||
:deep(.el-input) { |
||||
--el-fill-color-blank: #f7f7f7 !important; |
||||
--el-border-radius-base: 20px; |
||||
} |
||||
} |
||||
|
||||
//btn样式 面性 |
||||
.handleBtn{ |
||||
background: #E93323 !important; |
||||
color: #fff; |
||||
text-align: center; |
||||
border-radius: 25px; |
||||
font-size: 12px; |
||||
} |
||||
|
||||
//btn样式 线性 |
||||
.handleBtnBorder{ |
||||
text-align: center; |
||||
border-radius: 22px; |
||||
font-size: 12px; |
||||
border: 1px solid #CCCCCC; |
||||
} |
||||
|
||||
.el-dialog{ |
||||
border-radius: 16px !important; |
||||
} |
||||
|
||||
|
||||
//视频 |
||||
video, .wscnph{ |
||||
width: 100%; |
||||
height: 100%; |
||||
} |
||||
|
||||
.el-rate__text{ |
||||
color: #e93323 !important; |
||||
margin-left: 5px; |
||||
} |
||||
|
||||
// 模态框头部标题样式 |
||||
.el-dialog__header{ |
||||
padding: 30px 0 36px 0 !important; |
||||
} |
||||
.el-dialog__body{ |
||||
padding: 0 calc(var(--el-dialog-padding-primary) + 5px) 30px !important; |
||||
} |
||||
|
||||
|
||||
.el-upload-list{ |
||||
.el-icon--close-tip{ |
||||
display: none !important; |
||||
} |
||||
} |
||||
|
||||
//资讯信息选中菜单背景图去掉颜色 |
||||
.el-dropdown__popper{ |
||||
background: #fff !important; |
||||
--el-dropdown-menuItem-hover-fill: #fff !important; |
||||
} |
||||
.line-heightOne{ |
||||
line-height: 1; |
||||
} |
||||
|
||||
// 去掉input小箭头 |
||||
input::-webkit-outer-spin-button, |
||||
input::-webkit-inner-spin-button { |
||||
-webkit-appearance: none; |
||||
} |
||||
input[type="number"]{ |
||||
-moz-appearance: textfield; |
||||
} |
||||
|
||||
//富文本样式 |
||||
.htmlClass{ |
||||
img{ |
||||
max-width: 100%; |
||||
} |
||||
h1,h2,h3,h4,h5,h6,img, p{ |
||||
display: block; |
||||
margin-block-start: 1em; |
||||
margin-block-end: 1em; |
||||
margin-inline-start: 0px; |
||||
margin-inline-end: 0px; |
||||
} |
||||
*{ |
||||
line-height: 30px; |
||||
font-family: 'oppoSans-R'; |
||||
} |
||||
} |
||||
|
||||
.el-popover{ |
||||
min-width: auto !important; |
||||
} |
@ -0,0 +1,512 @@ |
||||
<!--登录弹窗组件--> |
||||
<template> |
||||
<el-dialog |
||||
class="user-login-dialog" |
||||
v-model="dialogVisible" |
||||
:align-center="true" |
||||
:width="420" |
||||
:append-to-body="true" |
||||
:show-close="false" |
||||
> |
||||
<div class="login-count"> |
||||
<!-- 登录弹窗 --> |
||||
<div class="wrapper-count"> |
||||
<span class="closeBtn iconfont icon-danchuangguanbi" @click="closeLogin"></span> |
||||
|
||||
<div class="wrapper"> |
||||
<div class="title"> |
||||
<span class="item_title" @click="current = 1" :class="current === 1 ? 'font18' : ''">登录/注册</span> |
||||
<span class="item_title" @click="current = 2" :class="current === 2 ? 'font18' : ''">密码登录</span> |
||||
</div> |
||||
<div class="iconfont icon-erweima2" @click="current = 3"></div> |
||||
<!--手机号--> |
||||
<div class="item phone acea-row row-middle"> |
||||
<div class="number mr-14px"><span class="iconfont icon-shoujihao"></span></div> |
||||
<input type="text" placeholder="请输入手机号" v-model="formData.phone" class="w-90% text-14px text-#333" /> |
||||
</div> |
||||
<!--验证码--> |
||||
<div |
||||
v-show="current === 1 || current === 4 || current === 5" |
||||
class="item phone verificat acea-row row-between-wrapper" |
||||
> |
||||
<div class="acea-row row-middle"> |
||||
<div class="number mr-14px"><span class="iconfont icon-yanzhengma"></span></div> |
||||
<input |
||||
type="text" |
||||
autocomplete="new-password" |
||||
placeholder="请输入验证码" |
||||
v-model="formData.captcha" |
||||
class="text-14px text-#333" |
||||
/> |
||||
</div> |
||||
<button |
||||
class="code font-color cursors" |
||||
:disabled="disabled" |
||||
:class="disabled === true ? 'on' : ''" |
||||
@click="handleSendcode()" |
||||
> |
||||
{{ text }} |
||||
</button> |
||||
</div> |
||||
|
||||
<!--密码登录--> |
||||
<div v-show="current === 2"> |
||||
<div class="item phone verificat acea-row"> |
||||
<div class="number mr-14px"><span class="iconfont icon-mima"></span></div> |
||||
<input |
||||
type="password" |
||||
class="text-14px text-#333 w-90%" |
||||
placeholder="请输入密码" |
||||
v-model="formData.password" |
||||
/> |
||||
</div> |
||||
</div> |
||||
|
||||
<!--底部按钮--> |
||||
<div class="checkbox-wrapper item_protocol acea-row row-middle" style="margin-top: 25px;"> |
||||
<label class="well-check" style="line-height: 2"> |
||||
<input type="checkbox" name="" value="" :checked="isAgreement" @click="isAgreement = !isAgreement" /> |
||||
<i class="iconfont mr-7px icon" style="top: 11px"></i> |
||||
<span>十天内免登录(公共场合慎选)</span> |
||||
</label> |
||||
</div> |
||||
<div class="checkbox-wrapper item_protocol acea-row row-middle"> |
||||
<label class="well-check" style="line-height: 2"> |
||||
<input type="checkbox" name="" value="" :checked="isAgreement" @click="isAgreement = !isAgreement" /> |
||||
<i class="iconfont mr-7px icon" style="top: 11px"></i> |
||||
<span>我已阅读并同意</span> |
||||
</label> |
||||
<nuxt-link |
||||
:to="{ path: `/users/agreement_rules`, query: { type: 'userinfo', name: '用户协议' } }" |
||||
target="_blank" |
||||
class="show_protocol" |
||||
>《用户协议》 |
||||
</nuxt-link> |
||||
与 |
||||
<nuxt-link |
||||
:to="{ path: `/users/agreement_rules`, query: { type: 'userprivacyinfo', name: '隐私协议' } }" |
||||
target="_blank" |
||||
class="show_protocol" |
||||
>《隐私协议》 |
||||
</nuxt-link> |
||||
</div> |
||||
<div class="signIn bg-color" @click="handleLogin">登录</div> |
||||
<div class="forgot-password"> |
||||
<div class="left-btn">忘记密码</div> |
||||
<div class="left-btn">注册</div> |
||||
</div> |
||||
</div> |
||||
<div class="wxLogin wrapper" v-if="current === 3"> |
||||
<div class="inner">扫码登录</div> |
||||
<div class="iconfont icon-zhanghaodenglu1" @click="current = 1"></div> |
||||
<div class="wxCode"> |
||||
<span class="iconfont icon-erweimabianjiao"></span> |
||||
<span class="iconfont icon-erweimabianjiao"></span> |
||||
<span class="iconfont icon-erweimabianjiao"></span> |
||||
<span class="iconfont icon-erweimabianjiao"></span> |
||||
<img v-if="qrCode" :src="qrCode" /> |
||||
</div> |
||||
<div class="tip">请使用微信扫一扫登录</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<VerifitionVerify ref="verifyRef" :phone="formData.phone" @success="handlerOnVerSuccess"></VerifitionVerify> |
||||
</el-dialog> |
||||
</template> |
||||
<script setup lang="ts"> |
||||
import { PhoneReg } from '~/utils/validate' |
||||
import { Debounce } from '~/utils/util' |
||||
import { useUserStore } from '~/stores/user' |
||||
import { loginMobile, loginPassword, registerVerify, userPhoneCodeApi } from '~/server/userApi' |
||||
import feedback from '~/utils/feedback' |
||||
import useOrder from '~/composables/useOrder' |
||||
import {ref, reactive} from 'vue' |
||||
const userStore = useUserStore() |
||||
const emit = defineEmits(['onLoginSucceeded']) |
||||
const handleEmit = () => { |
||||
emit('onLoginSucceeded') |
||||
} |
||||
|
||||
//是否勾选协议 |
||||
const isAgreement = ref(false) |
||||
|
||||
// 登录提交数据 |
||||
const formData = reactive({ |
||||
captcha: '', |
||||
phone: '', |
||||
spreadPid: 0, |
||||
password: '', |
||||
}) |
||||
|
||||
//扫码登录二维码 |
||||
const qrCode = ref('') |
||||
|
||||
//验证码 |
||||
const { disabled, text, handleCodeSend } = useSmsCode() |
||||
const isSendCode = ref(false) |
||||
|
||||
// 手机号码验证 |
||||
const checkPhone = (rule: any, value: any, callback: any) => { |
||||
if (!value) { |
||||
return callback(new Error('请输入手机号码')) |
||||
} |
||||
setTimeout(() => { |
||||
if (PhoneReg.test(value)) { |
||||
callback() |
||||
} else { |
||||
callback(new Error('请输入有效的电话号码')) |
||||
} |
||||
}, 150) |
||||
} |
||||
|
||||
// 手机号码验证 |
||||
const inputPhone = (e: any) => { |
||||
isSendCode.value = PhoneReg.test(e) |
||||
} |
||||
|
||||
// 打开登录弹窗 |
||||
const { bool: dialogVisible, DialogOpen, DialogClose } = useDialog() |
||||
const open = () => { |
||||
DialogOpen() |
||||
} |
||||
defineExpose({ open }) |
||||
|
||||
const colse = () => { |
||||
DialogClose() |
||||
formData.phone = '' |
||||
formData.captcha = '' |
||||
} |
||||
//关闭登录弹窗 |
||||
const closeLogin = () => { |
||||
DialogClose() |
||||
} |
||||
|
||||
// 获取验证码 |
||||
const verifyRef = shallowRef() |
||||
const handleSendcode = () => { |
||||
if (!formData.phone) return feedback.msgWarning('请填写手机号') |
||||
verifyRef.value.show() |
||||
} |
||||
|
||||
// 发送成功后的回调 |
||||
const handlerOnVerSuccess = async (e: any) => { |
||||
await registerVerify({ phone: formData.phone }) |
||||
feedback.msgSuccess('发送成功') |
||||
handleCodeSend() |
||||
} |
||||
const agreementConfirm = async () => { |
||||
if (isAgreement.value) { |
||||
return |
||||
} |
||||
await feedback.confirm('确认已阅读并同意《服务协议》和《隐私政策》') |
||||
isAgreement.value = true |
||||
} |
||||
|
||||
// 登录按钮提交 |
||||
//显示标识 1快速登录 2密码登录 |
||||
const current = ref<number>(1) |
||||
const { onGetCartCount } = useOrder() |
||||
const handleLogin = Debounce(async () => { |
||||
if (!formData.phone) return feedback.msgError('请填写手机号码') |
||||
if (!/^1(3|4|5|7|8|9|6)\d{9}$/i.test(formData.phone)) return feedback.msgError('请输入正确的手机号码') |
||||
if (!isAgreement.value) return feedback.msgError('请勾选用户隐私协议') |
||||
//快速登录 |
||||
if (current.value === 1) { |
||||
await getLoginMobile() |
||||
} else { |
||||
//账号密码登录 |
||||
if (!formData.password) return feedback.msgError('请填写密码') |
||||
await getloginPassword() |
||||
} |
||||
await colse() |
||||
await handleEmit() //登录成功后操作 |
||||
await onGetCartCount() |
||||
},500) |
||||
|
||||
|
||||
//密码登录 |
||||
const getloginPassword = async () => { |
||||
const params = { |
||||
phone: formData.phone, |
||||
password: formData.password, |
||||
spreadPid: formData.spreadPid, |
||||
} |
||||
const data = await loginPassword(params) |
||||
userStore.login(data.token) |
||||
userStore.setUserInfo(data) |
||||
} |
||||
|
||||
//快速/验证码登录 |
||||
const getLoginMobile = async () => { |
||||
const params = { |
||||
phone: formData.phone, |
||||
captcha: formData.captcha, |
||||
spreadPid: formData.spreadPid, |
||||
} |
||||
const data = await loginMobile(params) |
||||
userStore.login(data.token) |
||||
userStore.setUserInfo(data) |
||||
} |
||||
</script> |
||||
<style scoped lang="scss"> |
||||
@import '@/assets/scss/checkbox.scss'; |
||||
input::-webkit-input-placeholder { |
||||
/* 修改placeholder颜色 */ |
||||
color: #cccccc; |
||||
} |
||||
.login-count { |
||||
width: 100%; |
||||
height: 100%; |
||||
background-color: rgba(0, 0, 0, 0.5); |
||||
position: fixed; |
||||
top: 0; |
||||
left: 0; |
||||
z-index: 100; |
||||
} |
||||
|
||||
.wrapper-count { |
||||
position: relative; |
||||
top: 50%; |
||||
left: 50%; |
||||
transform: translate(-50%, -50%); |
||||
width: 430px; |
||||
height: 428px; |
||||
background: #ffffff; |
||||
border-radius: 16px 16px 16px 16px; |
||||
opacity: 1; |
||||
|
||||
.closeBtn { |
||||
color: #fff; |
||||
position: absolute; |
||||
top: -33px; |
||||
right: -33px; |
||||
text-align: center; |
||||
font-size: 24px; |
||||
cursor: pointer; |
||||
} |
||||
} |
||||
.wrapper { |
||||
position: relative; |
||||
background-color: #fff; |
||||
text-align: center; |
||||
padding: 60px 0 0 0; |
||||
margin: 0 auto; |
||||
border-radius: 16px 16px 16px 16px; |
||||
.font_red { |
||||
color: #e93323; |
||||
} |
||||
|
||||
.title { |
||||
font-size: 18px; |
||||
font-weight: 400; |
||||
color: #999999; |
||||
position: relative; |
||||
|
||||
.item_title { |
||||
cursor: pointer; |
||||
|
||||
&:first-child { |
||||
margin-right: 70px; |
||||
} |
||||
} |
||||
|
||||
.iconfont { |
||||
position: absolute; |
||||
top: -71px; |
||||
right: 0; |
||||
font-size: 60px; |
||||
cursor: pointer; |
||||
} |
||||
} |
||||
|
||||
.item { |
||||
width: 370px; |
||||
height: 50px; |
||||
border: 1px solid #cccccc; |
||||
border-radius: 8px 8px 8px 8px; |
||||
margin: 0 auto; |
||||
padding: 0 14px; |
||||
|
||||
.code { |
||||
height: 96%; |
||||
border: 0; |
||||
background-color: #fff; |
||||
font-size: 14px; |
||||
|
||||
|
||||
img { |
||||
width: 100%; |
||||
height: 100%; |
||||
} |
||||
|
||||
&.on { |
||||
color: #ccc !important; |
||||
} |
||||
} |
||||
&.phone { |
||||
margin-top: 30px; |
||||
|
||||
.number { |
||||
height: 100%; |
||||
line-height: 49px; |
||||
color: #cccccc; |
||||
} |
||||
} |
||||
|
||||
&.pwd { |
||||
margin-top: 20px; |
||||
} |
||||
|
||||
&.verificat { |
||||
margin-top: 20px; |
||||
} |
||||
|
||||
input { |
||||
/*height: 96%;*/ |
||||
border: 0; |
||||
outline: none; |
||||
font-size: 14px; |
||||
} |
||||
} |
||||
|
||||
.signIn { |
||||
width: 370px; |
||||
height: 50px; |
||||
border-radius: 8px 8px 8px 8px; |
||||
text-align: center; |
||||
line-height: 50px; |
||||
margin: 15px auto 0 auto; |
||||
color: #fff; |
||||
cursor: pointer; |
||||
background: linear-gradient(to right, #FFE9AF, #FF4E8D); |
||||
} |
||||
.forgot-password{ |
||||
display: flex; |
||||
justify-content: space-between; |
||||
padding: 20px 30px; |
||||
color: #8A8E99; |
||||
font-size: 12px; |
||||
} |
||||
.fastLogin { |
||||
margin-top: 14px; |
||||
cursor: pointer; |
||||
color: #cccccc; |
||||
} |
||||
|
||||
.title + .iconfont { |
||||
position: absolute; |
||||
top: 0; |
||||
right: 0; |
||||
font-size: 46px; |
||||
color: #282828; |
||||
} |
||||
|
||||
&.wxLogin { |
||||
position: relative; |
||||
padding-top: 98px; |
||||
|
||||
.inner { |
||||
position: absolute; |
||||
top: 34px; |
||||
left: 30px; |
||||
font-size: 20px; |
||||
color: #282828; |
||||
} |
||||
|
||||
.icon-zhanghaodenglu1 { |
||||
position: absolute; |
||||
top: 0; |
||||
right: 0; |
||||
font-size: 46px; |
||||
color: #282828; |
||||
} |
||||
} |
||||
|
||||
.wxCode { |
||||
position: relative; |
||||
width: 213px; |
||||
height: 213px; |
||||
padding: 10px; |
||||
margin: 0 auto; |
||||
|
||||
img { |
||||
display: block; |
||||
width: 100%; |
||||
} |
||||
|
||||
.iconfont { |
||||
font-size: 22px; |
||||
color: #cbcbcb; |
||||
} |
||||
|
||||
.iconfont:nth-child(1) { |
||||
position: absolute; |
||||
top: 0; |
||||
left: 0; |
||||
} |
||||
|
||||
.iconfont:nth-child(2) { |
||||
position: absolute; |
||||
top: 0; |
||||
right: 0; |
||||
transform: rotate(90deg); |
||||
} |
||||
|
||||
.iconfont:nth-child(3) { |
||||
position: absolute; |
||||
right: 0; |
||||
bottom: 0; |
||||
transform: rotate(180deg); |
||||
} |
||||
|
||||
.iconfont:nth-child(4) { |
||||
position: absolute; |
||||
bottom: 0; |
||||
left: 0; |
||||
transform: rotate(270deg); |
||||
} |
||||
} |
||||
|
||||
.tip { |
||||
margin-top: 20px; |
||||
font-size: 16px; |
||||
color: #666; |
||||
} |
||||
|
||||
.item_protocol { |
||||
margin: 0 auto 0; |
||||
text-align: left; |
||||
padding-left: 30px; |
||||
font-size: 12px; |
||||
|
||||
.icon { |
||||
width: 14px; |
||||
height: 14px; |
||||
} |
||||
|
||||
.show_protocol { |
||||
color: #43A7E5; |
||||
cursor: pointer; |
||||
} |
||||
|
||||
.forget_password { |
||||
float: right; |
||||
color: #999999; |
||||
cursor: pointer; |
||||
|
||||
.icon-wangjimima { |
||||
display: inline-block; |
||||
width: 12px; |
||||
height: 12px; |
||||
line-height: 12px; |
||||
margin-right: 5px; |
||||
position: relative; |
||||
top: 1px; |
||||
text-align: center; |
||||
border: 1px solid #999999; |
||||
border-radius: 100%; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,311 @@ |
||||
<!--地址弹窗页面--> |
||||
<script lang="ts" setup> |
||||
import { getCityListApi, addressAddApi, agreementInfoApi, addressEditApi } from '~/server/userApi' |
||||
import feedback from '~/utils/feedback' |
||||
import { ref, reactive, watch, toRefs } from 'vue' |
||||
import { AddressInfo } from '~/types/user' |
||||
import { addressDefault } from '~/pages/users/defaultUser' |
||||
|
||||
const props = defineProps({ |
||||
//编辑数据 |
||||
addressInfo: { |
||||
type: Object, |
||||
default: null, |
||||
}, |
||||
//选中的地址数组 |
||||
selAddressData: { |
||||
type: Array, |
||||
default: [], |
||||
}, |
||||
//模态框状态 |
||||
isShowDialog: { |
||||
type: Boolean, |
||||
default: false, |
||||
}, |
||||
}) |
||||
const { addressInfo, selAddressData, isShowDialog } = toRefs(props) |
||||
|
||||
const dialogVisibleAddress = computed({ |
||||
get: () => isShowDialog?.value, |
||||
set: (value) => {}, |
||||
}) |
||||
|
||||
//地址选择框显示状态 |
||||
const isShowSelect = ref(false) |
||||
//按钮加载状态 |
||||
const loading = ref(false) |
||||
//地址加载状态 |
||||
const loadingAddress = ref(false) |
||||
//选择的省市区 |
||||
const selectedAddress = ref<any[]>([]) |
||||
//地址选择索引 |
||||
const selectedIndex = ref<number>(0) |
||||
// 城市数据 |
||||
const cityData = reactive({ |
||||
parentId: 0, |
||||
step: 1, |
||||
list: [], |
||||
}) |
||||
|
||||
//表单提交默认数据 |
||||
|
||||
watch( |
||||
() => selAddressData, |
||||
(value) => { |
||||
selectedAddress.value = value?.value |
||||
}, |
||||
{ |
||||
immediate: true, |
||||
}, |
||||
) |
||||
|
||||
//表单提交数据 |
||||
const formData = reactive<AddressInfo>(addressInfo ? Object.assign(addressInfo.value) : addressDefault()) |
||||
|
||||
const { DialogOpen, DialogClose } = useDialog() |
||||
// 打开 |
||||
defineExpose({ DialogOpen }) |
||||
|
||||
//选择开启地址弹窗 |
||||
const handleChangeAddress = async () => { |
||||
isShowSelect.value = !isShowSelect.value |
||||
await getCityList(1, 1) |
||||
} |
||||
|
||||
//点击选择已经选择的省市区 进行重选 |
||||
const handleChangeProvince = async (item: any, index: number) => { |
||||
cityData.list = [] |
||||
isShowSelect.value = !isShowSelect.value |
||||
if (index == selectedIndex.value) { |
||||
return |
||||
} |
||||
await getCityList(item.parentId, item.regionType) |
||||
selectedIndex.value = index |
||||
} |
||||
|
||||
// 城市列表数据 |
||||
const CACHE_ADDRESS = reactive<any>({}) |
||||
const getCityList = async (parentId: number, regionType: any) => { |
||||
if (CACHE_ADDRESS[parentId]) { |
||||
cityData.list = CACHE_ADDRESS[parentId] |
||||
return |
||||
} |
||||
isShowSelect.value = true |
||||
loadingAddress.value = true |
||||
await getCityListApi({ parentId: parentId, regionType: regionType }) |
||||
.then(async (result: any) => { |
||||
CACHE_ADDRESS[parentId] = result |
||||
cityData.list = result |
||||
loadingAddress.value = false |
||||
}) |
||||
.catch(async (err: any) => { |
||||
loadingAddress.value = false |
||||
}) |
||||
} |
||||
|
||||
//点击选择加载出的城市数据,选择省市区 |
||||
const handleChangeCity = async (item: object) => { |
||||
if (selectedIndex.value > -1) { |
||||
selectedAddress.value.splice(selectedIndex.value + 1, 999) |
||||
selectedAddress.value[selectedIndex.value] = item |
||||
selectedIndex.value = -1 |
||||
} else if (item.regionType === 1) { |
||||
selectedAddress.value = [item] |
||||
} else { |
||||
selectedAddress.value.push(item) |
||||
} |
||||
if (item.isChild) { |
||||
await getCityList(item.regionId, item.regionType + 1) |
||||
} else { |
||||
isShowSelect.value = false |
||||
} |
||||
} |
||||
|
||||
//获取省市区的提交数据 |
||||
const getData = () => { |
||||
formData.province = getAddressVal(0).regionName |
||||
formData.provinceId = getAddressVal(0).regionId |
||||
formData.city = getAddressVal(1).regionName |
||||
formData.cityId = getAddressVal(1).regionId |
||||
formData.district = getAddressVal(2).regionName |
||||
formData.districtId = getAddressVal(2).regionId |
||||
formData.street = getAddressVal(3).regionName |
||||
} |
||||
const getAddressVal = (num: number) => { |
||||
return selectedAddress.value[num] |
||||
} |
||||
|
||||
const validate = () => { |
||||
if (!formData.realName) { |
||||
return feedback.msgWarning('请填写姓名') |
||||
} |
||||
if (!formData.phone || !/^1(3|4|5|7|8|9|6)\d{9}$/i.test(formData.phone)) { |
||||
return feedback.msgWarning('请填写正确的手机号码') |
||||
} |
||||
if (!selectedAddress.value.length) { |
||||
return feedback.msgWarning('请选择省市区') |
||||
} |
||||
if (!formData.detail) { |
||||
return feedback.msgWarning('请填写详细地址') |
||||
} |
||||
} |
||||
//保存提交 |
||||
const handleSubmit = async () => { |
||||
if (!formData.realName) { |
||||
return feedback.msgWarning('请填写姓名') |
||||
} |
||||
if (!formData.phone || !/^1(3|4|5|7|8|9|6)\d{9}$/i.test(formData.phone)) { |
||||
return feedback.msgWarning('请填写正确的手机号码') |
||||
} |
||||
if (!selectedAddress.value.length) { |
||||
return feedback.msgWarning('请选择省市区') |
||||
} |
||||
if (!formData.detail) { |
||||
return feedback.msgWarning('请填写详细地址') |
||||
} |
||||
await getData() |
||||
loading.value = true |
||||
await handlerSave() |
||||
} |
||||
const handlerSave=async ()=>{ |
||||
let addressId = 0 |
||||
try { |
||||
loading.value = true |
||||
if (formData.id > 0) { |
||||
addressId = await addressEditApi(formData) |
||||
} else { |
||||
addressId = await addressAddApi(formData) |
||||
} |
||||
feedback.msgSuccess('提交成功') |
||||
loading.value = true |
||||
dialogVisibleAddress.value = false |
||||
await handleEmit(addressId) |
||||
} catch (err) { |
||||
loading.value = false |
||||
} |
||||
} |
||||
|
||||
const handleEmit = (addressId: number) => { |
||||
emit('handleSubmitAddress', addressId) |
||||
} |
||||
const emit = defineEmits(['handleSubmitAddress', 'handleSubmitClose']) |
||||
//取消 |
||||
const handleClose = async () => { |
||||
emit('handleSubmitClose') |
||||
isShowSelect.value = false |
||||
loading.value = false |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<el-dialog |
||||
class="user-login-dialog" |
||||
v-model="dialogVisibleAddress" |
||||
:align-center="true" |
||||
width="690px" |
||||
:append-to-body="true" |
||||
:show-close="false" |
||||
:title="formData.id > 0 ? '编辑收货地址' : '添加收货地址'" |
||||
center |
||||
:close-on-click-modal="false" |
||||
> |
||||
<div class="form-box"> |
||||
<div class="input-item mbtom20" style="width: 48%; display: inline-block"> |
||||
<el-input class="iptHeight" v-model="formData.realName" maxlength="25" placeholder="姓名"></el-input> |
||||
</div> |
||||
<div class="input-item mbtom20" style="width: 48%; display: inline-block; margin-left: 3%"> |
||||
<el-input class="iptHeight" v-model="formData.phone" placeholder="手机号"></el-input> |
||||
</div> |
||||
<div class="input-item text-wrapper mbtom20"> |
||||
<div @click="handleChangeAddress" v-if="!selectedAddress.length">请选择省/市/区/街道</div> |
||||
<div v-if="selectedAddress.length" style="color: #333"> |
||||
<span v-for="(item, index) in selectedAddress" class="mr-7px" @click="handleChangeAddress" :key="index"> |
||||
{{ item.regionName }} |
||||
</span> |
||||
</div> |
||||
<div class="select-wrapper" v-if="isShowSelect"> |
||||
<div class="borderBotSol" v-loading="loadingAddress"> |
||||
<div class="acea-row borderBotSol"> |
||||
<div |
||||
class="title-box acea-row" |
||||
@click="handleChangeProvince(item, index)" |
||||
v-for="(item, index) in selectedAddress" |
||||
:key="index" |
||||
> |
||||
{{ item.regionName ? item.regionName : '请选择' }} |
||||
<span v-if="index !== selectedAddress.length - 1" class="ml-2px mr-2px">/</span> |
||||
</div> |
||||
</div> |
||||
<div class="label-txt"> |
||||
<span |
||||
v-for="(item, index) in cityData.list" |
||||
:key="index" |
||||
@click.stop="handleChangeCity(item)" |
||||
:class="{ on: cityData.parentId == item.regionId }" |
||||
>{{ item.regionName }}</span |
||||
> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="input-item mbtom20"> |
||||
<el-input type="textarea" rows="3" v-model="formData.detail" placeholder="详细地址"></el-input> |
||||
</div> |
||||
<div class="input-item"> |
||||
<el-checkbox v-model="formData.isDefault">设为默认</el-checkbox> |
||||
</div> |
||||
</div> |
||||
<template #footer class="dialog-footer"> |
||||
<span class="dialog-footer"> |
||||
<el-button size="large" @click="handleClose" round>取 消</el-button> |
||||
<el-button size="large" color="#E93323" @click="handleSubmit" round :loading="loading">保 存</el-button> |
||||
</span> |
||||
</template> |
||||
</el-dialog> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped> |
||||
.iptHeight { |
||||
height: 50px; |
||||
} |
||||
.text-wrapper { |
||||
position: relative; |
||||
height: 40px; |
||||
line-height: 40px; |
||||
border: 1px solid #dcdfe6; |
||||
padding: 0 15px; |
||||
box-sizing: border-box; |
||||
border-radius: 4px; |
||||
color: #cfcfcf; |
||||
.select-wrapper { |
||||
z-index: 10; |
||||
position: absolute; |
||||
left: 0; |
||||
top: 45px; |
||||
width: 100%; |
||||
padding: 0 15px; |
||||
background: #fff; |
||||
border: 1px solid #e93323; |
||||
border-radius: 4px; |
||||
line-height: 2; |
||||
.title-box { |
||||
height: 40px; |
||||
line-height: 40px; |
||||
color: #e93323; |
||||
font-size: 14px; |
||||
} |
||||
.label-txt { |
||||
margin: 8px 0 18px; |
||||
color: #666666; |
||||
font-size: 14px; |
||||
span { |
||||
margin-right: 10px; |
||||
cursor: pointer; |
||||
&.on { |
||||
color: #e93323; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,120 @@ |
||||
<!--商品分类--> |
||||
<script setup lang="ts"> |
||||
import { ref, toRefs } from 'vue' |
||||
import { categoryTreeApi } from '~/server/goodsApi' |
||||
import { linkNavigateTo } from '~/utils/util' |
||||
const route = useRoute() |
||||
|
||||
// 获取首页路由地址 |
||||
const homeRoutePath = route.path |
||||
/** |
||||
* 平台端商品分类 |
||||
*/ |
||||
const categoryCurrent = ref<any[]>([]) |
||||
const current = ref<number| null>(null) |
||||
|
||||
const { data: productClassify } = await useAsyncData(async () => categoryTreeApi()) |
||||
|
||||
/** |
||||
* 鼠标移入 |
||||
* @param idx |
||||
*/ |
||||
const seen = ref<boolean>(false) |
||||
const handlerEnter = (idx: number) => { |
||||
seen.value = true |
||||
current.value = idx |
||||
categoryCurrent.value = productClassify.value[idx].childList |
||||
} |
||||
|
||||
/** |
||||
* 鼠标移出 |
||||
*/ |
||||
const emit = defineEmits(['handleSubmitLeave']) |
||||
const handlerLeave = () => { |
||||
seen.value = false |
||||
current.value = null |
||||
emit('handleSubmitLeave') |
||||
} |
||||
|
||||
const handleEnterKey = (id: number) => { |
||||
linkNavigateTo(`/product/product_list`, { productCid: id }) |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div> |
||||
<div |
||||
class="acea-row absolute z-11 classifyCard overflow-hidden" |
||||
:class="homeRoutePath !== '/' ? 'boxShadow' : ''" |
||||
@mouseleave="handlerLeave" |
||||
> |
||||
<el-scrollbar height="376px" class="w-200px px-20px py-17px bg-#FFFFFF"> |
||||
<div class="classify-left bg-#FFFFFF"> |
||||
<div v-for="(item, index) in productClassify" :key="item.id" class="flex classifyClassA cursors"> |
||||
<el-image :src="item.icon" class="w-40px h-40px b-rd-50%"></el-image> |
||||
<div class="ml-10px"> |
||||
<div |
||||
@mouseenter="handlerEnter(index)" |
||||
class="fonts14 line1 mb-8px w-109px" |
||||
:class="index === current ? 'font-color' : 'fontColor333'" |
||||
> |
||||
{{ item.name }} |
||||
</div> |
||||
<div class="acea-row"> |
||||
<div |
||||
v-for="(items, idx) in item.childList.length === 2 ? item.childList : item.childList.slice(0, 2)" |
||||
:key="index" |
||||
class="text-12px text-#999 classifyClassB line1" |
||||
> |
||||
{{ items.name }} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</el-scrollbar> |
||||
<div v-if="seen" class="classify-rgiht bg-#FAFAFA w-1000px px-40px py-20px"> |
||||
<el-scrollbar height="360px"> |
||||
<div v-for="(item, index) in categoryCurrent" :key="index" class="mb18px acea-row"> |
||||
<div class="fonts14 fontColor333 mr30px font-500 oppoSans-M">{{ item.name }}</div> |
||||
<div class="acea-row w-85%"> |
||||
<span |
||||
@click="handleEnterKey(items.id)" |
||||
v-for="(items, idx) in item.childList" |
||||
:key="idx" |
||||
class="fonts14 fontColor333 mr20px mb-10px cursors oppoSans-R childName" |
||||
>{{ items.name }}</span |
||||
> |
||||
</div> |
||||
</div> |
||||
</el-scrollbar> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
.childName:hover{ |
||||
color: #e93323; |
||||
} |
||||
.classifyCard { |
||||
background: #fafafa; |
||||
border-radius: 0px 0px 16px 16px; |
||||
} |
||||
.boxShadow { |
||||
box-shadow: 0px 1px 6px 0px rgba(0, 0, 0, 0.1); |
||||
} |
||||
.classifyClassA { |
||||
margin-bottom: 24px; |
||||
&:last-child { |
||||
margin-bottom: 0; |
||||
} |
||||
} |
||||
.classifyClassB { |
||||
max-width: 49px; |
||||
margin-right: 10px; |
||||
&:last-child { |
||||
margin-right: 0; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,55 @@ |
||||
<!--下单页中的商品信息--> |
||||
<script setup lang="ts"> |
||||
import { Mul } from '~/utils/util' |
||||
import useOrder from '~/composables/useOrder' |
||||
import { productTypeFilter } from '~/utils/filter' |
||||
const { handlerNuxtLink } = useOrder() |
||||
const props = defineProps({ |
||||
//列表数据 |
||||
list: { |
||||
type: Object, |
||||
default: null, |
||||
}, |
||||
//订单类型:0-普通订单,1-视频号订单,2-秒杀订单 |
||||
productType: { |
||||
type: Number, |
||||
default: 0, |
||||
}, |
||||
//秒杀状态 |
||||
// seckillStatus: { |
||||
// type: Number, |
||||
// default: 0, |
||||
// } |
||||
// //秒杀时间 |
||||
// datatime: { |
||||
// type: Number, |
||||
// default: 0, |
||||
// } |
||||
}) |
||||
const { list } = toRefs(props) |
||||
</script> |
||||
|
||||
<template> |
||||
<div |
||||
@click="handlerNuxtLink(list.productId, productTypeFilter(productType))" |
||||
class="flex-between-center mbtom30 cursors" |
||||
> |
||||
<div class="acea-row justify-between"> |
||||
<el-image :src="list.image" class="backImg w100px h100px b-rd-12px" lazy></el-image> |
||||
<div class="ml-20px acea-row flex-col justify-center"> |
||||
<div class="text-14px fontColor333 font-500 line1 mb14px w-580px oppoSans-M">{{ list.productName }}</div> |
||||
<div class="borRadius text-14px fontColor333 line1 oppoSans-R">规格:{{ list.sku }}</div> |
||||
</div> |
||||
</div> |
||||
<div class="flex-x-center flex-y-center"> |
||||
<div v-if="list.price || list.productPrice" class="w-160px text-14px oppoSans-R fontColor333">¥{{ list.price || list.productPrice }}</div> |
||||
<div v-if="list.payNum" class="w-100px text-right text-14px oppoSans-R fontColor333">×{{ list.payNum }}</div> |
||||
<div v-if="list.price && list.payNum" class="w-160px text-right font-color text-12px"> |
||||
<span class="oppoSans-M">¥</span |
||||
><span class="dinProSemiBold text-18px fw-600">{{ Mul(list.price, list.payNum) }}</span> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"></style> |
@ -0,0 +1,191 @@ |
||||
<!--倒计时展示 fontSize--> |
||||
<template> |
||||
<view class="time" :style="justifyLeft"> |
||||
<text class="text" style="width: auto;">{{ tipText }}</text> |
||||
<text class="styleAll p6 is_day" v-if="isDay === true && day>0" |
||||
:style="{color:bgColor.bgColor, fontSize:bgColor.fontSize}">{{ day }}{{bgColor.isDay?'天':''}}</text> |
||||
<text class="timeTxt" v-if="dayText" |
||||
:style="{width:bgColor.timeTxtwidth,color:bgColor.bgColor}"> {{ dayText }} </text> |
||||
<text class="styleAll" :class='isCol?"timeCol":""' |
||||
:style="{background:bgColor.bgColor,color:bgColor.Color,width:bgColor.width, fontSize:bgColor.fontSize}">{{ hour }}</text> |
||||
<text class="timeTxt" v-if="hourText" :class='isCol?"whit":""' |
||||
:style="{width:bgColor.timeTxtwidth,color:bgColor.bgColor}"> {{ hourText }} </text> |
||||
<text class="styleAll" :class='isCol?"timeCol":""' |
||||
:style="{background:bgColor.bgColor,color:bgColor.Color,width:bgColor.width, fontSize:bgColor.fontSize}">{{ minute }}</text> |
||||
<text class="timeTxt" v-if="minuteText" :class='isCol?"whit":""' |
||||
:style="{width:bgColor.timeTxtwidth,color:bgColor.bgColor}"> {{ minuteText }}</text> |
||||
<text class="styleAll" :class='isCol?"timeCol":""' |
||||
:style="{background:bgColor.bgColor,color:bgColor.Color,width:bgColor.width,fontSize:bgColor.fontSize}">{{ second }}</text> |
||||
<text class="timeTxt" v-if="secondText"> {{ secondText }} </text> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "countDown", |
||||
props: { |
||||
justifyLeft: { |
||||
type: String, |
||||
default: "" |
||||
}, |
||||
//距离开始提示文字 |
||||
tipText: { |
||||
type: String, |
||||
default: "倒计时" |
||||
}, |
||||
dayText: { |
||||
type: String, |
||||
default: "天" |
||||
}, |
||||
hourText: { |
||||
type: String, |
||||
default: "时" |
||||
}, |
||||
minuteText: { |
||||
type: String, |
||||
default: "分" |
||||
}, |
||||
secondText: { |
||||
type: String, |
||||
default: "秒" |
||||
}, |
||||
datatime: { |
||||
type: Number, |
||||
default: 0 |
||||
}, |
||||
isDay: { |
||||
type: Boolean, |
||||
default: true |
||||
}, |
||||
isCol: { |
||||
type: Boolean, |
||||
default: false |
||||
}, |
||||
bgColor: { |
||||
type: Object, |
||||
default: null |
||||
}, |
||||
status: { |
||||
type: Number, |
||||
default: -1 |
||||
}, |
||||
//用于列表重处理单条数据,这条数据的对象 |
||||
dataItem: { |
||||
type: Object, |
||||
default: null |
||||
} |
||||
}, |
||||
data() { |
||||
return { |
||||
day: "00", |
||||
hour: "00", |
||||
minute: "00", |
||||
second: "00", |
||||
intv: undefined |
||||
}; |
||||
}, |
||||
watch: { |
||||
status(nal) { |
||||
if (nal !== -1) { |
||||
this.intv = undefined |
||||
this.show_time(); |
||||
} |
||||
}, |
||||
datatime(val){ |
||||
this.show_time(); |
||||
} |
||||
}, |
||||
mounted() { |
||||
this.show_time(); |
||||
}, |
||||
methods: { |
||||
show_time() { |
||||
let that = this; |
||||
this.intv = setInterval(function() { |
||||
that.runTime() |
||||
}, 1000) |
||||
}, |
||||
runTime() { |
||||
let that = this; |
||||
//时间函数 |
||||
let intDiff = that.datatime - Date.parse(new Date()) / 1000; //获取数据中的时间戳的时间差; |
||||
let day = 0, |
||||
hour = 0, |
||||
minute = 0, |
||||
second = 0; |
||||
if (intDiff > 0) { |
||||
//转换时间 |
||||
if (that.isDay === true) { |
||||
day = Math.floor(intDiff / (60 * 60 * 24)); |
||||
} else { |
||||
day = 0; |
||||
} |
||||
hour = Math.floor(intDiff / (60 * 60)) - day * 24; |
||||
minute = Math.floor(intDiff / 60) - day * 24 * 60 - hour * 60; |
||||
second = |
||||
Math.floor(intDiff) - |
||||
day * 24 * 60 * 60 - |
||||
hour * 60 * 60 - |
||||
minute * 60; |
||||
if (hour <= 9) hour = "0" + hour; |
||||
if (minute <= 9) minute = "0" + minute; |
||||
if (second <= 9) second = "0" + second; |
||||
that.day = day; |
||||
that.hour = hour; |
||||
that.minute = minute; |
||||
that.second = second; |
||||
} else { |
||||
that.day = "00"; |
||||
that.hour = "00"; |
||||
that.minute = "00"; |
||||
that.second = "00"; |
||||
that.$emit('stopTime', false, this.dataItem) |
||||
clearInterval(that.intv) |
||||
} |
||||
}, |
||||
|
||||
}, |
||||
beforeDestroy() { |
||||
clearInterval(this.intv) |
||||
} |
||||
}; |
||||
</script> |
||||
|
||||
<style scoped lang="scss"> |
||||
.p6 { |
||||
padding: 0 8px; |
||||
} |
||||
|
||||
.styleAll { |
||||
font-size: 22px; |
||||
} |
||||
|
||||
.timeTxt { |
||||
text-align: center; |
||||
display: inline-block; |
||||
font-weight: 600; |
||||
} |
||||
|
||||
.whit { |
||||
color: #fff !important; |
||||
} |
||||
|
||||
.time { |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
} |
||||
|
||||
.red { |
||||
color: #fc4141; |
||||
margin: 0 2px; |
||||
} |
||||
.text{ |
||||
font-size: 10px; |
||||
color: #2D2D2D; |
||||
line-height: 18px; |
||||
} |
||||
.timeCol { |
||||
color: #E93323; |
||||
} |
||||
</style> |
@ -0,0 +1,163 @@ |
||||
<!--商户主页优惠券列表、我的优惠券--> |
||||
<script setup lang="ts"> |
||||
import { couponTypeFilter } from '~/utils/filter' |
||||
import { toRefs } from 'vue' |
||||
import { couponReceiveApi } from '~/server/merchantApi' |
||||
import { getCouponTime } from '~/utils/util' |
||||
import feedback from '~/utils/feedback' |
||||
|
||||
const props = defineProps({ |
||||
//列表数据 |
||||
list: { |
||||
type: Array, |
||||
default: [], |
||||
}, |
||||
//领取后的文字 |
||||
receivedText:{ |
||||
type: String, |
||||
default: '', |
||||
}, |
||||
//是否要跳转到可使用优惠券的商品列表 |
||||
isShowJump: { |
||||
type: Boolean, |
||||
default: false, |
||||
} |
||||
}) |
||||
const { list, isShowJump, receivedText } = toRefs(props) |
||||
|
||||
const loading = ref<boolean>(false) |
||||
//领券优惠券 |
||||
const handleReceiveCoupon = async (item: any) => { |
||||
if (item.isUse) return |
||||
loading.value = true |
||||
try { |
||||
await couponReceiveApi(item.id) |
||||
feedback.msgSuccess('领取成功') |
||||
item.isUse = !item.isUse |
||||
loading.value = false |
||||
} catch (e) { |
||||
loading.value = false |
||||
} |
||||
} |
||||
|
||||
//去使用 |
||||
import { linkNavigateTo } from '~/utils/util' |
||||
const handleToUse = async (item: any) => { |
||||
if(!isShowJump.value && item.validStr === 'unusable') return |
||||
await linkNavigateTo('/activity/coupon_goods_list', { type: 2,couponId: item.id, isUserReceive: true,money:item.money,minPrice:item.minPrice }) |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<!-- validStr 优惠券有效状态:usable-可用,unusable-已用,overdue-过期,notStart-未开始 || !item.validStr ? 'usable' : ''--> |
||||
<div class="acea-row" v-loading="loading"> |
||||
<div |
||||
v-for="item in list" |
||||
:key="item.id" |
||||
class="coupon-item mb-20px w-306px h-130px flex-between-center pos-relative" |
||||
:class="item.validStr ? (item.validStr === 'usable'||item.validStr === 'notStart' ? 'usable' : 'unusable'):'usable'" |
||||
> |
||||
<div class="w-254px h-130px pl-14px pr-5px"> |
||||
<div class="flex"> |
||||
<div class="mt-27px"> |
||||
<div |
||||
class="text-18px flex-y-center mb-6px" |
||||
:class="item.validStr ? (item.validStr === 'usable' ? 'font-color' : 'text-#999') : 'font-color'" |
||||
> |
||||
<span class="mt-13px oppoSans-M">¥</span><span class="text-34px dinProSemiBold">{{ item.money }}</span> |
||||
</div> |
||||
<div |
||||
class="text-12px left-10px text-center" |
||||
> |
||||
<span :class="item.validStr ? (item.validStr === 'usable'||item.validStr === 'notStart' ? 'font-color bg-#FFE1DE' : 'text-#999 bg-#EDEDED') : 'font-color bg-#FFE1DE'" class="line-heightOne px-7px py-2px b-rd-8px inline-block">{{ couponTypeFilter(item.category) }}</span> |
||||
</div> |
||||
</div> |
||||
<div class="ml-14px mt-24px"> |
||||
<div |
||||
class="text-14px mb-14px font-500 h-34px lh-18px line2" |
||||
:class="item.validStr ? (item.validStr === 'usable' ? 'fontColor333' : 'text-#999') : 'fontColor333'" |
||||
> |
||||
{{ item.name }} |
||||
</div> |
||||
<div |
||||
class="text-14px" |
||||
:class="item.validStr ? (item.validStr === 'usable' ? 'text-#666' : 'text-#999') : 'text-#666'" |
||||
> |
||||
满 {{ item.minPrice }} 可用 |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<!--领券中心时间显示--> |
||||
<div v-if="item.validStr"> |
||||
<div |
||||
v-if="item.startTime && item.endTime && (item.validStr === 'usable' || item.validStr ==='notStart')" |
||||
class="pos-absolute bottom-15px left-14px text-12px text-#666" |
||||
> |
||||
{{ getCouponTime(item.startTime, item.endTime) }} |
||||
</div> |
||||
<div v-if="item.validStr === 'unusable'" class="pos-absolute bottom-15px left-14px text-12px text-#666"> |
||||
该优惠券已使用 |
||||
</div> |
||||
<div v-if="item.validStr === 'overdue'" class="pos-absolute bottom-15px left-14px text-12px text-#666"> |
||||
该优惠券已失效无法使用 |
||||
</div> |
||||
</div> |
||||
<!--商户主页领券时间显示--> |
||||
<div v-else> |
||||
<div v-show="item.isFixedTime" class="pos-absolute bottom-15px left-14px text-12px text-#666"> |
||||
{{ item.useStartTimeStr }} ~ {{ item.useEndTimeStr }} 可用 |
||||
</div> |
||||
<div |
||||
v-show="!item.isFixedTime && !item.validStr" |
||||
class="pos-absolute bottom-15px left-14px text-12px text-#666" |
||||
> |
||||
{{ '领取后' + item.day + '天内可用' }} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div v-if="item.validStr" class="w-50px text-#fff text-16px verticalClass" :class="item.validStr === 'unusable'?'':'cursors'" @click="handleToUse(item)"> |
||||
{{ |
||||
item.validStr === 'usable' || item.validStr === 'notStart' |
||||
? receivedText |
||||
: item.validStr === 'unusable' |
||||
? '已使用' |
||||
: '已失效' |
||||
}} |
||||
</div> |
||||
<div v-else class="w-50px text-#fff text-16px verticalClass" :class="!item.isUse?'cursors':''" @click="handleReceiveCoupon(item)"> |
||||
{{ item.isUse ? '已领取' : '立即领取' }} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
.manage { |
||||
display: flex; |
||||
align-items: center; /* 在交叉轴上居中对齐 */ |
||||
writing-mode: vertical-rl; |
||||
text-orientation: upright; |
||||
white-space: nowrap; |
||||
} |
||||
.coupon { |
||||
&-item { |
||||
margin-right: 20px; |
||||
&:nth-child(3n) { |
||||
margin-right: 0; |
||||
} |
||||
} |
||||
.item { |
||||
position: relative; |
||||
cursor: pointer; |
||||
&.on { |
||||
color: #e93323; |
||||
} |
||||
} |
||||
.usable { |
||||
background-image: url('@/assets/images/mycoupon.png'); |
||||
} |
||||
.unusable { |
||||
background-image: url('@/assets/images/mycouponhui.png'); |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,36 @@ |
||||
/** |
||||
* 头部导航菜单 |
||||
*/ |
||||
export const headerMenuListDefault = () => { |
||||
return [ |
||||
{ |
||||
name: '商品分类', |
||||
id: 'classify', |
||||
}, |
||||
{ |
||||
name: '限时秒杀', |
||||
id: 'seckill', |
||||
pc_url: '/activity/seckill_list', |
||||
}, |
||||
{ |
||||
name: '品牌好店', |
||||
id: 'merchant', |
||||
pc_url: '/merchant/merchant_street', |
||||
}, |
||||
{ |
||||
name: '领券中心', |
||||
id: 'coupon', |
||||
pc_url: '/activity/coupon_list', |
||||
}, |
||||
{ |
||||
name: '订单中心', |
||||
id: '1', |
||||
pc_url: '/users/order_list', |
||||
}, |
||||
{ |
||||
name: '资讯信息', |
||||
id: 'information', |
||||
pc_url: '/activity/information_list', |
||||
}, |
||||
] |
||||
} |
@ -0,0 +1,44 @@ |
||||
<!--数据空状态页面--> |
||||
<script setup lang="ts"> |
||||
import {toRefs, reactive} from "vue"; |
||||
//窗口的高度 |
||||
const { getWindowHeight } = useScrollHeight() |
||||
const ScrollHeight = ref<number>(getWindowHeight() - 176-200) |
||||
|
||||
const props = defineProps({ |
||||
//列表数据 resolve |
||||
title: { |
||||
type: String, |
||||
default: '', |
||||
}, |
||||
mTop: { |
||||
type: String, |
||||
default: '0', |
||||
} |
||||
}) |
||||
const { title, mTop } = toRefs(props) |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="empty-box h-100% m-auto" :style="{ height: ScrollHeight + 'px' }"> |
||||
<slot name="emptyImage"></slot> |
||||
<div class="txt">{{title}}</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
.empty-box{ |
||||
display: flex; |
||||
flex-direction: column; |
||||
justify-content: center; |
||||
align-items: center; |
||||
image{ |
||||
width: 274px; |
||||
height: 147px; |
||||
} |
||||
.txt{ |
||||
font-size: 13px; |
||||
color: #999; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,83 @@ |
||||
<script setup lang="ts"> |
||||
|
||||
import {auto} from "@popperjs/core"; |
||||
</script> |
||||
|
||||
<template> |
||||
<div> |
||||
<!--黑条--> |
||||
<div class="header bg-#F5F5F5" :class="showSeach ? 'headerfixed' : ''"> |
||||
<div class="headerCon acea-row row-between-wrapper"> |
||||
<div class="flex-y-center text-12px"> |
||||
<div class="mr-30px flex-y-center"> |
||||
<nuxt-link :to="{ path: '/' }" class="flex-y-center" |
||||
><span class="iconfont icon-shangchengshouye inline-block"></span>商城首页</nuxt-link |
||||
> |
||||
</div> |
||||
<el-popover |
||||
v-if="pcHomeCon && pcHomeCon.goPhoneQrCodeType" |
||||
:width="auto" |
||||
:minWidth="auto" |
||||
popper-style="box-shadow: rgb(14 18 22 / 35%) 0px 10px 38px -10px, rgb(14 18 22 / 20%) 0px 10px 20px -15px; padding: 20px;" |
||||
> |
||||
<template #reference> |
||||
<div class="flex-y-center mobileMall"> |
||||
<span class="iconfont icon-shoujishangcheng inline-block"></span>手机商城 |
||||
</div> |
||||
</template> |
||||
<template #default> |
||||
<div class="acea-row"> |
||||
<div v-if="pcHomeCon.goPhoneQrCodeType.includes('1')"> |
||||
<div :class="pcHomeCon.goPhoneQrCodeType.includes('2')?'mr-26px':''"> |
||||
<div class="borderSol-eee w-80px h-80px flex-center b-rd-4px mb-10px"> |
||||
<el-image :src="wechatQrcode" class="w-72px h-72px"></el-image> |
||||
</div> |
||||
<div class="font-400 text-12px text-#333 w-80px text-center">小程序商城</div> |
||||
</div> |
||||
</div> |
||||
<div v-if="pcHomeCon.goPhoneQrCodeType.includes('2')"> |
||||
<div class="borderSol-eee w-80px h-80px flex-center b-rd-4px mb-10px"> |
||||
<qrcode-vue :value="indexDomain" :size="72" level="H" /> |
||||
</div> |
||||
<div class="font-400 text-12px text-#333 w-80px text-center">H5商城</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
</el-popover> |
||||
</div> |
||||
<div class="user acea-row row-middle"> |
||||
<div v-if="!userStore.isLogin" class="item" @click="handlerLogin">登录/注册</div> |
||||
<div v-else class="acea-row row-middle" @click.stop="handlerNuxtLink('/users/user_info', 11)"> |
||||
<span class="line1 font-color" style="max-width: 135px">Hi,{{ userStore.userInfo.nikeName }}</span> |
||||
<p class="ml-10px item" @click.stop="handleHeaderLogout">退出</p> |
||||
</div> |
||||
|
||||
<div class="item" @click="handlerNuxtLink('/users/order_list', 1)">我的订单</div> |
||||
<div class="item" @click="handlerNuxtLink('/users/collect_products', 3)">我的收藏</div> |
||||
<el-dropdown class="user"> |
||||
<span class="el-dropdown-link text-12px item"> |
||||
商户入驻 |
||||
<el-icon class="el-icon--right"> |
||||
<arrow-down /> |
||||
</el-icon> |
||||
</span> |
||||
<template #dropdown> |
||||
<el-dropdown-menu> |
||||
<el-dropdown-item class="text-12px" @click="handlerNuxtLink('/merchant/merchant_settled', 0)" |
||||
>商户入驻</el-dropdown-item |
||||
> |
||||
<el-dropdown-item class="text-12px" @click="handlerNuxtLink('/merchant/application_record', 0)" |
||||
>申请记录</el-dropdown-item |
||||
> |
||||
</el-dropdown-menu> |
||||
</template> |
||||
</el-dropdown> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
|
||||
</style> |
@ -0,0 +1,48 @@ |
||||
<script setup lang="ts"> |
||||
import { toRefs, watch, ref } from 'vue' |
||||
import {useAppStore} from "~/stores/app"; |
||||
const useStore = useAppStore() |
||||
const props = defineProps({ |
||||
//列表数据 |
||||
pcHomeCon: { |
||||
type: Object, |
||||
default: null, |
||||
} |
||||
}) |
||||
const { pcHomeCon } = toRefs(props) |
||||
|
||||
//是否展示头部广告 |
||||
const isShowAdvertisement = ref<boolean>(true) |
||||
</script> |
||||
|
||||
<template> |
||||
<div> |
||||
<div class="relative wrapper_1200"> |
||||
<span @click="isShowAdvertisement=false" v-if="isShowAdvertisement" class="iconfont icon-weitongguo1 pos-absolute z-10 text-#9EA9AD cursors right-0px top-20px" style="font-size: 22px"></span> |
||||
</div> |
||||
<div class="advertisement" :class="isShowAdvertisement ? 'on' : ''"> |
||||
<a v-if="pcHomeCon.advertisement.linkUrl" :href="pcHomeCon.advertisement.linkUrl" class="w-100% h-auto" target="_blank"> |
||||
<img :src="pcHomeCon.advertisement.imageUrl" class="w-100% h-auto"> |
||||
</a> |
||||
<img v-else :src="pcHomeCon.advertisement.imageUrl" class="w-100% h-auto"> |
||||
</div> |
||||
</div> |
||||
|
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
.advertisement{ |
||||
width: 100%; |
||||
min-width: 1200px; |
||||
position: fixed; |
||||
line-height: 0; |
||||
transition: all 0.3s cubic-bezier(0.25, 0.5, 0.5, 0.9); |
||||
transform: translate3d(0, -100%, 0); |
||||
top: 0; |
||||
left: 0; |
||||
&.on { |
||||
transform: translate3d(0, 0, 0); |
||||
position: static; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,152 @@ |
||||
<script setup lang="ts"> |
||||
//底部信息 |
||||
import {ref} from "vue"; |
||||
import {useAppStore} from "~/stores/app"; |
||||
|
||||
const props = defineProps({ |
||||
//列表数据 |
||||
pcHomeCon: { |
||||
type: Object, |
||||
default: null, |
||||
}, |
||||
}) |
||||
const { pcHomeCon } = toRefs(props) |
||||
|
||||
const { getFooterIsIntersecting } = useAppStore() |
||||
const targetElement = ref(null); |
||||
onMounted(() => { |
||||
const observer = new IntersectionObserver((entries) => { |
||||
entries.forEach((entry) => { |
||||
if (entry.isIntersecting) { |
||||
// 元素进入可视区域 |
||||
getFooterIsIntersecting(true) |
||||
} else { |
||||
getFooterIsIntersecting(false) |
||||
// 元素离开可视区域 |
||||
} |
||||
}); |
||||
}); |
||||
if (targetElement.value) { |
||||
observer.observe(targetElement.value); |
||||
} |
||||
}); |
||||
</script> |
||||
<!--页面底部--> |
||||
<template> |
||||
<div v-if="pcHomeCon" class="layoutFooter mt-20px" ref="targetElement"> |
||||
<div |
||||
v-if="pcHomeCon.philosophyList && pcHomeCon.philosophyList.length" |
||||
class="w-100% h-90px bg-#FFFFFF" |
||||
style="border-top: 1px solid #f5f5f5;" |
||||
> |
||||
<div class="wrapper_1200 h-90px flex-between-center"> |
||||
<div v-for="item in pcHomeCon.philosophyList" :key="item.id"> |
||||
<div class="flex-y-center"> |
||||
<img :src="item.imageUrl" class="w-40px h-40px b-rd-50% mr-14px" /> |
||||
<div class="text-16px text-#333 font-500">{{ item.name }}</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<!-- 底部黑色模块 --> |
||||
<div class="py-40px bg-#333333"> |
||||
<div class="wrapper_1200 flex justify-between"> |
||||
<!-- 左边 --> |
||||
<div class="color-#FFFFFF l-box"> |
||||
<div v-for="(val, index) in pcHomeCon.quickEntryList" :key="index"> |
||||
<div class="l-title" @click="openUrl(val.linkUrl)"><span class="font-500 oppoSans-M">{{ val.name }}</span></div> |
||||
<div class="l-tips" v-for="(tip, index) in val.linkList" :key="index"> |
||||
<a v-if="tip.linkUrl" :href="tip.linkUrl" target="_blank" class="font-400 oppoSans-R"> {{ tip.name }}</a> |
||||
<span v-else class="font-400 oppoSans-R">{{ tip.name }}</span> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<!-- 右边 --> |
||||
<div class="flex"> |
||||
<div class="code-r" v-for="(code, index) in pcHomeCon.qrCodeList"> |
||||
<div class="code"> |
||||
<img :src="code.imageUrl" alt="" /> |
||||
</div> |
||||
<span class="mt-12px">{{ code.name }}</span> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<!-- 分割线 --> |
||||
<div class="wrapper_1200 divisionLine"></div> |
||||
<!-- 企业信息 --> |
||||
<div v-if="pcHomeCon.friendlyLinkList.length" class="wrapper_1200 flex-x-center text-s mb-40px"> |
||||
<div class="w-953px flex flex-justify-center flex-wrap lh-19px"> |
||||
<div class="mr-15px text-#999" v-for="item in pcHomeCon.friendlyLinkList"> |
||||
<a style="color: #999999" v-if="item.linkUrl" :href="item.linkUrl" target="_blank"> {{ item.name }}</a> |
||||
<span v-else>{{ item.name }}</span> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="min_wrapper_1200 flex-col flex-items-center flex-x-center text-s"> |
||||
<div> |
||||
<span v-if="pcHomeCon.phone" class="mr-20px">联系电话:{{ pcHomeCon.phone }}</span> <span v-if="pcHomeCon.address">地址:{{ pcHomeCon.address }}</span> |
||||
</div> |
||||
<div class="mt-17px">{{ pcHomeCon.authInfo }}<a style="color: #999999" href="https://beian.mps.gov.cn/#/query/webSearch" target="_blank"> {{ pcHomeCon.filingNum }}</a></div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<style lang="scss" scoped> |
||||
.l-box { |
||||
display: grid; |
||||
grid-template-columns: repeat(6,minmax(0,1fr)); |
||||
grid-template-rows: none; |
||||
grid-gap: 40px; |
||||
font-weight: 400; |
||||
font-family: PingFang SC-Regular, PingFang SC; |
||||
|
||||
.l-title { |
||||
font-size: 16px; |
||||
margin-bottom: 26px; |
||||
} |
||||
.l-tips { |
||||
cursor: pointer; |
||||
margin-bottom: 14px; |
||||
color: #cccccc !important; |
||||
font-size: 14px; |
||||
a{ |
||||
color: #cccccc !important; |
||||
} |
||||
} |
||||
} |
||||
.code-r { |
||||
color: #ffffff; |
||||
display: flex; |
||||
flex-direction: column; |
||||
align-items: center; |
||||
font-weight: 400; |
||||
font-size: 14px; |
||||
|
||||
font-family: PingFang SC-Regular, PingFang SC; |
||||
.code { |
||||
width: 100px; |
||||
height: 100px; |
||||
background-color: #fff; |
||||
img { |
||||
width: 100%; |
||||
height: 100%; |
||||
} |
||||
} |
||||
} |
||||
.code-r:nth-child(1) { |
||||
margin-right: 40px; |
||||
} |
||||
.divisionLine { |
||||
margin-top: 10px; |
||||
border: 0; |
||||
border-top: 2px dashed rgba(255, 255, 255, 0.15); |
||||
margin-bottom: 40px; |
||||
} |
||||
|
||||
.text-s { |
||||
font-size: 12px; |
||||
font-family: PingFang SC-Regular, PingFang SC; |
||||
font-weight: 400; |
||||
color: #999999; |
||||
} |
||||
</style> |
@ -0,0 +1,487 @@ |
||||
<!--页面头部--> |
||||
<template> |
||||
<div class="relative w-100%"> |
||||
<!--黑条--> |
||||
<div class="header bg-#F5F5F5" :class="isShowTop && useApp.isHomePage ? 'headerfixed' : ''"> |
||||
<div class="headerCon acea-row row-between-wrapper"> |
||||
<div class="flex-y-center text-12px"> |
||||
<div class="mr-30px flex-y-center"> |
||||
<nuxt-link :to="{ path: '/' }" class="flex-y-center" |
||||
><span class="iconfont icon-shangchengshouye inline-block"></span>商城首页</nuxt-link |
||||
> |
||||
</div> |
||||
<el-popover |
||||
v-if="pcHomeCon && pcHomeCon.goPhoneQrCodeType" |
||||
:width="auto" |
||||
:minWidth="auto" |
||||
popper-style="box-shadow: rgb(14 18 22 / 35%) 0px 10px 38px -10px, rgb(14 18 22 / 20%) 0px 10px 20px -15px; padding: 20px;" |
||||
> |
||||
<template #reference> |
||||
<div class="flex-y-center mobileMall"> |
||||
<span class="iconfont icon-shoujishangcheng inline-block"></span>手机商城 |
||||
</div> |
||||
</template> |
||||
<template #default> |
||||
<div class="acea-row"> |
||||
<div v-if="pcHomeCon.goPhoneQrCodeType.includes('1')"> |
||||
<div :class="pcHomeCon.goPhoneQrCodeType.includes('2')?'mr-26px':''"> |
||||
<div class="borderSol-eee w-80px h-80px flex-center b-rd-4px mb-10px"> |
||||
<el-image :src="wechatQrcode" class="w-72px h-72px"></el-image> |
||||
</div> |
||||
<div class="font-400 text-12px text-#333 w-80px text-center">小程序商城</div> |
||||
</div> |
||||
</div> |
||||
<div v-if="pcHomeCon.goPhoneQrCodeType.includes('2')"> |
||||
<div class="borderSol-eee w-80px h-80px flex-center b-rd-4px mb-10px"> |
||||
<qrcode-vue :value="indexDomain" :size="72" level="H" /> |
||||
</div> |
||||
<div class="font-400 text-12px text-#333 w-80px text-center">H5商城</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
</el-popover> |
||||
</div> |
||||
<div class="user acea-row row-middle"> |
||||
<div v-if="!userStore.isLogin" class="item" @click="handlerLogin">登录/注册</div> |
||||
<div v-else class="acea-row row-middle" @click.stop="handlerNuxtLink('/users/user_info', 11)"> |
||||
<span class="line1 font-color" style="max-width: 135px">Hi,{{ userStore.userInfo.nikeName }}</span> |
||||
<p class="ml-10px item" @click.stop="handleHeaderLogout">退出</p> |
||||
</div> |
||||
|
||||
<div class="item" @click="handlerNuxtLink('/users/order_list', 1)">我的订单</div> |
||||
<div class="item" @click="handlerNuxtLink('/users/collect_products', 3)">我的收藏</div> |
||||
<el-dropdown class="user"> |
||||
<span class="el-dropdown-link text-12px item"> |
||||
商户入驻 |
||||
<el-icon class="el-icon--right"> |
||||
<arrow-down /> |
||||
</el-icon> |
||||
</span> |
||||
<template #dropdown> |
||||
<el-dropdown-menu> |
||||
<el-dropdown-item class="text-12px" @click="handlerNuxtLink('/merchant/merchant_settled', 0)" |
||||
>商户入驻</el-dropdown-item |
||||
> |
||||
<el-dropdown-item class="text-12px" @click="handlerNuxtLink('/merchant/application_record', 0)" |
||||
>申请记录</el-dropdown-item |
||||
> |
||||
</el-dropdown-menu> |
||||
</template> |
||||
</el-dropdown> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<!--菜单/搜索框/菜单--> |
||||
<div class="w-100% bg-white"> |
||||
<!--搜索框--> |
||||
<div :class="isShowTop && useApp.isHomePage ? 'menufixed' : ''" class="w-100% bg-white"> |
||||
<div class="min_wrapper_1200"> |
||||
<div class="flex flex-justify-between pt-30px"> |
||||
<!--logo--> |
||||
<nuxt-link :to="{ path: '/' }" |
||||
><el-image lazy :src="pcHomeCon?.leftTopLogo" class="w100px h36px"></el-image |
||||
></nuxt-link> |
||||
<!--搜索框商品详情页、商户主页--> |
||||
<div v-if="useApp.routePath === '/merchant/merchant_home' || useApp.routePath.includes('product/detail')" class="acea-row"> |
||||
<div class="w-560px ml-5px"> |
||||
<el-input |
||||
v-model="searchVal" |
||||
placeholder="搜索全站/本店" |
||||
class="input-with-select" |
||||
@keyup.enter="handleEnterKey" |
||||
> |
||||
<template #append> |
||||
<div @click="handleEnterKey" class="cursors w-80px h-40px bg-color b-rd-20px text-center lh-40px"> |
||||
<span class="text-#fff text-14px font-400">搜全站</span> |
||||
</div> |
||||
</template> |
||||
</el-input> |
||||
</div> |
||||
<div @click="handleEnterKeyHome" class="cursors w-80px h-40px bg-#333333 b-rd-20px text-center lh-40px ml-10px"> |
||||
<span class="text-#fff text-14px font-400">搜本店</span> |
||||
</div> |
||||
</div> |
||||
<!--搜索框--> |
||||
<div v-else class="w-560px ml-5px"> |
||||
<el-input |
||||
v-model="searchVal" |
||||
placeholder="搜索商品/店铺" |
||||
class="input-with-select" |
||||
@keyup.enter="handleEnterKey" |
||||
> |
||||
<template #prepend> |
||||
<el-select v-model="selectVal" placeholder="Select" style="width: 115px"> |
||||
<el-option label="商品" value="1" /> |
||||
<el-option label="店铺" value="2" /> |
||||
</el-select> |
||||
</template> |
||||
<template #append> |
||||
<div @click="handleEnterKey" class="cursors w-80px h-40px bg-color b-rd-20px text-center lh-40px"> |
||||
<span class="iconfont icon-sousuo text-#fff font-400"></span> |
||||
</div> |
||||
</template> |
||||
</el-input> |
||||
</div> |
||||
<!--购物车--> |
||||
<div |
||||
@click="handlerNuxtLink('/order/shopping_cart', 0)" |
||||
class="h-40px borderSol b-rd-20px font-color text-14px flex-center mb-26px cursors w-140px mr-5px" |
||||
> |
||||
<span class="iconfont icon-gouwuche mr-10px"></span>购物车({{ useApp.carNumber }}) |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<!--菜单分类--> |
||||
<div v-if="useApp.isShowHeaderMenu" class="w-100% bg-white header-menu"> |
||||
<div class="min_wrapper_1200"> |
||||
<div @mouseenter="handlerMenuEnter" @mouseleave="handleSubmitLeave"> |
||||
<!--菜单分类--> |
||||
<div class="menu flex-y-center"> |
||||
<div |
||||
v-for="(item, index) in menuList" |
||||
:key="item.id" |
||||
@mouseenter="handlerMenuEnter(index)" |
||||
@click="handleGoPage(item, index)" |
||||
:class="index === 0 ? 'classification' : current===item.id ? 'font-color':''" |
||||
class="cursor-pointer menu-item fontColor333 text-16px mr-40px font-500 oppoSans-M" |
||||
> |
||||
<span v-show="index === 0" class="iconfont icon-shangpinfenlei mr-8px"></span>{{ item.name }} |
||||
</div> |
||||
</div> |
||||
<!--分类显示 --> |
||||
<div v-if="showMenu" class="absolute w-1200px yop-179px z-11" :class="showSeach ? 'on' : ''"> |
||||
<div class="classify z-8"> |
||||
<!--分类组件--> |
||||
<classify-card @handleSubmitLeave="handleSubmitLeave"></classify-card> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<!--登录弹窗--> |
||||
<user-login ref="userLoginRef" @on-login-succeeded="onLoginSucceeded"></user-login> |
||||
</div> |
||||
</template> |
||||
<script setup lang="ts"> |
||||
import { headerMenuListDefault } from '~/components/defaultComponents' |
||||
import { ArrowDown } from '@element-plus/icons-vue' |
||||
import {b64toBlob, Debounce, linkNavigateTo} from '~/utils/util' |
||||
import { loginLogout } from '~/server/userApi' |
||||
import feedback from '~/utils/feedback' |
||||
|
||||
defineOptions({ name: 'layoutHeader' }) |
||||
import { useUserStore } from '@/stores/user' |
||||
import {onMounted, ref, watch} from 'vue' |
||||
import { searcKeywordApi } from '~/server/goodsApi' |
||||
import { useAppStore } from '~/stores/app' |
||||
import QrcodeVue from 'qrcode.vue' |
||||
import { indexDomainApi, wechatQrcodeApi } from '~/server/homeApi' |
||||
import {auto} from "@popperjs/core"; |
||||
const userStore = useUserStore() |
||||
const useApp = useAppStore() |
||||
const route = useRoute() |
||||
|
||||
/** |
||||
* 监听页面滚动 |
||||
*/ |
||||
onMounted(() => { |
||||
window.addEventListener('scroll', handleScroll) |
||||
}) |
||||
const isShowTop = ref<boolean>(false) |
||||
const handleScroll = () => { |
||||
const t = document.documentElement.scrollTop || document.body.scrollTop |
||||
if (t > 100) { |
||||
isShowTop.value = true |
||||
} else { |
||||
isShowTop.value = false |
||||
} |
||||
} |
||||
|
||||
|
||||
//头部信息,首页接口调用 |
||||
const props = defineProps({ |
||||
pcHomeCon: { |
||||
type: Object, |
||||
default: null, |
||||
}, |
||||
}) |
||||
const { pcHomeCon } = toRefs(props) |
||||
|
||||
//移动端域名 |
||||
const { data: indexDomain } = useAsyncData(() => indexDomainApi()) |
||||
|
||||
//移动端域名 |
||||
const wechatQrcode = ref<string>('') |
||||
const getWechatQrcode = async () => { |
||||
let data = await wechatQrcodeApi({ |
||||
scene: 'id=0', |
||||
path: 'pages/index/index', |
||||
env_version: 'trial', |
||||
}) |
||||
let blob = b64toBlob(data.code) |
||||
wechatQrcode.value = URL.createObjectURL(blob) |
||||
} |
||||
getWechatQrcode(); |
||||
|
||||
//搜索 |
||||
const searchVal = ref<string>('') //搜索框输入值 |
||||
const selectVal = ref<string>('1') //搜索框选择值 |
||||
searchVal.value = route.query.searchValue ? route.query.searchValue.toString() : searchVal.value.toString() |
||||
|
||||
onMounted(() => { |
||||
selectVal.value = '1' |
||||
}); |
||||
|
||||
//搜本店 |
||||
const handleEnterKeyHome = async()=>{ |
||||
linkNavigateTo(`/merchant/merchant_home`, { merId: useApp.pcMerId, searchValue: searchVal.value, currentVal: 1 }) |
||||
} |
||||
|
||||
|
||||
//搜索跳入页面 |
||||
const handleEnterKey = async () => { |
||||
//useApp.routePath === '/merchant/merchant_home' || useApp.routePath.includes('product/detail') |
||||
if(useApp.routePath === '/merchant/merchant_home'|| useApp.routePath.includes('product/detail')){ |
||||
await linkNavigateTo(`/product/product_list`, { searchValue: searchVal.value }) |
||||
}else{ |
||||
if (selectVal.value === '1') { |
||||
await linkNavigateTo(`/product/product_list`, { searchValue: searchVal.value }) |
||||
} else { |
||||
await linkNavigateTo(`/merchant/merchant_list`, { searchValue: searchVal.value }) |
||||
} |
||||
} |
||||
|
||||
showSeach.value = !showSeach.value |
||||
showMenu.value = false |
||||
} |
||||
|
||||
//头部菜单 |
||||
const menuList = ref<ItemObject[]>(headerMenuListDefault()) |
||||
|
||||
//动画 |
||||
const { $aos } = useNuxtApp() |
||||
onMounted(() => { |
||||
$aos().init({ |
||||
easing: 'ease-out-back', |
||||
duration: 1000, |
||||
}) |
||||
}) |
||||
|
||||
//登录成功后回调 |
||||
const onLoginSucceeded = () => { |
||||
// getCartCount() |
||||
} |
||||
|
||||
/** |
||||
* 热门搜索词 |
||||
*/ |
||||
const showSeach = ref<boolean>(false) |
||||
const { data: searcKeywordList } = await useAsyncData(async () => searcKeywordApi()) |
||||
|
||||
//清空搜索词 |
||||
const nuxtApp = useNuxtApp() |
||||
nuxtApp.provide('onClearSearchVal', () => { |
||||
searchVal.value = '' |
||||
}) |
||||
|
||||
/** |
||||
* 菜单移入 |
||||
*/ |
||||
const checkedIndex = ref<number>(10) //分类鼠标悬浮索引 |
||||
const showMenu = ref<boolean>(false) //是否展示分类菜单 |
||||
const handlerMenuEnter = (idx: number | undefined) => { |
||||
checkedIndex.value = idx ? idx : 10 |
||||
if (idx === 0 && !useApp.isHomePage) { |
||||
showMenu.value = true |
||||
} else { |
||||
showMenu.value = false |
||||
} |
||||
} |
||||
|
||||
//鼠标移出回调 |
||||
const handleSubmitLeave = () => { |
||||
showMenu.value = false |
||||
} |
||||
|
||||
// 跳入页面 |
||||
|
||||
const current = ref<string>(route.query.type?<string>route.query.type:'') |
||||
watch( |
||||
() => <string>route.query.type, |
||||
(newValue) => { |
||||
current.value = newValue |
||||
}, |
||||
) |
||||
|
||||
const handleGoPage = async (obj: any, idx: number) => { |
||||
current.value = obj.id |
||||
if (idx === 0) return |
||||
await linkNavigateTo(obj.pc_url, { type: obj.id }) |
||||
} |
||||
|
||||
/** |
||||
* 登录 |
||||
*/ |
||||
const userLoginRef = shallowRef() |
||||
const handlerLogin = async () => { |
||||
if (userStore.isLogin) { |
||||
// await linkNavigateTo('/forum/create') |
||||
} else { |
||||
//data.backUrl = '/forum/create' |
||||
userLoginRef.value.open() |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 地址跳转 |
||||
*/ |
||||
const handlerNuxtLink = async (url: string, type: number) => { |
||||
if (userStore.token) { |
||||
await linkNavigateTo(url, { type: type }) |
||||
} else { |
||||
userLoginRef.value.open() |
||||
} |
||||
} |
||||
|
||||
//退出登录 |
||||
//购物车没登录状态下显示0 |
||||
const { getCarNumber } = useAppStore() |
||||
const handleHeaderLogout = Debounce(async () => { |
||||
await feedback.confirm('确定退出吗?') |
||||
await loginLogout() |
||||
await getCarNumber(0) |
||||
await userStore.logout() |
||||
},500) |
||||
</script> |
||||
<style scoped lang="scss"> |
||||
:deep(.el-dropdown-menu__item){ |
||||
font-size: 12px !important; |
||||
} |
||||
:deep(.el-dropdown__popper.el-popper), :deep(.el-popper){ |
||||
font-size: 12px !important; |
||||
} |
||||
.header-menu { |
||||
border-bottom: 2px solid #e93323; |
||||
} |
||||
.mobileMall{ |
||||
&:hover { |
||||
color: #e93323; |
||||
} |
||||
} |
||||
.classification { |
||||
width: 200px; |
||||
height: 40px; |
||||
background: #e93323; |
||||
border-radius: 12px 12px 0px 0px; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
color: #fff; |
||||
} |
||||
:deep(.el-input-group) { |
||||
height: 40px; |
||||
background: #ffffff; |
||||
border-radius: 20px 20px 20px 20px; |
||||
border: 1px solid #e93323; |
||||
overflow: hidden; |
||||
} |
||||
:deep(.el-input__wrapper) { |
||||
padding-left: 20px !important; |
||||
} |
||||
:deep(.el-input__wrapper), |
||||
:deep(.el-input-group__prepend), |
||||
:deep(.el-input-group__prepend:hover), |
||||
:deep(.el-input-group__append) { |
||||
padding: 0; |
||||
box-shadow: none !important; |
||||
--el-fill-color-light: rgba(255, 255, 255, 0) !important; |
||||
} |
||||
:deep(.el-input-group__append) { |
||||
padding: 0; |
||||
} |
||||
:deep(.el-input) { |
||||
--el-input-hover-border-color: none !important; |
||||
--el-input-focus-border-color: none !important; |
||||
} |
||||
:deep(.el-select) { |
||||
margin: 0 !important; |
||||
width: 74px !important; |
||||
--el-select-input-focus-border-color: none !important; |
||||
} |
||||
:deep(.el-input-group__prepend:hover) { |
||||
border: none; |
||||
} |
||||
.headerfixed { |
||||
top: 0; |
||||
z-index: 99; |
||||
position: fixed; |
||||
} |
||||
.menufixed { |
||||
top: 40px; |
||||
z-index: 99; |
||||
position: fixed; |
||||
} |
||||
.seachfixed { |
||||
position: fixed; |
||||
top: 124px; |
||||
z-index: 99; |
||||
} |
||||
.classify { |
||||
border-radius: 0px 0px 26px 26px; |
||||
overflow: hidden; |
||||
box-shadow: 0px 1px 6px 0px rgba(0, 0, 0, 0.1); |
||||
} |
||||
.search { |
||||
&-ipt { |
||||
outline: none; |
||||
border: 0; |
||||
background: none; |
||||
} |
||||
|
||||
&-history { |
||||
border-radius: 0px 0px 16px 16px; |
||||
} |
||||
} |
||||
.header { |
||||
flex: 1; |
||||
width: 100%; |
||||
height: 40px; |
||||
font-size: 12px; |
||||
color: #666666; |
||||
cursor: pointer; |
||||
|
||||
.headerCon { |
||||
height: 100%; |
||||
position: relative; |
||||
max-width: 1200px; |
||||
margin: 0 auto; |
||||
|
||||
a { |
||||
color: #666666; |
||||
|
||||
&:hover { |
||||
color: #e93323; |
||||
} |
||||
} |
||||
|
||||
.iconfont { |
||||
margin-right: 5px; |
||||
} |
||||
|
||||
.user { |
||||
.item { |
||||
margin-right: 8px; |
||||
position: relative; |
||||
padding-left: 8px; |
||||
color: #666666; |
||||
&:hover { |
||||
color: #e93323; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,65 @@ |
||||
<!--商户列表,关注商品、店铺街--> |
||||
<script setup lang="ts"> |
||||
import { toRefs } from 'vue' |
||||
const props = defineProps({ |
||||
//列表数据 |
||||
list: { |
||||
type: Array, |
||||
default: [], |
||||
}, |
||||
//是否展示关注 |
||||
isShowFollow: { |
||||
type: Boolean, |
||||
default: false, |
||||
}, |
||||
}) |
||||
const { list, isShowFollow } = toRefs(props) |
||||
const { onUnfollowMerchant } = useMerchant() |
||||
const emit = defineEmits(['handleSubmitSuccess']) |
||||
const handleCancel = (merId: number) => { |
||||
onUnfollowMerchant(merId).then(() => { |
||||
emit('handleSubmitSuccess') |
||||
}) |
||||
} |
||||
|
||||
const colors = ['#E93323', '#E93323', '#EEEEEE'] |
||||
|
||||
// 跳入页面 |
||||
import { linkNavigateTo } from '~/utils/util' |
||||
const handleGoPage = (id) => { |
||||
linkNavigateTo(`/merchant/merchant_home`, { merId: id }) |
||||
} |
||||
|
||||
const isNthChild = (index: number) => { |
||||
return index % 5 === 0 |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div v-for="(item, index) in list" :key="index" class="mb-15px mer-item cursors"> |
||||
<div |
||||
class="w-228px acea-row flex-col b-rd-16px items-center overflow-hidden pb-20px borderSol-eee bg-#FFFFFF" |
||||
@click="handleGoPage(item.id || item.merId)" |
||||
> |
||||
<el-image class="w-228px h-228px" :src="item.pcGoodStoreCoverImage" lazy></el-image> |
||||
<el-image class="w-80px h-80px b-rd-50% mb-18px borderSol-eee" :src="item.pcLogo" style="margin-top: -36px;"></el-image> |
||||
<div class="flex-center mb-8px w-212px line1"> |
||||
<div v-if="item.isSelf" class="text-12px bg-color text-#fff b-rd-2px py-2px px-4px mr-6px lh-12px">自营</div> |
||||
<div v-else class="h-16px"></div> |
||||
<div class="fontColor333 text-16px line1">{{ item.merName || item.name }}</div> |
||||
</div> |
||||
<div v-if="item.starLevel"><el-rate v-model="item.starLevel" disabled/></div> |
||||
<div |
||||
v-if="isShowFollow" |
||||
@click.stop="handleCancel(item.merId)" |
||||
class="mt-10px w-86px h-30px lh-29px text-12px font-color text-center b-rd-16px cursors borderSolE9" |
||||
> |
||||
取消关注 |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
|
||||
</style> |
@ -0,0 +1,141 @@ |
||||
<!--商户头像 资质 星级等--> |
||||
<script setup lang="ts"> |
||||
import { toRefs } from 'vue' |
||||
const props = defineProps({ |
||||
//商户信息 |
||||
merchantInfo: { |
||||
type: Object, |
||||
default: null, |
||||
}, |
||||
//商户id |
||||
merId: { |
||||
type: Number, |
||||
default: 0, |
||||
}, |
||||
//使用的来源,productDetail:商品页面使用 |
||||
fromType: { |
||||
type: String, |
||||
default: '', |
||||
}, |
||||
}) |
||||
const { merchantInfo,merId,fromType } = toRefs(props) |
||||
|
||||
const useMerchants = useMerchant() |
||||
//关注 |
||||
const emit = defineEmits(['handleSubmitSuccess']) |
||||
const handleFollow = () =>{ |
||||
if(merchantInfo.value.isCollect){ |
||||
//取消关注 |
||||
useMerchants.onUnfollowMerchant(merId?.value).then(() => { |
||||
emit('handleSubmitSuccess') |
||||
}) |
||||
}else{ |
||||
// 关注 |
||||
useMerchants.onfollowMerchant(merId?.value).then(() => { |
||||
emit('handleSubmitSuccess') |
||||
}) |
||||
} |
||||
} |
||||
|
||||
// 跳入页面 |
||||
import { linkNavigateTo } from '~/utils/util' |
||||
import useOrder from "~/composables/useOrder"; |
||||
const handleGoPage = () => { |
||||
linkNavigateTo(`/merchant/merchant_home`, { merId: merId.value }) |
||||
} |
||||
|
||||
//营业执照 |
||||
const { bool: dialogVisible, DialogOpen, DialogClose } = useDialog() |
||||
|
||||
|
||||
//联系客服 |
||||
const { chatConfig } = useOrder() |
||||
const handleChat = async () => { |
||||
let chatConfigData = { |
||||
serviceLink:merchantInfo?.value.serviceLink, |
||||
servicePhone:merchantInfo?.value.servicePhone, |
||||
serviceType:merchantInfo?.value.serviceType |
||||
} |
||||
await chatConfig(chatConfigData) |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div> |
||||
<div class="w-210px b-rd-12px bg-#FFFFFF overflow-hidden flex-col px-20px py-20px merchantNews"> |
||||
<el-image @click="handleGoPage" class="w-60px h-60px b-rd-50%" :class="fromType ==='productDetail'?'cursors':''" :src="merchantInfo.avatar" lazy style="margin: 0 auto"></el-image> |
||||
<div @click="handleGoPage" class="flex-center w-170px mt-16px mb-20px" :class="fromType ==='productDetail'?'cursors':''"> |
||||
<div v-if="merchantInfo.isSelf" class="text-12px bg-color text-#fff b-rd-2px py-2px mr-6px px-4px lh-12px min-w-33px">自营</div> |
||||
<div class="fontColor333 text-14px">{{ merchantInfo.name }}</div> |
||||
</div> |
||||
|
||||
<div v-if="fromType !=='productDetail'" @click="DialogOpen()" class="text-12px text-#999 mb-15px cursors">资质证照:<img src="@/assets/images/zhizhao.png"></div> |
||||
<div class="text-12px text-#999 flex-y-center">店铺星级:<el-rate size="small" v-model="merchantInfo.starLevel" disabled /></div> |
||||
<el-divider border-style="dashed" /> |
||||
<div class="acea-row"> |
||||
<div v-if="fromType ==='productDetail'" class="cursors w-79px h-30px lh-29px borderSol text-12px text-#333 text-center b-rd-15px mr-10px" @click="handleGoPage">进店逛逛</div> |
||||
<div v-else @click="handleChat" class="cursors w-79px h-30px lh-29px borderSol text-12px text-#333 text-center b-rd-15px mr-10px">联系客服</div> |
||||
<div @click="handleFollow" :class="merchantInfo.isCollect?'borderSol text-#333':'borderSolE9 font-color'" class="w-79px h-30px lh-29px text-12px text-center b-rd-15px cursors">{{merchantInfo.isCollect?'已关注':'关注店铺'}}</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<el-dialog |
||||
class="merchantNewsDialog" |
||||
v-model="dialogVisible" |
||||
:append-to-body="true" |
||||
:show-close="false" |
||||
center |
||||
align-center |
||||
width="532px" |
||||
title="资质证照" |
||||
> |
||||
<span class="closeBtn iconfont icon-danchuangguanbi" @click="DialogClose()"></span> |
||||
<div class=""> |
||||
<el-carousel :interval="5000" height="340px" arrow="always" class="w-100% m-auto"> |
||||
<el-carousel-item v-for="item in JSON.parse(merchantInfo.qualificationPicture)" :key="item" class="w-340px h-340px el-image"> |
||||
<el-image :src="item" class="w-340px h-340px el-image b-rd-12px" fit="fill" /> |
||||
</el-carousel-item> |
||||
</el-carousel> |
||||
</div> |
||||
</el-dialog> |
||||
</div> |
||||
</template> |
||||
|
||||
<style> |
||||
.merchantNews{ |
||||
:deep(.el-dialog__header){ |
||||
padding: 30px 0 !important; |
||||
} |
||||
} |
||||
</style> |
||||
<style scoped lang="scss"> |
||||
//星级 |
||||
:deep(.el-rate){ |
||||
height:auto !important; |
||||
--el-rate-icon-margin: 0 !important; |
||||
--el-rate-fill-color:#E93323 !important; |
||||
--el-rate-icon-size: 12px !important; |
||||
} |
||||
.merchantNewsDialog{ |
||||
.el-carousel__item{ |
||||
width: 340px !important; |
||||
left: 71px !important; |
||||
} |
||||
} |
||||
|
||||
.closeBtn { |
||||
color: #fff; |
||||
position: absolute; |
||||
top: -33px; |
||||
right: -33px; |
||||
text-align: center; |
||||
font-size: 24px; |
||||
cursor: pointer; |
||||
} |
||||
.merchantNews{ |
||||
:deep(.el-divider--horizontal){ |
||||
margin: 15px 0 15px 0 !important; |
||||
} |
||||
} |
||||
|
||||
</style> |
@ -0,0 +1,23 @@ |
||||
<!--没登录页面展示--> |
||||
<script setup lang="ts"> |
||||
//调用layouts的方法 |
||||
const nuxtApp = useNuxtApp() |
||||
//窗口的高度 |
||||
const { getWindowHeight } = useScrollHeight() |
||||
const ScrollHeight = ref<number>(getWindowHeight() - 215) |
||||
</script> |
||||
|
||||
<template> |
||||
<!--未登录页面--> |
||||
<div class="error-page w-1000px h-100% flex ml-20px" :style="{ height: ScrollHeight + 'px' }"> |
||||
<div class="m-auto"> |
||||
<img src="@/assets/images/cuowu.png"> |
||||
<div class="text-#333 text-24px mt-35px text-center mb-26px">请登录查看</div> |
||||
<div class="handleBtnBorder w-100px h-40px text-14px lh-40px m-auto cursors" @click="nuxtApp.$onHandlerLogin()">登录</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
|
||||
</style> |
@ -0,0 +1,70 @@ |
||||
<!--商品信息,订单列表、评论列表中展示--> |
||||
<script setup lang="ts"> |
||||
import { toRefs } from 'vue' |
||||
import {orderProductTypeFilter, productTypeFilter} from '~/utils/filter' |
||||
import useOrder from '~/composables/useOrder' |
||||
const { handlerNuxtLink } = useOrder() |
||||
const props = defineProps({ |
||||
//列表数据 |
||||
list: { |
||||
type: Object, |
||||
default: null, |
||||
}, |
||||
refundStatus: { |
||||
type: Number, |
||||
default: null, |
||||
}, |
||||
}) |
||||
const { list,refundStatus } = toRefs(props) |
||||
|
||||
//商品详情跳转 |
||||
const handlerNuxtLinks = async (list: any) => { |
||||
if(refundStatus.value && refundStatus.value===-1) { |
||||
await handlerNuxtLink(list.productId, orderProductTypeFilter(list.productType)) |
||||
} |
||||
} |
||||
|
||||
//跳转至申请记录 |
||||
const emit = defineEmits(['onHandlerToRecord']) |
||||
const handlerToRecord = async (orderNo:string)=>{ |
||||
emit('onHandlerToRecord',orderNo) |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="flex-between-center orderProduct cursors"> |
||||
<div |
||||
class="acea-row justify-between" |
||||
@click="handlerNuxtLinks(list)" |
||||
> |
||||
<el-image :src="list.image" class="backImg w100px h100px b-rd-12px" lazy></el-image> |
||||
<div class="ml-20px acea-row flex-col justify-center"> |
||||
<div class="text-14px fontColor333 line1 mb14px w-635px font-500 oppoSans-M">{{ list.productName }}</div> |
||||
<div class="borRadius text-14px fontColor333 line1 mb-15px font-400 oppoSans-R">规格:{{ list.sku }}</div> |
||||
<div class="acea-row"> |
||||
<div v-if="list.price && list.payNum" class="text-14px fontColor333 oppoSans-R mr-20px"> |
||||
¥{{ list.price }} ×{{ list.payNum }} |
||||
</div> |
||||
<div @click.stop="handlerToRecord(list.orderNo)" v-if="(list.applyRefundNum+list.refundNum) && refundStatus === -1" class="text-14px font-color oppoSans-R mr-20px flex-y-center"> |
||||
{{ list.applyRefundNum+list.refundNum }}件商品已申请售后 <span class="iconfont icon-gengduo"></span> |
||||
</div> |
||||
<div v-if="list.applyRefundNum && refundStatus > -1" class="text-14px fontColor333 oppoSans-R mr-20px"> |
||||
申请数量:{{ list.applyRefundNum }} |
||||
</div> |
||||
<div v-if="list.afterSalesType" class="text-14px font-color oppoSans-R mr-20px"> |
||||
{{ list.afterSalesType === 1 ? '仅退款' : '退货退款' }} |
||||
</div> |
||||
<div v-if="list.refundStatus === 3" class="text-14px font-color"> |
||||
已退款:{{ list.refundPrice }} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
.orderProduct { |
||||
margin-bottom: 20px; |
||||
} |
||||
</style> |
@ -0,0 +1,39 @@ |
||||
<!--页面中头部展示标题使用--> |
||||
<script setup lang="ts"> |
||||
import { toRefs } from 'vue' |
||||
import { linkNavigateTo } from '~/utils/util' |
||||
const props = defineProps({ |
||||
//副标题 |
||||
title: { |
||||
type: String, |
||||
default: '', |
||||
}, |
||||
//主标题 |
||||
menuTitle:{ |
||||
type: String, |
||||
default: '', |
||||
}, |
||||
navigateToTitle: { |
||||
type: Object, |
||||
default: null, |
||||
} |
||||
}) |
||||
const { title } = toRefs(props) |
||||
|
||||
const router = useRouter(); |
||||
|
||||
const goBack =()=>{ |
||||
router.back(); // 回退到上一个路由 |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="mt-20px mb-20px"> |
||||
<span class="fontColor333 fonts14 cursors" @click="linkNavigateTo('/')">首页 </span> |
||||
<span v-if="navigateToTitle" class="fontColor333 fonts14 cursors" @click="linkNavigateTo(navigateToTitle.linkUrl, {type: 0})"> > {{navigateToTitle.title}}</span> |
||||
<span v-if="menuTitle" class="fontColor333 fonts14 cursors" @click="goBack"> > {{menuTitle}}</span> |
||||
<span class="text-#666 fonts14"> > {{title}}</span> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"></style> |
@ -0,0 +1,64 @@ |
||||
<!--商品列表,推荐商品,首页商品展示--> |
||||
<script setup lang="ts"> |
||||
import {linkNavigateTo} from "~/utils/util"; |
||||
import { toRefs } from 'vue' |
||||
const props = defineProps({ |
||||
//列表数据 |
||||
item: { |
||||
type: Object, |
||||
default: null, |
||||
}, |
||||
//图片大小 |
||||
sizeData: { |
||||
type: Object, |
||||
default: { |
||||
width: '228px', |
||||
height: '228px', |
||||
}, |
||||
} |
||||
}) |
||||
const { item, sizeData } = toRefs(props) |
||||
|
||||
/** |
||||
* 地址跳转 |
||||
*/ |
||||
const handlerNuxtLink = async (id: number)=>{ |
||||
await linkNavigateTo(`/product/detail/${id}`,{type: 'normal'}) |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="productItem cursors"> |
||||
<div data-aos-anchor-placement="top-bottom" @click="handlerNuxtLink(item.id)"> |
||||
<div class="borRadius bg-#FFFFFF" :style="{width:sizeData.width}"> |
||||
<el-image class="backImg" :src="item.image" :style="{width:sizeData.width,height:sizeData.height}"></el-image> |
||||
<div class="bg-white px15px py-14px content h-132px flex flex-justify-between" style="margin-top: -5px;flex-direction: column;"> |
||||
<div class="line2 text-14px fontColor333 mb15px products-name h-36px lh-19px acea-row"> |
||||
<div v-if="item.productTags && item.productTags.locationLeftTitle.length" class="inline-block text-12px bg-color text-#fff b-rd-2px py-2px mr-6px px-4px line-heightOne">{{item.productTags.locationLeftTitle[0].tagName}}</div>{{item.name}}</div> |
||||
<div v-if="item.productTags && item.productTags.locationUnderTitle.length" class="acea-row mb14px"> |
||||
<div v-for="items in item.productTags.locationUnderTitle.length>3?item.productTags.locationUnderTitle.slice(0,3):item.productTags.locationUnderTitle" :key="items.id" class="text-12px line-heightOne font-color base-border b-color-#E93323 b-rd-2px px4px py2px mr8px productTags">{{items.tagName}}</div> |
||||
</div> |
||||
<div class="flex-between-center"> |
||||
<div class="text-12px font-color"><span class="oppoSans-M">¥</span><span class="dinProSemiBold text-20px lh-19px font-600">{{item.price}}</span></div> |
||||
<div class="text-12px text-#999">已售{{item.sales}}{{item.unitName}}</div> |
||||
</div> |
||||
<div v-if="item.merName" class="mt-15px"> |
||||
<span class="iconfont icon-guanzhudianpu mr-5px font-color"></span> |
||||
<span class="text-12px text-#666">{{item.merName}}</span> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
.backImg{ |
||||
border-top-left-radius: 16px; |
||||
border-top-right-radius: 16px; |
||||
} |
||||
.content{ |
||||
border-bottom-left-radius: 16px; |
||||
border-bottom-right-radius: 16px; |
||||
} |
||||
</style> |
@ -0,0 +1,104 @@ |
||||
<!--商品列表,可用优惠券商品列表、商品搜索列表使用--> |
||||
<script setup lang="ts"> |
||||
import { linkNavigateTo } from '~/utils/util' |
||||
import { toRefs } from 'vue' |
||||
import { Mul } from '~/utils/util' |
||||
const props = defineProps({ |
||||
//列表数据 |
||||
list: { |
||||
type: Array, |
||||
default: [], |
||||
}, |
||||
//图片大小 |
||||
sizeData: { |
||||
type: Object, |
||||
default: { |
||||
width: '228px', |
||||
height: '228px', |
||||
}, |
||||
}, |
||||
}) |
||||
const { list, sizeData } = toRefs(props) |
||||
|
||||
/** |
||||
* 地址跳转 |
||||
*/ |
||||
const handlerNuxtLink = async (id: number) => { |
||||
await linkNavigateTo(`/product/detail/${id}`, { type: 'normal' }) |
||||
} |
||||
|
||||
// 跳入页面 |
||||
const handleGoPage = (id) => { |
||||
linkNavigateTo(`/merchant/merchant_home`, { merId: id }) |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="acea-row mt-20px"> |
||||
<div v-for="item in list" :key="item.id" class="mb15px item cursors"> |
||||
<div data-aos="fade-up" data-aos-anchor-placement="top-bottom" @click="handlerNuxtLink(item.id)"> |
||||
<div class="w228px borRadius overflow-hidden bg-#FFFFFF" :style="{ width: sizeData.width }"> |
||||
<el-image |
||||
class="backImg w228px h228px" |
||||
:src="item.image" |
||||
:style="{ width: sizeData.width, height: sizeData.height }" |
||||
></el-image> |
||||
<div |
||||
class="bg-white pt-15px pb-13px px-15px flex flex-justify-between" |
||||
style="flex-direction: column; margin-top: -5px" |
||||
> |
||||
<div class="text-12px font-color mb-8px font-500"> |
||||
<span class="oppoSans-M">¥</span |
||||
><span class="text-20px lh-20px font-600 dinProSemiBold">{{ item.price }}</span> |
||||
</div> |
||||
<div class="line2 text-14px fontColor333 mb-12px h-38px lh-19px acea-row"> |
||||
<div |
||||
v-if="item.productTags && item.productTags.locationLeftTitle.length" |
||||
class="inline-block text-12px bg-color text-#fff b-rd-2px py-2px mr-6px px-4px lh-12px" |
||||
> |
||||
{{ item.productTags.locationLeftTitle[0].tagName }} |
||||
</div>{{ item.name }} |
||||
</div> |
||||
<div class="flex mb-10px h-16px"> |
||||
<div class="text-12px text-#999 mr-10px"> |
||||
<text v-if="Math.floor(item.replyNum) > 0">{{ item.replyNum }}条评论</text> |
||||
<text v-if="item.replyNum === 0 || !item.replyNum">暂无评论</text> |
||||
<text v-if="item.positiveRatio && Number(item.positiveRatio) > 0" class="ml-2px" |
||||
>好评{{ Mul(item.positiveRatio, 100) }}%</text |
||||
> |
||||
</div> |
||||
<div class="text-12px text-#999">已售{{ item.sales }}{{ item.unitName }}</div> |
||||
</div> |
||||
<div v-if="item.productTags" class="h-20px mb-10px"> |
||||
<div v-if="item.productTags && item.productTags.locationUnderTitle.length" class="acea-row"> |
||||
<div |
||||
v-for="items in item.productTags.locationUnderTitle.length > 3 |
||||
? item.productTags.locationUnderTitle.slice(0, 3) |
||||
: item.productTags.locationUnderTitle" |
||||
:key="items.id" |
||||
class="text-12px lh-12px font-color base-border b-color-#E93323 b-rd-2px px3px py2px mr8px" |
||||
> |
||||
{{ items.tagName }} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="cursors acea-row items-center" @click.stop="handleGoPage(item.merId)"> |
||||
<span class="iconfont icon-guanzhudianpu mr-5px text-12px font-color"></span> |
||||
<span class="text-12px text-#666 line1 w-175px">{{ item.merName }}</span> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
.item { |
||||
margin-right: 15px; |
||||
&:nth-child(5n) { |
||||
margin-right: 0; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,91 @@ |
||||
<!--推荐商品列表组件--> |
||||
<script setup lang="ts"> |
||||
import { productRecommendListApi } from '~/server/goodsApi' |
||||
import {PageQuery} from "~/types/global"; |
||||
const example = useScrollHeight() |
||||
|
||||
/** |
||||
* 监听页面滚动 |
||||
*/ |
||||
onMounted(() => { |
||||
window.addEventListener('scroll', handleScroll) |
||||
}) |
||||
|
||||
/** |
||||
* 推荐商品列表 |
||||
*/ |
||||
const where = reactive<PageQuery>({ |
||||
page: 1, |
||||
limit: 10, |
||||
cid: '0', |
||||
}) |
||||
const tableData = reactive<PageResult>({ |
||||
list: [], |
||||
total: 0 |
||||
}) |
||||
const pullRefreshss =ref<boolean>(false) |
||||
const loading = ref<boolean>(false) |
||||
const getList = async () =>{ |
||||
loading.value = true |
||||
await productRecommendListApi(where) |
||||
.then(async (data:PageResult) => { |
||||
tableData.total = data.total; |
||||
let proList = data.list; |
||||
tableData.list = tableData.list.concat(proList); |
||||
loading.value = false |
||||
}) |
||||
.catch(async (err: any) => { |
||||
loading.value = false |
||||
}) |
||||
} |
||||
getList() |
||||
|
||||
/** |
||||
* 滚动加载 |
||||
*/ |
||||
const handleScroll = () => { |
||||
let scollY = example.getScrollTop() + example.getWindowHeight() - example.getScrollHeight()+636 |
||||
// 把下拉刷新置为false,防止多次请求 |
||||
if (scollY >= -50) { |
||||
if (where.page >= Math.ceil(tableData.total / where.limit)) { |
||||
pullRefreshss.value = true; |
||||
return false; |
||||
} |
||||
if (!pullRefreshss.value) { |
||||
return false; |
||||
} |
||||
pullRefreshss.value = false; |
||||
if (tableData.list.length < tableData.total) { |
||||
// 加页码数 |
||||
where.page++; |
||||
getList() |
||||
} |
||||
} else { |
||||
// 其他时候把下拉刷新置为true |
||||
pullRefreshss.value = true; |
||||
} |
||||
} |
||||
|
||||
</script> |
||||
|
||||
<template> |
||||
<div class="wrapper_1200" v-if="tableData.list.length"> |
||||
<div class="w-100% text-28px fontColor333 fw-700 text-center mb-30px alimama">猜你喜欢</div> |
||||
<div class="pb-10px item grid-column-5 gap-15px"> |
||||
<div v-for="item in tableData.list" :key="item.id" class="productItem cursors"> |
||||
<product :item="item"></product> |
||||
</div> |
||||
</div> |
||||
<div |
||||
class="loadingicon acea-row row-center-wrapper" |
||||
v-if="loading" |
||||
> |
||||
<span class="loading iconfont icon-jiazai">加载中。。。</span |
||||
> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
|
||||
</style> |
@ -0,0 +1,180 @@ |
||||
<script setup lang="ts"> |
||||
import { useAppStore } from '~/stores/app' |
||||
const useStore = useAppStore() |
||||
import { ref, toRefs } from 'vue' |
||||
import useOrder from '~/composables/useOrder' |
||||
import { indexCustomerServiceApi } from '~/server/homeApi' |
||||
const { handleIntoPage } = useOrder() |
||||
|
||||
const props = defineProps({ |
||||
//列表数据 |
||||
isShowCoupon: { |
||||
type: Boolean, |
||||
default: false, |
||||
}, |
||||
}) |
||||
const { isShowCoupon } = toRefs(props) |
||||
|
||||
/** |
||||
* 监听页面滚动 |
||||
*/ |
||||
onMounted(() => { |
||||
window.addEventListener('scroll', handleScroll) |
||||
}) |
||||
const isShowTop = ref<boolean>(false) |
||||
const handleScroll = () => { |
||||
const t = document.documentElement.scrollTop || document.body.scrollTop |
||||
if (t > 100) { |
||||
isShowTop.value = true |
||||
} else { |
||||
isShowTop.value = false |
||||
} |
||||
} |
||||
|
||||
//向上 |
||||
const handleTop = () => { |
||||
;(function n() { |
||||
var t = document.documentElement.scrollTop || document.body.scrollTop |
||||
if (t > 0) { |
||||
window.requestAnimationFrame(n) |
||||
window.scrollTo(0, t - t / 5) |
||||
} |
||||
})() |
||||
} |
||||
|
||||
//客服 |
||||
const { data: indexCustomerService } = useAsyncData(() => indexCustomerServiceApi()) |
||||
const { chatConfig } = useOrder() |
||||
const handleChat = async () => { |
||||
let chatConfigData = { |
||||
serviceLink: indexCustomerService.value?.consumerH5Url, |
||||
servicePhone: indexCustomerService.value?.consumerHotline, |
||||
serviceType: indexCustomerService.value?.consumerType, |
||||
} |
||||
await chatConfig(chatConfigData) |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div |
||||
class="flex-col-center w-40px bg-#222222 h-100% fixed top-0px text-#fff rightNavigation z-2010" |
||||
:class="isShowCoupon ? 'right-278px' : 'right-0px'" |
||||
> |
||||
<div |
||||
@click="handleIntoPage('/users/user_info', { type: '11' })" |
||||
class="floatClass flex-col-center w-40px h-40px cursors mb-3px" |
||||
> |
||||
<span class="iconfont icon-wode"></span> |
||||
<div class="absolute floatTitle">个人信息</div> |
||||
</div> |
||||
|
||||
<div |
||||
@click="handleIntoPage('/order/shopping_cart', { type: '0' })" |
||||
class="w-40px h-136px bg-color cursors flex-col-center mb-3px" |
||||
> |
||||
<span class="iconfont icon-gouwuche mb-10px"></span> |
||||
<span class="text-14px text-#fff mb-10px verticalClass">购物车</span> |
||||
<span class="w-16px h-16px b-rd-50% text-center bg-#FFFFFF flex-between-center text-12px font-color block">{{ |
||||
useStore.carNumber |
||||
}}</span> |
||||
</div> |
||||
<div |
||||
@click="handleIntoPage('/users/order_list', { type: '1' })" |
||||
class="floatClass flex-col-center w-40px h-40px cursors mb-3px relative" |
||||
> |
||||
<span class="iconfont icon-dingdan"></span> |
||||
<div class="absolute floatTitle">我的订单</div> |
||||
</div> |
||||
<div |
||||
@click="handleIntoPage('/users/user_coupon', { type: '6' })" |
||||
class="floatClass flex-col-center w-40px h-40px cursors mb-3px" |
||||
> |
||||
<span class="iconfont icon-youhuiquan"></span> |
||||
<div class="absolute floatTitle w-110px text-center">优惠券</div> |
||||
</div> |
||||
<div |
||||
@click="handleIntoPage('/users/collect_products', { type: '3' })" |
||||
class="floatClass flex-col-center w-40px h-40px cursors mb-3px" |
||||
> |
||||
<span class="iconfont icon-shoucang"></span> |
||||
<div class="absolute floatTitle">我的收藏</div> |
||||
</div> |
||||
<div |
||||
@click="handleIntoPage('/users/browsing_history', { type: '8' })" |
||||
class="floatClass flex-col-center w-40px h-40px cursors mb-3px" |
||||
> |
||||
<span class="iconfont icon-zuji"></span> |
||||
<div class="absolute floatTitle">我的足迹</div> |
||||
</div> |
||||
<div @click="handleChat" class="floatClass absolute flex-col-center w-40px h-40px cursors bottom-75px mb-3px"> |
||||
<span class="iconfont icon-lianxikefu"></span> |
||||
<div class="absolute floatTitle">联系客服</div> |
||||
</div> |
||||
<div |
||||
v-show="isShowTop" |
||||
@click="handleTop" |
||||
class="floatClass flex-col-center w-40px h-40px cursors mb-3px absolute bottom-30px isShowTop" |
||||
> |
||||
<span class="iconfont icon-zhiding"></span> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
.iconfont { |
||||
font-size: 18px; |
||||
} |
||||
.isShowTop { |
||||
transition: top 10s ease-out; |
||||
} |
||||
.floatClass:hover .floatTitle { |
||||
display: block !important; |
||||
animation: slideRight 0.5s forwards; /* 调用名为slideRight的动画,持续2秒 */ |
||||
} |
||||
|
||||
@keyframes slideRight { |
||||
from { |
||||
left: -150px; /* 元素初始位置在左侧屏幕外 */ |
||||
} |
||||
to { |
||||
left: -122px; /* 元素最终位置靠左边缘 */ |
||||
} |
||||
} |
||||
.floatClass:hover { |
||||
width: 100%; |
||||
background: #e93323; |
||||
} |
||||
.floatTitle { |
||||
height: 40px; |
||||
background: #222222; |
||||
padding: 0 27px; |
||||
font-size: 14px; |
||||
color: #fff; |
||||
line-height: 40px; |
||||
left: -122px; |
||||
border-radius: 3px; |
||||
display: none; |
||||
} |
||||
.floatTitle::before { |
||||
content: ''; |
||||
position: absolute; |
||||
top: 50%; |
||||
right: -16px; |
||||
border-width: 8px; |
||||
border-style: solid; |
||||
border-color: transparent transparent transparent #222222; /* 右边三角形的颜色 */ |
||||
transform: translateY(-50%); |
||||
} |
||||
.right-278px, |
||||
.right-0px { |
||||
transition: all 1s; |
||||
} |
||||
.main_Box { |
||||
//height: 78vh; |
||||
} |
||||
.rightNavigation { |
||||
.title { |
||||
display: none; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,124 @@ |
||||
<!--搜索条件筛选,可多选,商品搜索列表、商户搜索列表中使用--> |
||||
<script setup lang="ts"> |
||||
import { toRefs, ref, onMounted, shallowRef } from 'vue' |
||||
const props = defineProps({ |
||||
//列表数据 |
||||
list: { |
||||
type: Array, |
||||
default: [], |
||||
}, |
||||
}) |
||||
const { list } = toRefs(props) |
||||
const emit = defineEmits(['handleSubmitChecked']) |
||||
const selectedList = ref({}) |
||||
|
||||
//判断是否展示 展开收起 按钮 |
||||
onMounted(() => { |
||||
list.value.forEach((item:any,index:number)=>{ |
||||
if(document.getElementById(`myElement${index}`)){ |
||||
let myElement = document.getElementById(`myElement${index}`); |
||||
if(myElement.offsetHeight>38){ |
||||
item.isShowBtnMore=true |
||||
}else{ |
||||
item.isShowBtnMore=false |
||||
} |
||||
} |
||||
}) |
||||
}); |
||||
|
||||
/** |
||||
* 点击菜单 |
||||
* @param list 当前选中的大类 |
||||
* @param item 当前选中菜单的对象 |
||||
*/ |
||||
const handleMenu = (list: any, item: any) => { |
||||
list.list.forEach((val, i) => { |
||||
if (val.id == item.id) { |
||||
val.isChoose = !val.isChoose |
||||
}else{ |
||||
if(!list.multiple) val.isChoose =false |
||||
} |
||||
}) |
||||
//多选 |
||||
if(list.multiple){ |
||||
if (item.isChoose) { |
||||
list.checked.push(item.id) |
||||
} else { |
||||
let index = list.checked.findIndex((itemn) => itemn === item.id) |
||||
list.checked.splice(index,1) |
||||
} |
||||
selectedList.value[list.type] = list.checked |
||||
}else{ |
||||
//单选 |
||||
if (item.isChoose) { |
||||
selectedList.value[list.type] = item.id |
||||
}else{ |
||||
selectedList.value[list.type] = '' |
||||
} |
||||
} |
||||
emit('handleSubmitChecked', selectedList.value) |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="b-rd-16px bg-#FFFFFF px-30px" v-if="list.length>0"> |
||||
<template v-for="(item, index) in list" :key="index"> |
||||
<div v-if="item.list && item.list.length" class="sort acea-row"> |
||||
<div class="name mr-20px">{{ item.name }}:</div> |
||||
<div class="listCon acea-row row-between text-#333 text-14px cursors w-1050px" :class="item.showMore?'on':''"> |
||||
<div :ref="'myElement'+index" :id="'myElement'+index" class="list acea-row w-960px h-auto"> |
||||
<div |
||||
class="item" |
||||
:class="(items.isChoose &&item.multiple) || (selectedList[item.type]===items.id && !item.multiple) ? 'font-color' : ''" |
||||
v-for="(items, idx) in item.list" |
||||
:key="idx" |
||||
@click="handleMenu(item, items)" |
||||
> |
||||
{{ items.name || items.tagName }} |
||||
</div> |
||||
</div> |
||||
<div v-if="!item.showMore && item.isShowBtnMore" class="cursors fontColor6 text-14px text-center" @click="item.showMore = !item.showMore"> |
||||
收起 <span class="iconfont icon-gao"></span> |
||||
</div> |
||||
<div v-if="item.showMore && item.isShowBtnMore" class="fontColor6 text-14px text-center cursors" @click="item.showMore = !item.showMore"> |
||||
展开 <span class="iconfont icon-di"></span> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
.on { |
||||
height: 37px; |
||||
overflow: hidden; |
||||
} |
||||
.sort { |
||||
border-bottom: 1px dashed #eeeeee; |
||||
padding-bottom: 11px; |
||||
padding-top: 30px; |
||||
&:last-child { |
||||
border: none; |
||||
} |
||||
.name { |
||||
width: 70px; |
||||
font-size: 14px; |
||||
color: #666666; |
||||
} |
||||
|
||||
.item { |
||||
margin-right: 30px; |
||||
margin-bottom: 19px; |
||||
.icon { |
||||
font-size: 15px; |
||||
margin-left: 5px; |
||||
} |
||||
.iconfont { |
||||
font-size: 15px; |
||||
color: #e2e2e2; |
||||
margin-left: 5px; |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,118 @@ |
||||
<!--销量、价格、搜索搜索条件,商品列表、商户主页,搜索展示--> |
||||
<script setup lang="ts"> |
||||
import { toRefs, ref } from 'vue' |
||||
import {Search} from "@element-plus/icons-vue"; |
||||
const props = defineProps({ |
||||
//列表数据 |
||||
where: { |
||||
type: Object, |
||||
default: null, |
||||
}, |
||||
}) |
||||
const { where } = toRefs(props) |
||||
|
||||
const stock = ref<number>(0) //销量 |
||||
const price = ref<number>(0) //价格 |
||||
|
||||
//点击筛选 |
||||
const emit = defineEmits(['handleSubmitSelect']) |
||||
const iSdefaults = ref<number>(1); |
||||
const handleSeachList = async (e: number) => { |
||||
where.value.page = 1; |
||||
iSdefaults.value = e; |
||||
switch (e) { |
||||
case 1: |
||||
price.value = 0 |
||||
stock.value = 0 |
||||
break |
||||
case 2: |
||||
if (price.value == 0) price.value = 1 |
||||
else if (price.value == 1) price.value = 2 |
||||
else if (price.value == 2) price.value = 0 |
||||
stock.value = 0 |
||||
break |
||||
case 3: |
||||
if (stock.value == 0) stock.value = 1 |
||||
else if (stock.value == 1) stock.value = 2 |
||||
else if (stock.value == 2) stock.value = 0 |
||||
price.value = 0 |
||||
break |
||||
case 4: |
||||
price.value = 0 |
||||
stock.value = 0 |
||||
break |
||||
} |
||||
await setWhere() |
||||
emit('handleSubmitSelect', where.value) |
||||
} |
||||
|
||||
//设置where条件 |
||||
const setWhere = () => { |
||||
if (price.value == 0) where.value.priceOrder = '' |
||||
else if (price.value == 1) where.value.priceOrder = 'asc' |
||||
else if (price.value == 2) where.value.priceOrder = 'desc' |
||||
if (stock.value == 0) where.value.salesOrder = '' |
||||
else if (stock.value == 1) where.value.salesOrder = 'asc' |
||||
else if (stock.value == 2) where.value.salesOrder = 'desc' |
||||
} |
||||
|
||||
</script> |
||||
|
||||
<template> |
||||
<div class="acea-row sortSeach items-center mb-20px w-100% h-50px bg-#FFFFFF b-rd-12px pl-10px"> |
||||
<div class="items" :class="iSdefaults === 1 ? 'font-color' : ''" @click="handleSeachList(1)">综合排序</div> |
||||
<div class="items flex-center" :class="stock !== 0 ? 'font-color' : ''" @click="handleSeachList(3)"> |
||||
销量 |
||||
<div> |
||||
<span class="iconfont icon-gao text-12px lh-7px" :class="stock==1?'font-color':'text-#999'"></span> |
||||
<span class="iconfont icon-di text-12px lh-7px" :class="stock==2?'font-color':'text-#999'"></span> |
||||
</div> |
||||
</div> |
||||
<div class="items flex-center" :class="price !== 0 ? 'font-color' : ''" @click="handleSeachList(2)"> |
||||
<span>价格</span> |
||||
<div> |
||||
<span class="iconfont icon-gao text-12px lh-7px" :class="price==1?'font-color':'text-#999'"></span> |
||||
<span class="iconfont icon-di text-12px lh-7px" :class="price==2?'font-color':'text-#999'"></span> |
||||
</div> |
||||
</div> |
||||
<div class="item price-range flex-center"> |
||||
<div class="price-count mr-10px"> |
||||
<el-input style="width: 120px" min="0" type="number" v-model.number="where.minPrice"> <template #prefix>¥</template></el-input> |
||||
- |
||||
<el-input style="width: 120px" min="0" type="number" v-model.number="where.maxPrice"> |
||||
<template #prefix>¥</template> |
||||
</el-input> |
||||
</div> |
||||
<el-button plain @click="handleSeachList">确定</el-button> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
.sort { |
||||
background: #fff; |
||||
.items { |
||||
margin-right: 30px; |
||||
cursor: pointer; |
||||
font-size: 14px; |
||||
color: #333333; |
||||
&:hover { |
||||
color: #e93323; |
||||
} |
||||
.icon { |
||||
font-size: 15px; |
||||
margin-left: 5px; |
||||
color: #e2e2e2; |
||||
} |
||||
.iconfont { |
||||
font-size: 12px; |
||||
margin-left: 5px; |
||||
display: block; |
||||
} |
||||
} |
||||
.name { |
||||
color: #969696; |
||||
margin-right: 20px; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,60 @@ |
||||
<!--成功提示弹窗--> |
||||
<script setup lang="ts"> |
||||
import { ref, reactive, toRefs } from 'vue' |
||||
//弹窗状态 |
||||
const dialogVisible = ref(false) |
||||
|
||||
const props = defineProps({ |
||||
//数据 |
||||
successInfo: { |
||||
type: Object, |
||||
default: null, |
||||
}, |
||||
}) |
||||
const { successInfo } = toRefs(props) |
||||
|
||||
const open = () => { |
||||
dialogVisible.value = true |
||||
} |
||||
defineExpose({ open }) |
||||
|
||||
//返回列表 |
||||
const emit = defineEmits(['handleSubmitSuccess']) |
||||
const handleGoBack =()=>{ |
||||
emit('handleSubmitSuccess') |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<el-dialog |
||||
class="success-dialog borRadius" |
||||
v-model="dialogVisible" |
||||
center |
||||
width="600px" |
||||
:append-to-body="true" |
||||
:show-close="false" |
||||
:close-on-click-modal="false" |
||||
> |
||||
<div class="mb-30px flex justify-center"> |
||||
<img src="~/assets/images/tijiaocheng.png" alt="" class="w-140px h-140px" /> |
||||
</div> |
||||
<div class="text-24px fontColor333 mb-20px text-center">{{ successInfo.title }}</div> |
||||
<div class="text-16px text-#666 mb-50px text-center">{{ successInfo.tips }}</div> |
||||
<div class="flex justify-center"> |
||||
<nuxt-link :to="{ path: successInfo.url, query: { type: successInfo.type } }"> |
||||
<div @click="handleGoBack" class="w-150px h-50px text-center borderSol b-rd-25px text-16px text-#333 lh-48px mr-20px">返回列表</div> |
||||
</nuxt-link> |
||||
<nuxt-link :to="{ path: '/' }"> |
||||
<div class="w-150px h-50px text-center b-rd-25px text-16px text-#fff bg-color lh-48px">去首页</div> |
||||
</nuxt-link> |
||||
</div> |
||||
</el-dialog> |
||||
</template> |
||||
|
||||
<style lang="scss"> |
||||
.success-dialog { |
||||
:deep(.el-dialog__body) { |
||||
padding: 25px 0 55px 0 !important; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,93 @@ |
||||
<!--swiper轮播--> |
||||
<script setup lang="ts"> |
||||
import { reactive, toRefs } from 'vue' |
||||
// |
||||
//import {Pagination, Navigation} from 'swiper/modules' |
||||
import { Swiper, SwiperSlide } from 'swiper/vue' |
||||
import type SwiperClass from 'swiper' |
||||
import 'swiper/css' |
||||
import 'swiper/css/pagination' |
||||
import 'swiper/css/navigation' |
||||
|
||||
import { ItemObject } from '~/types/global' |
||||
import { merchantRecommendApi } from '~/server/merchantApi' |
||||
import { Keyboard, Navigation, Pagination } from 'swiper' |
||||
|
||||
const props = defineProps({ |
||||
//列表数据 swiperImage |
||||
swiperSlideList: { |
||||
type: Array, |
||||
default: [], |
||||
}, |
||||
swiperData: { |
||||
type: Object, |
||||
default: null, |
||||
}, |
||||
}) |
||||
const { swiperSlideList, swiperData } = toRefs(props) |
||||
|
||||
let swiperRef: SwiperClass | null = null |
||||
const setSwiperRef = (swiper: SwiperClass) => { |
||||
swiperRef = swiper |
||||
} |
||||
|
||||
//跳到指定位置 |
||||
const slideTo = (index: number) => { |
||||
swiperRef?.slideTo(index - 1, 0) |
||||
} |
||||
defineExpose({ slideTo }) |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="swiper" :style="swiperData.boxPadding"> |
||||
<swiper |
||||
:space-between="swiperData.spaceBetween" |
||||
:slides-per-view="swiperData.slidesPerView" |
||||
:modules="swiperData.modules" |
||||
@swiper="setSwiperRef" |
||||
:navigation="{ |
||||
nextEl: '.swiper-button-next', |
||||
prevEl: '.swiper-button-prev', |
||||
}" |
||||
:pagination="{ clickable: true, el: '.swiper-pagination'}" |
||||
class="swiper h-100% proList acea-row" |
||||
> |
||||
<swiper-slide v-for="(item, index) in swiperSlideList" :key="index" class="h-100%" :class="swiperData.slideWidth"> |
||||
<slot |
||||
:value="{ |
||||
swiperItem: item, |
||||
swiperIndex: index, |
||||
}" |
||||
></slot> |
||||
</swiper-slide> |
||||
</swiper> |
||||
<div class="swiper-button-prev iconfont icon-zuo1 text-center" :style="swiperData.navigationBgColor+';'+ swiperData.navigationColor" slot="button-prev"></div> |
||||
<div class="swiper-button-next iconfont icon-you1 text-center" :style="swiperData.navigationBgColor+';'+ swiperData.navigationColor" slot="button-prev"></div> |
||||
<div class="swiper-pagination"></div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
.swiper-button-prev:after, |
||||
.swiper-button-next:after { |
||||
display: none !important; |
||||
} |
||||
.swiper-button-prev { |
||||
text-align: center; |
||||
width: 30px; |
||||
height: 42px; |
||||
//background: rgba(0, 0, 0, 0.3); |
||||
border-radius: 0px 23px 23px 0px; |
||||
// color: #fff; |
||||
margin-left: -10px; |
||||
} |
||||
.swiper-button-next { |
||||
text-align: center; |
||||
width: 30px; |
||||
height: 40px; |
||||
// background: rgba(0, 0, 0, 0.3); |
||||
border-radius: 23px 0 0 23px; |
||||
// color: #fff; |
||||
margin-right: -9px; |
||||
} |
||||
</style> |
@ -0,0 +1,148 @@ |
||||
<!--上传组件--> |
||||
<script setup lang="ts"> |
||||
import { ref, reactive, toRefs,watch } from 'vue' |
||||
import { uploadImageApi } from '~/server/userApi' |
||||
import { Plus } from '@element-plus/icons-vue' |
||||
const props = defineProps({ |
||||
//是否显示已上传文件列表 |
||||
isShow: { |
||||
type: Boolean, |
||||
default: true, |
||||
}, |
||||
// 最多可上传的张数 |
||||
maxlength: { |
||||
type: Number, |
||||
default: 5, |
||||
}, |
||||
// 最多可上传的张数 |
||||
limitNum: { |
||||
type: Number, |
||||
default: 5, |
||||
}, |
||||
pictureList: { |
||||
type: Array, |
||||
default: [], |
||||
}, |
||||
}) |
||||
const { isShow, limitNum, maxlength, pictureList } = toRefs(props) |
||||
|
||||
//图片列表 |
||||
const picList = ref<ItemObject[]>(pictureList?.value as ItemObject[]) |
||||
|
||||
watch( |
||||
() => pictureList.value, |
||||
(newValue) => { |
||||
picList.value = newValue |
||||
}, |
||||
) |
||||
|
||||
|
||||
const hideUploadEdit = ref<boolean>(false) |
||||
const myHeaders = reactive<any>({ |
||||
'Content-Type': 'multipart/form-data', |
||||
}) |
||||
|
||||
/** |
||||
* 上传前 |
||||
* @param file |
||||
*/ |
||||
const beforeUpload = (file: { name: string; size: number }) => { |
||||
const fileTypeName = file.name.substr(file.name.lastIndexOf('.') + 1) |
||||
const isImage = ['jpeg', 'gif', 'bmp', 'png', 'jpg'].includes(fileTypeName) |
||||
const isLtSize = file.size / 1024 / 1024 < 10 |
||||
if (!isImage) { |
||||
feedback.msgError('仅支持 ' + ['jpeg', 'gif', 'bmp', 'png', 'jpg'].join(',') + ' 格式') |
||||
return false |
||||
} |
||||
if (!isLtSize) { |
||||
//feedback.msgError('图片大小不能超过 ' + data.uploadSize + ' MB!') |
||||
return false |
||||
} |
||||
return true |
||||
} |
||||
|
||||
/** |
||||
* 自定义图片上传 |
||||
* @param options |
||||
*/ |
||||
const emit = defineEmits(['handleComplete','handleRemoveComplete']) |
||||
const pictureData = ref<ItemObject[]>(pictureList?.value as ItemObject[]) //图片上传后的集合 |
||||
const handleUploadForm = async (options: { file: string | Blob }) => { |
||||
let formData = new FormData() |
||||
formData.append('multipart', options.file) |
||||
const res = await uploadImageApi('merchant', '7', formData) |
||||
pictureData.value.push({ |
||||
name: res.fileName, |
||||
url: res.url, |
||||
}) |
||||
emit('handleComplete', res) |
||||
// emit('handleComplete', res) |
||||
} |
||||
|
||||
const beforeRemove =(file, fileList)=> { |
||||
let index = fileList.indexOf(file) |
||||
emit('handleRemoveComplete', index) |
||||
} |
||||
|
||||
/** |
||||
* 删除图片 |
||||
* @param file |
||||
* @param fileList |
||||
*/ |
||||
const handleRemove = async (file, fileList) => { |
||||
let index = fileList.indexOf(file) |
||||
|
||||
} |
||||
|
||||
const handleExceed = async () => { |
||||
feedback.msgWarning(`最多可上传${limitNum.value}张`) |
||||
} |
||||
|
||||
const handleEditChange = async (file, fileList) => { |
||||
// picList.value = JSON.parse((JSON.stringify(fileList))) |
||||
//hideUploadEdit.value = fileList.length >= limitNum.value |
||||
} |
||||
|
||||
//查看图片 |
||||
import type { UploadProps } from 'element-plus' |
||||
import {ItemObject} from "~/types/global"; |
||||
import {merchantFormDefault} from "~/pages/merchant/defaultMerchant"; |
||||
const dialogImageUrl = ref('') |
||||
const dialogVisible = ref(false) |
||||
const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => { |
||||
dialogImageUrl.value = uploadFile.url! |
||||
dialogVisible.value = true |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="el-form"> |
||||
<div class="img-box-wrapper"> |
||||
<el-upload |
||||
v-model:file-list="picList" |
||||
list-type="picture-card" |
||||
:limit="limitNum" |
||||
:action="'#'" |
||||
:headers="myHeaders" |
||||
:on-remove="handleRemove" |
||||
:before-remove="beforeRemove" |
||||
:http-request="handleUploadForm" |
||||
:on-exceed="handleExceed" |
||||
:show-file-list="isShow" |
||||
:before-upload="beforeUpload" |
||||
:on-change="handleEditChange" |
||||
:class="{ hide: hideUploadEdit }" |
||||
:disabled="hideUploadEdit" |
||||
:on-preview="handlePictureCardPreview" |
||||
> |
||||
<!-- <div v-if="picList" class="w-100px h-100px">--> |
||||
<!-- <img v-for="(item, index) in picList" :src="item" alt="" class="w-100px h-100px" />--> |
||||
<!-- </div>--> |
||||
<el-icon><Plus /></el-icon> |
||||
</el-upload> |
||||
</div> |
||||
<el-dialog v-model="dialogVisible"> |
||||
<img w-full :src="dialogImageUrl" alt="Preview Image" /> |
||||
</el-dialog> |
||||
</div> |
||||
</template> |
@ -0,0 +1,243 @@ |
||||
<!--商品列表,收藏商品、我的足迹使用--> |
||||
<template> |
||||
<div class="productList"> |
||||
<div v-if="type === 'collect_products'" class="acea-row row-middle mbtom20"> |
||||
<el-button size="large" round plain @click="isBatch = !isBatch">{{ isBatch ? '批量操作' : '取消' }}</el-button> |
||||
<div v-show="!isBatch" class="allSelect"> |
||||
<div class="checkbox-wrapper"> |
||||
<label class="well-check acea-row row-middle"> |
||||
<input type="checkbox" name="" value="" v-model="isAllSelect" /> |
||||
<i class="icon cursors allIcon" style="top: 11px"></i> |
||||
<span class="checkAll fontColor333 fonts14 cursors">全选</span> |
||||
</label> |
||||
</div> |
||||
</div> |
||||
<div v-show="!isBatch" class="fontColor333 fonts14 cursors" @click.stop="handleAllCancel">取消收藏</div> |
||||
</div> |
||||
<div v-loading="loading"> |
||||
<div v-if="type === 'collect_products'" class="products"> |
||||
<div v-for="(item, index) in list" :key="index"> |
||||
<div class="item"> |
||||
<div v-show="!isBatch" class="smegma b-rd-16px"> |
||||
<div class="checkbox-wrapper one-checkbox"> |
||||
<label class="well-check"> |
||||
<input type="checkbox" name="" value="" v-model="item.checked" /> |
||||
<span class="icon top-20px cursors oneIcon"></span> |
||||
</label> |
||||
</div> |
||||
</div> |
||||
<div v-show="isBatch" class="btn font12 cursors" @click.stop="handleCancel(item.productId)">取消收藏</div> |
||||
<el-image class="backImg img" :src="item.image" lazy @click="handlerNuxtLink(item)"></el-image> |
||||
<div class="cent" @click="handlerNuxtLink(item)"> |
||||
<div class="name text-14px text-#333 line2">{{ item.name }}</div> |
||||
<div class="price font12 fontColor333"> |
||||
<span class="oppoSans-M">¥</span><span class="font20 dinProSemiBold">{{ item.price }}</span> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div v-else> |
||||
<div v-for="(item, index) in list" :key="item.id" class="mb-30px"> |
||||
<div class="text-14px fontColor333 mb-18px text font-500 ml-10px">{{ item.date }}</div> |
||||
<div class="products"> |
||||
<div v-for="(itm, i) in item.list" :key="i" class="item" @click="handlerNuxtLink(itm)"> |
||||
<el-image class="backImg img" :src="itm.image" lazy :style="{opacity:itm.isShow?'1':'.5'}"></el-image> |
||||
<div class="cent"> |
||||
<div class="name text-14px line2" :class="itm.isShow?'fontColor333':'text-#CCCCCC'">{{ itm.name }}</div> |
||||
<div v-if="itm.isShow" class="price font12 fontColor333"> |
||||
<span class="oppoSans-M">¥</span><span class="font20 dinProSemiBold">{{ itm.price }}</span> |
||||
</div> |
||||
<div v-else class="fontColor333 text-14px oppoSans-R">商品已下架</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import {addressDelApi, getCollectCancelProApi} from '~/server/userApi' |
||||
import { ElButton, Image } from 'element-plus' |
||||
import feedback from '@/utils/feedback' |
||||
import { linkNavigateTo } from '~/utils/util' |
||||
import { ref, toRefs } from 'vue' |
||||
const props = defineProps({ |
||||
//列表数据 |
||||
list: { |
||||
type: Array, |
||||
default: [], |
||||
}, |
||||
//用于区分是足迹还是商品收藏,browsing_history足迹,collect_products商品收藏 |
||||
type: { |
||||
type: String, |
||||
default: '', |
||||
}, |
||||
}) |
||||
const { list } = toRefs(props) |
||||
const loading = ref(false) |
||||
|
||||
/** |
||||
* 地址跳转 |
||||
*/ |
||||
const handlerNuxtLink = async (item: any) => { |
||||
if(!item.isShow) return |
||||
linkNavigateTo(`/product/detail/${item.productId}`, { type: 'normal' }) |
||||
} |
||||
|
||||
//选中的值,将isAllSelect定义成计算属性 |
||||
const isAllSelect = computed({ |
||||
get() { |
||||
let flag = list.value.map((item: any) => { |
||||
if (!item.checked) { |
||||
return false |
||||
} else { |
||||
return true |
||||
} |
||||
}) |
||||
return !flag.includes(false) |
||||
}, |
||||
set(newVal) { |
||||
if (newVal) { |
||||
list.value.map((value: any, _index, _array) => { |
||||
value.checked = true |
||||
}) |
||||
} else { |
||||
list.value.map((value: any, _index, _array) => { |
||||
value.checked = false |
||||
}) |
||||
} |
||||
}, |
||||
}) |
||||
|
||||
//批量操作控制显示隐藏 |
||||
const isBatch = ref(true) |
||||
|
||||
//全部取消收藏 |
||||
const handleAllCancel = async () => { |
||||
let idArr = [] as any |
||||
Object.keys(list.value).forEach((item: any) => { |
||||
if (list.value[item].checked) idArr.push(list.value[item].productId) |
||||
}) |
||||
if (idArr.length === 0) return feedback.msgWarning('请至少选择一个商品') |
||||
let ids = idArr.join(',') |
||||
await onCancel(ids) |
||||
} |
||||
|
||||
//单独取消收藏 |
||||
const handleCancel = async (id: string) => { |
||||
await onCancel(id) |
||||
} |
||||
//取消请求 |
||||
const onCancel = async (id: string) => { |
||||
await feedback.confirm('确认取消收藏吗?') |
||||
try { |
||||
await getCollectCancelProApi({ ids: id }) |
||||
feedback.msgSuccess('取消成功') |
||||
loading.value = false |
||||
emit('handleComplete') |
||||
}catch (e) { |
||||
loading.value = false |
||||
} |
||||
} |
||||
const emit = defineEmits(['handleComplete']) |
||||
</script> |
||||
|
||||
<style scoped lang="scss"> |
||||
@import '@/assets/scss/checkbox.scss'; |
||||
:deep(.el-button){ |
||||
width: 90px !important; |
||||
height: 32px !important; |
||||
--el-button-text-color: #333333 !important; |
||||
font-size: 14px !important; |
||||
} |
||||
.allIcon{ |
||||
width: 18px !important; |
||||
height: 18px !important; |
||||
} |
||||
.oneIcon{ |
||||
width: 20px !important; |
||||
height: 20px !important; |
||||
} |
||||
.one-checkbox input:checked + .icon{ |
||||
background-size: 18px 13px !important; |
||||
} |
||||
.top-20px { |
||||
top: 28px !important; |
||||
left: 10px !important; |
||||
background-color: #fff; |
||||
} |
||||
.smegma { |
||||
background-color: rgba(0, 0, 0, 0.2); |
||||
transition: opacity 0.5s ease-in-out; |
||||
width: 228px; |
||||
height: 100%; |
||||
position: absolute; |
||||
z-index: 999; |
||||
} |
||||
.allSelect { |
||||
margin-left: 20px; |
||||
margin-right: 20px; |
||||
position: relative; |
||||
.checkAll { |
||||
margin-left: 10px; |
||||
} |
||||
} |
||||
.products { |
||||
position: relative; |
||||
display: grid; |
||||
grid-template-columns: repeat(4, 1fr); |
||||
grid-row-gap: 16px; |
||||
grid-column-gap: 16px; |
||||
grid-template-rows: auto; |
||||
.item { |
||||
width: 228px; |
||||
border-radius: 16px; |
||||
border: 1px solid #eeeeee; |
||||
position: relative; |
||||
cursor: pointer; |
||||
|
||||
// opacity: 0; |
||||
.btn { |
||||
cursor: pointer; |
||||
width: 70px; |
||||
height: 26px; |
||||
background: #000000; |
||||
border-radius: 4px 4px 4px 4px; |
||||
opacity: 0.5; |
||||
position: absolute; |
||||
z-index: 1; |
||||
text-align: center; |
||||
line-height: 26px; |
||||
color: #fff; |
||||
display: none; |
||||
left: 10px; |
||||
top: 10px; |
||||
} |
||||
} |
||||
.item:hover .btn { |
||||
display: block; |
||||
} |
||||
.backImg { |
||||
width: 227px; |
||||
height: 227px; |
||||
display: inline-block !important; |
||||
border-radius: 16px 16px 0 0; |
||||
} |
||||
.cent { |
||||
margin-top: -4px; |
||||
height: 112px; |
||||
padding: 15px 20px 20px 20px; |
||||
display: flex; |
||||
flex-direction: column; |
||||
justify-content: space-between; |
||||
} |
||||
.price { |
||||
span { |
||||
font-weight: 600; |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,273 @@ |
||||
<template> |
||||
<div style="position: relative"> |
||||
<div class="verify-img-out"> |
||||
<div |
||||
class="verify-img-panel" |
||||
:style="{ |
||||
width: setSize.imgWidth, |
||||
height: setSize.imgHeight, |
||||
'background-size': setSize.imgWidth + ' ' + setSize.imgHeight, |
||||
'margin-bottom': vSpace + 'px', |
||||
}" |
||||
> |
||||
<div v-show="showRefresh" class="verify-refresh" style="z-index: 3" @click="refresh"> |
||||
<i class="iconfont icon-refresh" /> |
||||
</div> |
||||
<img |
||||
ref="canvas" |
||||
:src="pointBackImgBase ? 'data:image/png;base64,' + pointBackImgBase : defaultImg" |
||||
alt="" |
||||
style="width: 100%; height: 100%; display: block" |
||||
@click="bindingClick ? canvasClick($event) : undefined" |
||||
/> |
||||
|
||||
<div |
||||
v-for="(tempPoint, index) in tempPoints" |
||||
:key="index" |
||||
class="point-area" |
||||
:style="{ |
||||
'background-color': '#1abd6c', |
||||
color: '#fff', |
||||
'z-index': 9999, |
||||
width: '20px', |
||||
height: '20px', |
||||
'text-align': 'center', |
||||
'line-height': '20px', |
||||
'border-radius': '50%', |
||||
position: 'absolute', |
||||
top: parseInt(tempPoint.y - 10) + 'px', |
||||
left: parseInt(tempPoint.x - 10) + 'px', |
||||
}" |
||||
> |
||||
{{ index + 1 }} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<!-- 'height': this.barSize.height, --> |
||||
<div |
||||
class="verify-bar-area" |
||||
:style="{ |
||||
width: setSize.imgWidth, |
||||
color: this.barAreaColor, |
||||
'border-color': this.barAreaBorderColor, |
||||
'line-height': this.barSize.height, |
||||
}" |
||||
> |
||||
<span class="verify-msg">{{ text }}</span> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script type="text/babel"> |
||||
/** |
||||
* VerifyPoints |
||||
* @description 点选 |
||||
* */ |
||||
import { resetSize, _code_chars, _code_color1, _code_color2 } from './../utils/util'; |
||||
import { aesEncrypt } from './../utils/ase'; |
||||
// import { reqGet, reqCheck } from './../api/index'; |
||||
|
||||
export default { |
||||
name: 'VerifyPoints', |
||||
props: { |
||||
// 弹出式pop,固定fixed |
||||
mode: { |
||||
type: String, |
||||
default: 'fixed', |
||||
}, |
||||
captchaType: { |
||||
type: String, |
||||
}, |
||||
// 间隔 |
||||
vSpace: { |
||||
type: Number, |
||||
default: 5, |
||||
}, |
||||
imgSize: { |
||||
type: Object, |
||||
default() { |
||||
return { |
||||
width: '310px', |
||||
height: '155px', |
||||
}; |
||||
}, |
||||
}, |
||||
barSize: { |
||||
type: Object, |
||||
default() { |
||||
return { |
||||
width: '310px', |
||||
height: '40px', |
||||
}; |
||||
}, |
||||
}, |
||||
defaultImg: { |
||||
type: String, |
||||
default: '', |
||||
}, |
||||
}, |
||||
data() { |
||||
return { |
||||
secretKey: '', // 后端返回的ase加密秘钥 |
||||
checkNum: 3, // 默认需要点击的字数 |
||||
fontPos: [], // 选中的坐标信息 |
||||
checkPosArr: [], // 用户点击的坐标 |
||||
num: 1, // 点击的记数 |
||||
pointBackImgBase: '', // 后端获取到的背景图片 |
||||
poinTextList: [], // 后端返回的点击字体顺序 |
||||
backToken: '', // 后端返回的token值 |
||||
setSize: { |
||||
imgHeight: 0, |
||||
imgWidth: 0, |
||||
barHeight: 0, |
||||
barWidth: 0, |
||||
}, |
||||
tempPoints: [], |
||||
text: '', |
||||
barAreaColor: undefined, |
||||
barAreaBorderColor: undefined, |
||||
showRefresh: true, |
||||
bindingClick: true, |
||||
}; |
||||
}, |
||||
computed: { |
||||
resetSize() { |
||||
return resetSize; |
||||
}, |
||||
}, |
||||
watch: { |
||||
// type变化则全面刷新 |
||||
type: { |
||||
immediate: true, |
||||
handler() { |
||||
this.init(); |
||||
}, |
||||
}, |
||||
}, |
||||
mounted() { |
||||
// 禁止拖拽 |
||||
this.$el.onselectstart = function () { |
||||
return false; |
||||
}; |
||||
}, |
||||
methods: { |
||||
init() { |
||||
// 加载页面 |
||||
this.fontPos.splice(0, this.fontPos.length); |
||||
this.checkPosArr.splice(0, this.checkPosArr.length); |
||||
this.num = 1; |
||||
this.getPictrue(); |
||||
this.$nextTick(() => { |
||||
this.setSize = this.resetSize(this); // 重新设置宽度高度 |
||||
this.$parent.$emit('ready', this); |
||||
}); |
||||
}, |
||||
canvasClick(e) { |
||||
this.checkPosArr.push(this.getMousePos(this.$refs.canvas, e)); |
||||
if (this.num == this.checkNum) { |
||||
this.num = this.createPoint(this.getMousePos(this.$refs.canvas, e)); |
||||
// 按比例转换坐标值 |
||||
this.checkPosArr = this.pointTransfrom(this.checkPosArr, this.setSize); |
||||
// 等创建坐标执行完 |
||||
setTimeout(() => { |
||||
// var flag = this.comparePos(this.fontPos, this.checkPosArr); |
||||
// 发送后端请求 |
||||
var captchaVerification = this.secretKey |
||||
? aesEncrypt(this.backToken + '---' + JSON.stringify(this.checkPosArr), this.secretKey) |
||||
: this.backToken + '---' + JSON.stringify(this.checkPosArr); |
||||
const data = { |
||||
captchaType: this.captchaType, |
||||
pointJson: this.secretKey |
||||
? aesEncrypt(JSON.stringify(this.checkPosArr), this.secretKey) |
||||
: JSON.stringify(this.checkPosArr), |
||||
token: this.backToken, |
||||
}; |
||||
reqCheck(data).then((res) => { |
||||
if (res.repCode == '0000') { |
||||
this.barAreaColor = '#4cae4c'; |
||||
this.barAreaBorderColor = '#5cb85c'; |
||||
this.text = '验证成功'; |
||||
this.bindingClick = false; |
||||
if (this.mode == 'pop') { |
||||
setTimeout(() => { |
||||
this.$parent.clickShow = false; |
||||
this.refresh(); |
||||
}, 1500); |
||||
} |
||||
this.$parent.$emit('success', { captchaVerification }); |
||||
} else { |
||||
this.$parent.$emit('error', this); |
||||
this.barAreaColor = '#d9534f'; |
||||
this.barAreaBorderColor = '#d9534f'; |
||||
this.text = '验证失败'; |
||||
setTimeout(() => { |
||||
this.refresh(); |
||||
}, 700); |
||||
} |
||||
}); |
||||
}, 400); |
||||
} |
||||
if (this.num < this.checkNum) { |
||||
this.num = this.createPoint(this.getMousePos(this.$refs.canvas, e)); |
||||
} |
||||
}, |
||||
|
||||
// 获取坐标 |
||||
getMousePos: function (obj, e) { |
||||
var x = e.offsetX; |
||||
var y = e.offsetY; |
||||
return { x, y }; |
||||
}, |
||||
// 创建坐标点 |
||||
createPoint: function (pos) { |
||||
this.tempPoints.push(Object.assign({}, pos)); |
||||
return ++this.num; |
||||
}, |
||||
refresh: function () { |
||||
this.tempPoints.splice(0, this.tempPoints.length); |
||||
this.barAreaColor = '#000'; |
||||
this.barAreaBorderColor = '#ddd'; |
||||
this.bindingClick = true; |
||||
this.fontPos.splice(0, this.fontPos.length); |
||||
this.checkPosArr.splice(0, this.checkPosArr.length); |
||||
this.num = 1; |
||||
this.getPictrue(); |
||||
this.text = '验证失败'; |
||||
this.showRefresh = true; |
||||
}, |
||||
|
||||
// 请求背景图片和验证图片 |
||||
getPictrue() { |
||||
const data = { |
||||
captchaType: this.captchaType, |
||||
clientUid: localStorage.getItem('point'), |
||||
ts: Date.now(), // 现在的时间戳 |
||||
}; |
||||
reqGet(data).then((res) => { |
||||
if (res.repCode == '0000') { |
||||
this.pointBackImgBase = res.repData.originalImageBase64; |
||||
this.backToken = res.repData.token; |
||||
this.secretKey = res.repData.secretKey; |
||||
this.poinTextList = res.repData.wordList; |
||||
this.text = '请依次点击【' + this.poinTextList.join(',') + '】'; |
||||
} else { |
||||
this.text = res.repMsg; |
||||
} |
||||
|
||||
// 判断接口请求次数是否失效 |
||||
if (res.repCode == '6201') { |
||||
this.pointBackImgBase = null; |
||||
} |
||||
}); |
||||
}, |
||||
// 坐标转换函数 |
||||
pointTransfrom(pointArr, imgSize) { |
||||
var newPointArr = pointArr.map((p) => { |
||||
const x = Math.round((310 * p.x) / parseInt(imgSize.imgWidth)); |
||||
const y = Math.round((155 * p.y) / parseInt(imgSize.imgHeight)); |
||||
return { x, y }; |
||||
}); |
||||
return newPointArr; |
||||
}, |
||||
}, |
||||
}; |
||||
</script> |
@ -0,0 +1,384 @@ |
||||
<template> |
||||
<div style="position: relative;"> |
||||
<div v-if="type === '2'" class="verify-img-out" :style="{ height: parseInt(setSize.imgHeight) + vSpace + 'px' }"> |
||||
<div v-loading="loading" class="verify-img-panel" :style="{ width: setSize.imgWidth, height: setSize.imgHeight }"> |
||||
<img |
||||
:src="backImgBase ? 'data:image/png;base64,' + backImgBase : defaultImg" |
||||
alt="" |
||||
style="width: 100%; height: 100%; display: block;" |
||||
/> |
||||
<div v-show="showRefresh" class="verify-refresh" @click="refresh"><i class="iconfont icon-refresh" /></div> |
||||
<transition name="tips"> |
||||
<span v-if="tipWords" class="verify-tips" :class="passFlag ? 'suc-bg' : 'err-bg'">{{ tipWords }}</span> |
||||
</transition> |
||||
</div> |
||||
</div> |
||||
<!-- 公共部分 --> |
||||
<div |
||||
class="verify-bar-area" |
||||
:style="{ width: setSize.imgWidth, height: barSize.height, 'line-height': barSize.height }" |
||||
> |
||||
<span class="verify-msg" v-text="text" /> |
||||
<div |
||||
class="verify-left-bar" |
||||
:style="{ |
||||
width: leftBarWidth !== undefined ? leftBarWidth : barSize.height, |
||||
height: barSize.height, |
||||
'border-color': leftBarBorderColor, |
||||
transaction: transitionWidth, |
||||
}" |
||||
> |
||||
<span class="verify-msg" v-text="finishText" /> |
||||
<div |
||||
class="verify-move-block" |
||||
:style="{ |
||||
width: barSize.height, |
||||
height: barSize.height, |
||||
'background-color': moveBlockBackgroundColor, |
||||
left: moveBlockLeft, |
||||
transition: transitionLeft, |
||||
}" |
||||
@touchstart="start" |
||||
@mousedown="start" |
||||
> |
||||
<i :class="['verify-icon iconfont', iconClass]" :style="{ color: iconColor }" /> |
||||
<div |
||||
v-if="type === '2'" |
||||
class="verify-sub-block" |
||||
:style="{ |
||||
width: Math.floor((parseInt(setSize.imgWidth) * 47) / 310) + 'px', |
||||
height: setSize.imgHeight, |
||||
top: '-' + (parseInt(setSize.imgHeight) + vSpace) + 'px', |
||||
'background-size': setSize.imgWidth + ' ' + setSize.imgHeight, |
||||
}" |
||||
> |
||||
<img |
||||
:src="'data:image/png;base64,' + blockBackImgBase" |
||||
alt="" |
||||
style="width: 100%; height: 100%; display: block;" |
||||
/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script type="text/babel"> |
||||
/** |
||||
* VerifySlide |
||||
* @description 滑块 |
||||
* */ |
||||
import { aesEncrypt } from './../utils/ase' |
||||
import { resetSize } from './../utils/util' |
||||
import { knowUserCaptchaApi, knowUserSmsCaptchaApi } from '~/server/userApi' |
||||
// "captchaType":"blockPuzzle", |
||||
export default { |
||||
name: 'VerifySlide', |
||||
props: { |
||||
captchaType: { |
||||
type: String, |
||||
}, |
||||
type: { |
||||
type: String, |
||||
default: '1', |
||||
}, |
||||
phone: { |
||||
type: String, |
||||
default: '', |
||||
}, |
||||
// 弹出式pop,固定fixed |
||||
mode: { |
||||
type: String, |
||||
default: 'fixed', |
||||
}, |
||||
vSpace: { |
||||
type: Number, |
||||
default: 5, |
||||
}, |
||||
explain: { |
||||
type: String, |
||||
default: '向右滑动完成验证', |
||||
}, |
||||
imgSize: { |
||||
type: Object, |
||||
default() { |
||||
return { |
||||
width: '310px', |
||||
height: '155px', |
||||
} |
||||
}, |
||||
}, |
||||
blockSize: { |
||||
type: Object, |
||||
default() { |
||||
return { |
||||
width: '50px', |
||||
height: '50px', |
||||
} |
||||
}, |
||||
}, |
||||
barSize: { |
||||
type: Object, |
||||
default() { |
||||
return { |
||||
width: '310px', |
||||
height: '40px', |
||||
} |
||||
}, |
||||
}, |
||||
defaultImg: { |
||||
type: String, |
||||
default: '', |
||||
}, |
||||
}, |
||||
data() { |
||||
return { |
||||
secretKey: '', // 后端返回的加密秘钥 字段 |
||||
passFlag: '', // 是否通过的标识 |
||||
backImgBase: '', // 验证码背景图片 |
||||
blockBackImgBase: '', // 验证滑块的背景图片 |
||||
backToken: '', // 后端返回的唯一token值 |
||||
startMoveTime: '', // 移动开始的时间 |
||||
endMovetime: '', // 移动结束的时间 |
||||
tipsBackColor: '', // 提示词的背景颜色 |
||||
tipWords: '', |
||||
text: '', |
||||
finishText: '', |
||||
setSize: { |
||||
imgHeight: 0, |
||||
imgWidth: 0, |
||||
barHeight: 0, |
||||
barWidth: 0, |
||||
}, |
||||
top: 0, |
||||
left: 0, |
||||
moveBlockLeft: undefined, |
||||
leftBarWidth: undefined, |
||||
// 移动中样式 |
||||
moveBlockBackgroundColor: undefined, |
||||
leftBarBorderColor: '#ddd', |
||||
iconColor: undefined, |
||||
iconClass: 'icon-right', |
||||
status: false, // 鼠标状态 |
||||
isEnd: false, // 是够验证完成 |
||||
showRefresh: true, |
||||
transitionLeft: '', |
||||
transitionWidth: '', |
||||
loading: false //加载效果 |
||||
} |
||||
}, |
||||
computed: { |
||||
barArea() { |
||||
return this.$el.querySelector('.verify-bar-area') |
||||
}, |
||||
resetSize() { |
||||
return resetSize |
||||
}, |
||||
}, |
||||
watch: { |
||||
// type变化则全面刷新 |
||||
type: { |
||||
immediate: true, |
||||
handler() { |
||||
this.init() |
||||
}, |
||||
}, |
||||
}, |
||||
mounted() { |
||||
// 禁止拖拽 |
||||
this.$el.onselectstart = function () { |
||||
return false |
||||
} |
||||
}, |
||||
methods: { |
||||
init() { |
||||
this.text = this.explain |
||||
this.getPictrue() |
||||
this.$nextTick(() => { |
||||
const setSize = this.resetSize(this) // 重新设置宽度高度 |
||||
this.setSize.imgWidth = setSize.imgWidth |
||||
this.setSize.imgHeight = setSize.imgHeight |
||||
this.setSize.barHeight = setSize.barHeight |
||||
this.setSize.barWidth = setSize.barWidth |
||||
this.$parent.$emit('ready', this) |
||||
}) |
||||
var _this = this |
||||
window.removeEventListener('touchmove', function (e) { |
||||
_this.move(e) |
||||
}) |
||||
window.removeEventListener('mousemove', function (e) { |
||||
_this.move(e) |
||||
}) |
||||
|
||||
// 鼠标松开 |
||||
window.removeEventListener('touchend', function () { |
||||
_this.end() |
||||
}) |
||||
window.removeEventListener('mouseup', function () { |
||||
_this.end() |
||||
}) |
||||
|
||||
window.addEventListener('touchmove', function (e) { |
||||
_this.move(e) |
||||
}) |
||||
window.addEventListener('mousemove', function (e) { |
||||
_this.move(e) |
||||
}) |
||||
|
||||
// 鼠标松开 |
||||
window.addEventListener('touchend', function () { |
||||
_this.end() |
||||
}) |
||||
window.addEventListener('mouseup', function () { |
||||
_this.end() |
||||
}) |
||||
}, |
||||
|
||||
// 鼠标按下 |
||||
start: function (e) { |
||||
e = e || window.event |
||||
if (!e.touches) { |
||||
// 兼容PC端 |
||||
var x = e.clientX |
||||
} else { |
||||
// 兼容移动端 |
||||
var x = e.touches[0].pageX |
||||
} |
||||
this.startLeft = Math.floor(x - this.barArea.getBoundingClientRect().left) |
||||
this.startMoveTime = +new Date() // 开始滑动的时间 |
||||
if (this.isEnd == false) { |
||||
this.text = '' |
||||
this.moveBlockBackgroundColor = '#337ab7' |
||||
this.leftBarBorderColor = '#337AB7' |
||||
this.iconColor = '#fff' |
||||
e.stopPropagation() |
||||
this.status = true |
||||
} |
||||
}, |
||||
// 鼠标移动 |
||||
move: function (e) { |
||||
e = e || window.event |
||||
if (this.status && this.isEnd == false) { |
||||
if (!e.touches) { |
||||
// 兼容PC端 |
||||
var x = e.clientX |
||||
} else { |
||||
// 兼容移动端 |
||||
var x = e.touches[0].pageX |
||||
} |
||||
var bar_area_left = this.barArea.getBoundingClientRect().left |
||||
var move_block_left = x - bar_area_left // 小方块相对于父元素的left值 |
||||
if (move_block_left >= this.barArea.offsetWidth - parseInt(parseInt(this.blockSize.width) / 2) - 2) { |
||||
move_block_left = this.barArea.offsetWidth - parseInt(parseInt(this.blockSize.width) / 2) - 2 |
||||
} |
||||
if (move_block_left <= 0) { |
||||
move_block_left = parseInt(parseInt(this.blockSize.width) / 2) |
||||
} |
||||
// 拖动后小方块的left值 |
||||
this.moveBlockLeft = move_block_left - this.startLeft + 'px' |
||||
this.leftBarWidth = move_block_left - this.startLeft + 'px' |
||||
} |
||||
}, |
||||
|
||||
// 鼠标松开 |
||||
end() { |
||||
this.endMovetime = +new Date() |
||||
var _this = this |
||||
// 判断是否重合 |
||||
if (this.status && this.isEnd == false) { |
||||
var moveLeftDistance = parseInt((this.moveBlockLeft || '').replace('px', '')) |
||||
moveLeftDistance = (moveLeftDistance * 310) / parseInt(this.setSize.imgWidth) |
||||
const params = { |
||||
captchaType: this.captchaType, |
||||
pointJson: this.secretKey |
||||
? aesEncrypt(JSON.stringify({ x: moveLeftDistance, y: 5.0 }), this.secretKey) |
||||
: JSON.stringify({ x: moveLeftDistance, y: 5.0 }), |
||||
token: this.backToken, |
||||
} |
||||
|
||||
knowUserSmsCaptchaApi(params).then((res) => { |
||||
if (res.repCode == '0000') { |
||||
this.moveBlockBackgroundColor = '#5cb85c'; |
||||
this.leftBarBorderColor = '#5cb85c'; |
||||
this.iconColor = '#fff'; |
||||
this.iconClass = 'icon-check'; |
||||
this.showRefresh = false; |
||||
this.isEnd = true; |
||||
if (this.mode == 'pop') { |
||||
setTimeout(() => { |
||||
this.$parent.clickShow = false; |
||||
this.refresh(); |
||||
}, 1500); |
||||
} |
||||
this.passFlag = true; |
||||
this.tipWords = `${((this.endMovetime - this.startMoveTime) / 1000).toFixed(2)}s验证成功`; |
||||
var captchaVerification = this.secretKey |
||||
? aesEncrypt(this.backToken + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 }), this.secretKey) |
||||
: this.backToken + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 }); |
||||
setTimeout(() => { |
||||
this.tipWords = ''; |
||||
this.$emit('success', { captchaVerification }); |
||||
}, 1000); |
||||
} else { |
||||
this.moveBlockBackgroundColor = '#d9534f'; |
||||
this.leftBarBorderColor = '#d9534f'; |
||||
this.iconColor = '#fff'; |
||||
this.iconClass = 'icon-close'; |
||||
this.passFlag = false; |
||||
setTimeout(function () { |
||||
_this.refresh(); |
||||
}, 1000); |
||||
this.$emit('error', this); |
||||
this.tipWords = '验证失败'; |
||||
setTimeout(() => { |
||||
this.tipWords = ''; |
||||
}, 1000); |
||||
} |
||||
}) |
||||
} |
||||
}, |
||||
|
||||
refresh: function () { |
||||
this.showRefresh = true |
||||
this.finishText = '' |
||||
this.transitionLeft = 'left .3s' |
||||
this.moveBlockLeft = undefined |
||||
this.leftBarWidth = undefined |
||||
this.transitionWidth = 'width .3s' |
||||
this.leftBarBorderColor = '#ddd' |
||||
this.moveBlockBackgroundColor = '#fff' |
||||
this.iconColor = '#000' |
||||
this.iconClass = 'icon-right' |
||||
this.isEnd = false |
||||
|
||||
this.getPictrue() |
||||
setTimeout(() => { |
||||
this.status = false |
||||
this.transitionWidth = '' |
||||
this.transitionLeft = '' |
||||
this.text = this.explain |
||||
}, 300) |
||||
}, |
||||
|
||||
// 请求背景图片和验证图片 |
||||
async getPictrue() { |
||||
const params = { |
||||
captchaType: this.captchaType, |
||||
clientUid: localStorage.getItem('slider'), |
||||
ts: Date.now(), // 现在的时间戳 |
||||
} |
||||
this.loading = true |
||||
const res = await knowUserCaptchaApi(params) |
||||
if (res.repCode == '0000') { |
||||
this.backImgBase = res.repData.originalImageBase64; |
||||
this.blockBackImgBase = res.repData.jigsawImageBase64 |
||||
this.backToken = res.repData.token; |
||||
this.secretKey = res.repData.secretKey; |
||||
} else { |
||||
this.text = res.repMsg; |
||||
} |
||||
this.loading = false |
||||
}, |
||||
}, |
||||
} |
||||
</script> |
@ -0,0 +1,11 @@ |
||||
import CryptoJS from 'crypto-js' |
||||
/** |
||||
* @word 要加密的内容 |
||||
* @keyWord String 服务器随机返回的关键字 |
||||
* */ |
||||
export function aesEncrypt(word, keyWord = 'XwKsGlMcdPMEhR1B') { |
||||
var key = CryptoJS.enc.Utf8.parse(keyWord) |
||||
var srcs = CryptoJS.enc.Utf8.parse(word) |
||||
var encrypted = CryptoJS.AES.encrypt(srcs, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }) |
||||
return encrypted.toString() |
||||
} |
@ -0,0 +1,89 @@ |
||||
import { ElMessage, ElMessageBox, ElNotification, ElLoading, type ElMessageBoxOptions } from 'element-plus' |
||||
import type { LoadingInstance } from 'element-plus/es/components/loading/src/loading' |
||||
|
||||
export class Tips { |
||||
private loadingInstance: LoadingInstance | null = null |
||||
static instance: Tips | null = null |
||||
static getInstance() { |
||||
return this.instance ?? (this.instance = new Tips()) |
||||
} |
||||
// 消息提示
|
||||
msg(msg: string) { |
||||
ElMessage.info(msg) |
||||
} |
||||
// 错误消息
|
||||
msgError(msg: string) { |
||||
ElMessage.error(msg) |
||||
} |
||||
// 成功消息
|
||||
msgSuccess(msg: string) { |
||||
ElMessage.success(msg) |
||||
} |
||||
// 警告消息
|
||||
msgWarning(msg: string) { |
||||
ElMessage.warning(msg) |
||||
} |
||||
// 弹出提示
|
||||
alert(msg: string) { |
||||
ElMessageBox.alert(msg, '系统提示') |
||||
} |
||||
// 错误提示
|
||||
alertError(msg: string) { |
||||
ElMessageBox.alert(msg, '系统提示', { type: 'error' }) |
||||
} |
||||
// 成功提示
|
||||
alertSuccess(msg: string) { |
||||
ElMessageBox.alert(msg, '系统提示', { type: 'success' }) |
||||
} |
||||
// 警告提示
|
||||
alertWarning(msg: string) { |
||||
ElMessageBox.alert(msg, '系统提示', { type: 'warning' }) |
||||
} |
||||
// 通知提示
|
||||
notify(msg: string) { |
||||
ElNotification.info(msg) |
||||
} |
||||
// 错误通知
|
||||
notifyError(msg: string) { |
||||
ElNotification.error(msg) |
||||
} |
||||
// 成功通知
|
||||
notifySuccess(msg: string) { |
||||
ElNotification.success(msg) |
||||
} |
||||
// 警告通知
|
||||
notifyWarning(msg: string) { |
||||
ElNotification.warning(msg) |
||||
} |
||||
// 确认窗体
|
||||
confirm(msg: string) { |
||||
return ElMessageBox.confirm(msg, '温馨提示', { |
||||
confirmButtonText: '确定', |
||||
cancelButtonText: '取消', |
||||
type: 'warning', |
||||
}) |
||||
} |
||||
// 提交内容
|
||||
prompt(content: string, title: string, options?: ElMessageBoxOptions) { |
||||
return ElMessageBox.prompt(content, title, { |
||||
confirmButtonText: '确定', |
||||
cancelButtonText: '取消', |
||||
...options, |
||||
}) |
||||
} |
||||
// 打开全局loading
|
||||
loading(msg: string) { |
||||
this.loadingInstance = ElLoading.service({ |
||||
lock: true, |
||||
text: msg, |
||||
}) |
||||
} |
||||
// 关闭全局loading
|
||||
closeLoading() { |
||||
this.loadingInstance?.close() |
||||
} |
||||
} |
||||
|
||||
const tips = Tips.getInstance() |
||||
|
||||
export default tips |
@ -0,0 +1,36 @@ |
||||
export function resetSize(vm) { |
||||
var img_width, img_height, bar_width, bar_height // 图片的宽度、高度,移动条的宽度、高度
|
||||
|
||||
var parentWidth = vm.$el.parentNode.offsetWidth || window.offsetWidth |
||||
var parentHeight = vm.$el.parentNode.offsetHeight || window.offsetHeight |
||||
|
||||
if (vm.imgSize.width.indexOf('%') != -1) { |
||||
img_width = parseInt(this.imgSize.width) / 100 * parentWidth + 'px' |
||||
} else { |
||||
img_width = this.imgSize.width |
||||
} |
||||
|
||||
if (vm.imgSize.height.indexOf('%') != -1) { |
||||
img_height = parseInt(this.imgSize.height) / 100 * parentHeight + 'px' |
||||
} else { |
||||
img_height = this.imgSize.height |
||||
} |
||||
|
||||
if (vm.barSize.width.indexOf('%') != -1) { |
||||
bar_width = parseInt(this.barSize.width) / 100 * parentWidth + 'px' |
||||
} else { |
||||
bar_width = this.barSize.width |
||||
} |
||||
|
||||
if (vm.barSize.height.indexOf('%') != -1) { |
||||
bar_height = parseInt(this.barSize.height) / 100 * parentHeight + 'px' |
||||
} else { |
||||
bar_height = this.barSize.height |
||||
} |
||||
|
||||
return { imgWidth: img_width, imgHeight: img_height, barWidth: bar_width, barHeight: bar_height } |
||||
} |
||||
|
||||
export const _code_chars = [1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] |
||||
export const _code_color1 = ['#fffff0', '#f0ffff', '#f0fff0', '#fff0f0'] |
||||
export const _code_color2 = ['#FF0033', '#006699', '#993366', '#FF9900', '#66CC66', '#FF33CC'] |
@ -0,0 +1,37 @@ |
||||
import feedback from "~/utils/feedback"; |
||||
|
||||
export function useCopy() { |
||||
const handleCopy = (text:string) =>{ |
||||
if (navigator.clipboard && window.isSecureContext) { |
||||
const textToCopy = text |
||||
navigator.clipboard |
||||
.writeText(textToCopy) |
||||
.then(() => { |
||||
feedback.msgSuccess('复制成功') |
||||
}) |
||||
.catch((error) => { |
||||
console.error('复制文本时出错:', error) |
||||
}) |
||||
} else { |
||||
// 创建text area
|
||||
const textArea = document.createElement('textarea') |
||||
textArea.value = text |
||||
// 使text area不在viewport,同时设置不可见
|
||||
document.body.appendChild(textArea) |
||||
textArea.focus() |
||||
textArea.select() |
||||
return new Promise((resolve, reject) => { |
||||
// 执行复制命令并移除文本框
|
||||
document.execCommand('copy') ? resolve() : reject(new Error('出错了')) |
||||
textArea.remove() |
||||
}).then(() => { |
||||
feedback.msgSuccess('复制成功') |
||||
}, () => { |
||||
feedback.msgError('复制失败') |
||||
}) |
||||
} |
||||
} |
||||
return{ |
||||
handleCopy |
||||
} |
||||
} |
@ -0,0 +1,16 @@ |
||||
export default function useDialog(initValue: boolean = false) { |
||||
const bool: globalThis.Ref<boolean> = ref(initValue) |
||||
const DialogOpen = () => { |
||||
bool.value = true |
||||
} |
||||
const DialogClose = () => { |
||||
bool.value = false |
||||
} |
||||
|
||||
return { |
||||
bool, |
||||
DialogOpen, |
||||
DialogClose, |
||||
// /handleDialogOpen,
|
||||
} |
||||
} |
@ -0,0 +1,48 @@ |
||||
import {couponReceiveApi, getMerCollectAddApi, getMerCollectCancelApi} from '~/server/merchantApi' |
||||
import {ref} from "vue"; |
||||
|
||||
export default function useMerchant() { |
||||
//店铺关注
|
||||
const onfollowMerchant = (merId: number) => { |
||||
return new Promise((resolve, reject) => { |
||||
getMerCollectAddApi(merId).then(() => { |
||||
feedback.msgSuccess('关注店铺成功') |
||||
//linkNavigateTo(`/users/order_list`, { type: 1 })
|
||||
return resolve() |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
//店铺取消关注
|
||||
const onUnfollowMerchant = (merId: number) => { |
||||
return new Promise((resolve, reject) => { |
||||
feedback.confirm('确认取消关注吗?').then(async () => { |
||||
await getMerCollectCancelApi(merId).then(() => { |
||||
//linkNavigateTo(`/users/order_list`, { type: 1 })
|
||||
return resolve() |
||||
}) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
//领取优惠券
|
||||
const loading = ref<boolean>(false) |
||||
const onReceiveCoupon = (couponId: number) => { |
||||
loading.value = true |
||||
return new Promise(async(resolve, reject) => { |
||||
try { |
||||
await couponReceiveApi(couponId) |
||||
feedback.msgSuccess('领取成功') |
||||
return resolve() |
||||
loading.value = false |
||||
} catch (e) { |
||||
loading.value = false |
||||
} |
||||
}) |
||||
} |
||||
return { |
||||
onfollowMerchant, |
||||
onUnfollowMerchant, |
||||
onReceiveCoupon |
||||
} |
||||
} |