首次提交
@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: ['@commitlint/config-conventional'],
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
# API 和 HTTP 请求规范
|
||||||
|
|
||||||
|
## HTTP 请求封装
|
||||||
|
- 可以使用 `简单http` 或者 `alova` 或者 `@tanstack/vue-query` 进行请求管理
|
||||||
|
- HTTP 配置在 [src/http/](mdc:src/http/) 目录下
|
||||||
|
- `简单http` - [src/http/http.ts](mdc:src/http/http.ts)
|
||||||
|
- `alova` - [src/http/alova.ts](mdc:src/http/alova.ts)
|
||||||
|
- `vue-query` - [src/http/vue-query.ts](mdc:src/http/vue-query.ts)
|
||||||
|
- 请求拦截器在 [src/http/interceptor.ts](mdc:src/http/interceptor.ts)
|
||||||
|
- 支持请求重试、缓存、错误处理
|
||||||
|
|
||||||
|
## API 接口规范
|
||||||
|
- API 接口定义在 [src/api/](mdc:src/api/) 目录下
|
||||||
|
- 按功能模块组织 API 文件
|
||||||
|
- 使用 TypeScript 定义请求和响应类型
|
||||||
|
- 支持 `简单http`、`alova` 和 `vue-query` 三种请求方式
|
||||||
|
|
||||||
|
|
||||||
|
## 示例代码结构
|
||||||
|
```typescript
|
||||||
|
// API 接口定义
|
||||||
|
export interface LoginParams {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoginResponse {
|
||||||
|
token: string
|
||||||
|
userInfo: UserInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// alova 方式
|
||||||
|
export const login = (params: LoginParams) =>
|
||||||
|
http.Post<LoginResponse>('/api/login', params)
|
||||||
|
|
||||||
|
// vue-query 方式
|
||||||
|
export const useLogin = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (params: LoginParams) =>
|
||||||
|
http.post<LoginResponse>('/api/login', params)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 错误处理
|
||||||
|
- 统一错误处理在拦截器中配置
|
||||||
|
- 支持网络错误、业务错误、认证错误等
|
||||||
|
- 自动处理 token 过期和刷新
|
||||||
|
---
|
||||||
|
globs: src/api/*.ts,src/http/*.ts
|
||||||
|
---
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
# 开发工作流程
|
||||||
|
|
||||||
|
## 项目启动
|
||||||
|
1. 安装依赖:`pnpm install`
|
||||||
|
2. 开发环境:
|
||||||
|
- H5: `pnpm dev` 或 `pnpm dev:h5`
|
||||||
|
- 微信小程序: `pnpm dev:mp`
|
||||||
|
- 支付宝小程序: `pnpm dev:mp-alipay`
|
||||||
|
- APP: `pnpm dev:app`
|
||||||
|
|
||||||
|
## 代码规范
|
||||||
|
- 使用 ESLint 进行代码检查:`pnpm lint`
|
||||||
|
- 自动修复代码格式:`pnpm lint:fix`
|
||||||
|
- 使用 eslint 格式化代码
|
||||||
|
- 遵循 TypeScript 严格模式
|
||||||
|
|
||||||
|
## 构建和部署
|
||||||
|
- H5 构建:`pnpm build:h5`
|
||||||
|
- 微信小程序构建:`pnpm build:mp`
|
||||||
|
- 支付宝小程序构建:`pnpm build:mp-alipay`
|
||||||
|
- APP 构建:`pnpm build:app`
|
||||||
|
- 类型检查:`pnpm type-check`
|
||||||
|
|
||||||
|
## 开发工具
|
||||||
|
- 推荐使用 VSCode 编辑器
|
||||||
|
- 安装 Vue 和 TypeScript 相关插件
|
||||||
|
- 使用 uni-app 开发者工具调试小程序
|
||||||
|
- 使用 HBuilderX 调试 APP
|
||||||
|
|
||||||
|
## 调试技巧
|
||||||
|
- 使用 console.log 和 uni.showToast 调试
|
||||||
|
- 利用 Vue DevTools 调试组件状态
|
||||||
|
- 使用网络面板调试 API 请求
|
||||||
|
- 平台差异测试和兼容性检查
|
||||||
|
|
||||||
|
## 性能优化
|
||||||
|
- 使用懒加载和代码分割
|
||||||
|
- 优化图片和静态资源
|
||||||
|
- 减少不必要的重渲染
|
||||||
|
- 合理使用缓存策略
|
||||||
|
---
|
||||||
|
description: 开发工作流程和最佳实践指南
|
||||||
|
---
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
# 样式和 CSS 开发规范
|
||||||
|
|
||||||
|
## UnoCSS 原子化 CSS
|
||||||
|
- 项目使用 UnoCSS 作为原子化 CSS 框架
|
||||||
|
- 配置在 [uno.config.ts](mdc:uno.config.ts)
|
||||||
|
- 支持预设和自定义规则
|
||||||
|
- 优先使用原子化类名,减少自定义 CSS
|
||||||
|
|
||||||
|
## SCSS 规范
|
||||||
|
- 使用 SCSS 预处理器
|
||||||
|
- 样式文件使用 `lang="scss"` 和 `scoped` 属性
|
||||||
|
- 遵循 BEM 命名规范
|
||||||
|
- 使用变量和混入提高复用性
|
||||||
|
|
||||||
|
## 样式组织
|
||||||
|
- 全局样式在 [src/style/](mdc:src/style/) 目录下
|
||||||
|
- 组件样式使用 scoped 作用域
|
||||||
|
- 图标字体在 [src/style/iconfont.css](mdc:src/style/iconfont.css)
|
||||||
|
- 主题变量在 [src/uni_modules/uni-scss/](mdc:src/uni_modules/uni-scss/) 目录下
|
||||||
|
|
||||||
|
## 示例代码结构
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<view class="container flex flex-col items-center p-4">
|
||||||
|
<text class="title text-lg font-bold mb-2">标题</text>
|
||||||
|
<view class="content bg-gray-100 rounded-lg p-3">
|
||||||
|
<!-- 内容 -->
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
min-height: 100vh;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
## 响应式设计
|
||||||
|
- 使用 rpx 单位适配不同屏幕
|
||||||
|
- 支持横屏和竖屏布局
|
||||||
|
- 使用 flexbox 和 grid 布局
|
||||||
|
- 考虑不同平台的样式差异
|
||||||
|
---
|
||||||
|
globs: *.vue,*.scss,*.css
|
||||||
|
---
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
# uni-app 开发规范
|
||||||
|
|
||||||
|
## 页面开发
|
||||||
|
- 页面文件放在 [src/pages/](mdc:src/pages/) 目录下
|
||||||
|
- 使用约定式路由,文件名即路由路径
|
||||||
|
- 页面配置在仅需要在 宏`definePage` 中配置标题等内容即可,会自动生成到 `pages.json` 中
|
||||||
|
- definePage的顺序在最上面
|
||||||
|
|
||||||
|
## 组件开发
|
||||||
|
- 组件文件放在 [src/components/](mdc:src/components/) 或者 [src/pages/xx/components/](mdc:src/pages/xx/components/) 目录下
|
||||||
|
- 使用 uni-app 内置组件和第三方组件库
|
||||||
|
- 支持 wot-ui\uview-pro\uv-ui\sard-ui\uview-plus 等多种第三方组件库 和 z-paging 组件
|
||||||
|
- 自定义组件遵循 uni-app 组件规范
|
||||||
|
|
||||||
|
## 平台适配
|
||||||
|
- 使用条件编译处理平台差异
|
||||||
|
- 支持 H5、小程序、APP 多平台
|
||||||
|
- 注意各平台的 API 差异
|
||||||
|
- 使用 uni.xxx API 替代原生 API
|
||||||
|
|
||||||
|
## 示例代码结构
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
// #ifdef H5
|
||||||
|
import { h5Api } from '@/utils/h5'
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
import { mpApi } from '@/utils/mp'
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
// #ifdef H5
|
||||||
|
h5Api.showToast('H5 平台')
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
mpApi.showToast('微信小程序')
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="page">
|
||||||
|
<!-- uni-app 组件 -->
|
||||||
|
<button @click="handleClick">点击</button>
|
||||||
|
|
||||||
|
<!-- 条件渲染 -->
|
||||||
|
<!-- #ifdef H5 -->
|
||||||
|
<view>H5 特有内容</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 生命周期
|
||||||
|
- 使用 uni-app 页面生命周期
|
||||||
|
- onLoad、onShow、onReady、onHide、onUnload
|
||||||
|
- 组件生命周期遵循 Vue3 规范
|
||||||
|
- 注意页面栈和导航管理
|
||||||
|
---
|
||||||
|
globs: src/pages/*.vue,src/components/*.vue
|
||||||
|
---
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# registry = https://registry.npmjs.org
|
||||||
|
registry = https://registry.npmmirror.com
|
||||||
|
|
||||||
|
strict-peer-dependencies=false
|
||||||
|
auto-install-peers=true
|
||||||
|
shamefully-hoist=true
|
||||||
|
ignore-workspace-root-check=true
|
||||||
|
install-workspace-root=true
|
||||||
|
node-options=--max-old-space-size=8192
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"vue.volar",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"antfu.unocss",
|
||||||
|
"antfu.iconify",
|
||||||
|
"evils.uniapp-vscode",
|
||||||
|
"uni-helper.uni-helper-vscode",
|
||||||
|
"uni-helper.uni-app-schemas-vscode",
|
||||||
|
"uni-helper.uni-highlight-vscode",
|
||||||
|
"uni-helper.uni-ui-snippets-vscode",
|
||||||
|
"uni-helper.uni-app-snippets-vscode",
|
||||||
|
"streetsidesoftware.code-spell-checker"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,100 @@
|
|||||||
|
{
|
||||||
|
// 配置语言的文件关联
|
||||||
|
"files.associations": {
|
||||||
|
"pages.json": "jsonc", // pages.json 可以写注释
|
||||||
|
"manifest.json": "jsonc" // manifest.json 可以写注释
|
||||||
|
},
|
||||||
|
|
||||||
|
"stylelint.enable": false, // 禁用 stylelint
|
||||||
|
"css.validate": false, // 禁用 CSS 内置验证
|
||||||
|
"scss.validate": false, // 禁用 SCSS 内置验证
|
||||||
|
"less.validate": false, // 禁用 LESS 内置验证
|
||||||
|
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
|
"explorer.fileNesting.enabled": true,
|
||||||
|
"explorer.fileNesting.expand": false,
|
||||||
|
"explorer.fileNesting.patterns": {
|
||||||
|
"README.md": "index.html,favicon.ico,robots.txt,CHANGELOG.md",
|
||||||
|
"docker.md": "Dockerfile,docker*.md,nginx*,.dockerignore",
|
||||||
|
"pages.config.ts": "manifest.config.ts,openapi-ts-request.config.ts",
|
||||||
|
"package.json": "tsconfig.json,pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitattributes,.gitignore,.gitpod.yml,CNAME,.npmrc,.browserslistrc",
|
||||||
|
"eslint.config.mjs": ".commitlintrc.*,.prettier*,.editorconfig,.commitlint.cjs,.eslint*"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Disable the default formatter, use eslint instead
|
||||||
|
"prettier.enable": false,
|
||||||
|
"editor.formatOnSave": false,
|
||||||
|
|
||||||
|
// Auto fix
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": "explicit",
|
||||||
|
"source.organizeImports": "never"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Silent the stylistic rules in you IDE, but still auto fix them
|
||||||
|
"eslint.rules.customizations": [
|
||||||
|
{ "rule": "style/*", "severity": "off", "fixable": true },
|
||||||
|
{ "rule": "format/*", "severity": "off", "fixable": true },
|
||||||
|
{ "rule": "*-indent", "severity": "off", "fixable": true },
|
||||||
|
{ "rule": "*-spacing", "severity": "off", "fixable": true },
|
||||||
|
{ "rule": "*-spaces", "severity": "off", "fixable": true },
|
||||||
|
{ "rule": "*-order", "severity": "off", "fixable": true },
|
||||||
|
{ "rule": "*-dangle", "severity": "off", "fixable": true },
|
||||||
|
{ "rule": "*-newline", "severity": "off", "fixable": true },
|
||||||
|
{ "rule": "*quotes", "severity": "off", "fixable": true },
|
||||||
|
{ "rule": "*semi", "severity": "off", "fixable": true }
|
||||||
|
],
|
||||||
|
|
||||||
|
// Enable eslint for all supported languages
|
||||||
|
"eslint.validate": [
|
||||||
|
"javascript",
|
||||||
|
"javascriptreact",
|
||||||
|
"typescript",
|
||||||
|
"typescriptreact",
|
||||||
|
"vue",
|
||||||
|
"html",
|
||||||
|
"markdown",
|
||||||
|
"json",
|
||||||
|
"jsonc",
|
||||||
|
"yaml",
|
||||||
|
"toml",
|
||||||
|
"xml",
|
||||||
|
"gql",
|
||||||
|
"graphql",
|
||||||
|
"astro",
|
||||||
|
"svelte",
|
||||||
|
"css",
|
||||||
|
"less",
|
||||||
|
"scss",
|
||||||
|
"pcss",
|
||||||
|
"postcss"
|
||||||
|
],
|
||||||
|
"cSpell.words": [
|
||||||
|
"alova",
|
||||||
|
"Aplipay",
|
||||||
|
"attributify",
|
||||||
|
"chooseavatar",
|
||||||
|
"climblee",
|
||||||
|
"commitlint",
|
||||||
|
"dcloudio",
|
||||||
|
"iconfont",
|
||||||
|
"oxlint",
|
||||||
|
"qrcode",
|
||||||
|
"refresherrefresh",
|
||||||
|
"scrolltolower",
|
||||||
|
"tabbar",
|
||||||
|
"Toutiao",
|
||||||
|
"uniapp",
|
||||||
|
"unibest",
|
||||||
|
"unocss",
|
||||||
|
"uview",
|
||||||
|
"uvui",
|
||||||
|
"Wechat",
|
||||||
|
"WechatMiniprogram",
|
||||||
|
"Weixin"
|
||||||
|
],
|
||||||
|
"i18n-ally.localesPaths": [
|
||||||
|
"src/locale"
|
||||||
|
],
|
||||||
|
"i18n-ally.keystyle": "nested"
|
||||||
|
}
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
// Place your unibest 工作区 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
|
||||||
|
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
|
||||||
|
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
|
||||||
|
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||||
|
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
|
||||||
|
// Placeholders with the same ids are connected.
|
||||||
|
// Example:
|
||||||
|
// "Print to console": {
|
||||||
|
// "scope": "javascript,typescript",
|
||||||
|
// "prefix": "log",
|
||||||
|
// "body": [
|
||||||
|
// "console.log('$1');",
|
||||||
|
// "$2"
|
||||||
|
// ],
|
||||||
|
// "description": "Log output to console"
|
||||||
|
// }
|
||||||
|
"Print unibest Vue3 SFC": {
|
||||||
|
"scope": "vue",
|
||||||
|
"prefix": "v3",
|
||||||
|
"body": [
|
||||||
|
"<script lang=\"ts\" setup>",
|
||||||
|
"definePage({",
|
||||||
|
" style: {",
|
||||||
|
" navigationBarTitleText: '$1',",
|
||||||
|
" },",
|
||||||
|
"})",
|
||||||
|
"defineOptions({",
|
||||||
|
" name: '$2',",
|
||||||
|
" options: { ",
|
||||||
|
" virtualHost: true,",
|
||||||
|
" },",
|
||||||
|
"})",
|
||||||
|
"</script>\n",
|
||||||
|
"<template>",
|
||||||
|
" <view class=\"\">$3</view>",
|
||||||
|
"</template>\n",
|
||||||
|
"<style lang=\"scss\" scoped>",
|
||||||
|
"//$4",
|
||||||
|
"</style>\n",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"Print unibest style": {
|
||||||
|
"scope": "vue",
|
||||||
|
"prefix": "st",
|
||||||
|
"body": [
|
||||||
|
"<style lang=\"scss\" scoped>",
|
||||||
|
"//",
|
||||||
|
"</style>\n"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"Print unibest script with definePage": {
|
||||||
|
"scope": "vue",
|
||||||
|
"prefix": "sc",
|
||||||
|
"body": [
|
||||||
|
"<script lang=\"ts\" setup>",
|
||||||
|
"definePage({",
|
||||||
|
" style: {",
|
||||||
|
" navigationBarTitleText: '$1',",
|
||||||
|
" },",
|
||||||
|
"})",
|
||||||
|
"defineOptions({",
|
||||||
|
" name: '$2',",
|
||||||
|
" options: { ",
|
||||||
|
" virtualHost: true,",
|
||||||
|
" },",
|
||||||
|
"})",
|
||||||
|
"</script>\n"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"Print unibest template": {
|
||||||
|
"scope": "vue",
|
||||||
|
"prefix": "te",
|
||||||
|
"body": [
|
||||||
|
"<template>",
|
||||||
|
" <view class=\"\">$1</view>",
|
||||||
|
"</template>\n"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 菲鸽
|
||||||
|
|
||||||
|
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,9 @@
|
|||||||
|
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||||
|
NODE_ENV = 'development'
|
||||||
|
# 是否去除console 和 debugger
|
||||||
|
VITE_DELETE_CONSOLE = false
|
||||||
|
# 是否开启sourcemap
|
||||||
|
VITE_SHOW_SOURCEMAP = false
|
||||||
|
|
||||||
|
# 后台请求地址
|
||||||
|
# VITE_SERVER_BASEURL = 'https://dev.xxx.com'
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||||
|
NODE_ENV = 'production'
|
||||||
|
# 是否去除console 和 debugger
|
||||||
|
VITE_DELETE_CONSOLE = true
|
||||||
|
# 是否开启sourcemap
|
||||||
|
VITE_SHOW_SOURCEMAP = false
|
||||||
|
|
||||||
|
# 后台请求地址
|
||||||
|
# VITE_SERVER_BASEURL = 'https://prod.xxx.com'
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||||
|
NODE_ENV = 'development'
|
||||||
|
# 是否去除console 和 debugger
|
||||||
|
VITE_DELETE_CONSOLE = false
|
||||||
|
# 是否开启sourcemap
|
||||||
|
VITE_SHOW_SOURCEMAP = false
|
||||||
|
|
||||||
|
# 后台请求地址
|
||||||
|
# VITE_SERVER_BASEURL = 'https://test.xxx.com'
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
import uniHelper from '@uni-helper/eslint-config'
|
||||||
|
|
||||||
|
export default uniHelper({
|
||||||
|
unocss: true,
|
||||||
|
vue: true,
|
||||||
|
markdown: false,
|
||||||
|
ignores: [
|
||||||
|
// 忽略uni_modules目录
|
||||||
|
'**/uni_modules/',
|
||||||
|
// 忽略原生插件目录
|
||||||
|
'**/nativeplugins/',
|
||||||
|
'dist',
|
||||||
|
// unplugin-auto-import 生成的类型文件,每次提交都改变,所以加入这里吧,与 .gitignore 配合使用
|
||||||
|
'auto-import.d.ts',
|
||||||
|
// vite-plugin-uni-pages 生成的类型文件,每次切换分支都一堆不同的,所以直接 .gitignore
|
||||||
|
'uni-pages.d.ts',
|
||||||
|
// 插件生成的文件
|
||||||
|
'src/pages.json',
|
||||||
|
'src/manifest.json',
|
||||||
|
// 忽略自动生成文件
|
||||||
|
'src/service/**',
|
||||||
|
],
|
||||||
|
// https://eslint-config.antfu.me/rules
|
||||||
|
rules: {
|
||||||
|
'no-useless-return': 'off',
|
||||||
|
'no-console': 'off',
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
'vue/no-unused-refs': 'off',
|
||||||
|
'unused-imports/no-unused-vars': 'off',
|
||||||
|
'eslint-comments/no-unlimited-disable': 'off',
|
||||||
|
'jsdoc/check-param-names': 'off',
|
||||||
|
'jsdoc/require-returns-description': 'off',
|
||||||
|
'ts/no-empty-object-type': 'off',
|
||||||
|
'no-extend-native': 'off',
|
||||||
|
'vue/singleline-html-element-content-newline': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
externalIgnores: ['text'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// vue SFC 调换顺序改这里
|
||||||
|
'vue/block-order': ['error', {
|
||||||
|
order: [['script', 'template'], 'style'],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
formatters: {
|
||||||
|
/**
|
||||||
|
* Format CSS, LESS, SCSS files, also the `<style>` blocks in Vue
|
||||||
|
* By default uses Prettier
|
||||||
|
*/
|
||||||
|
css: true,
|
||||||
|
/**
|
||||||
|
* Format HTML files
|
||||||
|
* By default uses Prettier
|
||||||
|
*/
|
||||||
|
html: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,26 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html build-time="%BUILD_TIME%">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
|
||||||
|
<script>
|
||||||
|
var coverSupport =
|
||||||
|
'CSS' in window &&
|
||||||
|
typeof CSS.supports === 'function' &&
|
||||||
|
(CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
|
||||||
|
document.write(
|
||||||
|
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
||||||
|
(coverSupport ? ', viewport-fit=cover' : '') +
|
||||||
|
'" />',
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
<title>%VITE_APP_TITLE%</title>
|
||||||
|
<!--preload-links-->
|
||||||
|
<!--app-context-->
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app"><!--app-html--></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { defineConfig } from 'openapi-ts-request'
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
{
|
||||||
|
describe: 'unibest-openapi-test',
|
||||||
|
schemaPath: 'https://ukw0y1.laf.run/unibest-opapi-test.json',
|
||||||
|
serversPath: './src/service',
|
||||||
|
requestLibPath: `import request from '@/http/vue-query';\n import { CustomRequestOptions_ } from '@/http/types';`,
|
||||||
|
requestOptionsType: 'CustomRequestOptions_',
|
||||||
|
isGenReactQuery: false,
|
||||||
|
reactQueryMode: 'vue',
|
||||||
|
isGenJavaScript: false,
|
||||||
|
},
|
||||||
|
])
|
||||||
@ -0,0 +1,192 @@
|
|||||||
|
{
|
||||||
|
"name": "shop",
|
||||||
|
"type": "module",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"unibest": {
|
||||||
|
"platforms": [
|
||||||
|
"h5",
|
||||||
|
"mp-weixin",
|
||||||
|
"app"
|
||||||
|
],
|
||||||
|
"uiLibrary": "wot-ui",
|
||||||
|
"loginStrategy": true,
|
||||||
|
"i18n": true,
|
||||||
|
"cliVersion": "3.3.0",
|
||||||
|
"Version": "4.3.0"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@10.10.0",
|
||||||
|
"description": "shop - 一个 uniapp 项目",
|
||||||
|
"generate-time": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20",
|
||||||
|
"pnpm": ">=9"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"openapi": "openapi-ts",
|
||||||
|
"init": "git init",
|
||||||
|
"init-baseFiles": "node ./scripts/create-base-files.js",
|
||||||
|
"prepare": "pnpm init-husky & pnpm init-baseFiles",
|
||||||
|
"predev": "pnpm init-baseFiles",
|
||||||
|
"predev:app": "pnpm init-baseFiles",
|
||||||
|
"predev:mp": "pnpm init-baseFiles",
|
||||||
|
"preinstall": "npx only-allow pnpm",
|
||||||
|
"uvm": "npx @dcloudio/uvm@latest",
|
||||||
|
"uvm-rm": "node ./scripts/postupgrade.js",
|
||||||
|
"postuvm": "echo upgrade uni-app success!",
|
||||||
|
"dev:app": "uni -p app",
|
||||||
|
"dev:app:test": "uni -p app --mode test",
|
||||||
|
"dev:app:prod": "uni -p app --mode production",
|
||||||
|
"dev:app-android": "uni -p app-android",
|
||||||
|
"dev:app-ios": "uni -p app-ios",
|
||||||
|
"dev:custom": "uni -p",
|
||||||
|
"dev": "uni",
|
||||||
|
"dev:test": "uni --mode test",
|
||||||
|
"dev:prod": "uni --mode production",
|
||||||
|
"dev:h5": "uni",
|
||||||
|
"dev:h5:test": "uni --mode test",
|
||||||
|
"dev:h5:prod": "uni --mode production",
|
||||||
|
"dev:h5:ssr": "uni --ssr",
|
||||||
|
"dev:mp": "uni -p mp-weixin",
|
||||||
|
"dev:mp:test": "uni -p mp-weixin --mode test",
|
||||||
|
"dev:mp:prod": "uni -p mp-weixin --mode production",
|
||||||
|
"dev:mp-alipay": "uni -p mp-alipay",
|
||||||
|
"dev:mp-baidu": "uni -p mp-baidu",
|
||||||
|
"dev:mp-jd": "uni -p mp-jd",
|
||||||
|
"dev:mp-kuaishou": "uni -p mp-kuaishou",
|
||||||
|
"dev:mp-lark": "uni -p mp-lark",
|
||||||
|
"dev:mp-qq": "uni -p mp-qq",
|
||||||
|
"dev:mp-toutiao": "uni -p mp-toutiao",
|
||||||
|
"dev:mp-weixin": "uni -p mp-weixin",
|
||||||
|
"dev:mp-xhs": "uni -p mp-xhs",
|
||||||
|
"dev:quickapp-webview": "uni -p quickapp-webview",
|
||||||
|
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
|
||||||
|
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
|
||||||
|
"build:app": "uni build -p app",
|
||||||
|
"build:app:test": "uni build -p app --mode test",
|
||||||
|
"build:app:prod": "uni build -p app --mode production",
|
||||||
|
"build:app-android": "uni build -p app-android",
|
||||||
|
"build:app-ios": "uni build -p app-ios",
|
||||||
|
"build:custom": "uni build -p",
|
||||||
|
"build:h5": "uni build",
|
||||||
|
"build:h5:test": "uni build --mode test",
|
||||||
|
"build:h5:prod": "uni build --mode production",
|
||||||
|
"build": "uni build",
|
||||||
|
"build:test": "uni build --mode test",
|
||||||
|
"build:prod": "uni build --mode production",
|
||||||
|
"build:h5:ssr": "uni build --ssr",
|
||||||
|
"build:mp-alipay": "uni build -p mp-alipay",
|
||||||
|
"build:mp": "uni build -p mp-weixin",
|
||||||
|
"build:mp:test": "uni build -p mp-weixin --mode test",
|
||||||
|
"build:mp:prod": "uni build -p mp-weixin --mode production",
|
||||||
|
"build:mp-baidu": "uni build -p mp-baidu",
|
||||||
|
"build:mp-jd": "uni build -p mp-jd",
|
||||||
|
"build:mp-kuaishou": "uni build -p mp-kuaishou",
|
||||||
|
"build:mp-lark": "uni build -p mp-lark",
|
||||||
|
"build:mp-qq": "uni build -p mp-qq",
|
||||||
|
"build:mp-toutiao": "uni build -p mp-toutiao",
|
||||||
|
"build:mp-weixin": "uni build -p mp-weixin",
|
||||||
|
"build:mp-xhs": "uni build -p mp-xhs",
|
||||||
|
"build:quickapp-webview": "uni build -p quickapp-webview",
|
||||||
|
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
|
||||||
|
"build:quickapp-webview-union": "uni build -p quickapp-webview-union",
|
||||||
|
"type-check": "vue-tsc --noEmit",
|
||||||
|
"lint": "eslint",
|
||||||
|
"lint:fix": "eslint --fix"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@alova/adapter-uniapp": "^2.0.14",
|
||||||
|
"@alova/shared": "^1.3.1",
|
||||||
|
"@dcloudio/uni-app": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-app-harmony": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-app-plus": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-components": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-h5": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-mp-alipay": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-mp-baidu": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-mp-harmony": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-mp-jd": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-mp-kuaishou": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-mp-lark": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-mp-qq": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-mp-toutiao": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-mp-weixin": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-mp-xhs": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-quickapp-webview": "3.0.0-4070620250821001",
|
||||||
|
"abortcontroller-polyfill": "^1.7.8",
|
||||||
|
"alova": "^3.3.3",
|
||||||
|
"dayjs": "1.11.10",
|
||||||
|
"pinia": "2.0.36",
|
||||||
|
"pinia-plugin-persistedstate": "3.2.1",
|
||||||
|
"vue": "^3.4.21",
|
||||||
|
"vue-i18n": "9.1.9",
|
||||||
|
"vue-router": "4.5.1",
|
||||||
|
"wot-design-uni": "latest",
|
||||||
|
"z-paging": "2.8.7"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@commitlint/cli": "^19.8.1",
|
||||||
|
"@commitlint/config-conventional": "^19.8.1",
|
||||||
|
"@dcloudio/types": "^3.4.8",
|
||||||
|
"@dcloudio/uni-automator": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-cli-shared": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-stacktracey": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/vite-plugin-uni": "3.0.0-4070620250821001",
|
||||||
|
"@esbuild/darwin-arm64": "0.20.2",
|
||||||
|
"@esbuild/darwin-x64": "0.20.2",
|
||||||
|
"@iconify-json/carbon": "^1.2.4",
|
||||||
|
"@iconify/utils": "^3.0.2",
|
||||||
|
"@rollup/rollup-darwin-x64": "^4.28.0",
|
||||||
|
"@types/node": "^20.17.9",
|
||||||
|
"@uni-helper/eslint-config": "0.5.0",
|
||||||
|
"@uni-helper/plugin-uni": "0.1.0",
|
||||||
|
"@uni-helper/uni-env": "0.1.8",
|
||||||
|
"@uni-helper/uni-types": "1.0.0-alpha.6",
|
||||||
|
"@uni-helper/unocss-preset-uni": "0.2.11",
|
||||||
|
"@uni-helper/vite-plugin-uni-components": "0.2.3",
|
||||||
|
"@uni-helper/vite-plugin-uni-layouts": "0.1.11",
|
||||||
|
"@uni-helper/vite-plugin-uni-manifest": "0.2.12",
|
||||||
|
"@uni-helper/vite-plugin-uni-pages": "0.3.22",
|
||||||
|
"@uni-helper/vite-plugin-uni-platform": "0.0.5",
|
||||||
|
"@uni-ku/bundle-optimizer": "v1.3.15-beta.2",
|
||||||
|
"@uni-ku/root": "1.4.1",
|
||||||
|
"@unocss/eslint-plugin": "^66.2.3",
|
||||||
|
"@unocss/preset-legacy-compat": "66.0.0",
|
||||||
|
"@vue/runtime-core": "^3.4.21",
|
||||||
|
"@vue/tsconfig": "^0.1.3",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"cross-env": "^10.0.0",
|
||||||
|
"eslint": "^9.31.0",
|
||||||
|
"eslint-plugin-format": "^1.0.1",
|
||||||
|
"lint-staged": "^15.2.10",
|
||||||
|
"miniprogram-api-typings": "^4.1.0",
|
||||||
|
"openapi-ts-request": "^1.10.0",
|
||||||
|
"postcss": "^8.4.49",
|
||||||
|
"postcss-html": "^1.8.0",
|
||||||
|
"postcss-scss": "^4.0.9",
|
||||||
|
"rollup-plugin-visualizer": "^6.0.3",
|
||||||
|
"sass": "1.77.8",
|
||||||
|
"std-env": "^3.9.0",
|
||||||
|
"typescript": "~5.8.0",
|
||||||
|
"unocss": "66.0.0",
|
||||||
|
"unplugin-auto-import": "^20.0.0",
|
||||||
|
"vite": "5.2.8",
|
||||||
|
"vite-plugin-restart": "^1.0.0",
|
||||||
|
"vue-tsc": "^3.0.6"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"overrides": {
|
||||||
|
"unconfig": "7.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"unconfig": "7.3.2"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"bin-wrapper": "npm:bin-wrapper-china",
|
||||||
|
"unconfig": "7.3.2"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*": "eslint --fix"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages'
|
||||||
|
import { tabBar } from './src/tabbar/config'
|
||||||
|
|
||||||
|
export default defineUniPages({
|
||||||
|
globalStyle: {
|
||||||
|
navigationStyle: 'default',
|
||||||
|
navigationBarTitleText: 'unibest',
|
||||||
|
navigationBarBackgroundColor: '#f8f8f8',
|
||||||
|
navigationBarTextStyle: 'black',
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
},
|
||||||
|
easycom: {
|
||||||
|
autoscan: true,
|
||||||
|
custom: {
|
||||||
|
'^fg-(.*)': '@/components/fg-$1/fg-$1.vue',
|
||||||
|
'^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)':
|
||||||
|
'z-paging/components/z-paging$1/z-paging$1.vue',
|
||||||
|
'^wd-(.*)': 'wot-design-uni/components/wd-$1/wd-$1.vue',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// tabbar 的配置统一在 “./src/tabbar/config.ts” 文件中
|
||||||
|
tabBar: tabBar as any,
|
||||||
|
})
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
// # 执行 `pnpm upgrade` 后会升级 `uniapp` 相关依赖
|
||||||
|
// # 在升级完后,会自动添加很多无用依赖,这需要删除以减小依赖包体积
|
||||||
|
// # 只需要执行下面的命令即可
|
||||||
|
|
||||||
|
import { exec } from 'node:child_process'
|
||||||
|
import { promisify } from 'node:util'
|
||||||
|
|
||||||
|
// 日志控制开关,设置为 true 可以启用所有日志输出
|
||||||
|
const FG_LOG_ENABLE = true
|
||||||
|
|
||||||
|
// 将 exec 转换为返回 Promise 的函数
|
||||||
|
const execPromise = promisify(exec)
|
||||||
|
|
||||||
|
// 定义要执行的命令
|
||||||
|
const dependencies = [
|
||||||
|
// TODO: 如果不需要某个平台的小程序,请手动删除或注释掉
|
||||||
|
'@dcloudio/uni-mp-baidu',
|
||||||
|
'@dcloudio/uni-mp-jd',
|
||||||
|
'@dcloudio/uni-mp-kuaishou',
|
||||||
|
'@dcloudio/uni-mp-qq',
|
||||||
|
'@dcloudio/uni-mp-xhs',
|
||||||
|
'@dcloudio/uni-quickapp-webview',
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带开关的日志输出函数
|
||||||
|
* @param {string} message 日志消息
|
||||||
|
* @param {string} type 日志类型 (log, error)
|
||||||
|
*/
|
||||||
|
function log(message, type = 'log') {
|
||||||
|
if (FG_LOG_ENABLE) {
|
||||||
|
if (type === 'error') {
|
||||||
|
console.error(message)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卸载单个依赖包
|
||||||
|
* @param {string} dep 依赖包名
|
||||||
|
* @returns {Promise<boolean>} 是否成功卸载
|
||||||
|
*/
|
||||||
|
async function uninstallDependency(dep) {
|
||||||
|
try {
|
||||||
|
log(`开始卸载依赖: ${dep}`)
|
||||||
|
const { stdout, stderr } = await execPromise(`pnpm un ${dep}`)
|
||||||
|
if (stdout) {
|
||||||
|
log(`stdout [${dep}]: ${stdout}`)
|
||||||
|
}
|
||||||
|
if (stderr) {
|
||||||
|
log(`stderr [${dep}]: ${stderr}`, 'error')
|
||||||
|
}
|
||||||
|
log(`成功卸载依赖: ${dep}`)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
// 单个依赖卸载失败不影响其他依赖
|
||||||
|
log(`卸载依赖 ${dep} 失败: ${error.message}`, 'error')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 串行卸载所有依赖包
|
||||||
|
*/
|
||||||
|
async function uninstallAllDependencies() {
|
||||||
|
log(`开始串行卸载 ${dependencies.length} 个依赖包...`)
|
||||||
|
|
||||||
|
let successCount = 0
|
||||||
|
let failedCount = 0
|
||||||
|
|
||||||
|
// 串行执行所有卸载命令
|
||||||
|
for (const dep of dependencies) {
|
||||||
|
const success = await uninstallDependency(dep)
|
||||||
|
if (success) {
|
||||||
|
successCount++
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
failedCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为了避免命令执行过快导致的问题,添加短暂延迟
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100))
|
||||||
|
}
|
||||||
|
|
||||||
|
log(`卸载操作完成: 成功 ${successCount} 个, 失败 ${failedCount} 个`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行串行卸载
|
||||||
|
uninstallAllDependencies().catch((err) => {
|
||||||
|
log(`串行卸载过程中出现未捕获的错误: ${err}`, 'error')
|
||||||
|
})
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
import { API_DOMAINS, http } from '@/http/alova'
|
||||||
|
|
||||||
|
export interface IFoo {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function foo() {
|
||||||
|
return http.Get<IFoo>('/foo', {
|
||||||
|
params: {
|
||||||
|
name: '菲鸽',
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
},
|
||||||
|
meta: { domain: API_DOMAINS.SECONDARY }, // 用于切换请求地址
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
import type { Ref } from 'vue'
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
|
||||||
|
interface UseScrollOptions<T> {
|
||||||
|
fetchData: (page: number, pageSize: number) => Promise<T[]>
|
||||||
|
pageSize?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseScrollReturn<T> {
|
||||||
|
list: Ref<T[]>
|
||||||
|
loading: Ref<boolean>
|
||||||
|
finished: Ref<boolean>
|
||||||
|
error: Ref<any>
|
||||||
|
refresh: () => Promise<void>
|
||||||
|
loadMore: () => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useScroll<T>({
|
||||||
|
fetchData,
|
||||||
|
pageSize = 10,
|
||||||
|
}: UseScrollOptions<T>): UseScrollReturn<T> {
|
||||||
|
const list = ref<T[]>([]) as Ref<T[]>
|
||||||
|
const loading = ref(false)
|
||||||
|
const finished = ref(false)
|
||||||
|
const error = ref<any>(null)
|
||||||
|
const page = ref(1)
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
if (loading.value || finished.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
error.value = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await fetchData(page.value, pageSize)
|
||||||
|
if (data.length < pageSize) {
|
||||||
|
finished.value = true
|
||||||
|
}
|
||||||
|
list.value.push(...data)
|
||||||
|
page.value++
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
error.value = err
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const refresh = async () => {
|
||||||
|
page.value = 1
|
||||||
|
finished.value = false
|
||||||
|
list.value = []
|
||||||
|
await loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadMore = async () => {
|
||||||
|
await loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
refresh()
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
list,
|
||||||
|
loading,
|
||||||
|
finished,
|
||||||
|
error,
|
||||||
|
refresh,
|
||||||
|
loadMore,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
import type { CustomRequestOptions } from '@/http/types'
|
||||||
|
import { http } from './http'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* openapi-ts-request 工具的 request 跨客户端适配方法
|
||||||
|
*/
|
||||||
|
export default function request<T extends { data?: any }>(
|
||||||
|
url: string,
|
||||||
|
options: Omit<CustomRequestOptions, 'url'> & {
|
||||||
|
params?: Record<string, unknown>
|
||||||
|
headers?: Record<string, unknown>
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const requestOptions = {
|
||||||
|
url,
|
||||||
|
...options,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.params) {
|
||||||
|
requestOptions.query = requestOptions.params
|
||||||
|
delete requestOptions.params
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.headers) {
|
||||||
|
requestOptions.header = options.headers
|
||||||
|
delete requestOptions.headers
|
||||||
|
}
|
||||||
|
|
||||||
|
return http<T['data']>(requestOptions)
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
<slot />
|
||||||
|
</template>
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
# 注意事项
|
||||||
|
|
||||||
|
> 文件夹名字必须为 `locale`, 这是 `uniapp` 官方约定的,如果改为别的,标题将不能正常切换多语言(其他内容还是正常)。
|
||||||
|
>
|
||||||
|
> `xxx.json` 的 `xxx` 多语言标识必须与 `uniapp` 官方约定的一致,否则也会出现 BUG。
|
||||||
|
>
|
||||||
|
> 查看截图 `screenshots/i18n.png`。
|
||||||
|
|
||||||
|
## 参考文档
|
||||||
|
|
||||||
|
[uniapp 国际化开发指南](https://uniapp.dcloud.net.cn/tutorial/i18n.html)
|
||||||
|
[uniapp 国际化-注意事项](https://uniapp.dcloud.net.cn/api/ui/locale.html#onlocalechange) 最下面的注意事项
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"tabbar.home": "Home",
|
||||||
|
"tabbar.about": "About",
|
||||||
|
"tabbar.me": "Me",
|
||||||
|
"i18n.title": "En Title",
|
||||||
|
"alova.title": "Alova Request",
|
||||||
|
"weight": "{heavy}KG",
|
||||||
|
"detail": "{0}cm, {1}KG",
|
||||||
|
"introduction": "I am {name},height:{detail.height},weight:{detail.weight}"
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"tabbar.home": "首页",
|
||||||
|
"tabbar.about": "关于",
|
||||||
|
"tabbar.cart": "购物车",
|
||||||
|
"tabbar.store": "商家",
|
||||||
|
"tabbar.me": "我的",
|
||||||
|
"i18n.title": "中文标题",
|
||||||
|
"alova.title": "Alova 请求",
|
||||||
|
"weight": "{heavy}公斤",
|
||||||
|
"detail": "{0}cm, {1}公斤",
|
||||||
|
"introduction": "我是 {name},身高:{detail.height},体重:{detail.weight}"
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
import { createSSRApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
import { requestInterceptor } from './http/interceptor'
|
||||||
|
import i18n from './locale/index'
|
||||||
|
import { routeInterceptor } from './router/interceptor'
|
||||||
|
|
||||||
|
import store from './store'
|
||||||
|
import '@/style/index.scss'
|
||||||
|
import 'virtual:uno.css'
|
||||||
|
|
||||||
|
export function createApp() {
|
||||||
|
const app = createSSRApp(App)
|
||||||
|
app.use(store)
|
||||||
|
app.use(i18n)
|
||||||
|
app.use(routeInterceptor)
|
||||||
|
app.use(requestInterceptor)
|
||||||
|
|
||||||
|
return {
|
||||||
|
app,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
# 登录页
|
||||||
|
需要输入账号、密码/验证码的登录页。
|
||||||
|
|
||||||
|
## 适用性
|
||||||
|
|
||||||
|
本页面主要用于 `h5` 和 `APP`。
|
||||||
|
|
||||||
|
小程序通常有平台的登录方式 `uni.login` 通常用不到登录页,所以不适用于 `小程序`。(即默认情况下,小程序环境是不会走登录拦截逻辑的。)
|
||||||
|
|
||||||
|
但是如果您的小程序也需要现实的 `登录页` 那也是可以使用的。
|
||||||
|
|
||||||
|
在 `src/router/config.ts` 中有一个变量 `LOGIN_PAGE_ENABLE_IN_MP` 来控制是否在小程序中使用 `H5的登录页`。
|
||||||
|
|
||||||
|
更多信息请看 `src/router` 文件夹的内容。
|
||||||
|
|
||||||
|
## 登录跳转
|
||||||
|
|
||||||
|
目前登录的跳转逻辑主要在 `src/router/interceptor.ts` 和 `src/pages/login/login.vue` 里面,默认会在登录后自动重定向到来源/配置的页面。
|
||||||
|
|
||||||
|
如果与您的业务不符,您可以自行修改。
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { LOGIN_PAGE } from '@/router/config'
|
||||||
|
|
||||||
|
definePage({
|
||||||
|
style: {
|
||||||
|
navigationBarTitleText: '注册',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
function doRegister() {
|
||||||
|
uni.showToast({
|
||||||
|
title: '注册成功',
|
||||||
|
})
|
||||||
|
// 注册成功后跳转到登录页
|
||||||
|
uni.navigateTo({
|
||||||
|
url: LOGIN_PAGE,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="login">
|
||||||
|
<view class="text-center">
|
||||||
|
注册页
|
||||||
|
</view>
|
||||||
|
<button class="mt-4 w-40 text-center" @click="doRegister">
|
||||||
|
点击模拟注册
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
//
|
||||||
|
</style>
|
||||||
@ -0,0 +1,179 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { CartItem } from '@/types/cart'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useCartStore } from '@/store/cart'
|
||||||
|
|
||||||
|
// 组件属性
|
||||||
|
const props = defineProps<{
|
||||||
|
item: CartItem
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// 购物车状态
|
||||||
|
const cartStore = useCartStore()
|
||||||
|
|
||||||
|
const quantity = computed({
|
||||||
|
get: () => props.item.quantity,
|
||||||
|
set: (value: number) => {
|
||||||
|
handleQuantityChange(value)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算属性 - 商品是否被选中
|
||||||
|
const isSelected = computed({
|
||||||
|
get: () => cartStore.selectedItems.includes(props.item.id),
|
||||||
|
set: (value: boolean) => {
|
||||||
|
handleItemSelect()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleQuantityChange(value: number) {
|
||||||
|
cartStore.updateQuantity(props.item.id, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换商品选择状态
|
||||||
|
function handleItemSelect() {
|
||||||
|
cartStore.toggleItemSelection(props.item.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 增加商品数量
|
||||||
|
function handleIncrease() {
|
||||||
|
cartStore.increaseQuantity(props.item.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 减少商品数量
|
||||||
|
function handleDecrease() {
|
||||||
|
cartStore.decreaseQuantity(props.item.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除商品
|
||||||
|
function handleDelete() {
|
||||||
|
uni.showModal({
|
||||||
|
title: '确认删除',
|
||||||
|
content: '确定要从购物车中删除该商品吗?',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
cartStore.removeItem(props.item.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<wd-swipe-action>
|
||||||
|
<template #right>
|
||||||
|
<view class="action">
|
||||||
|
<view class="action-button" style="background: #E2231A;" @click="handleDelete">
|
||||||
|
<wd-icon name="delete-thin" size="22px" color="white" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<view class="cart-item">
|
||||||
|
<!-- 商品选择 -->
|
||||||
|
<view class="item-select-section">
|
||||||
|
<wd-checkbox
|
||||||
|
v-model="isSelected"
|
||||||
|
size="large"
|
||||||
|
shape="circle"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 商品图片 -->
|
||||||
|
<view class="item-image-section">
|
||||||
|
<image
|
||||||
|
class="item-image"
|
||||||
|
:src="item.image"
|
||||||
|
mode="aspectFill"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 商品信息 -->
|
||||||
|
<view class="item-info-section">
|
||||||
|
<!-- 商品名称 -->
|
||||||
|
<text class="item-name">{{ item.productName }}</text>
|
||||||
|
|
||||||
|
<!-- 价格和数量 -->
|
||||||
|
<view class="item-price-quantity">
|
||||||
|
<!-- 价格 -->
|
||||||
|
<view class="price-section">
|
||||||
|
<text class="current-price">¥{{ item.price.toFixed(2) }}</text>
|
||||||
|
<text v-if="item.originalPrice" class="original-price">¥{{ item.originalPrice.toFixed(2) }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 数量调整 -->
|
||||||
|
<view class="quantity-section">
|
||||||
|
<wd-input-number
|
||||||
|
v-model="quantity"
|
||||||
|
:min="1"
|
||||||
|
:max="99"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</wd-swipe-action>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.action {
|
||||||
|
@apply h-[100%];
|
||||||
|
}
|
||||||
|
.action-button {
|
||||||
|
@apply flex items-center justify-center h-[100%] w-[140rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-item {
|
||||||
|
@apply flex items-center p-[24rpx] border-b border-gray-100;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
@apply border-b-0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 商品选择 */
|
||||||
|
.item-select-section {
|
||||||
|
@apply flex-shrink-0 mt-[8rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 商品图片 */
|
||||||
|
.item-image-section {
|
||||||
|
@apply flex-shrink-0 ml-[12rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-image {
|
||||||
|
@apply w-[160rpx] h-[160rpx] rounded-[8rpx] bg-gray-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 商品信息 */
|
||||||
|
.item-info-section {
|
||||||
|
@apply flex-1 ml-[16rpx] flex flex-col justify-between;
|
||||||
|
min-height: 160rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
@apply text-[26rpx] text-gray-800 mb-[12rpx] line-clamp-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-price-quantity {
|
||||||
|
@apply flex items-center justify-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 价格 */
|
||||||
|
.price-section {
|
||||||
|
@apply flex items-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-price {
|
||||||
|
@apply text-[32rpx] font-bold text-red-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.original-price {
|
||||||
|
@apply text-[22rpx] text-gray-400 line-through ml-[8rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 数量调整 */
|
||||||
|
.quantity-section {
|
||||||
|
@apply flex-shrink-0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,107 @@
|
|||||||
|
<!--
|
||||||
|
* @Author: chris
|
||||||
|
* @Date: 2026-02-04 17:43:09
|
||||||
|
* @LastEditors: chris
|
||||||
|
* @LastEditTime: 2026-02-11 09:33:19
|
||||||
|
-->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { StoreItem } from '@/types/cart'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useCartStore } from '@/store/cart'
|
||||||
|
import CartItem from './CartItem.vue'
|
||||||
|
|
||||||
|
// 组件属性
|
||||||
|
const props = defineProps<{
|
||||||
|
group: StoreItem
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// 购物车状态
|
||||||
|
const cartStore = useCartStore()
|
||||||
|
|
||||||
|
// 计算属性 - 店铺内商品是否全选
|
||||||
|
const isStoreAllSelected = computed({
|
||||||
|
get: () => props.group.items.every(item => cartStore.selectedItems.includes(item.id)),
|
||||||
|
set: (value: boolean) => {
|
||||||
|
handleStoreSelect()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 切换店铺全选
|
||||||
|
function handleStoreSelect() {
|
||||||
|
if (isStoreAllSelected.value) {
|
||||||
|
// 取消店铺内所有商品选择
|
||||||
|
props.group.items.forEach((item) => {
|
||||||
|
cartStore.unselectItem(item.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 选择店铺内所有商品
|
||||||
|
props.group.items.forEach((item) => {
|
||||||
|
cartStore.selectItem(item.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="cart-store-group">
|
||||||
|
<!-- 店铺头部 -->
|
||||||
|
<view class="store-header">
|
||||||
|
<!-- 店铺选择 -->
|
||||||
|
<view class="store-select-section">
|
||||||
|
<wd-checkbox
|
||||||
|
v-model="isStoreAllSelected"
|
||||||
|
size="large"
|
||||||
|
shape="circle"
|
||||||
|
/>
|
||||||
|
<text class="store-name">{{ group.storeName }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 进店按钮 -->
|
||||||
|
<view class="enter-store-section">
|
||||||
|
<text class="enter-store-text">进店</text>
|
||||||
|
<wd-icon name="arrow-right" size="16" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 商品列表 -->
|
||||||
|
<view class="store-products">
|
||||||
|
<CartItem
|
||||||
|
v-for="item in group.items"
|
||||||
|
:key="item.id"
|
||||||
|
:item="item"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.cart-store-group {
|
||||||
|
@apply bg-white mb-[16rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 店铺头部 */
|
||||||
|
.store-header {
|
||||||
|
@apply flex items-center justify-between p-[24rpx] border-b border-gray-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-select-section {
|
||||||
|
@apply flex items-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-name {
|
||||||
|
@apply ml-[12rpx] text-[28rpx] font-medium text-gray-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enter-store-section {
|
||||||
|
@apply flex items-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enter-store-text {
|
||||||
|
@apply text-[24rpx] text-gray-600 mr-[4rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 商品列表 */
|
||||||
|
.store-products {
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// 跳转到首页
|
||||||
|
function goToHome() {
|
||||||
|
uni.switchTab({
|
||||||
|
url: '/pages/index/index',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="empty-cart">
|
||||||
|
<!-- 空购物车图标 -->
|
||||||
|
<view class="empty-icon-section">
|
||||||
|
<wd-icon name="shopping-cart" size="80" color="#ccc" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 提示信息 -->
|
||||||
|
<view class="empty-info-section">
|
||||||
|
<text class="empty-title">购物车是空的</text>
|
||||||
|
<text class="empty-subtitle">快去挑选心仪的商品吧</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 去购物按钮 -->
|
||||||
|
<view class="empty-action-section">
|
||||||
|
<wd-button type="primary" size="large" @click="goToHome">
|
||||||
|
去购物
|
||||||
|
</wd-button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.empty-cart {
|
||||||
|
@apply flex flex-col items-center justify-center h-full px-[32rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 空购物车图标 */
|
||||||
|
.empty-icon-section {
|
||||||
|
@apply mb-[32rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 提示信息 */
|
||||||
|
.empty-info-section {
|
||||||
|
@apply mb-[48rpx] text-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-title {
|
||||||
|
@apply block text-[32rpx] font-bold text-gray-800 mb-[8rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-subtitle {
|
||||||
|
@apply block text-[24rpx] text-gray-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 去购物按钮 */
|
||||||
|
.empty-action-section {
|
||||||
|
@apply w-full max-w-[320rpx];
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
<!--
|
||||||
|
* @Author: chris
|
||||||
|
* @Date: 2026-01-26 16:34:25
|
||||||
|
* @LastEditors: chris
|
||||||
|
* @LastEditTime: 2026-01-26 16:42:56
|
||||||
|
-->
|
||||||
|
<script lang="ts" setup>
|
||||||
|
// 定义组件属性
|
||||||
|
const props = defineProps<{
|
||||||
|
banners: Array<{
|
||||||
|
id: number
|
||||||
|
image: string
|
||||||
|
url: string
|
||||||
|
title: string
|
||||||
|
}>
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// 定义组件事件
|
||||||
|
const emit = defineEmits(['click'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="banner-section">
|
||||||
|
<wd-swiper
|
||||||
|
:autoplay="true"
|
||||||
|
:interval="3000"
|
||||||
|
:duration="500"
|
||||||
|
indicator-dots
|
||||||
|
:list="banners"
|
||||||
|
height="400rpx"
|
||||||
|
@click="(index, item) => emit('click', index, item)"
|
||||||
|
>
|
||||||
|
<template #default="{ item }">
|
||||||
|
<!-- 使用CSS绘制banner占位图 -->
|
||||||
|
<view class="banner-placeholder">
|
||||||
|
<text class="banner-text">{{ (item as any).title }}</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</wd-swiper>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
/* banner图样式 */
|
||||||
|
.banner-section {
|
||||||
|
@apply bg-white px-6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* banner占位图样式 */
|
||||||
|
.banner-placeholder {
|
||||||
|
@apply w-full h-[400rpx] bg-gradient-to-r from-blue-500 to-purple-600 flex items-center justify-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-text {
|
||||||
|
@apply text-white text-[48rpx] font-bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
// 定义组件属性
|
||||||
|
const props = defineProps<{
|
||||||
|
categories: Array<{
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
icon: string
|
||||||
|
}>
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="category-section">
|
||||||
|
<wd-grid :column-num="3" :border="false">
|
||||||
|
<wd-grid-item
|
||||||
|
v-for="category in categories"
|
||||||
|
:key="category.id"
|
||||||
|
:text="category.name"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<wd-icon :name="category.icon" size="32" />
|
||||||
|
</template>
|
||||||
|
</wd-grid-item>
|
||||||
|
</wd-grid>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
/* 商品类别样式 */
|
||||||
|
.category-section {
|
||||||
|
@apply bg-white py-[32rpx];
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,195 @@
|
|||||||
|
<!--
|
||||||
|
* @Author: chris
|
||||||
|
* @Date: 2026-01-26 16:34:50
|
||||||
|
* @LastEditors: chris
|
||||||
|
* @LastEditTime: 2026-01-26 16:40:25
|
||||||
|
-->
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
// 定义组件属性
|
||||||
|
const props = defineProps<{
|
||||||
|
products: Array<{
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
price: number
|
||||||
|
image: string
|
||||||
|
category: string
|
||||||
|
date: string
|
||||||
|
clearance: boolean
|
||||||
|
}>
|
||||||
|
sortOptions: Array<{
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
value: string
|
||||||
|
}>
|
||||||
|
isLoading?: boolean
|
||||||
|
hasMore?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// 默认值处理
|
||||||
|
const isLoading = computed(() => props.isLoading || false)
|
||||||
|
const hasMore = computed(() => props.hasMore || true)
|
||||||
|
|
||||||
|
// 当前排序方式
|
||||||
|
const currentSort = ref('default')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="product-section">
|
||||||
|
<!-- 使用wd-tabs组件实现排序选项 -->
|
||||||
|
<wd-tabs v-model="currentSort" color="#3b82f6" :auto-line-width="true">
|
||||||
|
<wd-tab
|
||||||
|
v-for="option in sortOptions"
|
||||||
|
:key="option.id"
|
||||||
|
:title="option.name"
|
||||||
|
:name="option.value"
|
||||||
|
>
|
||||||
|
<!-- 商品列表 -->
|
||||||
|
<view class="product-list">
|
||||||
|
<view v-for="product in products" :key="product.id" class="product-item">
|
||||||
|
<!-- 使用CSS绘制商品图片占位图 -->
|
||||||
|
<view class="product-image-placeholder">
|
||||||
|
<text class="product-image-text">{{ product.name.charAt(0) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="product-info">
|
||||||
|
<text class="product-name">{{ product.name }}</text>
|
||||||
|
<view class="product-bottom">
|
||||||
|
<text class="product-price">¥{{ product.price }}</text>
|
||||||
|
<text v-if="product.clearance" class="product-clearance">清仓</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 加载状态 -->
|
||||||
|
<view v-if="isLoading" class="loading-container">
|
||||||
|
<wd-loading type="ring" color="#3b82f6" />
|
||||||
|
<text class="loading-text">加载中...</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 没有更多数据的提示 -->
|
||||||
|
<view v-if="!hasMore" class="no-more-container">
|
||||||
|
<text class="no-more-text">没有更多数据了</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</wd-tab>
|
||||||
|
</wd-tabs>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
/* 商品列表样式 */
|
||||||
|
.product-section {
|
||||||
|
@apply bg-white mt-[24rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 商品列表样式 */
|
||||||
|
.product-list {
|
||||||
|
@apply grid grid-cols-2 gap-[24rpx] p-[24rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-item {
|
||||||
|
@apply bg-white rounded-[16rpx] overflow-hidden shadow-sm transition-all duration-300;
|
||||||
|
/* 添加hover效果 */
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1);
|
||||||
|
transform: translateY(-4rpx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-item:active {
|
||||||
|
@apply transform scale-95;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 商品图片占位图样式优化 */
|
||||||
|
.product-image-placeholder {
|
||||||
|
@apply w-full h-[320rpx] bg-gradient-to-br from-blue-400 via-purple-400 to-pink-400 flex items-center justify-center relative overflow-hidden;
|
||||||
|
/* 添加渐变背景和装饰效果 */
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||||
|
transition: left 0.5s ease;
|
||||||
|
}
|
||||||
|
&:hover::before {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image-text {
|
||||||
|
@apply text-white text-[80rpx] font-bold;
|
||||||
|
text-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.2);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-item:hover .product-image-text {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-info {
|
||||||
|
@apply p-[20rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-name {
|
||||||
|
@apply text-[28rpx] font-medium text-gray-800 mb-[12rpx] line-clamp-2;
|
||||||
|
/* 添加文字渐变效果 */
|
||||||
|
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-bottom {
|
||||||
|
@apply flex items-center justify-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-price {
|
||||||
|
@apply text-[32rpx] font-bold text-red-600;
|
||||||
|
/* 添加价格样式优化 */
|
||||||
|
&::before {
|
||||||
|
content: '¥';
|
||||||
|
font-size: 24rpx;
|
||||||
|
margin-right: 4rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-clearance {
|
||||||
|
@apply text-[20rpx] font-medium text-white bg-red-500 px-[16rpx] py-[6rpx] rounded-full ml-[12rpx];
|
||||||
|
/* 添加清仓标签样式优化 */
|
||||||
|
box-shadow: 0 4rpx 8rpx rgba(239, 68, 68, 0.3);
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 添加脉冲动画 */
|
||||||
|
@keyframes pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载状态样式 */
|
||||||
|
.loading-container {
|
||||||
|
@apply flex items-center justify-center py-[60rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
@apply text-[28rpx] text-gray-500 ml-[16rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 没有更多数据样式 */
|
||||||
|
.no-more-container {
|
||||||
|
@apply flex items-center justify-center py-[40rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-more-text {
|
||||||
|
@apply text-[26rpx] text-gray-400;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
// 定义组件事件
|
||||||
|
const emit = defineEmits(['search'])
|
||||||
|
|
||||||
|
// 搜索关键词
|
||||||
|
const searchKeyword = ref('')
|
||||||
|
|
||||||
|
// 搜索函数
|
||||||
|
function handleSearch() {
|
||||||
|
emit('search', searchKeyword.value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="search-section">
|
||||||
|
<wd-search
|
||||||
|
v-model="searchKeyword"
|
||||||
|
placeholder="搜索商品"
|
||||||
|
class="home-search"
|
||||||
|
hide-cancel
|
||||||
|
@search="handleSearch"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.search-section {
|
||||||
|
@apply p-[10rpx] bg-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-search {
|
||||||
|
@apply w-full;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,158 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
// 定义组件属性
|
||||||
|
const props = defineProps<{
|
||||||
|
store: {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
platform: string
|
||||||
|
activity: string
|
||||||
|
fans: number
|
||||||
|
years: number
|
||||||
|
reviews: number
|
||||||
|
orders: number
|
||||||
|
logo: string
|
||||||
|
products: Array<{
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
price: number
|
||||||
|
image: string
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// 定义事件
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'enterStore', storeId: number): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// 进店函数
|
||||||
|
function enterStore() {
|
||||||
|
emit('enterStore', props.store.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化数字
|
||||||
|
function formatNumber(num: number): string {
|
||||||
|
if (num >= 100000000) {
|
||||||
|
return `${(num / 100000000).toFixed(1)}亿`
|
||||||
|
}
|
||||||
|
else if (num >= 10000) {
|
||||||
|
return `${(num / 10000).toFixed(0)}万`
|
||||||
|
}
|
||||||
|
return num.toString()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="store-item">
|
||||||
|
<!-- 商家头部信息 -->
|
||||||
|
<view class="store-header">
|
||||||
|
<!-- 商家logo -->
|
||||||
|
<view class="store-logo-placeholder">
|
||||||
|
<text class="store-logo-text">{{ store.name.charAt(0) }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 商家基本信息 -->
|
||||||
|
<view class="store-info">
|
||||||
|
<view class="store-name-section">
|
||||||
|
<text class="store-name">{{ store.name }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 进店按钮 -->
|
||||||
|
<wd-button type="primary" size="small" class="enter-store-btn" @click="enterStore">
|
||||||
|
进店
|
||||||
|
</wd-button>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 商品展示 -->
|
||||||
|
<view class="store-content">
|
||||||
|
<!-- 商品列表 -->
|
||||||
|
<view class="product-grid">
|
||||||
|
<view v-for="product in store.products" :key="product.id" class="product-item">
|
||||||
|
<!-- 商品占位图 -->
|
||||||
|
<view class="product-image-placeholder">
|
||||||
|
<text class="product-image-text">{{ product.name.charAt(0) }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="product-name">{{ product.name }}</text>
|
||||||
|
<text class="product-price">¥{{ product.price }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.store-item {
|
||||||
|
@apply bg-white mb-[20rpx] overflow-hidden shadow-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 商家头部信息 */
|
||||||
|
.store-header {
|
||||||
|
@apply flex items-center p-[30rpx] border-b border-gray-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-logo-placeholder {
|
||||||
|
@apply w-[60rpx] h-[60rpx] bg-blue-500 rounded-[8rpx] flex items-center justify-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-logo-text {
|
||||||
|
@apply text-white text-[32rpx] font-bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-info {
|
||||||
|
@apply flex-1 ml-[16rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-name-section {
|
||||||
|
@apply flex items-center mb-[8rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-name {
|
||||||
|
@apply text-[28rpx] font-bold text-gray-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enter-store-btn {
|
||||||
|
@apply ml-[16rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 商品展示 */
|
||||||
|
.store-content {
|
||||||
|
@apply p-[30rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 商品网格布局 */
|
||||||
|
.product-grid {
|
||||||
|
@apply grid grid-cols-3 gap-[16rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-item {
|
||||||
|
@apply flex flex-col;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image-placeholder {
|
||||||
|
@apply w-full h-[200rpx] bg-gray-200 rounded-[8rpx] flex items-center justify-center mb-[8rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image-text {
|
||||||
|
@apply text-gray-400 text-[48rpx];
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-name {
|
||||||
|
@apply text-[22rpx] text-gray-800 mb-[4rpx] line-clamp-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-price {
|
||||||
|
@apply text-[24rpx] font-bold text-red-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media screen and (max-width: 750rpx) {
|
||||||
|
.product-grid {
|
||||||
|
@apply grid-cols-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image-placeholder {
|
||||||
|
@apply h-[180rpx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// @ts-ignore
|
||||||
|
export * from './types';
|
||||||
|
|
||||||
|
export * from './listAll';
|
||||||
|
export * from './info';
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// @ts-ignore
|
||||||
|
import request from '@/http/vue-query';
|
||||||
|
import { CustomRequestOptions_ } from '@/http/types';
|
||||||
|
|
||||||
|
import * as API from './types';
|
||||||
|
|
||||||
|
/** 用户信息 GET /user/info */
|
||||||
|
export function infoUsingGet({ options }: { options?: CustomRequestOptions_ }) {
|
||||||
|
return request<API.InfoUsingGetResponse>('/user/info', {
|
||||||
|
method: 'GET',
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// @ts-ignore
|
||||||
|
import request from '@/http/vue-query';
|
||||||
|
import { CustomRequestOptions_ } from '@/http/types';
|
||||||
|
|
||||||
|
import * as API from './types';
|
||||||
|
|
||||||
|
/** 用户列表 GET /user/listAll */
|
||||||
|
export function listAllUsingGet({
|
||||||
|
options,
|
||||||
|
}: {
|
||||||
|
options?: CustomRequestOptions_;
|
||||||
|
}) {
|
||||||
|
return request<API.ListAllUsingGetResponse>('/user/listAll', {
|
||||||
|
method: 'GET',
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// @ts-ignore
|
||||||
|
|
||||||
|
export type InfoUsingGetResponse = {
|
||||||
|
code: number;
|
||||||
|
msg: string;
|
||||||
|
data: UserItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InfoUsingGetResponses = {
|
||||||
|
200: InfoUsingGetResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ListAllUsingGetResponse = {
|
||||||
|
code: number;
|
||||||
|
msg: string;
|
||||||
|
data: UserItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ListAllUsingGetResponses = {
|
||||||
|
200: ListAllUsingGetResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UserItem = {
|
||||||
|
userId: number;
|
||||||
|
username: string;
|
||||||
|
nickname: string;
|
||||||
|
avatar: string;
|
||||||
|
};
|
||||||
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 574 B |
|
After Width: | Height: | Size: 780 B |
|
After Width: | Height: | Size: 985 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 560 B |
@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="_图层_2" data-name="图层 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 113.39 113.39">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1 {
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-2 {
|
||||||
|
fill: #d14328;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-3 {
|
||||||
|
fill: #2c8d3a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="_图层_1-2" data-name="图层 1">
|
||||||
|
<g>
|
||||||
|
<rect class="cls-1" width="113.39" height="113.39" />
|
||||||
|
<g>
|
||||||
|
<path class="cls-3"
|
||||||
|
d="M86.31,11.34H25.08c-8.14,0-14.74,6.6-14.74,14.74v61.23c0,8.14,6.6,14.74,14.74,14.74h61.23c.12,0,.24-.02,.37-.02-9.76-.2-17.64-8.18-17.64-17.99,0-.56,.03-1.12,.08-1.67H34.1c-1.57,0-2.83-1.27-2.83-2.83V32.43c0-.78,.63-1.42,1.42-1.42h9.17c.78,0,1.42,.63,1.42,1.42v36.52c0,.78,.63,1.42,1.42,1.42h22.02c.78,0,1.42-.63,1.42-1.42V32.43c0-.78,.63-1.42,1.42-1.42h9.17c.78,0,1.42,.63,1.42,1.42v34.99c2.13-.89,4.47-1.39,6.92-1.39,5.66,0,10.7,2.63,14.01,6.72V26.08c0-8.14-6.6-14.74-14.74-14.74Z" />
|
||||||
|
<g>
|
||||||
|
<path class="cls-2"
|
||||||
|
d="M87.04,68.03c-8.83,0-16.01,7.18-16.01,16.01s7.18,16.01,16.01,16.01,16.01-7.18,16.01-16.01-7.18-16.01-16.01-16.01Zm-.27,24.84h-7.2v-3h1.18v-10.48h4.58v2.81h1.42c.84,0,1.46-.16,1.88-.48s.62-.87,.62-1.64c0-.69-.25-1.17-.74-1.45s-1.19-.42-2.09-.42h-6.84v-3h7.2c2.38,0,4.15,.38,5.31,1.15,1.16,.77,1.74,1.93,1.74,3.48,0,1.71-.83,2.93-2.5,3.64,1.07,.4,1.87,.95,2.39,1.65s.79,1.56,.79,2.58c0,3.44-2.58,5.16-7.73,5.16Z" />
|
||||||
|
<path class="cls-2"
|
||||||
|
d="M86.49,85.17h-1.16v4.7h1.8c.81,0,1.46-.18,1.94-.55s.72-.95,.72-1.73c0-.86-.25-1.48-.74-1.85s-1.35-.56-2.56-.56Z" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1762219859937" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8816" id="mx_n_1762219859938" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64z m0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z" p-id="8817"></path><path d="M517.6 351.3c53 0 89 33.8 93 83.4 0.3 4.2 3.8 7.4 8 7.4h56.7c2.6 0 4.7-2.1 4.7-4.7 0-86.7-68.4-147.4-162.7-147.4C407.4 290 344 364.2 344 486.8v52.3C344 660.8 407.4 734 517.3 734c94 0 162.7-58.8 162.7-141.4 0-2.6-2.1-4.7-4.7-4.7h-56.8c-4.2 0-7.6 3.2-8 7.3-4.2 46.1-40.1 77.8-93 77.8-65.3 0-102.1-47.9-102.1-133.6v-52.6c0.1-87 37-135.5 102.2-135.5z" p-id="8818"></path></svg>
|
||||||
|
After Width: | Height: | Size: 934 B |