瑶瑶电商-代码备份

main
甜甜圈 2023-12-19 13:45:35 +08:00
commit 9c7ffeae0b
1856 changed files with 551527 additions and 0 deletions

10
jjstore/.env Normal file
View File

@ -0,0 +1,10 @@
# 本地运行端口号
VITE_PORT=3000
# 启动时自动打开浏览器
VITE_OPEN=false
# 开发环境跨域代理,可配置多个
VITE_PROXY=[["/api","http://trans.yyhome2022.com/"]]
#VITE_PROXY=[]

10
jjstore/.env.development Normal file
View File

@ -0,0 +1,10 @@
# 本地环境
VITE_USER_NODE_ENV = development
# 打包时是否删除 console
VITE_DROP_CONSOLE = true
# 接口地址
# VITE_API_URL = "/api"
VITE_API_URL = "http://127.0.0.1:8888/"

8
jjstore/.env.production Normal file
View File

@ -0,0 +1,8 @@
# 线上环境
VITE_USER_NODE_ENV = production
# 打包时是否删除 console
VITE_DROP_CONSOLE = true
# 接口地址
VITE_API_URL = "https://trans.yyhome2022.com/"

1
jjstore/.eslintignore Normal file
View File

@ -0,0 +1 @@
postcss.config.js

19
jjstore/.eslintrc.cjs Normal file
View File

@ -0,0 +1,19 @@
/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution");
module.exports = {
root: true,
extends: [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/eslint-config-typescript",
"@vue/eslint-config-prettier/skip-formatting"
],
rules: {
// 关闭组件命名规则
"vue/multi-word-component-names": "off"
},
parserOptions: {
ecmaVersion: "latest"
},
};

30
jjstore/.gitignore vendored Normal file
View File

@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
jjstore
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
jjstore.zip

9
jjstore/.prettierignore Normal file
View File

@ -0,0 +1,9 @@
/dist/*
.local
/node_modules/**
**/*.svg
**/*.sh
/public/*
stats.html

41
jjstore/.prettierrc.cjs Normal file
View File

@ -0,0 +1,41 @@
// @see: https://www.prettier.cn
module.exports = {
// 指定最大换行长度
printWidth: 130,
// 缩进制表符宽度 | 空格数
tabWidth: 2,
// 使用制表符而不是空格缩进行 (true制表符false空格)
useTabs: false,
// 结尾不用分号 (truefalse没有)
semi: true,
// 使用单引号 (true单引号false双引号)
singleQuote: false,
// 在对象字面量中决定是否将属性名用引号括起来 可选值 "<as-needed|consistent|preserve>"
quoteProps: "as-needed",
// 在JSX中使用单引号而不是双引号 (true单引号false双引号)
jsxSingleQuote: false,
// 多行时尽可能打印尾随逗号 可选值"<none|es5|all>"
trailingComma: "none",
// 在对象,数组括号与文字之间加空格 "{ foo: bar }" (truefalse没有)
bracketSpacing: true,
// 将 > 多行元素放在最后一行的末尾,而不是单独放在下一行 (true放末尾false单独一行)
bracketSameLine: false,
// (x) => {} 箭头函数参数只有一个时是否要有小括号 (avoid省略括号always不省略括号)
arrowParens: "avoid",
// 指定要使用的解析器,不需要写文件开头的 @prettier
requirePragma: false,
// 可以在文件顶部插入一个特殊标记,指定该文件已使用 Prettier 格式化
insertPragma: false,
// 用于控制文本是否应该被换行以及如何进行换行
proseWrap: "preserve",
// 在html中空格是否是敏感的 "css" - 遵守 CSS 显示属性的默认值, "strict" - 空格被认为是敏感的 "ignore" - 空格被认为是不敏感的
htmlWhitespaceSensitivity: "css",
// 控制在 Vue 单文件组件中 <script> 和 <style> 标签内的代码缩进方式
vueIndentScriptAndStyle: false,
// 换行符使用 lf 结尾是 可选值 "<auto|lf|crlf|cr>"
endOfLine: "auto",
// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码 (rangeStart开始rangeEnd结束)
rangeStart: 0,
rangeEnd: Infinity
};

46
jjstore/README.md Normal file
View File

@ -0,0 +1,46 @@
# JSStore-web
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension
1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
pnpm install
```
### Compile and Hot-Reload for Development
```sh
pnpm dev
```
### Type-Check, Compile and Minify for Production
```sh
pnpm build
```
### Lint with [ESLint](https://eslint.org/)
```sh
pnpm lint
```

1
jjstore/env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

22
jjstore/index.html Normal file
View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>甬恒瑶瑶</title>
<script>
var _hmt = _hmt || [];
(function () {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?ed3b73bcf1748a77e36d3ab107055408";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

9
jjstore/package.bat Normal file
View File

@ -0,0 +1,9 @@
@echo off
rmdir /s /q jjstore
del jjstore.zip
call pnpm build
7z a -tzip jjstore.zip jjstore

49
jjstore/package.json Normal file
View File

@ -0,0 +1,49 @@
{
"name": "jsstore-web",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "run-p type-check build-only",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit -p tsconfig.json --composite false",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/"
},
"dependencies": {
"@element-plus/icons-vue": "^2.1.0",
"@wangeditor/editor": "^5.1.23",
"axios": "^1.4.0",
"pinia": "^2.1.3",
"vue": "^3.3.4",
"vue-router": "^4.2.2"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.2.0",
"@tsconfig/node18": "^2.0.1",
"@types/node": "^18.16.17",
"@types/qrcode": "^1.5.0",
"@vitejs/plugin-vue": "^4.2.3",
"@vitejs/plugin-vue-jsx": "^3.0.1",
"@vue/eslint-config-prettier": "^7.1.0",
"@vue/eslint-config-typescript": "^11.0.3",
"@vue/tsconfig": "^0.4.0",
"@vueuse/core": "^10.2.1",
"@wangeditor/editor-for-vue": "^5.1.12",
"element-plus": "^2.3.7",
"eslint": "^8.39.0",
"eslint-plugin-vue": "^9.11.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.8",
"qrcode": "^1.5.3",
"sass": "^1.63.6",
"typescript": "~5.0.4",
"unplugin-auto-import": "^0.16.4",
"unplugin-vue-components": "^0.25.1",
"unplugin-vue-setup-extend-plus": "^1.0.0",
"vite": "^4.3.9",
"vite-plugin-svg-icons": "^2.0.1",
"vue-tsc": "^1.6.5"
}
}

6239
jjstore/pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

BIN
jjstore/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

11
jjstore/src/App.vue Normal file
View File

@ -0,0 +1,11 @@
<template>
<el-config-provider :locale="zhCn">
<RouterView />
</el-config-provider>
</template>
<script setup lang="ts">
import zhCn from "element-plus/dist/locale/zh-cn.mjs";
import { RouterView } from "vue-router";
</script>
<style scoped></style>

View File

@ -0,0 +1,39 @@
import http from "@/utils/http";
import { Address, Resp, RespPage } from "./types/types";
/**
*
* @returns
*/
export const addressPage = (params: any) => {
return http.post<RespPage<Address.AddressInfo>>("/shop-user-service/api/user/address/page", params);
};
/**
*
* @param id ID
*/
export const getAddress = (id: string) => {
return http.get<Resp<Address.AddressInfo>>(`/shop-user-service/api/user/address/get/${id}`);
};
/**
*
* @param params
*/
export const addAddress = (params: Partial<Address.AddressInfo>) => {
return http.post<Resp<boolean>>("/shop-user-service/api/user/address/add", params);
};
/**
*
* @param params
*/
export const updateAddress = (params: Partial<Address.AddressInfo>) => {
return http.put<Resp<boolean>>("/shop-user-service/api/user/address/upd", params);
};
/**
*
* @param id ID
*/
export const deleteAddress = (id: string) => {
return http.delete<Resp<boolean>>(`/shop-user-service/api/user/address/del/${id}`);
};

17
jjstore/src/api/banner.ts Normal file
View File

@ -0,0 +1,17 @@
import http from "@/utils/http";
import { Banner, Resp } from "./types/types";
/**
* banner
* @returns
*/
export const getBanner1 = () => {
return http.get<Resp<Banner.BannerInfo[]>>("/shop-platform-service/api/banner/listByType/1");
};
/**
* banner
* @returns
*/
export const getBanner2 = () => {
return http.get<Resp<Banner.BannerInfo[]>>("/shop-platform-service/api/banner/listByType/2");
};

View File

@ -0,0 +1,18 @@
import http from "@/utils/http";
import { Classify, Resp } from "./types/types";
/**
*
* @returns
*/
export const queryClassifyAll = () => {
return http.get<Resp<Classify.ClassifyInfo[]>>("/shop-shop-service/api/user/classify/queryClassifyAll");
};
/**
*
* @returns
*/
export const queryTopAll = () => {
return http.get<Resp<Classify.ClassifyInfo[]>>("/shop-shop-service/api/user/classify/queryTopAll");
};

10
jjstore/src/api/common.ts Normal file
View File

@ -0,0 +1,10 @@
import http from "@/utils/http";
import { Resp } from "./types/types";
/**
* @name:
*/
// 文件上传
export const uploadFile = (params: FormData) => {
return http.post<Resp<string>>("/shop-platform-service/api/source/image", params);
};

View File

@ -0,0 +1,17 @@
import http from "@/utils/http";
import { Information, Resp, RespPage } from "./types/types";
/**
*
* @param params
*/
export const queryInformationPage = (params: Information.PageReq) => {
return http.post<RespPage<Information.InformationPageRes>>("/shop-consult-service/api/consult/page", params);
};
/**
*
* @param id ID
*/
export const getInformationInfo = (id: string) => {
return http.get<Resp<Information.InformationPageRes>>(`/shop-consult-service/api/consult/getById/${id}`);
};

20
jjstore/src/api/login.ts Normal file
View File

@ -0,0 +1,20 @@
import http from "@/utils/http";
import { Login, Resp } from "./types/types";
/**
*
* @param params
* @returns
*/
export const login = (params: Login.LoginReq) => {
return http.post<Resp<Login.LoginRes>>("/shop-user-service/api/user/login", params);
};
/**
*
* @param phone
* @returns
*/
export const sendSms = (phone: string) => {
return http.get<Resp<boolean>>(`/shop-user-service/api/user/sendSms/${phone}`);
};

View File

@ -0,0 +1,11 @@
import http from "@/utils/http";
import { Message, RespPage } from "./types/types";
/**
*
* @param params
* @returns
*/
export const getMessageList = (params: Message.PageReq) => {
return http.post<RespPage<Message.MessageInfo>>("/shop-platform-service/api/message/page", params);
};

40
jjstore/src/api/order.ts Normal file
View File

@ -0,0 +1,40 @@
import http from "@/utils/http";
import { Order, Resp, RespPage } from "./types/types";
/**
*
*/
export const userOrderPage = (params: Order.UserPageReq) => {
return http.post<RespPage<Order.UserOrderInfo>>("/shop-shop-service/api/shop/order-user/listPageUser", params);
};
/**
*
* @param params
*/
export const storeOrderPage = (params: Order.StorePageReq) => {
return http.post<RespPage<Order.StoreOrderInfo>>("/shop-shop-service/api/shop/order-store/listPage", params);
};
/**
*
* @param params
*/
export const orderLogistics = (params: Order.LogisticsReq) => {
return http.post<Resp<boolean>>("/shop-shop-service/api/shop/order-store/logistics", params);
};
/**
*
* @param orderId ID
*/
export const getOrderDetail = (orderId: string) => {
return http.get<Resp<Order.OrderDetail>>(`/shop-shop-service/api/shop/order-user/getInfo/${orderId}`);
};
/**
*
* @param orderId ID
*/
export const signOrder = (orderId: string) => {
return http.get<Resp<boolean>>(`/shop-shop-service/api/shop/order-user/signFor/${orderId}`);
};

View File

@ -0,0 +1,33 @@
import http from "@/utils/http";
import { OrderCard, Resp, RespPage } from "./types/types";
/**
*
*/
export const orderCardPage = (params: OrderCard.PageReq) => {
return http.post<RespPage<OrderCard.OrderCardInfo>>("/shop-shop-service/api/shop/card/cardPage", params);
};
/**
*
*/
export const addOrderCard = (params: OrderCard.AddOrderCardReq) => {
return http.post<Resp<boolean>>("/shop-shop-service/api/shop/card/addCard", params);
};
/**
*
* @param orderCardId ID
* @param type
*/
export const hitShelf = (orderCardId: string, type: string) => {
return http.get<Resp<boolean>>(`/shop-shop-service/api/shop/card/hitShelf/${orderCardId}/${type}`);
};
/**
*
* @param orderCardId ID
* @param newNumber
*/
export const hitShelfNumber = (orderCardId: string, newNumber: number) => {
return http.get<Resp<boolean>>(`/shop-shop-service/api/shop/card/hitShelfNumber/${orderCardId}/${newNumber}`);
};

17
jjstore/src/api/pay.ts Normal file
View File

@ -0,0 +1,17 @@
import http from "@/utils/http";
import { Pay, Resp } from "./types/types";
/**
*
* @param params
*/
export const createOrder = (params: Pay.CreateOrderReq) => {
return http.post<Resp<string>>("/shop-shop-service/api/shop/order-place/place", params);
};
/**
*
* @param orderId
*/
export const toPay = (orderId: string) => {
return http.get<Resp<Pay.PayInfo>>(`/shop-shop-service/api/shop/order-pay/pay/${orderId}`);
};

View File

@ -0,0 +1,10 @@
import http from "@/utils/http";
import { Config, Resp } from "./types/types";
/**
*
* @param config
*/
export const getConfig = (config: string) => {
return http.get<Resp<Config.ConfigInfo>>(`/shop-platform-service/api/config/getByCode/${config}`);
};

View File

@ -0,0 +1,71 @@
import http from "@/utils/http";
import { Product, Resp, RespPage } from "./types/types";
/**
*
* @returns
*/
export const productPage = (params: Product.PageReq) => {
return http.post<RespPage<Product.ProductInfo>>("/shop-shop-service/api/shop/product-my/queryPage", params);
};
/**
*
* @returns
*/
export const createProduct = (params: Product.ProductEdit) => {
return http.post<Resp<boolean>>("/shop-shop-service/api/shop/product-my/createProduct", params);
};
/**
*
* @returns
*/
export const getProductEdit = (productId: string) => {
return http.get<Resp<Product.ProductEdit>>(`/shop-shop-service/api/shop/product-my/getProductInfo/${productId}`);
};
/**
*
* @param params
*/
export const updateProduct = (productId: string, params: Product.ProductEdit) => {
return http.post<Resp<boolean>>("/shop-shop-service/api/shop/product-my/udpProduct", {
...params,
id: productId
});
};
/**
*
* @param productId ID
*/
export const deleteProduct = (productId: string) => {
return http.get<Resp<boolean>>(`/shop-shop-service/api/shop/product-my/deleteByProductId/${productId}`);
};
// ======================= 展示页面 START ======================
/**
* ,
*/
export const getProductInfo = (productId: string) => {
return http.get<
Resp<{
product: Product.ProductInfo;
skuList: Product.SkuDtoInfo[];
}>
>(`/shop-shop-service/api/shop/product/getById/${productId}`);
};
/**
*
* @param classifyId ID
*/
export const queryByClassifyId = (classifyId: string) => {
return http.get<Resp<Product.ProductInfo[]>>(`/shop-shop-service/api/shop/product/productHome/${classifyId}`);
};
/**
*
* @param params
*/
export const searchProduct = (params: Product.SearchProductReq) => {
return http.post<RespPage<Product.ProductInfo>>("/shop-shop-service/api/shop/product/queryProduct", params);
};
// ======================= 展示页面 END ======================

View File

@ -0,0 +1,19 @@
import http from "@/utils/http";
import { RespPage, Statistics, StoreUser } from "./types/types";
/**
*
*/
export const statisticsPage = (params: Statistics.PageReq) => {
return http.post<RespPage<Statistics.StatisticsInfo>>(
"/shop-shop-service/api/shop/store-statistics/statisticsProductPage",
params
);
};
/**
*
*/
export const statisticsUserPage = (params: StoreUser.PageReq) => {
return http.post<RespPage<StoreUser.StoreUser>>("/shop-shop-service/api/shop/store-statistics/statisticsUserPage", params);
};

29
jjstore/src/api/store.ts Normal file
View File

@ -0,0 +1,29 @@
import http from "@/utils/http";
import { Resp, Store } from "./types/types";
/**
*
* @returns
*/
export const createStore = (params: Store.createStoreReq) => {
return http.post<Resp<unknown>>("/shop-user-service/api/user/store/createStore", params);
};
/**
* ,
*/
export const getStoreEdit = () => {
return http.get<Resp<Store.getStoreEdit>>("/shop-user-service/api/user/store/getStore");
};
/**
*
*/
export const payDeposit = () => {
return http.post<Resp<{ qrCodeImageUrl: string }>>("/shop-user-service/api/user/store/payDeposit");
};
/**
*
* @param storeId ID
*/
export const getStoreInfo = (storeId: string) => {
return http.get<Resp<Store.StoreInfo>>(`/shop-user-service/api/user/store/getStoreInfo/${storeId}`);
};

View File

@ -0,0 +1,27 @@
import http from "@/utils/http";
import { Resp, RespPage, StoreWallet } from "./types/types";
/**
*
*/
export const getWallet = () => {
return http.get<Resp<StoreWallet.WalletInfo>>("/shop-user-service/api/user/store-wallet/getWallet");
};
/**
*
* @param params
* @returns
*/
export const queryWalletLog = (params: StoreWallet.PageReq) => {
return http.post<RespPage<StoreWallet.WalletLog>>("/shop-user-service/api/user/store-wallet/queryWalletLog", params);
};
/**
*
* @param params
* @returns
*/
export const withdraw = (params: StoreWallet.WithdrawReq) => {
return http.post<Resp<boolean>>("/shop-user-service/api/user/store-wallet/withdraw", params);
};

View File

@ -0,0 +1,641 @@
/**
*
*/
export interface Resp<T> {
code: string;
data: T;
message: string;
ok: boolean;
}
/**
*
*/
interface RespPageData<E> {
current: 1;
pages: 0;
records: E[];
size: 10;
total: 0;
}
export type RespPage<E> = Resp<RespPageData<E>>;
/**
*
*/
export interface BasePageReq<T> {
pageNum: number;
pageSize: number;
search: T;
}
/**
*
*/
export namespace Login {
/**
*
*/
export interface LoginReq {
username: string;
velCode: string;
type: number;
}
/**
*
*/
export interface LoginRes {
uid: string;
userName: string;
token: string;
}
}
/**
*
*/
export namespace UserInfo {
/**
*
*/
export interface UserInfoRes {
id: string;
createDate: string;
loginName: string;
codes: string;
userName: string;
avatar: string;
emails: string;
sex: number;
introduction: string;
type: number;
storeId: string;
}
}
/**
*
*/
export namespace Classify {
/**
*
*/
export interface ClassifyInfo {
id: string;
names: string;
voList: ClassifyInfo[];
}
}
/**
*
*/
export namespace Information {
/**
*
*/
export type PageReq = BasePageReq<string>;
/**
*
*/
export interface InformationPageRes {
id: string;
createDate: string;
updateDate: string;
titles: string;
images: string;
introduction: string;
contents: string;
}
}
/**
*
*/
export namespace Product {
/**
*
*/
export interface PageSearch {
classifyId?: string;
status?: number;
}
export type PageReq = BasePageReq<PageSearch>;
/**
*
*/
export interface SkuDtoEdit {
names: string;
inventory: number;
price: number;
}
/**
*
*/
export interface ProductEdit {
names: string;
cover: string;
carousels: string;
classifyId: string;
classifyErId: string;
describe: string;
introduce: string;
price: number;
status: number;
skuDtoList: SkuDtoEdit[];
}
/**
*
*/
export interface ProductInfo {
id: string;
names: string;
cover: string;
carousels: string;
classifyId: string;
classifyErId: string;
describe: string;
introduce: string;
price: string;
status: number;
storeId: string;
createDate: string;
updateDate: string;
listdDate: string;
}
/**
*
*/
export interface SkuDtoInfo {
id: string;
names: string;
inventory: number;
price: number;
productId: string;
updateDate: string;
createDate: string;
}
/**
* 使
*/
export interface SearchProductReq {
pageNum: number;
pageSize: number;
search: {
names: string;
storeId: string;
classifyId: string;
classifyErId: string;
province: string;
city: string;
orderByPrice: number;
};
}
}
/**
*
*/
export namespace Store {
/**
*
*/
export interface createStoreReq {
storeName: string;
avatar: string;
province: string;
city: string;
address: string;
manageMode: string;
chargeNames: string;
chargePhone: string;
chargeEmail: string;
legalPersonNames: string;
legalPersonPhone: string;
legalPersonEmail: string;
legalPersonIdcardFront: string;
legalPersonIdcardBack: string;
companyName: string;
companyCodes: string;
profession: string;
companyImages: string;
}
/**
*
*/
export interface getStoreEdit {
id: string;
createDate: string;
updateDate: string;
storeName: string;
avatar: string;
province: string;
city: string;
address: string;
manageMode: string;
label: string;
chargeNames: string;
chargePhone: string;
chargeEmail: string;
legalPersonNames: string;
legalPersonPhone: string;
legalPersonEmail: string;
legalPersonIdcardFront: string;
legalPersonIdcardBack: string;
companyName: string;
companyCodes: string;
profession: string;
companyImages: string;
guaranteePrice: number;
guaranteeDate: string | null;
expireDate: string | null;
step: number;
audit: number;
}
/**
*
*/
export interface StoreInfo {
id: string;
storeName: string;
avatar: string;
address: string;
manageMode: string;
label: string;
chargePhone: string;
profession: string;
auditDate: string;
}
}
/**
*
*/
export namespace Address {
/**
*
*/
export type PageReq = BasePageReq<string>;
/**
*
*/
export interface AddressInfo {
id: string;
createDate: string;
updateDate: string;
userId: string;
province: string;
city: string;
area: string;
address: string;
userName: string;
phones: string;
isDefault: number;
postcode: string;
}
}
/**
* 广
*/
export namespace Banner {
/**
* 广
*/
export interface BannerInfo {
id: string;
createDate: string;
updateDate: string;
names: string;
imargs: string;
contentUrl: string;
sort: number;
type: number;
}
}
/**
*
*/
export namespace OrderCard {
/**
*
*/
export type PageReq = BasePageReq<string>;
/**
*
*/
export interface OrderCardInfo {
id: string;
createDate: string;
updateDate: string;
userId: string;
storeId: string;
productId: string;
skuId: string;
numbers: number;
storeName: string;
cover: string;
productNames: string;
skuNames: string;
price: number;
sumPrice: number;
}
/**
*
*/
export interface AddOrderCardReq {
productId: string;
skuId: string;
numbers: number;
}
}
/**
*
*/
export namespace Order {
/**
*
*/
export interface UserPageSearch {
states?: number;
}
/**
*
*/
export type UserPageReq = BasePageReq<UserPageSearch>;
/**
*
*/
export interface UserOrderInfo {
id: string;
tradeId: string;
storeName: string;
productName: string;
price: number;
states: number;
payTime: string;
deliveryTime: string;
logisticsStates: number;
logisticsNames: string;
logisticsCode: string;
}
/**
*
*/
export interface StorePageSearch {
phones?: string;
states?: number;
beginData?: string;
endData?: string;
}
/**
*
*/
export type StorePageReq = BasePageReq<StorePageSearch>;
/**
*
*/
export interface StoreOrderInfo {
id: string;
createDate: string;
updateDate: string;
userId: string;
storeId: string;
tradeId: string;
outTradeId: string;
price: number;
payType: string;
payTime: string;
deliveryTime: string;
verifyTime: string;
orderTime: string;
states: number;
logisticsStates: number;
logisticsNames: string;
logisticsCode: string;
refundDate: string;
storeName: string;
productName: string;
userName: string;
userPhones: string;
}
/**
*
*/
export interface LogisticsReq {
id?: string;
logisticsCode?: string;
logisticsNames?: string;
}
/**
*
*/
export interface OrderDetail {
shopOrders: StoreOrderInfo;
productList: Product.ProductInfo[];
address: Address.AddressInfo;
}
}
/**
*
*/
export namespace Pay {
/**
*
*/
export interface CreateOrderReq {
productId: string;
skuId: string;
addressId: string;
number: number;
}
/**
*
*/
export interface PayInfo {
sn: string;
clientSn: string;
paywayName: string;
qrCodeImageUrl: string;
subject: string;
}
}
/**
*
*/
export namespace Statistics {
/**
*
*/
export interface PageSearch {
beginData?: string;
endData?: string;
}
/**
*
*/
export type PageReq = BasePageReq<PageSearch>;
/**
*
*/
export interface StatisticsInfo {
id: string;
names: string;
orderNumber: number;
orderPrices: number;
orderUserNumber: number;
orderNumberToDay: number;
}
}
/**
*
*/
export namespace Message {
/**
*
*/
export type PageReq = BasePageReq<string>;
/**
*
*/
export interface MessageInfo {
id: string;
createDate: string;
updateDate: string;
userId: string;
titles: string;
contents: string;
}
}
/**
*
*/
export namespace StoreUser {
/**
*
*/
export interface PageSearch {
phones: string;
}
/**
*
*/
export type PageReq = BasePageReq<PageSearch>;
/**
*
*/
export interface StoreUser {
id: string;
codes: string;
userName: string;
avatar: string;
loginName: string;
adress: string;
}
}
/**
*
*/
export namespace StoreWallet {
export interface PageSearch {
beginData?: string;
endData?: string;
}
/**
*
*/
export type PageReq = BasePageReq<PageSearch>;
/**
*
*/
export interface WalletLog {
id: string;
createDate: string;
updateDate: string;
storeId: string;
amount: number;
surplusAmount: number;
type: number;
isAudit: number;
reason: string;
bigType: number;
auditTime: string;
cardNumber: string;
cardName: string;
remitTime: string;
remitType: number;
}
/**
*
*/
export interface WalletInfo {
id: string;
createDate: string;
updateDate: string;
storeId: string;
amount: number;
surplusAmount: number;
withdrawAmount: number;
}
/**
*
*/
export interface WithdrawReq {
cardNumber: string;
cardName: string;
amount: number;
}
}
/**
*
*/
export namespace Config {
/**
*
*/
export interface ConfigInfo {
id: string;
createDate: string;
updateDate: string;
code: string;
names: string;
contents: string;
}
}

17
jjstore/src/api/user.ts Normal file
View File

@ -0,0 +1,17 @@
import http from "@/utils/http";
import { Resp, UserInfo } from "./types/types";
/**
*
* @returns
*/
export const getUserInfo = () => {
return http.get<Resp<UserInfo.UserInfoRes>>("/shop-user-service/api/user/userInfo");
};
/**
*
* @param params
* @returns
*/
export const saveUserInfo = (params: UserInfo.UserInfoRes) => {
return http.post<Resp<boolean>>("/shop-user-service/api/user/userInfo", params);
};

View File

@ -0,0 +1,5 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M24.2189 37.8686C24.4534 37.8686 24.6921 37.8542 24.9324 37.8256C27.2367 37.5491 29.1933 35.9384 29.9155 33.7189C29.97 33.5529 29.8913 33.375 29.7324 33.3021C29.5752 33.2289 29.3851 33.2921 29.2962 33.4411C29.2878 33.4553 28.3722 34.934 24.8081 35.4154C24.2745 35.487 23.7554 35.5245 23.2633 35.5245C20.7662 35.5229 19.6534 34.5817 19.6432 34.5731C19.5176 34.4613 19.3258 34.4584 19.197 34.5673C19.0669 34.6761 19.0369 34.8653 19.1257 35.0101C20.2127 36.7724 22.1633 37.8686 24.2189 37.8686ZM43.3831 15.7797C43.3517 15.7797 43.3231 15.7855 43.2932 15.7855C41.2048 6.74433 33.2252 0 23.6581 0C13.8278 0 5.66507 7.11677 3.85585 16.5321C2.09674 16.8487 0.757812 18.3948 0.757812 20.2647V27.7485C0.757812 29.846 2.43983 31.5469 4.51665 31.5469C5.68817 31.5469 6.72221 30.9936 7.41008 30.144C9.07215 34.54 12.4558 38.0845 16.738 39.9546C16.7482 39.9323 16.8292 39.7746 16.9268 39.649C16.9942 39.5619 17.0698 39.4906 17.1346 39.4906C17.2017 39.4906 17.2634 39.515 17.3161 39.5522C16.325 38.8143 12.7452 35.0214 11.9697 29.7285C11.6295 27.3988 13.3745 25.1118 15.4067 24.7336C18.6692 24.1261 21.9147 23.4341 25.1772 22.8381C27.2511 22.4598 28.6684 21.3221 29.5351 19.4279C29.7382 18.9851 30.0314 18.0898 30.166 16.7986C30.2017 16.6066 30.3591 16.4592 30.5608 16.4592C30.6954 16.4592 30.8097 16.5279 30.8856 16.6268L30.9756 16.5709C32.2614 18.4378 34.8115 22.5715 35.1777 26.9303C35.5982 31.9136 35.3636 35.3267 31.5493 38.8185C31.5448 38.8229 31.539 38.8284 31.5335 38.8329C31.4805 38.8901 31.449 38.9646 31.449 39.0464C31.449 39.1537 31.5062 39.2455 31.5891 39.3014C31.6206 39.3143 31.6521 39.3316 31.6836 39.3444C31.7092 39.3502 31.7337 39.3586 31.7594 39.3586C31.7853 39.3586 31.8081 39.3502 31.831 39.3444C31.8853 39.3158 31.9367 39.2828 31.9897 39.2542C35.8471 37.1291 38.8064 33.5802 40.1738 29.3116C40.7274 30.134 41.5941 30.7258 42.6009 30.9323C40.986 38.2768 34.4356 42.8735 26.3201 43.5712C25.8337 42.3648 24.6365 41.508 23.2305 41.508C21.3942 41.508 19.9053 42.9624 19.9053 44.7548C19.9053 46.5473 21.3939 48 23.2305 48C24.711 48 25.951 47.0488 26.3814 45.7419C35.7755 44.9783 43.306 39.3945 44.8781 30.7187C46.2668 30.1382 47.2421 28.7757 47.2421 27.1852V19.6128C47.2421 17.4964 45.5142 15.7797 43.3831 15.7797ZM39.9176 17.9549C37.4675 11.3179 31.1185 6.57665 23.6497 6.57665C16.212 6.57665 9.88586 11.2777 7.41296 17.8718C7.28994 17.7212 7.14562 17.5924 7.00399 17.462C8.40396 9.42655 15.3196 3.31703 23.6581 3.31703C31.9566 3.31703 38.8507 9.36645 40.2966 17.3461C40.1537 17.5381 40.022 17.7385 39.9176 17.9549ZM16.7385 39.9546C16.7382 39.9546 16.7382 39.9546 16.738 39.9546C16.7364 39.958 16.7364 39.9585 16.7385 39.9546Z"
fill="#0068B7" fill-opacity="0.5" />
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,8 @@
<svg width="37" height="36" viewBox="0 0 37 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M19.1652 18.9611C20.9793 17.8099 22.1862 15.7861 22.1862 13.4836C22.1862 9.90816 19.2781 7 15.7027 7C12.1272 7 9.22053 9.90816 9.22053 13.4836C9.22053 15.7861 10.4275 17.8114 12.2416 18.9611C8.59578 20.3587 6 23.8931 6 28.0244C6 28.4702 6.36077 28.831 6.8066 28.831C7.25243 28.831 7.6132 28.4702 7.6132 28.0244C7.6132 23.7201 10.9921 20.1916 15.2363 19.9481C15.3903 19.9598 15.5472 19.9672 15.7041 19.9672C15.861 19.9672 16.0165 19.9598 16.1719 19.9481C20.4161 20.1916 23.795 23.7201 23.795 28.0244C23.795 28.4702 24.1558 28.831 24.6016 28.831C25.0475 28.831 25.4082 28.4702 25.4082 28.0244C25.4082 23.8931 22.811 20.3587 19.1652 18.9611ZM10.8337 13.4836C10.8337 10.7983 13.0189 8.6132 15.7041 8.6132C18.3894 8.6132 20.5745 10.7983 20.5745 13.4836C20.5745 16.0163 18.6313 18.1032 16.1558 18.332C16.0062 18.3247 15.8552 18.3203 15.7041 18.3203C15.5531 18.3203 15.402 18.3247 15.2524 18.332C12.7769 18.1032 10.8337 16.0163 10.8337 13.4836Z"
fill="currentColor" />
<path
d="M26.7458 18.417C28.1126 17.4403 29.0057 15.8418 29.0057 14.0379C29.0057 11.0697 26.5918 8.65573 23.6235 8.65573C23.1777 8.65573 22.8169 9.0165 22.8169 9.46233C22.8169 9.90816 23.1777 10.2689 23.6235 10.2689C25.7016 10.2689 27.3925 11.9599 27.3925 14.0379C27.3925 15.9943 25.8937 17.6075 23.9828 17.7894C23.864 17.7835 23.7438 17.7806 23.6235 17.7806C23.1777 17.7806 22.8169 18.1413 22.8169 18.5872V18.6136C22.8169 19.0594 23.1777 19.4202 23.6235 19.4202C23.7496 19.4202 23.8757 19.4143 23.9989 19.4055C27.3338 19.6005 29.9868 22.3753 29.9868 25.7571C29.9868 26.2029 30.3476 26.5637 30.7934 26.5637C31.2393 26.5637 31.6 26.2029 31.6 25.7571C31.6015 22.4662 29.5967 19.6343 26.7458 18.417Z"
fill="currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,5 @@
<svg width="37" height="36" viewBox="0 0 37 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M16.4282 26.3125C17.2939 26.3125 17.9959 27.0398 17.9959 27.9371C17.9959 28.8346 17.2939 29.5612 16.4282 29.5612C15.5623 29.5612 14.8604 28.8346 14.8604 27.9371C14.8604 27.0398 15.5623 26.3125 16.4282 26.3125ZM23.4608 26.3125C24.3265 26.3125 25.0284 27.0398 25.0284 27.9371C25.0284 28.8346 24.3265 29.5612 23.4608 29.5612C22.5946 29.5612 21.893 28.8346 21.893 27.9371C21.893 27.0398 22.5946 26.3125 23.4608 26.3125ZM25.4141 19.6829C27.8521 19.5753 29.7971 17.4941 29.7971 14.9413C29.7971 12.3885 27.8522 10.3073 25.4141 10.1996V10.1945H10.6481L9.89783 7.95574C9.73115 6.76756 8.69471 5.83795 7.536 5.83795H5.18395C4.6645 5.83795 4.24329 6.27419 4.24329 6.81251C4.24329 7.35093 4.6645 7.78717 5.18395 7.78717H7.53605C7.76111 7.78717 8.00406 8.00546 8.03586 8.23658C8.03633 8.23993 8.03714 8.24322 8.03733 8.24647L11.6914 22.8956C11.8582 24.0832 12.8949 25.0129 14.0537 25.0129H26.4071C26.9268 25.0129 27.3479 24.5766 27.3479 24.0383C27.3479 23.5003 26.9268 23.0637 26.4071 23.0637H14.0537C13.8285 23.0637 13.585 22.8453 13.5535 22.6143C13.5529 22.611 13.5523 22.6081 13.552 22.6048L12.8696 19.6883H25.4141V19.6829ZM11.1137 12.1839H25.4141V12.1942C26.7908 12.2999 27.877 13.4878 27.877 14.9413C27.877 16.3948 26.7908 17.5827 25.4141 17.6883V17.6987H12.4038L11.1137 12.1839Z"
fill="currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,11 @@
<svg width="27" height="26" viewBox="0 0 27 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M6.44318 11.6382C7.06918 12.0291 7.80097 12.2392 8.55628 12.2392C9.31158 12.2392 10.0434 12.0291 10.6694 11.6382C11.2954 12.0291 12.0272 12.2392 12.7825 12.2392C13.5392 12.2392 14.2696 12.0291 14.8956 11.6382C15.5216 12.0291 16.2534 12.2392 17.0087 12.2392C17.7654 12.2392 18.4958 12.0291 19.1218 11.6382C19.7477 12.0291 20.4795 12.2392 21.2349 12.2392C23.0614 12.2392 24.6249 11.0401 25.0378 9.32231C25.1818 8.72571 25.1201 8.10118 24.863 7.5178L22.3281 1.79275C21.8462 0.703875 20.7441 0 19.52 0H6.16986C4.96931 0 3.87455 0.686242 3.38228 1.7472L0.726949 7.48107C0.435994 8.10853 0.378685 8.81387 0.565308 9.46926C1.02966 11.1004 2.57848 12.2392 4.33009 12.2392C5.08686 12.2392 5.81719 12.0291 6.44318 11.6382ZM2.12001 9.02695C2.03918 8.74481 2.06563 8.43622 2.19348 8.15996L4.84881 2.42756C5.07658 1.93529 5.5953 1.61789 6.16986 1.61789H19.5229C20.1078 1.61789 20.6294 1.94411 20.8528 2.44813L23.3876 8.17319C23.5008 8.42887 23.5287 8.69632 23.4699 8.94466C23.2319 9.93214 22.3149 10.6228 21.2378 10.6228C20.65 10.6228 20.0916 10.4112 19.6625 10.0291L19.1232 9.54714L18.5839 10.0291C18.1563 10.4112 17.5964 10.6228 17.0101 10.6228C16.4223 10.6228 15.8639 10.4112 15.4363 10.0291L14.897 9.54714L14.3577 10.0291C13.9301 10.4112 13.3703 10.6228 12.7825 10.6228C12.1947 10.6228 11.6363 10.4112 11.2072 10.0291L10.6679 9.54714L10.1286 10.0291C9.70099 10.4112 9.14113 10.6228 8.55481 10.6228C7.96849 10.6228 7.40862 10.4112 6.98101 10.0291L6.44171 9.54714L5.90242 10.0291C5.4748 10.4127 4.91493 10.6228 4.32715 10.6228C3.29705 10.6228 2.38745 9.96594 2.12001 9.02695Z"
fill="currentColor" />
<path
d="M7.77307 5.74265H17.8007V7.35907H7.77307V5.74265ZM20.1504 13.3971C16.8926 13.3971 14.2431 16.0466 14.2431 19.3044C14.2431 22.5622 16.8926 25.2116 20.1504 25.2116C23.4082 25.2116 26.0577 22.5622 26.0577 19.3044C26.0577 16.0466 23.4067 13.3971 20.1504 13.3971ZM20.1504 23.5952C17.7846 23.5952 15.8596 21.6702 15.8596 19.3044C15.8596 16.9385 17.7846 15.0135 20.1504 15.0135C22.5162 15.0135 24.4413 16.9385 24.4413 19.3044C24.4413 21.6702 22.5148 23.5952 20.1504 23.5952Z"
fill="currentColor" />
<path
d="M3.85836 21.3866V15.1502C4.55048 15.1061 5.32048 14.965 6.0126 14.6271C7.52763 15.5719 9.55843 15.2472 10.6943 14.6197C12.8765 15.7556 15.3011 14.7358 15.7875 14.2112L14.6031 13.1106C14.6149 13.0988 14.6281 13.0827 14.6487 13.068C14.4444 13.2208 12.6355 14.0408 11.1264 12.996L10.5944 12.6286L10.1183 13.0665C9.70244 13.45 7.73776 14.0731 6.60627 13.0591L6.13016 12.633L5.60556 12.9974C4.85025 13.5235 3.69672 13.5661 3.07661 13.5455L2.24048 13.5176V21.3866C2.24048 23.4262 3.89951 25.0838 5.93766 25.0838H14.5326V23.4674H5.9406C4.79295 23.4689 3.85836 22.5343 3.85836 21.3866ZM20.9586 16.6138H19.3422V18.4962H17.4583V20.1126H19.3422V21.9965H20.9586V20.1126H22.841V18.4962H20.9586V16.6138Z"
fill="currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1,8 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M17.6873 22C13.0016 22 9.18726 18.1883 9.18726 13.5C9.18726 8.81172 13.0016 5 17.6873 5C22.3729 5 26.1873 8.81172 26.1873 13.5C26.1873 18.1883 22.3729 22 17.6873 22ZM17.6873 6.7C13.9366 6.7 10.8873 9.74937 10.8873 13.5C10.8873 17.2506 13.9366 20.3 17.6873 20.3C21.4379 20.3 24.4873 17.2506 24.4873 13.5C24.4873 9.74937 21.4379 6.7 17.6873 6.7Z"
fill="currentColor" />
<path
d="M26.668 30.5H8.70642C8.16721 30.5 7.67314 30.253 7.34642 29.8227C7.02236 29.395 6.91877 28.8531 7.06486 28.3378C8.40361 23.6044 12.7731 20.3 17.6872 20.3C22.6013 20.3 26.9708 23.6044 28.3095 28.3352C28.4556 28.8505 28.352 29.3923 28.028 29.8227C27.7013 30.253 27.2045 30.5 26.668 30.5ZM17.6872 22C13.5302 22 9.83267 24.797 8.70111 28.8H26.668C25.5417 24.797 21.8442 22 17.6872 22ZM17.6872 18.6C16.0695 18.6 14.582 17.8563 13.6072 16.56C13.3256 16.1855 13.4 15.6516 13.7772 15.37C14.1544 15.0884 14.6856 15.1628 14.9672 15.54C15.6153 16.4033 16.6088 16.9 17.6872 16.9C18.7656 16.9 19.7591 16.4033 20.4072 15.54C20.6888 15.1655 21.2227 15.0911 21.5972 15.37C21.9717 15.6516 22.0488 16.1855 21.7672 16.56C20.7924 17.8563 19.3075 18.6 17.6872 18.6Z"
fill="currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M11.0065 8.00007L6.13781 3.13135L4.99365 4.2755L8.7101 8.00007L4.99365 11.7246L6.13781 12.8688L11.0065 8.00007Z"
fill="currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 267 B

View File

@ -0,0 +1,5 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M10.4889 6.16414L5.57827 11.0748C5.36662 11.2865 5.36662 11.6296 5.57827 11.8413C5.78992 12.0529 6.13306 12.0529 6.3447 11.8413L12 6.18597L6.3447 0.530672C6.13306 0.319024 5.78992 0.319024 5.57827 0.530672C5.36662 0.74232 5.36662 1.08547 5.57827 1.29712L10.4889 6.16414ZM5.06062 6.17294L0.158736 11.0748C-0.052912 11.2865 -0.052912 11.6296 0.158736 11.8413C0.370384 12.0529 0.713522 12.0529 0.92517 11.8413L6.58045 6.18597L0.925169 0.530672C0.713521 0.319024 0.370383 0.319024 0.158735 0.530672C-0.0529125 0.74232 -0.0529124 1.08547 0.158735 1.29712L5.06062 6.17294Z"
fill="currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 722 B

View File

@ -0,0 +1,5 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M26.1318 6.21601C26.1318 5.65846 25.6782 5.20648 25.1187 5.20648H8.0954C7.97125 5.20648 7.85239 5.22881 7.74248 5.26949L6.83074 2.4532C6.67147 1.97571 6.18863 1.69601 5.7056 1.77533C5.69164 1.77475 5.67764 1.77427 5.66357 1.77427H2.82629C2.26667 1.77427 1.81299 2.22628 1.81299 2.7838C1.81299 3.34126 2.26667 3.79324 2.82629 3.79324H5.13529L9.18901 16.315L6.22644 19.9619C5.87427 20.3953 5.94142 21.0308 6.3765 21.3816C6.65035 21.6024 7.00456 21.6575 7.31731 21.56H24.5106C25.0703 21.56 25.5239 21.108 25.5239 20.5505C25.5239 19.9931 25.0703 19.541 24.5106 19.541H9.17527L10.8154 17.5222H21.8761C22.4289 17.5222 22.878 17.081 22.8888 16.5328C22.8899 16.5298 22.8911 16.5268 22.8921 16.5238L26.0749 6.93999C26.1313 6.7704 26.1398 6.59658 26.1081 6.43283C26.1235 6.36294 26.1318 6.29046 26.1318 6.21601ZM21.0965 15.5032H11.0555L8.37572 7.22545H23.8456L21.0965 15.5032ZM14.1751 13.4842C14.7347 13.4842 15.1884 13.0322 15.1884 12.4747V9.8501C15.1884 9.29258 14.7347 8.84063 14.1751 8.84063C13.6155 8.84063 13.1619 9.29258 13.1619 9.8501V12.4747C13.1619 13.0322 13.6155 13.4842 14.1751 13.4842ZM18.6336 13.4842C19.1932 13.4842 19.6468 13.0322 19.6468 12.4747V9.8501C19.6468 9.29258 19.1932 8.84063 18.6336 8.84063C18.074 8.84063 17.6203 9.29258 17.6203 9.8501V12.4747C17.6203 13.0322 18.074 13.4842 18.6336 13.4842ZM9.51401 22.9733C8.61854 22.9733 7.89265 23.6964 7.89265 24.5885C7.89265 25.4805 8.61854 26.2037 9.51401 26.2037C10.4094 26.2037 11.1353 25.4805 11.1353 24.5885C11.1352 23.6964 10.4094 22.9733 9.51401 22.9733ZM22.0788 22.9733C21.1833 22.9733 20.4575 23.6964 20.4575 24.5885C20.4575 25.4805 21.1833 26.2037 22.0788 26.2037C22.9742 26.2037 23.7 25.4805 23.7 24.5885C23.7001 23.6964 22.9742 22.9733 22.0788 22.9733Z"
fill="#0068B7" fill-opacity="0.8" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,20 @@
<svg width="48" height="38" viewBox="0 0 48 38" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M19.0608 0H46.3982C47.2821 0 48 0.723213 48 1.60178V21.0214C48 21.9053 47.2768 22.6232 46.3982 22.6232H43.4786V28.2803H43.35L37.6929 22.6232H19.0608C18.1768 22.6232 17.459 21.9 17.459 21.0214V1.60178C17.459 0.717856 18.1768 0 19.0608 0Z"
fill="#A4BEFF" />
<path
d="M41.8715 10.7959H29.6197C29.1001 10.7959 28.6769 10.3727 28.6769 9.85307V9.312C28.6769 8.79235 29.1001 8.36914 29.6197 8.36914H41.8715C42.3911 8.36914 42.8144 8.79235 42.8144 9.312V9.85307C42.8144 10.3727 42.3911 10.7959 41.8715 10.7959Z"
fill="#E5ECFF" />
<path
d="M33.766 5.83984H18.0803V22.672H35.5339V7.6077C35.5339 6.6327 34.741 5.83984 33.766 5.83984Z"
fill="#83A4FF" />
<path
d="M31.8964 5.83984H1.76785C0.798213 5.83984 0 6.6327 0 7.6077V29.0094C0 29.9791 0.792856 30.7773 1.76785 30.7773H4.98749V37.013H5.12678L11.3625 30.7773H31.8964C32.866 30.7773 33.6642 29.9844 33.6642 29.0094V7.6077C33.6642 6.6327 32.8714 5.83984 31.8964 5.83984Z"
fill="#5B79FB" />
<path
d="M25.8374 15.8167H7.8267C7.29634 15.8167 6.85706 15.3828 6.85706 14.8471V14.29C6.85706 13.7596 7.29098 13.3203 7.8267 13.3203H25.8374C26.3677 13.3203 26.807 13.7542 26.807 14.29V14.8471C26.807 15.3775 26.3731 15.8167 25.8374 15.8167Z"
fill="#FF7E71" />
<path
d="M20.0517 22.0108H7.80528C7.28564 22.0108 6.86243 21.5876 6.86243 21.0679V20.5268C6.86243 20.0072 7.28564 19.584 7.80528 19.584H20.057C20.5767 19.584 20.9999 20.0072 20.9999 20.5268V21.0679C20.9945 21.5876 20.5713 22.0108 20.0517 22.0108Z"
fill="white" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,5 @@
<svg width="18" height="18" viewBox="0 0 18 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M8.99998 0.200195L0.599976 9.8002H17.4L8.99998 0.200195ZM8.99998 21.8002L17.4Z"
fill="currentColor" fill-opacity="0.5" />
</svg>

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
jjstore/src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -0,0 +1,7 @@
@forward "element-plus/theme-chalk/src/common/var.scss" with (
$colors: (
"primary": (
"base": #0068b7
)
)
);

View File

@ -0,0 +1,21 @@
html,
body,
* {
margin: 0;
padding: 0;
font-family: "PingFang SC";
}
body {
min-height: 100vh;
}
#app {
min-width: 1200px;
}
li {
list-style: none;
}
.el-pagination {
margin-top: 20px;
}

View File

@ -0,0 +1,64 @@
<template>
<el-cascader
:options="options"
:model-value="valueComputed"
@change="(value: CascaderValue) => valueChange(value as string[])"
:disabled="props.disabled"
:clearable="props.clearable"
:placeholder="props.placeholder"
/>
</template>
<script setup lang="ts">
import { CascaderOption, CascaderValue } from "element-plus";
// https://github.com/modood/Administrative-divisions-of-China/blob/master/dist/pca.json
import { computed } from "vue";
import CityJson from "./city.json";
//
function convert(cityJson: any): CascaderOption[] {
const result = [];
for (const key in cityJson) {
const item: CascaderOption = {};
const value = cityJson[key];
// valueconvert
if (typeof value === "object") {
item.children = convert(value);
item.value = key;
item.label = key;
} else {
item.value = value;
item.label = value;
}
result.push(item);
}
return result;
}
//
const options = convert(CityJson);
// v-model
const emits = defineEmits<{
(e: "update:province", value: string): void;
(e: "update:city", value: string): void;
}>();
//
const valueChange = (value: string[]) => {
emits("update:province", value[0] as string);
emits("update:city", value[1] as string);
};
//
const props = defineProps<{
//
province?: string;
//
city?: string;
disabled?: boolean;
clearable?: boolean;
placeholder?: string;
}>();
//
const valueComputed = computed(() => {
if (!props.province || !props.city) {
return [];
}
return [props.province, props.city];
});
</script>

View File

@ -0,0 +1,421 @@
{
"北京市": [
"东城区",
"西城区",
"朝阳区",
"丰台区",
"石景山区",
"海淀区",
"门头沟区",
"房山区",
"通州区",
"顺义区",
"昌平区",
"大兴区",
"怀柔区",
"平谷区",
"密云区",
"延庆区"
],
"天津市": [
"和平区",
"河东区",
"河西区",
"南开区",
"河北区",
"红桥区",
"东丽区",
"西青区",
"津南区",
"北辰区",
"武清区",
"宝坻区",
"滨海新区",
"宁河区",
"静海区",
"蓟州区"
],
"河北省": ["石家庄市", "唐山市", "秦皇岛市", "邯郸市", "邢台市", "保定市", "张家口市", "承德市", "沧州市", "廊坊市", "衡水市"],
"山西省": ["太原市", "大同市", "阳泉市", "长治市", "晋城市", "朔州市", "晋中市", "运城市", "忻州市", "临汾市", "吕梁市"],
"内蒙古自治区": [
"呼和浩特市",
"包头市",
"乌海市",
"赤峰市",
"通辽市",
"鄂尔多斯市",
"呼伦贝尔市",
"巴彦淖尔市",
"乌兰察布市",
"兴安盟",
"锡林郭勒盟",
"阿拉善盟"
],
"辽宁省": [
"沈阳市",
"大连市",
"鞍山市",
"抚顺市",
"本溪市",
"丹东市",
"锦州市",
"营口市",
"阜新市",
"辽阳市",
"盘锦市",
"铁岭市",
"朝阳市",
"葫芦岛市"
],
"吉林省": ["长春市", "吉林市", "四平市", "辽源市", "通化市", "白山市", "松原市", "白城市", "延边朝鲜族自治州"],
"黑龙江省": [
"哈尔滨市",
"齐齐哈尔市",
"鸡西市",
"鹤岗市",
"双鸭山市",
"大庆市",
"伊春市",
"佳木斯市",
"七台河市",
"牡丹江市",
"黑河市",
"绥化市",
"大兴安岭地区"
],
"上海市": [
"黄浦区",
"徐汇区",
"长宁区",
"静安区",
"普陀区",
"虹口区",
"杨浦区",
"闵行区",
"宝山区",
"嘉定区",
"浦东新区",
"金山区",
"松江区",
"青浦区",
"奉贤区",
"崇明区"
],
"江苏省": [
"南京市",
"无锡市",
"徐州市",
"常州市",
"苏州市",
"南通市",
"连云港市",
"淮安市",
"盐城市",
"扬州市",
"镇江市",
"泰州市",
"宿迁市"
],
"浙江省": ["杭州市", "宁波市", "温州市", "嘉兴市", "湖州市", "绍兴市", "金华市", "衢州市", "舟山市", "台州市", "丽水市"],
"安徽省": [
"合肥市",
"芜湖市",
"蚌埠市",
"淮南市",
"马鞍山市",
"淮北市",
"铜陵市",
"安庆市",
"黄山市",
"滁州市",
"阜阳市",
"宿州市",
"六安市",
"亳州市",
"池州市",
"宣城市"
],
"福建省": ["福州市", "厦门市", "莆田市", "三明市", "泉州市", "漳州市", "南平市", "龙岩市", "宁德市"],
"江西省": ["南昌市", "景德镇市", "萍乡市", "九江市", "新余市", "鹰潭市", "赣州市", "吉安市", "宜春市", "抚州市", "上饶市"],
"山东省": [
"济南市",
"青岛市",
"淄博市",
"枣庄市",
"东营市",
"烟台市",
"潍坊市",
"济宁市",
"泰安市",
"威海市",
"日照市",
"临沂市",
"德州市",
"聊城市",
"滨州市",
"菏泽市"
],
"河南省": [
"郑州市",
"开封市",
"洛阳市",
"平顶山市",
"安阳市",
"鹤壁市",
"新乡市",
"焦作市",
"濮阳市",
"许昌市",
"漯河市",
"三门峡市",
"南阳市",
"商丘市",
"信阳市",
"周口市",
"驻马店市",
"济源市"
],
"湖北省": [
"武汉市",
"黄石市",
"十堰市",
"宜昌市",
"襄阳市",
"鄂州市",
"荆门市",
"孝感市",
"荆州市",
"黄冈市",
"咸宁市",
"随州市",
"恩施土家族苗族自治州",
"仙桃市",
"潜江市",
"天门市",
"神农架林区"
],
"湖南省": [
"长沙市",
"株洲市",
"湘潭市",
"衡阳市",
"邵阳市",
"岳阳市",
"常德市",
"张家界市",
"益阳市",
"郴州市",
"永州市",
"怀化市",
"娄底市",
"湘西土家族苗族自治州"
],
"广东省": [
"广州市",
"韶关市",
"深圳市",
"珠海市",
"汕头市",
"佛山市",
"江门市",
"湛江市",
"茂名市",
"肇庆市",
"惠州市",
"梅州市",
"汕尾市",
"河源市",
"阳江市",
"清远市",
"东莞市",
"中山市",
"潮州市",
"揭阳市",
"云浮市"
],
"广西壮族自治区": [
"南宁市",
"柳州市",
"桂林市",
"梧州市",
"北海市",
"防城港市",
"钦州市",
"贵港市",
"玉林市",
"百色市",
"贺州市",
"河池市",
"来宾市",
"崇左市"
],
"海南省": [
"海口市",
"三亚市",
"三沙市",
"儋州市",
"五指山市",
"琼海市",
"文昌市",
"万宁市",
"东方市",
"定安县",
"屯昌县",
"澄迈县",
"临高县",
"白沙黎族自治县",
"昌江黎族自治县",
"乐东黎族自治县",
"陵水黎族自治县",
"保亭黎族苗族自治县",
"琼中黎族苗族自治县"
],
"重庆市": [
"万州区",
"涪陵区",
"渝中区",
"大渡口区",
"江北区",
"沙坪坝区",
"九龙坡区",
"南岸区",
"北碚区",
"綦江区",
"大足区",
"渝北区",
"巴南区",
"黔江区",
"长寿区",
"江津区",
"合川区",
"永川区",
"南川区",
"璧山区",
"铜梁区",
"潼南区",
"荣昌区",
"开州区",
"梁平区",
"武隆区",
"城口县",
"丰都县",
"垫江县",
"忠县",
"云阳县",
"奉节县",
"巫山县",
"巫溪县",
"石柱土家族自治县",
"秀山土家族苗族自治县",
"酉阳土家族苗族自治县",
"彭水苗族土家族自治县"
],
"四川省": [
"成都市",
"自贡市",
"攀枝花市",
"泸州市",
"德阳市",
"绵阳市",
"广元市",
"遂宁市",
"内江市",
"乐山市",
"南充市",
"眉山市",
"宜宾市",
"广安市",
"达州市",
"雅安市",
"巴中市",
"资阳市",
"阿坝藏族羌族自治州",
"甘孜藏族自治州",
"凉山彝族自治州"
],
"贵州省": [
"贵阳市",
"六盘水市",
"遵义市",
"安顺市",
"毕节市",
"铜仁市",
"黔西南布依族苗族自治州",
"黔东南苗族侗族自治州",
"黔南布依族苗族自治州"
],
"云南省": [
"昆明市",
"曲靖市",
"玉溪市",
"保山市",
"昭通市",
"丽江市",
"普洱市",
"临沧市",
"楚雄彝族自治州",
"红河哈尼族彝族自治州",
"文山壮族苗族自治州",
"西双版纳傣族自治州",
"大理白族自治州",
"德宏傣族景颇族自治州",
"怒江傈僳族自治州",
"迪庆藏族自治州"
],
"西藏自治区": ["拉萨市", "日喀则市", "昌都市", "林芝市", "山南市", "那曲市", "阿里地区"],
"陕西省": ["西安市", "铜川市", "宝鸡市", "咸阳市", "渭南市", "延安市", "汉中市", "榆林市", "安康市", "商洛市"],
"甘肃省": [
"兰州市",
"嘉峪关市",
"金昌市",
"白银市",
"天水市",
"武威市",
"张掖市",
"平凉市",
"酒泉市",
"庆阳市",
"定西市",
"陇南市",
"临夏回族自治州",
"甘南藏族自治州"
],
"青海省": [
"西宁市",
"海东市",
"海北藏族自治州",
"黄南藏族自治州",
"海南藏族自治州",
"果洛藏族自治州",
"玉树藏族自治州",
"海西蒙古族藏族自治州"
],
"宁夏回族自治区": ["银川市", "石嘴山市", "吴忠市", "固原市", "中卫市"],
"新疆维吾尔自治区": [
"乌鲁木齐市",
"克拉玛依市",
"吐鲁番市",
"哈密市",
"昌吉回族自治州",
"博尔塔拉蒙古自治州",
"巴音郭楞蒙古自治州",
"阿克苏地区",
"克孜勒苏柯尔克孜自治州",
"喀什地区",
"和田地区",
"伊犁哈萨克自治州",
"塔城地区",
"阿勒泰地区",
"石河子市",
"阿拉尔市",
"图木舒克市",
"五家渠市",
"北屯市",
"铁门关市",
"双河市",
"可克达拉市",
"昆玉市",
"胡杨河市",
"新星市"
]
}

View File

@ -0,0 +1,68 @@
<template>
<el-cascader
:options="options"
:model-value="valueComputed"
@change="(value: CascaderValue) => valueChange(value as string[])"
:disabled="props.disabled"
:clearable="props.clearable"
:placeholder="props.placeholder"
/>
</template>
<script setup lang="ts">
import { CascaderOption, CascaderValue } from "element-plus";
// https://github.com/modood/Administrative-divisions-of-China/blob/master/dist/pca.json
import { computed } from "vue";
import CityJson from "./city.json";
//
function convert(cityJson: any): CascaderOption[] {
const result = [];
for (const key in cityJson) {
const item: CascaderOption = {};
const value = cityJson[key];
// valueconvert
if (typeof value === "object") {
item.children = convert(value);
item.value = key;
item.label = key;
} else {
item.value = value;
item.label = value;
}
result.push(item);
}
return result;
}
//
const options = convert(CityJson);
// v-model
const emits = defineEmits<{
(e: "update:province", value: string): void;
(e: "update:city", value: string): void;
(e: "update:district", value: string): void;
}>();
//
const valueChange = (value: string[]) => {
emits("update:province", value[0] as string);
emits("update:city", value[1] as string);
emits("update:district", value[2] as string);
};
//
const props = defineProps<{
//
province?: string;
//
city?: string;
//
district?: string;
disabled?: boolean;
clearable?: boolean;
placeholder?: string;
}>();
//
const valueComputed = computed(() => {
if (!props.province || !props.city || !props.district) {
return [];
}
return [props.province, props.city, props.district];
});
</script>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
<template>
<svg aria-hidden="true" class="svg-icon" :width="props.size" :height="props.size">
<use :xlink:href="symbolId" />
</svg>
</template>
<script setup lang="ts">
import { computed } from "vue";
const props = defineProps({
prefix: {
type: String,
default: "icon"
},
name: {
type: String,
required: true
},
color: {
type: String,
default: "#333"
},
size: {
type: String,
default: "20px"
}
});
const symbolId = computed(() => `#${props.prefix}-${props.name}`);
</script>

View File

@ -0,0 +1,314 @@
<template>
<div class="upload-box">
<el-upload
action="#"
:id="uuid"
:class="['upload', self_disabled ? 'disabled' : '', drag ? 'no-border' : '']"
:multiple="false"
:disabled="self_disabled"
:show-file-list="false"
:http-request="handleHttpUpload"
:before-upload="beforeUpload"
:on-success="uploadSuccess"
:on-error="uploadError"
:drag="drag"
:accept="fileType?.join(',')"
>
<template v-if="imageUrl">
<img :src="imageUrl" class="upload-image" />
<div class="upload-handle" @click.stop>
<div class="handle-icon" @click="editImg" v-if="!self_disabled">
<el-icon>
<Edit />
</el-icon>
<span>编辑</span>
</div>
<div class="handle-icon" @click="imgViewVisible = true">
<el-icon>
<ZoomIn />
</el-icon>
<span>查看</span>
</div>
<div class="handle-icon" @click="deleteImg" v-if="!self_disabled">
<el-icon>
<Delete />
</el-icon>
<span>删除</span>
</div>
</div>
</template>
<template v-else>
<div class="upload-empty">
<slot name="empty">
<el-icon>
<Plus />
</el-icon>
<!-- <span>请上传图片</span> -->
</slot>
</div>
</template>
</el-upload>
<div class="el-upload__tip">
<slot name="tip"></slot>
</div>
<el-image-viewer v-if="imgViewVisible" @close="imgViewVisible = false" :url-list="[imageUrl]" />
</div>
</template>
<script setup lang="ts" name="UploadImg">
import { uploadFile } from "@/api/common";
import { generateUUID } from "@/utils/uuid";
import type { UploadProps, UploadRequestOptions } from "element-plus";
import { ElNotification, formContextKey, formItemContextKey } from "element-plus";
import { computed, inject, ref } from "vue";
//
interface UploadFileProps {
imageUrl: string; // ==>
api?: (params: any) => Promise<any>; // api api ==>
drag?: boolean; // ==> true
disabled?: boolean; // ==> false
fileSize?: number; // ==> 5M
fileType?: File.ImageMimeType[]; // ==> ["image/jpeg", "image/png", "image/gif"]
height?: string; // ==> 150px
width?: string; // ==> 150px
borderRadius?: string; // ==> 8px
}
//
const props = withDefaults(defineProps<UploadFileProps>(), {
imageUrl: "",
drag: true,
disabled: false,
fileSize: 5,
fileType: () => ["image/jpeg", "image/png", "image/gif"],
height: "150px",
width: "150px",
borderRadius: "8px"
});
// id
const uuid = ref("id-" + generateUUID());
//
const imgViewVisible = ref(false);
// el-form
const formContext = inject(formContextKey, void 0);
// el-form-item
const formItemContext = inject(formItemContextKey, void 0);
//
const self_disabled = computed(() => {
return props.disabled || formContext?.disabled;
});
/**
* @description 图片上传
* @param options upload 所有配置项
* */
interface UploadEmits {
(e: "update:imageUrl", value: string): void;
}
const emit = defineEmits<UploadEmits>();
const handleHttpUpload = async (options: UploadRequestOptions) => {
let formData = new FormData();
formData.append("file", options.file);
try {
// todo
const api = props.api ?? uploadFile;
const { data } = await api(formData);
emit("update:imageUrl", data.data);
// el-form
formItemContext?.prop && formContext?.validateField([formItemContext.prop as string]);
} catch (error) {
options.onError(error as any);
}
};
/**
* @description 删除图片
* */
const deleteImg = () => {
emit("update:imageUrl", "");
};
/**
* @description 编辑图片
* */
const editImg = () => {
const dom = document.querySelector(`#${uuid.value} .el-upload__input`);
dom && dom.dispatchEvent(new MouseEvent("click"));
};
/**
* @description 文件上传之前判断
* @param rawFile 选择的文件
* */
const beforeUpload: UploadProps["beforeUpload"] = rawFile => {
const imgSize = rawFile.size / 1024 / 1024 < props.fileSize;
const imgType = props.fileType.includes(rawFile.type as File.ImageMimeType);
if (!imgType)
ElNotification({
title: "温馨提示",
message: "上传图片不符合所需的格式!",
type: "warning"
});
if (!imgSize)
setTimeout(() => {
ElNotification({
title: "温馨提示",
message: `上传图片大小不能超过 ${props.fileSize}M`,
type: "warning"
});
}, 0);
return imgType && imgSize;
};
/**
* @description 图片上传成功
* */
const uploadSuccess = () => {
ElNotification({
title: "温馨提示",
message: "图片上传成功!",
type: "success"
});
};
/**
* @description 图片上传错误
* */
const uploadError = () => {
ElNotification({
title: "温馨提示",
message: "图片上传失败,请您重新上传!",
type: "error"
});
};
</script>
<style scoped lang="scss">
.is-error {
.upload {
:deep(.el-upload),
:deep(.el-upload-dragger) {
border: 1px dashed var(--el-color-danger) !important;
&:hover {
border-color: var(--el-color-primary) !important;
}
}
}
}
:deep(.disabled) {
.el-upload,
.el-upload-dragger {
cursor: not-allowed !important;
background: var(--el-disabled-bg-color);
border: 1px dashed var(--el-border-color-darker) !important;
&:hover {
border: 1px dashed var(--el-border-color-darker) !important;
}
}
}
.upload-box {
.no-border {
:deep(.el-upload) {
border: none !important;
}
}
:deep(.upload) {
.el-upload {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: v-bind(width);
height: v-bind(height);
overflow: hidden;
border: 1px dashed var(--el-border-color-darker);
border-radius: v-bind(borderRadius);
transition: var(--el-transition-duration-fast);
&:hover {
border-color: var(--el-color-primary);
.upload-handle {
opacity: 1;
}
}
.el-upload-dragger {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
padding: 0;
overflow: hidden;
background-color: transparent;
border: 1px dashed var(--el-border-color-darker);
border-radius: v-bind(borderRadius);
&:hover {
border: 1px dashed var(--el-color-primary);
}
}
.el-upload-dragger.is-dragover {
background-color: var(--el-color-primary-light-9);
border: 2px dashed var(--el-color-primary) !important;
}
.upload-image {
width: 100%;
height: 100%;
object-fit: contain;
}
.upload-empty {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 12px;
line-height: 30px;
color: var(--el-color-info);
.el-icon {
font-size: 28px;
color: var(--el-text-color-secondary);
}
}
.upload-handle {
position: absolute;
top: 0;
right: 0;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
cursor: pointer;
background: rgb(0 0 0 / 60%);
opacity: 0;
transition: var(--el-transition-duration-fast);
.handle-icon {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0 6%;
color: aliceblue;
.el-icon {
margin-bottom: 40%;
font-size: 130%;
line-height: 130%;
}
span {
font-size: 85%;
line-height: 85%;
}
}
}
}
}
.el-upload__tip {
line-height: 18px;
text-align: center;
}
}
</style>

View File

@ -0,0 +1,324 @@
<template>
<div class="upload-box">
<el-upload
action="#"
list-type="picture-card"
:class="['upload', self_disabled ? 'disabled' : '', drag ? 'no-border' : '']"
v-model:file-list="_fileList"
:multiple="true"
:disabled="self_disabled"
:limit="limit"
:http-request="handleHttpUpload"
:before-upload="beforeUpload"
:on-exceed="handleExceed"
:on-success="uploadSuccess"
:on-error="uploadError"
:drag="drag"
:accept="fileType.join(',')"
>
<div class="upload-empty">
<slot name="empty">
<el-icon>
<Plus />
</el-icon>
<!-- <span>请上传图片</span> -->
</slot>
</div>
<template #file="{ file }">
<img :src="file.url" class="upload-image" />
<div class="upload-handle" @click.stop>
<div class="handle-icon" @click="handlePictureCardPreview(file)">
<el-icon>
<ZoomIn />
</el-icon>
<span>查看</span>
</div>
<div class="handle-icon" @click="handleRemove(file)" v-if="!self_disabled">
<el-icon>
<Delete />
</el-icon>
<span>删除</span>
</div>
</div>
</template>
</el-upload>
<div class="el-upload__tip">
<slot name="tip"></slot>
</div>
<el-image-viewer v-if="imgViewVisible" @close="imgViewVisible = false" :url-list="[viewImageUrl]" />
</div>
</template>
<script setup lang="ts" name="UploadImgs">
import { uploadFile } from "@/api/common";
import { Plus } from "@element-plus/icons-vue";
import type { UploadFile, UploadProps, UploadRequestOptions, UploadUserFile } from "element-plus";
import { ElNotification, formContextKey, formItemContextKey } from "element-plus";
import { computed, inject, ref, watch } from "vue";
interface UploadFileProps {
fileList: UploadUserFile[];
api?: (params: any) => Promise<any>; // api api ==>
drag?: boolean; // ==> true
disabled?: boolean; // ==> false
limit?: number; // ==> 5
fileSize?: number; // ==> 5M
fileType?: File.ImageMimeType[]; // ==> ["image/jpeg", "image/png", "image/gif"]
height?: string; // ==> 150px
width?: string; // ==> 150px
borderRadius?: string; // ==> 8px
}
const props = withDefaults(defineProps<UploadFileProps>(), {
fileList: () => [],
drag: true,
disabled: false,
limit: 5,
fileSize: 5,
fileType: () => ["image/jpeg", "image/png", "image/gif"],
height: "150px",
width: "150px",
borderRadius: "8px"
});
// el-form
const formContext = inject(formContextKey, void 0);
// el-form-item
const formItemContext = inject(formItemContextKey, void 0);
//
const self_disabled = computed(() => {
return props.disabled || formContext?.disabled;
});
const _fileList = ref<UploadUserFile[]>(props.fileList);
// props.fileList
watch(
() => props.fileList,
(n: UploadUserFile[]) => {
_fileList.value = n;
}
);
/**
* @description 文件上传之前判断
* @param rawFile 选择的文件
* */
const beforeUpload: UploadProps["beforeUpload"] = rawFile => {
const imgSize = rawFile.size / 1024 / 1024 < props.fileSize;
const imgType = props.fileType.includes(rawFile.type as File.ImageMimeType);
if (!imgType)
ElNotification({
title: "温馨提示",
message: "上传图片不符合所需的格式!",
type: "warning"
});
if (!imgSize)
setTimeout(() => {
ElNotification({
title: "温馨提示",
message: `上传图片大小不能超过 ${props.fileSize}M`,
type: "warning"
});
}, 0);
return imgType && imgSize;
};
/**
* @description 图片上传
* @param options upload 所有配置项
* */
const handleHttpUpload = async (options: UploadRequestOptions) => {
let formData = new FormData();
formData.append("file", options.file);
try {
const api = props.api ?? uploadFile;
const { data } = await api(formData);
options.onSuccess(data);
} catch (error) {
options.onError(error as any);
}
};
/**
* @description 图片上传成功
* @param response 上传响应结果
* @param uploadFile 上传的文件
* */
interface UploadEmits {
(e: "update:fileList", value: UploadUserFile[]): void;
}
const emit = defineEmits<UploadEmits>();
const uploadSuccess = (response: { data: string } | undefined, uploadFile: UploadFile) => {
if (!response) return;
uploadFile.url = response.data;
emit("update:fileList", _fileList.value);
// el-form
formItemContext?.prop && formContext?.validateField([formItemContext.prop as string]);
ElNotification({
title: "温馨提示",
message: "图片上传成功!",
type: "success"
});
};
/**
* @description 删除图片
* @param file 删除的文件
* */
const handleRemove = (file: UploadFile) => {
_fileList.value = _fileList.value.filter(item => item.url !== file.url || item.name !== file.name);
emit("update:fileList", _fileList.value);
};
/**
* @description 图片上传错误
* */
const uploadError = () => {
ElNotification({
title: "温馨提示",
message: "图片上传失败,请您重新上传!",
type: "error"
});
};
/**
* @description 文件数超出
* */
const handleExceed = () => {
ElNotification({
title: "温馨提示",
message: `当前最多只能上传 ${props.limit} 张图片,请移除后上传!`,
type: "warning"
});
};
/**
* @description 图片预览
* @param file 预览的文件
* */
const viewImageUrl = ref("");
const imgViewVisible = ref(false);
const handlePictureCardPreview: UploadProps["onPreview"] = file => {
viewImageUrl.value = file.url!;
imgViewVisible.value = true;
};
</script>
<style scoped lang="scss">
.is-error {
.upload {
:deep(.el-upload--picture-card),
:deep(.el-upload-dragger) {
border: 1px dashed var(--el-color-danger) !important;
&:hover {
border-color: var(--el-color-primary) !important;
}
}
}
}
:deep(.disabled) {
.el-upload--picture-card,
.el-upload-dragger {
cursor: not-allowed;
background: var(--el-disabled-bg-color) !important;
border: 1px dashed var(--el-border-color-darker);
&:hover {
border-color: var(--el-border-color-darker) !important;
}
}
}
.upload-box {
.no-border {
:deep(.el-upload--picture-card) {
border: none !important;
}
}
:deep(.upload) {
.el-upload-dragger {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
padding: 0;
overflow: hidden;
border: 1px dashed var(--el-border-color-darker);
border-radius: v-bind(borderRadius);
&:hover {
border: 1px dashed var(--el-color-primary);
}
}
.el-upload-dragger.is-dragover {
background-color: var(--el-color-primary-light-9);
border: 2px dashed var(--el-color-primary) !important;
}
.el-upload-list__item,
.el-upload--picture-card {
width: v-bind(width);
height: v-bind(height);
background-color: transparent;
border-radius: v-bind(borderRadius);
}
.upload-image {
width: 100%;
height: 100%;
object-fit: contain;
}
.upload-handle {
position: absolute;
top: 0;
right: 0;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
cursor: pointer;
background: rgb(0 0 0 / 60%);
opacity: 0;
transition: var(--el-transition-duration-fast);
.handle-icon {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0 6%;
color: aliceblue;
.el-icon {
margin-bottom: 15%;
font-size: 140%;
}
span {
font-size: 100%;
}
}
}
.el-upload-list__item {
&:hover {
.upload-handle {
opacity: 1;
}
}
}
.upload-empty {
display: flex;
flex-direction: column;
align-items: center;
font-size: 12px;
line-height: 30px;
color: var(--el-color-info);
.el-icon {
font-size: 28px;
color: var(--el-text-color-secondary);
}
}
}
.el-upload__tip {
line-height: 15px;
text-align: center;
}
}
</style>

View File

@ -0,0 +1,160 @@
<template>
<div :class="['editor-box', self_disabled ? 'editor-disabled' : '']">
<Toolbar class="editor-toolbar" :editor="editorRef" :default-config="toolbarConfig" :mode="mode" v-if="!hideToolBar" />
<Editor
class="editor-content'"
:style="{ height }"
:mode="mode!"
v-model="valueHtml"
:default-config="editorConfig"
@on-created="handleCreated"
@on-blur="handleBlur"
/>
</div>
</template>
<script setup lang="ts" name="WangEditor">
import { uploadFile } from "@/api/common";
import { IEditorConfig, IToolbarConfig } from "@wangeditor/editor";
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
import "@wangeditor/editor/dist/css/style.css";
import { formContextKey, formItemContextKey } from "element-plus";
import { computed, inject, nextTick, onBeforeUnmount, shallowRef } from "vue";
// DOM
const editorRef = shallowRef();
//
const handleCreated = (editor: any) => {
editorRef.value = editor;
};
//
interface RichEditorProps {
value: string; // ==>
toolbarConfig?: Partial<IToolbarConfig>; // ==>
editorConfig?: Partial<IEditorConfig>; // ==>
height?: string; // ==> 500px
mode?: "default" | "simple"; // ==> default
hideToolBar?: boolean; // ==> false
disabled?: boolean; // ==> false
}
const props = withDefaults(defineProps<RichEditorProps>(), {
toolbarConfig: () => {
return {
excludeKeys: []
};
},
editorConfig: () => {
return {
placeholder: "请输入内容...",
MENU_CONF: {}
};
},
height: "500px",
mode: "default",
hideToolBar: false,
disabled: false
});
// el-form
const formContext = inject(formContextKey, void 0);
// el-form-item
const formItemContext = inject(formItemContextKey, void 0);
//
const self_disabled = computed(() => {
return props.disabled || formContext?.disabled;
});
//
if (self_disabled.value) nextTick(() => editorRef.value.disable());
//
type EmitProps = {
(e: "update:value", val: string): void;
(e: "check-validate"): void;
};
const emit = defineEmits<EmitProps>();
const valueHtml = computed({
get() {
return props.value;
},
set(val: string) {
//
if (editorRef.value.isEmpty()) val = "";
emit("update:value", val);
}
});
/**
* @description 图片自定义上传
* @param file 上传的文件
* @param insertFn 上传成功后的回调函数插入到富文本编辑器中
* */
type InsertFnTypeImg = (url: string, alt?: string, href?: string) => void;
props.editorConfig.MENU_CONF!["uploadImage"] = {
async customUpload(file: File, insertFn: InsertFnTypeImg) {
if (!uploadImgValidate(file)) return;
let formData = new FormData();
formData.append("file", file);
try {
const { data } = await uploadFile(formData);
insertFn(data.data);
} catch (error) {
console.log(error);
}
}
};
//
const uploadImgValidate = (file: File): boolean => {
console.log(file);
return true;
};
/**
* @description 视频自定义上传
* @param file 上传的文件
* @param insertFn 上传成功后的回调函数插入到富文本编辑器中
* */
type InsertFnTypeVideo = (url: string, poster?: string) => void;
props.editorConfig.MENU_CONF!["uploadVideo"] = {
async customUpload(file: File, insertFn: InsertFnTypeVideo) {
if (!uploadVideoValidate(file)) return;
let formData = new FormData();
formData.append("file", file);
try {
const { data } = await uploadFile(formData);
insertFn(data.data);
} catch (error) {
console.log(error);
}
}
};
//
const uploadVideoValidate = (file: File): boolean => {
console.log(file);
return true;
};
//
const handleBlur = () => {
formItemContext?.prop && formContext?.validateField([formItemContext.prop as string]);
};
//
onBeforeUnmount(() => {
if (!editorRef.value) return;
editorRef.value.destroy();
});
defineExpose({
editor: editorRef
});
</script>
<style scoped lang="scss">
@import "./index.scss";
</style>

View File

@ -0,0 +1,28 @@
/* 富文本组件校验失败样式 */
.is-error {
.editor-box {
border-color: var(--el-color-danger);
.editor-toolbar {
border-bottom-color: var(--el-color-danger);
}
}
}
/* 富文本组件禁用样式 */
.editor-disabled {
cursor: not-allowed !important;
}
/* 富文本组件样式 */
.editor-box {
/* 防止富文本编辑器全屏时 tabs组件 在其层级之上 */
z-index: 2;
width: 100%;
border: 1px solid #cccccc;
.editor-toolbar {
border-bottom: 1px solid #cccccc;
}
.editor-content {
overflow-y: hidden;
}
}

View File

@ -0,0 +1,90 @@
<template>
<div class="product-detail-big" @click="toDetail">
<div class="img">
<el-image class="el-image" :src="product.cover" alt="" />
</div>
<div class="panel">
<div class="top">
<span class="tag">现货</span>
<span>{{ product.names }}</span>
</div>
<div class="info">
<div class="price"><span class="unit"></span>{{ product.price }}</div>
<svg-icon name="home-shop-car" color="#0068B7CC" size="24" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Product } from "@/api/types/types";
import SvgIcon from "@/component/SvgIcon/SvgIcon.vue";
const props = defineProps<{
product: Product.ProductInfo;
}>();
const router = useRouter();
const toDetail = () => {
router.push({ path: `/product/${props.product.id}` });
};
</script>
<style scoped lang="scss">
.product-detail-big {
height: 584px;
width: 320px;
display: flex;
flex-direction: column;
transition: all 0.5s;
cursor: pointer;
&:hover {
transform: translateY(-8px);
}
.img {
flex: 0 0 460px;
width: 100%;
.el-image {
width: 100%;
height: 100%;
}
}
.panel {
background-color: #f5f5f566;
flex: 1 1;
display: flex;
flex-direction: column;
padding: 0 20px;
.top {
flex: 1;
display: flex;
align-items: center;
font-size: 16px;
.tag {
text-align: center;
border-radius: 13px;
height: 24px;
line-height: 24px;
width: 50px;
font-size: 11px;
display: inline-block;
color: var(--el-color-primary);
background-color: #0068b733;
margin-right: 20px;
}
}
}
.info {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
.price {
font-size: 24px;
color: #ed7620;
.unit {
font-size: 16px;
}
}
}
}
</style>

View File

@ -0,0 +1,90 @@
<template>
<div class="product-detail-small" @click="toDetail">
<div class="img">
<el-image class="el-image" :src="product.cover" alt="" />
</div>
<div class="panel">
<div class="top">
<span class="tag">现货</span>
<span>{{ product.names }}</span>
</div>
<div class="info">
<div class="price"><span class="unit"></span>{{ product.price }}</div>
<svg-icon name="home-shop-car" color="#0068B7CC" size="24" class="icon-arrow-right" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Product } from "@/api/types/types";
import SvgIcon from "@/component/SvgIcon/SvgIcon.vue";
const props = defineProps<{
product: Product.ProductInfo;
}>();
const router = useRouter();
const toDetail = () => {
router.push({ path: `/product/${props.product.id}` });
};
</script>
<style scoped lang="scss">
.product-detail-small {
width: 248px;
height: 286px;
display: flex;
flex-direction: column;
transition: all 0.5s;
cursor: pointer;
&:hover {
transform: translateY(-8px);
}
.img {
flex: 0 0 196px;
width: 100%;
.el-image {
width: 84%;
height: 80%;
}
}
.panel {
background-color: #f5f5f566;
flex: 1 1;
display: flex;
flex-direction: column;
padding: 0 20px;
.top {
flex: 1;
display: flex;
align-items: center;
font-size: 16px;
.tag {
text-align: center;
border-radius: 13px;
height: 24px;
line-height: 24px;
width: 50px;
font-size: 11px;
display: inline-block;
color: var(--el-color-primary);
background-color: #0068b733;
margin-right: 20px;
}
}
.info {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
.price {
font-size: 24px;
color: #ed7620;
.unit {
font-size: 22px;
}
}
}
}
}
</style>

View File

@ -0,0 +1,29 @@
<template>
<div class="sidebar">
<div class="menu">
<sidebar-item
:sidebar-list="sidebarResList"
@side-click="item => emits('sideClick', item)"
:active-id="activeId"
></sidebar-item>
</div>
</div>
</template>
<script setup lang="ts">
import { Classify } from "@/api/types/types";
import SidebarItem from "./SidebarItem.vue";
//
const emits = defineEmits<{
(e: "sideClick", item: Classify.ClassifyInfo): void;
}>();
defineProps<{
sidebarResList: Classify.ClassifyInfo[];
// ID
activeId?: string;
}>();
</script>
<style scoped lang="scss">
.sidebar {
width: 220px;
}
</style>

View File

@ -0,0 +1,90 @@
<template>
<template v-for="sidebar in sidebarList" :key="sidebar.id">
<div
class="item"
:class="{ active: activeId === sidebar.id }"
@mouseenter.stop="mouseenter(sidebar.id)"
@mouseleave.stop="mouseleave(sidebar.id)"
@click.stop="() => emits('sideClick', sidebar)"
>
<div class="name">{{ sidebar.names }}</div>
<div v-if="sidebar.voList.length > 0" class="menu-group" v-show="showChild[sidebar.id]">
<sidebar-item @side-click="item => emits('sideClick', item)" :sidebar-list="sidebar.voList"></sidebar-item>
</div>
</div>
</template>
</template>
<script setup lang="ts" name="SidebarItem">
import { Classify } from "@/api/types/types";
//
const emits = defineEmits<{
(e: "sideClick", item: Classify.ClassifyInfo): void;
}>();
//
type ShowChildType = {
[key in string]: boolean;
};
const showChild = reactive<ShowChildType>({});
const props = defineProps<{
sidebarList: Classify.ClassifyInfo[];
// ID
activeId?: string;
}>();
//
const mouseenter = (id: string) => {
showChild[id] = true;
};
//
const mouseleave = (id: string) => {
showChild[id] = false;
};
onMounted(() => {
props.sidebarList.forEach(item => {
showChild[item.id] = false;
});
});
</script>
<style scoped lang="scss">
.item {
cursor: pointer;
width: 100%;
height: 60px;
line-height: 60px;
padding-left: 40px;
position: relative;
box-sizing: border-box;
color: #000;
background-color: #fff;
.name {
width: 100%;
height: 100%;
}
&.active {
color: var(--el-color-primary);
background-color: #f5f5f5;
}
&.active::after {
content: "";
height: 100%;
width: 4px;
background-color: var(--el-color-primary);
position: absolute;
right: 0;
top: 0;
}
&:hover {
color: var(--el-color-primary);
background-color: #f5f5f5;
}
.menu-group {
position: absolute;
top: 0;
left: 220px;
width: 100%;
z-index: 10;
background-color: #f5f5f5;
border: 1px solid #e6e6e6;
}
}
</style>

View File

@ -0,0 +1,58 @@
<template>
<div class="sort" @click="sort">
<div class="value"><slot /></div>
<div class="icon">
<svg-icon class="t" :class="{ active: modelValue === 1 }" name="product-sort" />
<svg-icon class="d" :class="{ active: modelValue === 2 }" name="product-sort" />
</div>
</div>
</template>
<script setup lang="ts">
import SvgIcon from "@/component/SvgIcon/SvgIcon.vue";
// : 1 2 3
const props = defineProps<{
modelValue: number;
}>();
const emits = defineEmits<{
(e: "update:modelValue", value: number): void;
}>();
//
const sort = () => {
if (props.modelValue === 3) {
emits("update:modelValue", 1);
} else {
emits("update:modelValue", props.modelValue + 1);
}
};
</script>
<style lang="scss" scoped>
//
.sort {
position: relative;
padding-right: 10px;
display: flex;
align-items: center;
.value {
margin-right: 10px;
}
.icon {
position: relative;
height: 30px;
.t,
.d {
position: absolute;
height: 30px;
&.active {
color: var(--el-color-primary);
}
}
.t {
top: 0;
}
.d {
bottom: 0;
transform: rotate(180deg);
}
}
}
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,74 @@
<template>
<div class="footer-bg">
<footer class="footer">
<div class="col">
<div class="title">联系我们</div>
<ul>
<li>电子邮销: postmaster@yy2021cn.com</li>
<li>公司地址:宁波市镇海区庄市街道启迪科技园A座</li>
</ul>
</div>
<div class="col">
<div class="title">网址导航</div>
<ul>
<li>网址首页</li>
<li>关于我们</li>
<li>我们的产品</li>
</ul>
</div>
<div class="col">
<div class="title">加入我们</div>
<ul>
<li>联系电话189 5824 5606</li>
<li>备案号浙ICP备2021039509号</li>
</ul>
</div>
</footer>
</div>
</template>
<script setup lang="ts"></script>
<style scoped lang="scss">
.footer-bg {
width: 100%;
background-color: #000;
color: #fff;
.footer {
width: 1200px;
height: 300px;
box-sizing: border-box;
padding-top: 40px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: flex-start;
.col {
// width: 400px;
.title {
font-size: 20px;
position: relative;
margin-bottom: 40px;
&::before {
content: "";
position: absolute;
height: 100%;
width: 5px;
background-color: #a6a6a6;
left: -10px;
top: 0;
}
}
ul {
li {
list-style: square;
margin-bottom: 10px;
font-weight: 100;
&:last-child {
margin-bottom: 0;
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,141 @@
<template>
<div class="header">
<!-- 头部菜单 -->
<div class="nav-bg">
<nav class="nav">
<div class="logo" @click="toHome">
<img src="@/assets/logo.png" alt="" />
</div>
<!-- 菜单栏1 -->
<div class="nav-list-left">
<menu-item path="/">首页</menu-item>
<menu-item path="/product">产品</menu-item>
</div>
<!-- 搜索框 -->
<div class="search">
<el-input @keydown.enter="search" placeholder="搜索产品" v-model="keyword" class="input" :suffix-icon="Search" />
</div>
<!-- 菜单栏2 -->
<div class="nav-list-right">
<!-- 店铺 -->
<menu-item class="item" path="/center/store-mgr">
<svg-icon name="header-store" size="25px"></svg-icon>
</menu-item>
<!-- 购物车 -->
<menu-item class="item" path="/order-cart">
<svg-icon name="header-shop-car" size="36px"></svg-icon>
</menu-item>
<!-- 个人中心 -->
<!--<menu-item class="item">
<svg-icon name="header-double-user" size="36px"></svg-icon>
</menu-item>-->
<!-- 个人中心 -->
<menu-item class="item" path="/center/my-detail">
<svg-icon name="header-user" size="36px"></svg-icon>
</menu-item>
</div>
</nav>
</div>
</div>
</template>
<script setup lang="ts">
import SvgIcon from "@/component/SvgIcon/SvgIcon.vue";
import { useSearchStore } from "@/stores/search";
import { Search } from "@element-plus/icons-vue";
import { storeToRefs } from "pinia";
import MenuItem from "./MenuItem.vue";
import { useUserStore } from "@/stores/user";
const { keywordRef: keyword } = storeToRefs(useSearchStore());
const { userInfo } = storeToRefs(useUserStore());
//
const router = useRouter();
const route = useRoute();
const search = () => {
if (route.path === "/product") {
router.push({
path: "/product",
query: { ...route.query, keyword: keyword.value }
});
} else {
router.push({
path: "/product",
query: { keyword: keyword.value }
});
}
};
const toHome = () => {
router.push({ path: "/" });
};
</script>
<style scoped lang="scss">
//
.nav-bg {
width: 100%;
height: 80px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
.nav {
display: flex;
justify-content: space-between;
align-items: center;
width: 1200px;
height: 100%;
margin: 0 auto;
//#region logo
.logo {
width: 150px;
cursor: pointer;
// height: 40px;
img {
width: 100%;
// height: 100%;
}
}
//#endregion
.nav-list-left {
display: flex;
height: 100%;
width: 100px;
align-items: center;
justify-content: space-around;
}
.search {
width: 300px;
height: 40px;
background-color: #f5f5f5;
border-radius: 20px;
:deep(.el-input__wrapper) {
box-shadow: none;
background-color: transparent;
}
:deep(.el-input__inner) {
height: 40px;
box-sizing: border-box;
padding: 0 14px;
}
:deep(.el-input__suffix-inner) {
padding-right: 5px;
}
:deep(.el-input__inner:focus ~ .el-input__suffix .el-input__suffix-inner) {
font-size: 20px;
color: var(--el-color-primary);
}
}
.nav-list-right {
display: flex;
width: 300px;
align-items: center;
justify-content: space-around;
height: 100%;
// > :last-child {
// margin-left: 40px;
// }
}
}
}
</style>

View File

@ -0,0 +1,48 @@
<template>
<div class="login-bg">
<div v-if="!userStore.userInfo?.id" class="login">
<div @click="loginStore.show()"></div>
<div @click="loginStore.show()"></div>
</div>
<div v-else class="tip">欢迎您 {{ userStore.userInfo?.userName }}</div>
</div>
</template>
<script setup lang="ts">
import { useLoginStore } from "@/stores/login";
import { useUserStore } from "@/stores/user";
const loginStore = useLoginStore();
const userStore = useUserStore();
</script>
<style scoped lang="scss">
//
.login-bg {
background-color: #000;
height: 24px;
width: 100%;
.tip {
color: #ffffff;
margin: 0 auto;
width: 1200px;
height: 100%;
text-align: right;
}
.login {
color: #b2b2b2;
margin: 0 auto;
display: flex;
justify-content: flex-end;
align-items: center;
width: 1200px;
height: 100%;
> div:first-child {
margin-right: 10px;
}
> div:hover {
color: #ffffff;
font-weight: bold;
cursor: pointer;
}
}
}
</style>

View File

@ -0,0 +1,33 @@
<template>
<div @click="jump" class="menu-item" :class="isActive ? 'active' : ''">
<slot />
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
path?: string;
}>();
const route = useRoute();
const router = useRouter();
//
const isActive = computed(() => {
return route.path === props.path;
});
//
const jump = () => {
if (props.path) {
router.push({ path: props.path });
}
};
</script>
<style scoped lang="scss">
.menu-item {
cursor: pointer;
&:hover {
color: var(--el-color-primary);
}
&.active {
color: var(--el-color-primary);
}
}
</style>

View File

@ -0,0 +1,89 @@
<template>
<div class="container">
<div>
<layout-login></layout-login>
<layout-header></layout-header>
<div class="main">
<slot />
<!-- 客服按钮 -->
<div class="el-affix-wrap">
<el-affix class="el-affix" ref="fixedElement" position="bottom" :offset="200">
<div class="content" @click="clickKf">
<svg-icon name="header" size="40px" />
<div class="name">客服</div>
</div>
</el-affix>
</div>
</div>
</div>
<layout-footer></layout-footer>
</div>
<login />
</template>
<script setup lang="ts">
import { getConfig } from "@/api/platformConfig";
import { Config } from "@/api/types/types";
import Login from "@/component/login/index.vue";
import SvgIcon from "../SvgIcon/SvgIcon.vue";
import LayoutFooter from "./component/LayoutFooter.vue";
import LayoutHeader from "./component/LayoutHeader.vue";
import LayoutLogin from "./component/LayoutLogin.vue";
const fixedElement = ref<HTMLDivElement>();
const configRef = ref<Config.ConfigInfo>();
const clipboard = useClipboard();
const clickKf = () => {
console.log("click", configRef.value);
const qq = configRef.value?.contents;
if (qq) {
// QQ
ElMessageBox.confirm(qq, "客服QQ号", {
cancelButtonText: "关闭",
confirmButtonText: "复制"
}).then(res => {
if (res === "confirm") {
// QQ
clipboard.copy(qq);
ElMessage.success("复制成功");
}
});
}
};
onMounted(() => {
getConfig("CUSTOMER").then(resp => {
configRef.value = resp.data.data;
});
});
</script>
<style scoped lang="scss">
.container {
width: 100%;
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-between;
.main {
width: 1200px;
margin: 0 auto;
padding: 20px 0;
}
.el-affix-wrap {
position: relative;
display: flex;
justify-content: flex-end;
.el-affix {
z-index: 999;
width: 40px;
.content {
cursor: pointer;
&:hover {
color: var(--el-color-primary);
}
.name {
text-align: center;
}
}
}
}
}
</style>

View File

@ -0,0 +1,253 @@
<template>
<div>
<el-dialog class="login-bg" width="500px" v-model="loginStore.isShow" v-loading="loading">
<div class="tab">
<div class="item" :class="formValue.type == 1 ? 'active' : ''" @click="formValue.type = 1">本机号码登录</div>
<div class="item" :class="formValue.type == 2 ? 'active' : ''" @click="formValue.type = 2">商家手机号登录</div>
</div>
<div class="form">
<div class="item">
<el-select class="prefix el-select mid" v-model="formValue.phonePrefix">
<el-option value="+86">+86</el-option>
</el-select>
<input type="text" placeholder="请输入您的手机号" v-model="formValue.phone" :disabled="isCountDown" />
<!-- 获取验证码按钮 -->
<el-button class="btn" type="primary" :disabled="verifyBtnDisable" @click="getVerifyCode" :loading="verifyBtnLoading">{{
isCountDown ? `${countDown}s` : "获取验证码"
}}</el-button>
</div>
<div class="item">
<div class="prefix">验证码</div>
<input type="text" placeholder="请输入短信验证码" v-model="formValue.code" />
</div>
</div>
<div class="submit-wrap">
<el-button :disabled="loginBtnDisable" class="submit-btn" type="primary" @click="submitLogin"></el-button>
</div>
<div class="agreement">
<el-checkbox class="checkbox" v-model="formValue.agreement">&nbsp;</el-checkbox>
登录表示您已阅读并同意<span class="text" @click.stop="() => (xieYiShow = true)">用户协议</span>&<span
class="text"
@click.stop="() => (YinSiShow = true)"
>隐私声明</span
>
</div>
</el-dialog>
<!-- 协议 -->
<el-dialog v-model="xieYiShow" title="用户协议">
<div style="height: 500px; overflow: auto">
<XieYi />
</div>
</el-dialog>
<!-- 隐私 -->
<el-dialog v-model="YinSiShow" title="隐私协议">
<div style="height: 500px; overflow: auto">
<YinSi />
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { login, sendSms } from "@/api/login";
import { useLoginStore } from "@/stores/login";
import { useUserStore } from "@/stores/user";
import { validatePhone } from "@/utils/validate";
import { ElMessage } from "element-plus";
import XieYi from "@/component/business/xieyi/XieYi.vue";
import YinSi from "@/component/business/xieyi/YinSi.vue";
//
const xieYiShow = ref(false);
//
const YinSiShow = ref(false);
//
const loading = ref(false);
//
const loginStore = useLoginStore();
//
const userStore = useUserStore();
//
const formValue = reactive({
type: 1,
phonePrefix: "+86",
phone: "",
code: "",
agreement: false
});
//
const countDown = ref(0);
//
const isCountDown = computed(() => {
return countDown.value > 0;
});
//
const verifyBtnDisable = computed(() => {
return isCountDown.value || formValue.phone.length != 11;
});
//
const loginBtnDisable = computed(() => {
return formValue.phone.length != 11 || formValue.code.length != 6 || !formValue.agreement;
});
//
const verifyBtnLoading = ref(false);
//
const timer = ref();
//
const getVerifyCode = () => {
//
if (!validatePhone(formValue.phone)) {
ElMessage.warning("请输入正确的手机号");
return;
}
verifyBtnLoading.value = true;
//
sendSms(formValue.phone)
.then(res => {
countDown.value = 60;
if (res.data.data) {
timer.value = setInterval(() => {
countDown.value--;
if (countDown.value === 0) {
clearInterval(timer.value);
}
}, 1000);
}
})
.finally(() => (verifyBtnLoading.value = false));
};
//
const submitLogin = () => {
loading.value = true;
login({
type: formValue.type,
username: formValue.phone,
velCode: formValue.code
})
.then(resp => {
const token = resp.data.data.token;
localStorage.setItem("token", token);
userStore.setToken(token);
userStore.pullUserInfo().then(() => {
ElMessage.success("登录成功");
loginStore.hide();
});
})
.finally(() => (loading.value = false));
};
</script>
<style scoped lang="scss">
.login-bg {
width: 500px;
height: 600px;
background-color: #fff;
//
.tab {
width: 100%;
display: flex;
justify-content: space-around;
.item {
font-size: 20px;
cursor: pointer;
position: relative;
&.active {
color: var(--el-color-primary);
&::after {
content: "";
width: 50px;
height: 4px;
background-color: var(--el-color-primary);
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
}
}
}
}
//
.form {
width: 460px;
margin: 0 auto;
margin-top: 60px;
.item {
display: flex;
height: 40px;
padding-top: 30px;
align-items: center;
border-bottom: 1px solid #e0e0e0;
// select
:deep(.el-select.prefix .el-input__wrapper) {
box-shadow: none;
}
:deep(.el-select.prefix .el-input__wrapper.is-focus) {
box-shadow: none !important;
}
:deep(.el-select.prefix .el-input__inner) {
text-align: right;
font-size: 18px;
}
.prefix {
width: 100px;
color: #000;
text-align: center;
font-size: 18px;
//
&.mid {
&::after {
content: "";
width: 2px;
height: 20px;
background-color: #00000033;
position: absolute;
top: 8px;
right: -2px;
}
}
}
input {
margin-left: 5px;
font-size: 18px;
flex: 1;
border: none;
outline: none;
padding: 0 10px;
position: relative;
}
.btn {
height: 35px;
width: 100px;
}
}
}
//
.submit-wrap {
text-align: center;
.submit-btn {
margin-top: 100px;
width: 90%;
height: 50px;
}
}
//
.agreement {
margin-top: 30px;
display: flex;
justify-content: center;
font-size: 14px;
align-items: center;
.text {
cursor: pointer;
color: var(--el-color-primary);
}
}
}
</style>

View File

@ -0,0 +1,56 @@
import { BasePageReq, RespPage } from "@/api/types/types";
import { AxiosResponse } from "axios";
/**
*
*/
export const useTableStatus = <T, S>(
api: (p: BasePageReq<S>) => Promise<AxiosResponse<RespPage<T>>>,
initSearchValue: S,
initData: boolean = true
) => {
// ref类型
const tableData: Ref<T[]> = ref([]);
// 搜索条件
const searchValue = ref(initSearchValue) as Ref<S>;
// 是否加载中
const tableLoading = ref(false);
// 获取数据
const loadData = async () => {
tableLoading.value = true;
try {
const res = await api({
pageNum: pageInfo.pageNum,
pageSize: pageInfo.pageSize,
search: unref(searchValue)
});
tableData.value = res.data.data.records;
pageInfo.total = res.data.data.total;
pageInfo.pageNum = res.data.data.current;
} finally {
tableLoading.value = false;
}
};
// 分页数据
const pageInfo = reactive({
pageNum: 1,
pageSize: 10,
total: 0,
handleSizeChange(val: number) {
pageInfo.pageSize = val;
pageInfo.pageNum = 1;
loadData();
},
handleCurrentChange(val: number) {
pageInfo.pageNum = val;
loadData();
}
});
onMounted(() => {
if (initData) {
loadData();
}
});
return { searchValue, tableData, pageInfo, tableLoading, loadData };
};

114
jjstore/src/hooks/toPay.tsx Normal file
View File

@ -0,0 +1,114 @@
import { addressPage } from "@/api/address";
import { addOrderCard } from "@/api/orderCard";
import { createOrder } from "@/api/pay";
import { Address } from "@/api/types/types";
import { ElCheckbox, ElMessageBox, ElPagination, ElTable, ElTableColumn } from "element-plus";
import { useTableStatus } from "./tableState";
/**
*
*/
export const usePayHooks = () => {
// 路由
const router = useRouter();
// 当前支付表单
const payFormRef = reactive({
// 产品
productId: "",
// 选择规格
skuId: "",
// 收货地址
addressId: "",
// 选择数量
number: 1
});
// 当前可选择收货地址表格数据
const { tableData, pageInfo, loadData } = useTableStatus<Address.AddressInfo, {}>(addressPage, {}, false);
// 加入购物车
const pushOrderCard = async () => {
const res = await addOrderCard({
numbers: payFormRef.number,
productId: payFormRef.productId,
skuId: payFormRef.skuId
});
if (res.data.data) {
ElMessage.success("加入购物车成功");
} else {
ElMessage.error(res.data.message);
}
};
// 发起支付
const startPay = async () => {
await loadData();
if (tableData.value.length > 0) {
payFormRef.addressId = tableData.value[0].id;
console.log(payFormRef);
}
const result = await ElMessageBox({
title: "选择收货地址",
showCancelButton: true,
showConfirmButton: true,
showClose: true,
draggable: true,
customStyle: {
width: "1000px",
maxWidth: "100%"
},
message() {
return (
<div>
<ElTable
data={tableData.value}
onSelection-change={val => {
payFormRef.addressId = val[0].id;
console.log(val);
}}
>
<ElTableColumn
label="选择"
width={80}
header-align="center"
align="center"
v-slots={(scope: any) => {
return (
<ElCheckbox
onUpdate:modelValue={() => {
payFormRef.addressId = scope.row.id;
}}
modelValue={payFormRef.addressId === scope.row.id}
/>
);
}}
/>
<ElTableColumn prop="userName" label="收货人" header-align="center" align="center" />
<ElTableColumn prop="area" label="地区" header-align="center" align="center" />
<ElTableColumn prop="address" label="详细地址" header-align="center" align="center" />
<ElTableColumn prop="postcode" label="邮政编码" header-align="center" align="center" />
<ElTableColumn prop="phones" label="联系电话" header-align="center" align="center" />
</ElTable>
<ElPagination
background
currentPage={pageInfo.pageNum}
pageSize={pageInfo.pageSize}
onUpdate:current-page={() => {}}
onUpdate:page-size={() => {}}
total={pageInfo.total}
layout="sizes, prev, pager, next, total"
onSize-change={pageInfo.handleSizeChange}
onCurrent-change={pageInfo.handleCurrentChange}
/>
</div>
);
}
});
if (result === "confirm") {
// toPay
const orderId = (await createOrder(toRaw(payFormRef))).data.data;
router.push(`/pay/${orderId}`);
}
};
return { payFormRef, startPay, pushOrderCard };
};

22
jjstore/src/main.ts Normal file
View File

@ -0,0 +1,22 @@
import "element-plus/es/components/message-box/style/css";
import "element-plus/es/components/message/style/css";
import { createPinia } from "pinia";
import { createApp } from "vue";
import "./assets/styles/main.scss";
import App from "./App.vue";
import router from "./router";
// 注册图标
import "virtual:svg-icons-register";
import { useUserStore } from "./stores/user";
const app = createApp(App);
app.use(createPinia());
// 验证登录状态
const userStore = useUserStore();
userStore.checkLogin().then(() => {
//启动应用
app.use(router);
app.mount("#app");
});

188
jjstore/src/router/index.ts Normal file
View File

@ -0,0 +1,188 @@
import { useLoginStore } from "@/stores/login";
import { useUserStore } from "@/stores/user";
import CenterView from "@/views/center/CenterView.vue";
import FinanceMgrView from "@/views/center/children/FinanceMgr/FinanceMgrView.vue";
import MyDetailView from "@/views/center/children/MyDetailView.vue";
import MyHarvestAddressEditView from "@/views/center/children/MyHarvestAddress/MyHarvestAddressEditView.vue";
import MyHarvestAddressView from "@/views/center/children/MyHarvestAddress/MyHarvestAddressView.vue";
import OrderDetailView from "@/views/center/children/OrderDetail/OrderDetailView.vue";
import ProductMgrEditView from "@/views/center/children/ProductMgr/ProductMgrEditView.vue";
import ProductMgrView from "@/views/center/children/ProductMgr/ProductMgrView.vue";
import StatisticsMgrView from "@/views/center/children/StatisticsMgr/StatisticsMgrView.vue";
import StoreMgrView from "@/views/center/children/StoreMgr/StoreMgrView.vue";
import StoreOrderMgrView from "@/views/center/children/StoreOrderMgr/StoreOrderMgrView.vue";
import StoreUserMgrView from "@/views/center/children/StoreUserMgr/StoreUserMgrView.vue";
import UserMessageView from "@/views/center/children/UserMessage/UserMessageView.vue";
import UserOrderMgrView from "@/views/center/children/UserOrderMgr/UserOrderMgrView.vue";
import HomeView from "@/views/home/HomeView.vue";
import InformationView from "@/views/information/InformationView.vue";
import InformationDetailView from "@/views/informationDetail/InformationDetailView.vue";
import OrderCartView from "@/views/orderCart/OrderCartView.vue";
import PayView from "@/views/pay/PayView.vue";
import ProductView from "@/views/product/ProductView.vue";
import ProductDetailView from "@/views/productDetail/ProductDetailView.vue";
import { createRouter, createWebHistory } from "vue-router";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "home",
component: HomeView
},
{
path: "/product",
name: "product",
component: ProductView
},
{
path: "/product/:id",
name: "productDetail",
component: ProductDetailView
},
{
path: "/order-cart",
name: "orderCart",
component: OrderCartView
},
{
path: "/information",
name: "information",
component: InformationView
},
{
path: "/information/:id",
name: "informationDetail",
component: InformationDetailView
},
{
path: "/pay/:id",
name: "pay",
component: PayView
},
{
path: "/center",
name: "center",
component: CenterView,
children: [
// 我的收货地址
{
path: "my-harvest-address",
name: "myHarvestAddress",
component: MyHarvestAddressView
},
{
path: "my-harvest-address-edit",
name: "myHarvestAddressEdit",
component: MyHarvestAddressEditView
},
// 个人资料
{
path: "my-detail",
name: "myDetail",
component: MyDetailView
},
// 用户订单
{
path: "user-order-mgr",
name: "userOrderMgr",
component: UserOrderMgrView
},
// 商家订单
{
path: "store-order-mgr",
name: "storeOrderMgr",
component: StoreOrderMgrView
},
// 用户消息
{
path: "user-message",
name: "userMessage",
component: UserMessageView
},
// 订单详情
{
path: "order-detail/:id",
name: "orderDetail",
component: OrderDetailView
},
// 商家用户管理
{
path: "store-user-mgr",
name: "storeUserMgr",
component: StoreUserMgrView
},
// 统计管理
{
path: "statistics-mgr",
name: "statisticsMgr",
component: StatisticsMgrView
},
// 店铺管理
{
path: "store-mgr",
name: "storeMgrView",
component: StoreMgrView
},
// 财务管理
{
path: "finance-mgr",
name: "financeMgr",
component: FinanceMgrView
},
// 商品管理相关
{
path: "product-mgr",
name: "productMgr",
component: ProductMgrView
},
{
path: "product-mgr-edit",
name: "productMgrEdit",
component: ProductMgrEditView
},
// 重定向到个人资料
{
path: ":id",
redirect: "my-detail"
}
]
},
// 重定向到首页
{
path: "/:id",
redirect: "/"
}
]
});
router.beforeEach(to => {
// 如果访问的是购物车和center开头的页面,则跳转到home页面
const userStore = useUserStore();
// 如果未登录
if (!userStore.userInfo) {
if (to.path.startsWith("/center") || to.path === "/order-cart") {
useLoginStore().show();
return "/";
}
} else {
if (to.path === "/order-cart" && userStore.userInfo.type != 1) {
ElMessage.error("非买家无法查看购物车");
useLoginStore().show();
return "/";
}
if (to.path === "/center/store-mgr" && userStore.userInfo.type != 2) {
ElMessage.error("非买家无法查看商家管理");
useLoginStore().show();
return "/";
}
}
// 跳转时滚动条回到顶部
window.scrollTo(0, 0);
return true;
});
export default router;

View File

@ -0,0 +1,23 @@
import { defineStore } from "pinia";
import { ref } from "vue";
export const useLoginStore = defineStore("login", () => {
/**
*
*/
const isShow = ref(false);
/**
*
*/
function show() {
isShow.value = true;
}
/**
*
*/
function hide() {
isShow.value = false;
}
return { isShow, show, hide };
});

View File

@ -0,0 +1,11 @@
import { defineStore } from "pinia";
import { ref } from "vue";
export const useSearchStore = defineStore("search", () => {
/**
*
*/
const keywordRef = ref<string>("");
return { keywordRef };
});

View File

@ -0,0 +1,67 @@
import { UserInfo } from "@/api/types/types";
import { getUserInfo, saveUserInfo } from "@/api/user";
import { defineStore } from "pinia";
import { ref } from "vue";
import { Router } from "vue-router";
export const useUserStore = defineStore("user", () => {
/**
*
*/
const userInfo = ref<UserInfo.UserInfoRes>();
/**
* token
*/
const token = ref("");
const checkLogin = () => {
return new Promise<void>(resolve => {
const newTOken = localStorage.getItem("token");
if (newTOken) {
setToken(newTOken);
pullUserInfo().then(() => {
resolve();
});
return;
}
resolve();
});
};
/**
* token
* @param newToken Token
*/
const setToken = (newToken: string) => {
token.value = newToken;
};
/**
*
*/
const pullUserInfo = async () => {
const resp = await getUserInfo();
userInfo.value = resp.data.data;
};
/**
*
*/
const pushUserInfo = async () => {
return saveUserInfo(userInfo.value!);
};
/**
* 退
*/
const logout = (router: Router) => {
ElMessageBox.confirm("是否退出登录?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
localStorage.removeItem("token");
token.value = "";
userInfo.value = undefined;
ElMessage.success("退出成功");
router.push({ path: "/login" });
});
};
return { userInfo, token, setToken, pullUserInfo, pushUserInfo, checkLogin, logout };
});

289
jjstore/src/types/auto-imports.d.ts vendored Normal file
View File

@ -0,0 +1,289 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-auto-import
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const ElMessage: typeof import('element-plus/es')['ElMessage']
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
const computed: typeof import('vue')['computed']
const computedAsync: typeof import('@vueuse/core')['computedAsync']
const computedEager: typeof import('@vueuse/core')['computedEager']
const computedInject: typeof import('@vueuse/core')['computedInject']
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
const controlledRef: typeof import('@vueuse/core')['controlledRef']
const createApp: typeof import('vue')['createApp']
const createEventHook: typeof import('@vueuse/core')['createEventHook']
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate']
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise']
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
const customRef: typeof import('vue')['customRef']
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
const effectScope: typeof import('vue')['effectScope']
const extendRef: typeof import('@vueuse/core')['extendRef']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
const inject: typeof import('vue')['inject']
const isDefined: typeof import('@vueuse/core')['isDefined']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
const onLongPress: typeof import('@vueuse/core')['onLongPress']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
const provide: typeof import('vue')['provide']
const reactify: typeof import('@vueuse/core')['reactify']
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
const reactive: typeof import('vue')['reactive']
const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed']
const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit']
const reactivePick: typeof import('@vueuse/core')['reactivePick']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const refAutoReset: typeof import('@vueuse/core')['refAutoReset']
const refDebounced: typeof import('@vueuse/core')['refDebounced']
const refDefault: typeof import('@vueuse/core')['refDefault']
const refThrottled: typeof import('@vueuse/core')['refThrottled']
const refWithControl: typeof import('@vueuse/core')['refWithControl']
const resolveComponent: typeof import('vue')['resolveComponent']
const resolveRef: typeof import('@vueuse/core')['resolveRef']
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const syncRef: typeof import('@vueuse/core')['syncRef']
const syncRefs: typeof import('@vueuse/core')['syncRefs']
const templateRef: typeof import('@vueuse/core')['templateRef']
const throttledRef: typeof import('@vueuse/core')['throttledRef']
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
const toRaw: typeof import('vue')['toRaw']
const toReactive: typeof import('@vueuse/core')['toReactive']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted']
const unref: typeof import('vue')['unref']
const unrefElement: typeof import('@vueuse/core')['unrefElement']
const until: typeof import('@vueuse/core')['until']
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
const useAnimate: typeof import('@vueuse/core')['useAnimate']
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']
const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']
const useArrayFind: typeof import('@vueuse/core')['useArrayFind']
const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex']
const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast']
const useArrayIncludes: typeof import('@vueuse/core')['useArrayIncludes']
const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin']
const useArrayMap: typeof import('@vueuse/core')['useArrayMap']
const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce']
const useArraySome: typeof import('@vueuse/core')['useArraySome']
const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique']
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
const useAttrs: typeof import('vue')['useAttrs']
const useBase64: typeof import('@vueuse/core')['useBase64']
const useBattery: typeof import('@vueuse/core')['useBattery']
const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
const useCached: typeof import('@vueuse/core')['useCached']
const useClipboard: typeof import('@vueuse/core')['useClipboard']
const useCloned: typeof import('@vueuse/core')['useCloned']
const useColorMode: typeof import('@vueuse/core')['useColorMode']
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
const useCounter: typeof import('@vueuse/core')['useCounter']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVar: typeof import('@vueuse/core')['useCssVar']
const useCssVars: typeof import('vue')['useCssVars']
const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement']
const useCycleList: typeof import('@vueuse/core')['useCycleList']
const useDark: typeof import('@vueuse/core')['useDark']
const useDateFormat: typeof import('@vueuse/core')['useDateFormat']
const useDebounce: typeof import('@vueuse/core')['useDebounce']
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
const useDraggable: typeof import('@vueuse/core')['useDraggable']
const useDropZone: typeof import('@vueuse/core')['useDropZone']
const useElementBounding: typeof import('@vueuse/core')['useElementBounding']
const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint']
const useElementHover: typeof import('@vueuse/core')['useElementHover']
const useElementSize: typeof import('@vueuse/core')['useElementSize']
const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility']
const useEventBus: typeof import('@vueuse/core')['useEventBus']
const useEventListener: typeof import('@vueuse/core')['useEventListener']
const useEventSource: typeof import('@vueuse/core')['useEventSource']
const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
const useFavicon: typeof import('@vueuse/core')['useFavicon']
const useFetch: typeof import('@vueuse/core')['useFetch']
const useFileDialog: typeof import('@vueuse/core')['useFileDialog']
const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess']
const useFocus: typeof import('@vueuse/core')['useFocus']
const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']
const useFps: typeof import('@vueuse/core')['useFps']
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
const useGamepad: typeof import('@vueuse/core')['useGamepad']
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
const useIdle: typeof import('@vueuse/core')['useIdle']
const useImage: typeof import('@vueuse/core')['useImage']
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
const useInterval: typeof import('@vueuse/core')['useInterval']
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
const useLink: typeof import('vue-router')['useLink']
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
const useMediaControls: typeof import('@vueuse/core')['useMediaControls']
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
const useMemoize: typeof import('@vueuse/core')['useMemoize']
const useMemory: typeof import('@vueuse/core')['useMemory']
const useMounted: typeof import('@vueuse/core')['useMounted']
const useMouse: typeof import('@vueuse/core')['useMouse']
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
const useMousePressed: typeof import('@vueuse/core')['useMousePressed']
const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']
const useNetwork: typeof import('@vueuse/core')['useNetwork']
const useNow: typeof import('@vueuse/core')['useNow']
const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl']
const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination']
const useOnline: typeof import('@vueuse/core')['useOnline']
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
const useParallax: typeof import('@vueuse/core')['useParallax']
const useParentElement: typeof import('@vueuse/core')['useParentElement']
const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver']
const usePermission: typeof import('@vueuse/core')['usePermission']
const usePointer: typeof import('@vueuse/core')['usePointer']
const usePointerLock: typeof import('@vueuse/core')['usePointerLock']
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast']
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
const usePrevious: typeof import('@vueuse/core')['usePrevious']
const useRafFn: typeof import('@vueuse/core')['useRafFn']
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
const useRoute: typeof import('vue-router')['useRoute']
const useRouter: typeof import('vue-router')['useRouter']
const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
const useScroll: typeof import('@vueuse/core')['useScroll']
const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
const useShare: typeof import('@vueuse/core')['useShare']
const useSlots: typeof import('vue')['useSlots']
const useSorted: typeof import('@vueuse/core')['useSorted']
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
const useStepper: typeof import('@vueuse/core')['useStepper']
const useStorage: typeof import('@vueuse/core')['useStorage']
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
const useSupported: typeof import('@vueuse/core')['useSupported']
const useSwipe: typeof import('@vueuse/core')['useSwipe']
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize']
const useThrottle: typeof import('@vueuse/core')['useThrottle']
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
const useTimeout: typeof import('@vueuse/core')['useTimeout']
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
const useTitle: typeof import('@vueuse/core')['useTitle']
const useToNumber: typeof import('@vueuse/core')['useToNumber']
const useToString: typeof import('@vueuse/core')['useToString']
const useToggle: typeof import('@vueuse/core')['useToggle']
const useTransition: typeof import('@vueuse/core')['useTransition']
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
const useVModel: typeof import('@vueuse/core')['useVModel']
const useVModels: typeof import('@vueuse/core')['useVModels']
const useVibrate: typeof import('@vueuse/core')['useVibrate']
const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
const useWebNotification: typeof import('@vueuse/core')['useWebNotification']
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
const useWebWorker: typeof import('@vueuse/core')['useWebWorker']
const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn']
const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
const watch: typeof import('vue')['watch']
const watchArray: typeof import('@vueuse/core')['watchArray']
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
const watchDebounced: typeof import('@vueuse/core')['watchDebounced']
const watchDeep: typeof import('@vueuse/core')['watchDeep']
const watchEffect: typeof import('vue')['watchEffect']
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
const watchImmediate: typeof import('@vueuse/core')['watchImmediate']
const watchOnce: typeof import('@vueuse/core')['watchOnce']
const watchPausable: typeof import('@vueuse/core')['watchPausable']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
const watchThrottled: typeof import('@vueuse/core')['watchThrottled']
const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable']
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
const whenever: typeof import('@vueuse/core')['whenever']
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode } from 'vue'
}

26
jjstore/src/types/global.d.ts vendored Normal file
View File

@ -0,0 +1,26 @@
/**
* oss
*/
declare module "ali-oss" {
const OSS: any;
export = OSS;
}
/* FileType */
declare namespace File {
type ImageMimeType =
| "image/apng"
| "image/bmp"
| "image/gif"
| "image/jpeg"
| "image/pjpeg"
| "image/png"
| "image/svg+xml"
| "image/tiff"
| "image/webp"
| "image/x-icon";
type ExcelMimeType = "application/vnd.ms-excel" | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
}
// 语言包支持
declare module "element-plus/dist/locale/zh-cn.mjs";

43
jjstore/src/utils/http.ts Normal file
View File

@ -0,0 +1,43 @@
import { useUserStore } from "@/stores/user";
import axios from "axios";
import { ElMessage } from "element-plus";
// 读取配置文件
const baseURL = import.meta.env.VITE_API_URL as string;
// 请求建立
const http = axios.create({
baseURL
});
// 请求拦截
http.interceptors.request.use(config => {
const userStore = useUserStore();
// 设置请求头
if (config.headers && typeof config.headers.set === "function") {
config.headers.set("Authorization", userStore.token);
}
return config;
});
// 响应拦截
http.interceptors.response.use(response => {
const { data } = response;
const code = data.code as string;
// 非成功响应
if (code !== "00000") {
// 未开通店铺
if (code === "A0214") {
useRouter().push("/center/store");
return Promise.reject(data);
}
const message = data.message as string | undefined;
// 显示错误消息
if (message) {
ElMessage.error(message);
} else {
ElMessage.error("请求失败");
}
return Promise.reject(data);
}
return response;
});
export default http;

13
jjstore/src/utils/uuid.ts Normal file
View File

@ -0,0 +1,13 @@
/**
* @description uuid
* @returns {String}
*/
export function generateUUID() {
let uuid = "";
for (let i = 0; i < 32; i++) {
const random = (Math.random() * 16) | 0;
if (i === 8 || i === 12 || i === 16 || i === 20) uuid += "-";
uuid += (i === 12 ? 4 : i === 16 ? (random & 3) | 8 : random).toString(16);
}
return uuid;
}

View File

@ -0,0 +1,43 @@
/**
*
* @param phone
*/
export const validatePhone = (phone: string) => {
const phoneReg = /^1[3|4|5|6|7|8][0-9]\d{8}$/;
return phoneReg.test(phone);
};
/**
* element-plus
*/
export const validatePhoneRule = (rule: any, value: any, callback: any) => {
if (!value) {
return callback(new Error("请输入手机号"));
}
if (!validatePhone(value)) {
return callback(new Error("请输入正确的手机号"));
}
return callback();
};
/**
*
* @param email
*/
export const validateEmail = (email: string) => {
// eslint-disable-next-line no-useless-escape
const emailReg = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
return emailReg.test(email);
};
/**
* element-plus
*/
export const validateEmailRule = (rule: any, value: any, callback: any) => {
if (!value) {
return callback(new Error("请输入邮箱"));
}
if (!validateEmail(value)) {
return callback(new Error("请输入正确的邮箱"));
}
return callback();
};

View File

@ -0,0 +1,70 @@
<template>
<layout>
<div class="center">
<div class="left">
<sidebar :sidebar-res-list="sidebarList" @side-click="sideClick" :active-id="route.path" />
</div>
<div class="right">
<div class="content">
<router-view />
</div>
</div>
</div>
</layout>
</template>
<script setup lang="ts">
import { Classify } from "@/api/types/types";
import Sidebar from "@/component/business/Sidebar.vue";
import layout from "@/component/layout/index.vue";
import { useUserStore } from "@/stores/user";
import { storeToRefs } from "pinia";
import MenuCustomerJson from "./menu-customer.json";
import MenuShopJson from "./menu-shop.json";
const userStore = useUserStore();
const { userInfo } = storeToRefs(userStore);
//
const sidebarList = computed(() => {
if (userInfo.value?.type === 1) {
return MenuCustomerJson;
}
if (userInfo.value?.type === 2) {
return MenuShopJson;
}
return [];
});
//
const route = useRoute();
const activeBar = ref<Classify.ClassifyInfo>(sidebarList.value.filter(item => item.id === route.path)[0]);
//
const router = useRouter();
//
const sideClick = (item: Classify.ClassifyInfo) => {
if (item.id === "/center/logout") {
userStore.logout(router);
return;
}
activeBar.value = item;
router.push({
path: item.id
});
};
</script>
<style scoped lang="scss">
.center {
display: flex;
justify-content: space-between;
.left {
flex: 0 0 220px;
}
.right {
width: 950px;
background-color: #f5f5f5;
margin-left: 14px;
padding: 12px 16px;
.content {
margin-top: 20px;
}
}
}
</style>

View File

@ -0,0 +1,147 @@
<template>
<div class="finance-mgr">
<!-- 我的钱包 -->
<el-row class="wallet-info" :gutter="10">
<!-- <el-col :span="8">
<el-card class="item">
<el-statistic :value="walletInfo?.amount" title="冻结金额" />
</el-card>
</el-col> -->
<el-col zne:span="8">
<el-card class="item">
<div style="font-size: 12px;">剩余金额</div>
<div style="font-size: 30px;">{{walletInfo?.surplusAmount }}</div>
<!-- <el-statistic :value="walletInfo?.surplusAmount" title="剩余金额">
<el-text>walletInfo?.surplusAmount</el-text>
</el-statistic> -->
</el-card>
</el-col>
<!-- <el-col :span="8">
<el-card class="item">
<el-statistic :value="walletInfo?.withdrawAmount" title="已经提现金额" />
</el-card>
</el-col> -->
</el-row>
<!-- 提现记录表格 -->
<el-card class="wallet-log">
<template #header>
<span>提现记录</span>
</template>
<div>
<el-form inline>
<el-form-item label="时间范围">
<el-date-picker type="daterange" v-model="dateRange" value-format="YYYY-MM-DD" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadData"> </el-button>
<el-button type="primary" @click="dialogVisible = true"> 提现</el-button>
</el-form-item>
</el-form>
</div>
<el-table :data="tableData" class-name="table" v-loading="tableLoading" row-key="id">
<el-table-column prop="createDate" label="时间" header-align="center" align="center" width="170" />
<el-table-column prop="surplusAmount" label="剩余金额" header-align="center" align="center" />
<el-table-column prop="bigType" label="类型" header-align="center" align="center">
<template #default="scope">
<span v-if="scope.row.bigType === 1"></span>
<span v-if="scope.row.bigType === 2"></span>
</template>
</el-table-column>
<el-table-column prop="type" label="操作" header-align="center" align="center">
<template #default="scope">
<span v-if="scope.row.type === 1"></span>
<span v-if="scope.row.type === 2"></span>
</template>
</el-table-column>
<el-table-column prop="amount" label="金额" header-align="center" align="center" />
<el-table-column prop="auditTime" label="审核时间" header-align="center" align="center" width="170" />
<el-table-column prop="cardNumber" label="打款账户" header-align="center" align="center" />
<el-table-column prop="cardName" label="打款银行" header-align="center" align="center" />
<el-table-column prop="remitTime" label="打款时间" header-align="center" align="center" />
<el-table-column prop="remitType" label="打款状态" header-align="center" align="center">
<template #default="scope">
<span v-if="scope.row.remitType === 0"></span>
<span v-if="scope.row.remitType === 1"></span>
<span v-if="scope.row.remitType === 2"></span>
</template>
</el-table-column>
</el-table>
<el-pagination background layout="sizes,prev, pager, next, total" :total="pageInfo.total"
:current-page="pageInfo.pageNum" :page-size="pageInfo.pageSize" @size-change="pageInfo.handleSizeChange"
@current-change="pageInfo.handleCurrentChange"></el-pagination>
</el-card>
<!-- 提现弹框 -->
<el-dialog title="提现" v-model="dialogVisible" destroy-on-close>
<template #title>
<span>提现</span>
</template>
<el-form :model="withdrawForm">
<el-form-item label="提现金额" prop="amount">
<el-input v-model="withdrawForm.amount" :controls="false">
<template #append></template>
</el-input>
</el-form-item>
<el-form-item label="提现账户" prop="cardNumber">
<el-input v-model="withdrawForm.cardNumber" />
</el-form-item>
<el-form-item label="提现银行" prop="cardName">
<el-input v-model="withdrawForm.cardName" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false"> 取消 </el-button>
<el-button type="primary" @click="submitWithdraw"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { getWallet, queryWalletLog, withdraw } from "@/api/storeWallet";
import { StoreWallet } from "@/api/types/types";
import { useTableStatus } from "@/hooks/tableState";
const dialogVisible = ref(false);
//
const withdrawForm = ref({
cardNumber: "",
cardName: "",
amount: 0
});
//
const submitWithdraw = () => {
withdraw(withdrawForm.value).then(res => {
ElMessage.success("提现成功");
dialogVisible.value = false;
loadData();
});
};
//
const walletInfo = ref<StoreWallet.WalletInfo>();
onMounted(() => {
//
getWallet().then(res => {
walletInfo.value = res.data.data;
});
});
//
const { searchValue, tableData, pageInfo, tableLoading, loadData } = useTableStatus<
StoreWallet.WalletLog,
StoreWallet.PageSearch
>(queryWalletLog, {});
const dateRange = ref<[string, string]>(["", ""]);
watchImmediate(dateRange, newValue => {
searchValue.value.beginData = newValue[0];
searchValue.value.endData = newValue[1];
});
</script>
<style scoped lang="scss">
.finance-mgr {
.wallet-log {
padding: 10px;
margin-top: 10px;
}
}
</style>

View File

@ -0,0 +1,120 @@
<template>
<el-card class="user-detail">
<template #header>
<span>基础信息</span>
</template>
<div class="main">
<!-- 头像 -->
<div class="col">
<div class="avatar">
<upload-img
border-radius="50%"
width="90px"
height="90px"
class="img"
v-if="userInfo"
v-model:image-url="userInfo.avatar"
/>
</div>
</div>
<!-- 用户名和ID -->
<div class="col">
<div>用户名{{ userInfo?.userName }}</div>
<div>用户ID{{ userInfo?.codes }}</div>
</div>
<!-- 手机号码和注册时间 -->
<div class="col">
<div>手机号码{{ userInfo?.loginName }}</div>
<div>注册时间{{ userInfo?.createDate }}</div>
</div>
</div>
<!-- 基础信息 -->
<div class="base-info">
<div class="title">基础信息</div>
<el-form v-if="userInfo">
<el-form-item>
<el-col :span="11">
<el-form-item label="性别">
<el-select v-model="userInfo.sex" style="width: 100%">
<el-option :value="1" label="男" />
<el-option :value="2" label="女" />
<el-option :value="3" label="保密" />
</el-select>
</el-form-item>
</el-col>
<el-col :offset="2" :span="11">
<el-form-item label="邮箱">
<el-input v-model="userInfo.emails" />
</el-form-item>
</el-col>
</el-form-item>
<el-form-item label="简介">
<el-input type="textarea" :autosize="{ minRows: 4, maxRows: 6 }" v-model="userInfo.introduction" />
</el-form-item>
</el-form>
<div class="btn-wrap">
<el-button @click="reset"></el-button>
<el-button @click="save" type="primary">保存</el-button>
</div>
</div>
</el-card>
</template>
<script setup lang="ts">
import UploadImg from "@/component/Upload/Img.vue";
import { useUserStore } from "@/stores/user";
import { ElMessage } from "element-plus";
import { storeToRefs } from "pinia";
const userStore = useUserStore();
const { userInfo } = storeToRefs(userStore);
//
const reset = () => {
userStore.pullUserInfo().then(() => {
ElMessage.success("重置成功");
});
};
//
const save = () => {
userStore.pushUserInfo().then(() => {
userStore.pullUserInfo();
ElMessage.success("保存成功");
});
};
</script>
<style scoped lang="scss">
.user-detail {
padding: 10px;
.main {
display: flex;
justify-content: space-around;
height: 100px;
padding: 30px 0;
border-bottom: 1px solid #f5f5f5;
.col {
display: flex;
flex-direction: column;
justify-content: space-around;
align-content: flex-start;
height: 100%;
.avatar {
width: 90px;
height: 90px;
}
}
}
.base-info {
margin-top: 30px;
.title {
margin: 20px 0;
font-size: 18px;
}
.btn-wrap {
display: flex;
width: 100%;
justify-content: center;
}
}
}
</style>

View File

@ -0,0 +1,134 @@
<template>
<el-card class="add" v-loading="loading">
<template #header>
<span>{{ title }}</span>
</template>
<el-form ref="ruleFormRef" label-width="100px" label-suffix=" :" :model="formValue" :rules="rules">
<!-- 级联地区 -->
<el-form-item label="所在地址" prop="area">
<cascader-district
v-model:province="formValue.province"
v-model:city="formValue.city"
v-model:district="formValue.area"
/>
</el-form-item>
<!-- 详细地址 -->
<el-form-item label="详细地址" prop="address">
<el-input type="textarea" v-model="formValue.address" placeholder="请输入详细地址" />
</el-form-item>
<!-- 手机号 -->
<el-form-item label="手机号" prop="phones">
<el-input v-model="formValue.phones" placeholder="请输入手机号" />
</el-form-item>
<!-- 收货人姓名 -->
<el-form-item label="收货人姓名" prop="userName">
<el-input v-model="formValue.userName" placeholder="请输入收货人姓名" />
</el-form-item>
<!-- 邮政编码 -->
<el-form-item label="邮政编码" prop="postcode">
<el-input v-model="formValue.postcode" placeholder="请输入邮政编码" />
</el-form-item>
</el-form>
<!-- 提交按钮 -->
<div class="footer">
<el-button @click="cancel"></el-button>
<el-button @click="submit" type="primary">保存</el-button>
</div>
</el-card>
</template>
<script setup lang="ts">
import { addAddress, getAddress, updateAddress } from "@/api/address";
import { Address } from "@/api/types/types";
import CascaderDistrict from "@/component/CascaderDistrict/CascaderDistrict.vue";
import { validatePhoneRule } from "@/utils/validate";
import { ElMessage, FormInstance, FormRules } from "element-plus";
const route = useRoute();
// ID,
const addressId = route.query.id as string;
const title = computed(() => {
return addressId ? "编辑收货地址" : "新增收货地址";
});
//
const loading = ref(false);
//
const router = useRouter();
const cancel = () => {
router.back();
};
//
const ruleFormRef = ref<FormInstance>();
//
const rules = reactive<FormRules<any>>({
area: [{ required: true, message: "请选择所在地区", trigger: "change" }],
address: [{ required: true, message: "请输入详细地址", trigger: "blur" }],
userName: [{ required: true, message: "请输入收货人姓名", trigger: "blur" }],
phones: [
{ required: true, message: "请输入手机号", trigger: "blur" },
{ validator: validatePhoneRule, trigger: "change" }
]
//postcode: [{ required: true, message: "", trigger: "blur" }]
});
//
const formValue = ref<Partial<Address.AddressInfo>>({});
onMounted(() => {
if (addressId) {
loading.value = true;
//
getAddress(addressId).then(res => {
formValue.value = res.data.data;
loading.value = false;
});
}
});
//
const submit = () => {
loading.value = true;
ruleFormRef
.value!.validate()
.then(() => {
if (addressId) {
//
updateAddress(formValue.value)
.then(res => {
if (res.data.data) {
router.back();
ElMessage.success("保存成功");
} else {
ElMessage.error("保存失败");
}
})
.finally(() => (loading.value = false));
} else {
addAddress(formValue.value)
.then(res => {
if (res.data.data) {
router.back();
ElMessage.success("保存成功");
} else {
ElMessage.error("保存失败");
}
})
.finally(() => (loading.value = false));
}
})
.finally(() => (loading.value = false));
};
</script>
<style scoped lang="scss">
.add {
background-color: #ffffff;
padding: 10px;
.footer {
margin-top: 30px;
width: 100%;
text-align: center;
.el-button {
width: 160px;
}
}
}
</style>

View File

@ -0,0 +1,110 @@
<template>
<el-card class="my-harvest-address">
<template #header>
<span>收货地址管理</span>
</template>
<div>
<el-form inline>
<!--<el-form-item label="商品状态">
<el-select v-model="formValue.status" clearable>
<el-option label="上架" :value="1" />
<el-option label="下架" :value="2" />
</el-select>
</el-form-item>-->
<el-form-item>
<!--<el-button type="primary" @click="loadData"> </el-button>-->
<el-button type="primary" @click="add"></el-button>
</el-form-item>
</el-form>
</div>
<el-table :data="tableData" class-name="table" v-loading="tableLoading" row-key="id">
<el-table-column prop="userName" label="收货人" header-align="center" align="center" />
<el-table-column prop="area" label="地区" header-align="center" align="center" />
<el-table-column prop="address" label="详细地址" header-align="center" align="center" />
<el-table-column prop="postcode" label="邮政编码" header-align="center" align="center" />
<el-table-column prop="phones" label="联系电话" header-align="center" align="center" />
<!-- 操作 -->
<el-table-column label="操作" width="140" header-align="center" align="center">
<template #default="scope">
<el-button-group>
<el-button type="primary" @click="edit(scope.row)" text>编辑</el-button>
<el-button type="primary" @click="del(scope.row)" text>删除</el-button>
</el-button-group>
</template>
</el-table-column>
<!-- 设为默认按钮 -->
<el-table-column label="设置" width="140" header-align="center" align="center">
<template #default="scope">
<el-button v-if="scope.row.isDefault === 2" type="primary" @click="setDefault(scope.row)" text>设为默认</el-button>
<el-text v-if="scope.row.isDefault === 1" type="info" text>默认</el-text>
</template>
</el-table-column>
</el-table>
<el-pagination
background
layout="sizes,prev, pager, next, total"
:total="pageInfo.total"
:current-page="pageInfo.pageNum"
:page-size="pageInfo.pageSize"
@size-change="pageInfo.handleSizeChange"
@current-change="pageInfo.handleCurrentChange"
></el-pagination>
</el-card>
</template>
<script setup lang="ts">
import { addressPage, deleteAddress, updateAddress } from "@/api/address";
import { Address, Product } from "@/api/types/types";
import { useTableStatus } from "@/hooks/tableState";
import { ElMessage, ElMessageBox } from "element-plus";
const router = useRouter();
const { tableData, pageInfo, tableLoading, loadData } = useTableStatus<Address.AddressInfo, {}>(addressPage, {});
//
const setDefault = (item: Address.AddressInfo) => {
ElMessageBox.confirm("此操作将设为默认地址, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
// TODO
tableLoading.value = true;
updateAddress(Object.assign({}, { ...item, isDefault: 1 })).then(res => {
if (res.data.data) {
loadData();
ElMessage.success("设为默认成功");
}
});
});
};
//
const add = () => {
router.push({ path: "/center/my-harvest-address-edit" });
};
//
const edit = (item: Product.ProductInfo) => {
router.push({ path: "/center/my-harvest-address-edit", query: { id: item.id } });
};
//
const del = (item: Product.ProductInfo) => {
ElMessageBox.confirm("此操作将永久删除该地址, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
// TODO
deleteAddress(item.id).then(res => {
if (res.data.data) {
loadData();
ElMessage.success("删除成功");
} else {
ElMessage.error("删除失败");
}
});
});
};
</script>
<style scoped lang="scss">
.my-harvest-address {
background-color: #ffffff;
padding: 10px;
}
</style>

View File

@ -0,0 +1,113 @@
<template>
<el-card class="order-detail">
<template #header>
<span>订单详情</span>
</template>
<div class="block">
<div class="title">用户信息</div>
<el-row class="row">
<el-col :span="12">用户昵称{{ orderDetail?.shopOrders.userName }} </el-col>
<el-col :span="12">用户手机号{{ orderDetail?.shopOrders.userPhones }} </el-col>
</el-row>
</div>
<div class="block">
<div class="title">收货信息</div>
<el-row class="row" :gutter="20">
<el-col class="col" :span="12">收货人姓名{{ orderDetail?.address.userName }} </el-col>
<el-col class="col" :span="12">收货人手机号{{ orderDetail?.address.phones }} </el-col>
<el-col class="col" :span="12">收货人地址{{ orderDetail?.address.address }} </el-col>
</el-row>
</div>
<div class="block">
<div class="title">订单信息</div>
<el-row class="row">
<el-col class="col" :span="12">店铺名称{{ orderDetail?.shopOrders.storeName }} </el-col>
<el-col class="col" :span="12">订单编号{{ orderDetail?.shopOrders.tradeId }} </el-col>
<el-col class="col" :span="12">订单状态{{ statusStr }} </el-col>
<el-col class="col" :span="12">支付金额{{ orderDetail?.shopOrders.price }} </el-col>
<el-col class="col" :span="12">支付方式{{ orderDetail?.shopOrders.payType }} </el-col>
<el-col class="col" :span="12">下单时间{{ orderDetail?.shopOrders.orderTime?.substring(0, 10) }} </el-col>
<el-col class="col" :span="12">物流编号{{ orderDetail?.shopOrders.logisticsCode }} </el-col>
<el-col class="col" :span="12">发货时间{{ orderDetail?.shopOrders.deliveryTime?.substring(0, 10) }} </el-col>
<el-col class="col" :span="12">确认收货时间{{ orderDetail?.shopOrders.deliveryTime?.substring(0, 10) }} </el-col>
</el-row>
</div>
<div class="block">
<div>商品信息</div>
<el-table :data="orderDetail?.productList">
<el-table-column prop="names" label="商品名称" header-align="center" align="center" />
<el-table-column prop="cover" label="商品图片" header-align="center" align="center">
<template #default="scope">
<el-image :src="scope.row.cover" preview-teleported :initial-index="4" :preview-src-list="[scope.row.cover]">
<template #placeholder>
<div class="image-slot">加载中<span class="dot">...</span></div>
</template>
</el-image>
</template>
</el-table-column>
<el-table-column prop="price" label="单价" header-align="center" align="center" />
<el-table-column prop="number" label="数量" header-align="center" align="center" />
</el-table>
</div>
</el-card>
</template>
<script setup lang="ts">
import { getOrderDetail } from "@/api/order";
import { Order } from "@/api/types/types";
const orderId = useRoute().params.id as string;
const orderDetail = ref<Order.OrderDetail>();
const statusStr = computed(() => {
//<!-- 2 3 4 5 7 退 8 退 9 退 -->
switch (orderDetail.value?.shopOrders.states) {
case 2:
return "支付成功";
case 3:
return "支付失败";
case 4:
return "发货";
case 5:
return "签收";
case 7:
return "退款中";
case 8:
return "退款成功";
case 9:
return "退款失败";
default:
return "未知";
}
});
onMounted(() => {
getOrderDetail(orderId).then(resp => {
orderDetail.value = resp.data.data;
});
});
</script>
<style scoped lang="scss">
.order-detail {
padding: 10px;
.block {
padding: 10px 0;
border-bottom: 1px solid #0000001a;
&:last-child {
border-bottom: none;
}
.title {
font-size: 18px;
font-weight: 400;
color: #000000b2;
margin: 20px 0 40px;
}
.row {
padding-left: 20px;
.col {
margin: 20px 0;
}
}
}
}
</style>

View File

@ -0,0 +1,358 @@
<template>
<div class="add" v-loading="loading">
<div class="block">
<div class="title">基础信息</div>
<el-form ref="ruleForm1Ref" label-width="100px" label-suffix=" :" :model="formValue" :rules="rules">
<el-form-item label="商品名称" prop="names">
<el-input v-model="formValue.names" placeholder="请输入商品名称" />
</el-form-item>
<el-form-item label="商品封面" prop="cover">
<upload-img v-model:image-url="formValue.cover" />
</el-form-item>
<el-form-item label="商品轮播图" prop="carousels">
<upload-imgs v-model:file-list="bannerListRef" />
</el-form-item>
<el-form-item label="一级分类" prop="classifyId">
<el-select v-model="formValue.classifyId" placeholder="请选择商品分类">
<el-option v-for="item in classifyList" :key="item.id" :label="item.names" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="二级分类" prop="classifyErId">
<el-select v-model="formValue.classifyErId" placeholder="请选择商品二级分类">
<el-option v-for="item in classifyTwo" :key="item.id" :label="item.names" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="商品描述" prop="describe">
<el-input v-model="formValue.describe" type="textarea" placeholder="请输入商品描述" />
</el-form-item>
<el-form-item label="商品详情" prop="introduce">
<wang-editor v-model:value="formValue.introduce" />
</el-form-item>
</el-form>
</div>
<!-- 规格信息 -->
<div class="block">
<div class="title">规格信息</div>
<div class="content">
<div class="tags">
<!-- 标签列表 -->
<el-tag
class="el-tag"
size="large"
v-for="item in formValue.skuDtoList"
:key="item.names"
closable
@close="closeSku(item)"
>
{{ item.names }}
</el-tag>
<!-- 新标签内容 -->
<el-input
v-if="skuInputVisible"
ref="skuInputRef"
v-model="skuInputValue"
class="new-sku-input"
size="large"
@keyup.enter="handleInputConfirm"
@blur="handleInputConfirm"
/>
<!-- 添加规格 -->
<el-button v-else class="new-sku-btn" size="large" @click="showInput" :icon="Plus">新规格</el-button>
</div>
<div class="table">
<el-form ref="ruleForm2Ref" :model="formValue" :rules="rules">
<el-table :data="formValue.skuDtoList" border>
<el-table-column prop="names" label="规格名称" align="center" header-align="center">
<template #default="scope">
<el-form-item
:rules="{ required: true, message: '请输入规格名称', trigger: 'blur' }"
:prop="`skuDtoList.${scope.$index}.names`"
>
<el-input v-model="formValue.skuDtoList[scope.$index].names" placeholder="请输入规格名称" />
</el-form-item>
</template>
</el-table-column>
<el-table-column prop="price" label="价格" align="center" header-align="center">
<template #default="scope">
<el-form-item
:rules="[
{ required: true, message: '请输入价格', trigger: 'blur' },
{ min: 0, type: 'number', message: '请输入正确的价格', trigger: 'blur' }
]"
:prop="`skuDtoList.${scope.$index}.price`"
>
<el-input-number v-model="formValue.skuDtoList[scope.$index].price" :precision="2" placeholder="请输入价格" />
</el-form-item>
</template>
</el-table-column>
<el-table-column prop="inventory" label="库存" align="center" header-align="center">
<template #default="scope">
<el-form-item
:rules="[
{ required: true, message: '请输入库存', trigger: 'blur' },
{ min: 0, type: 'number', message: '请输入正确的库存', trigger: 'blur' }
]"
:prop="`skuDtoList.${scope.$index}.inventory`"
>
<el-input-number
v-model="formValue.skuDtoList[scope.$index].inventory"
:precision="0"
placeholder="请输入库存"
/>
</el-form-item>
</template>
</el-table-column>
</el-table>
</el-form>
</div>
</div>
</div>
<!-- 销售信息 -->
<div class="block">
<div class="title">销售信息</div>
<div class="content">
<el-form ref="ruleForm3Ref" label-width="100px" label-suffix=" :" :model="formValue" :rules="rules">
<el-form-item label="销售价格">
<el-input class="el-input-width-auto" v-model="formValue.price">
<template #append></template>
</el-input>
</el-form-item>
<el-form-item label="销售状态">
<el-radio-group v-model="formValue.status">
<el-radio :label="1">上架</el-radio>
<el-radio :label="2">下架</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</div>
</div>
<!-- 提交按钮 -->
<div class="footer">
<el-button @click="cancel"></el-button>
<el-button @click="submit" type="primary">保存</el-button>
</div>
</div>
</template>
<script setup lang="ts">
import { queryClassifyAll } from "@/api/classify";
import { createProduct, getProductEdit, updateProduct } from "@/api/product";
import { Classify, Product } from "@/api/types/types";
import UploadImg from "@/component/Upload/Img.vue";
import UploadImgs from "@/component/Upload/Imgs.vue";
import WangEditor from "@/component/WangEditor/WangEditor.vue";
import { Plus } from "@element-plus/icons-vue";
import { ElInput, ElMessage, FormInstance, FormRules, UploadUserFile } from "element-plus";
const route = useRoute();
// ID,
const productId = route.query.id as string;
//
const loading = ref(false);
//
const router = useRouter();
const cancel = () => {
router.back();
};
//
const ruleForm1Ref = ref<FormInstance>();
const ruleForm2Ref = ref<FormInstance>();
const ruleForm3Ref = ref<FormInstance>();
//
const rules = reactive<FormRules<any>>({
names: [{ required: true, message: "请输入商品名称", trigger: "blur" }],
cover: [{ required: true, message: "请选择商品封面", trigger: "blur" }],
carousels: [{ required: true, message: "请选择商品轮播图", trigger: "blur" }],
classifyId: [{ required: true, message: "请选择商品分类", trigger: "blur" }],
classifyErId: [{ required: true, message: "请选择商品二级分类", trigger: "blur" }],
describe: [{ required: true, message: "请输入商品描述", trigger: "blur" }],
introduce: [{ required: true, message: "请输入商品详情", trigger: "blur" }],
price: [
{ required: true, message: "请输入销售价格", trigger: "blur" },
{ min: 0, type: "number", message: "请输入正确的价格", trigger: "blur" }
],
status: [{ required: true, message: "请选择商品状态", trigger: "blur" }]
});
//
const classifyList = ref<Classify.ClassifyInfo[]>([]);
//
const classifyTwo = computed(() => {
const classTowList = classifyList.value.filter(item => item.id === formValue.value.classifyId);
return classTowList.length ? classTowList[0].voList : [];
});
//
watchDeep(classifyTwo, () => {
formValue.value.classifyErId = "";
});
//
const bannerListRef = ref<UploadUserFile[]>([]);
//
watchDeep(bannerListRef, () => {
formValue.value.carousels = bannerListRef.value.map(item => item.url!).join(",");
console.log(formValue.value);
});
//
const formValue = ref<Product.ProductEdit>({
names: "",
cover: "",
carousels: "",
classifyId: "",
classifyErId: "",
describe: "",
introduce: "",
price: 0,
status: 0,
skuDtoList: [
{
names: "规格0",
inventory: 0,
price: 0
}
]
});
onMounted(() => {
//
queryClassifyAll().then(res => {
classifyList.value = res.data.data;
});
// ,
if (productId) {
loading.value = true;
//
getProductEdit(productId)
.then(resp => {
formValue.value = resp.data.data;
bannerListRef.value = resp.data.data.carousels.split(",").map(item => {
return {
name: "",
url: item
};
});
})
.finally(() => (loading.value = false));
}
});
const skuInputVisible = ref(false);
const skuInputValue = ref("");
const skuInputRef = ref<InstanceType<typeof ElInput>>();
//
const showInput = () => {
skuInputVisible.value = true;
nextTick(() => {
skuInputRef.value!.input!.focus();
});
};
//
const handleInputConfirm = () => {
if (skuInputValue.value) {
formValue.value.skuDtoList.push({
names: skuInputValue.value,
inventory: 0,
price: 0
});
}
skuInputVisible.value = false;
skuInputValue.value = "";
};
//
const closeSku = (item: Product.SkuDtoEdit) => {
//
if (formValue.value.skuDtoList.length == 1) {
ElMessage.error("至少保留一个规格");
return;
}
formValue.value.skuDtoList.splice(formValue.value.skuDtoList.indexOf(item), 1);
};
//
const submit = () => {
Promise.all([ruleForm1Ref.value!.validate(), ruleForm2Ref.value!.validate(), ruleForm3Ref.value!.validate()]).then(() => {
if (productId) {
// TODO
updateProduct(productId, toRaw(formValue.value)).then(resp => {
if (resp.data.data) {
ElMessage.success("编辑成功");
router.back();
} else {
ElMessage.error(resp.data.message);
}
});
return;
}
createProduct(toRaw(formValue.value)).then(resp => {
if (resp.data.data) {
ElMessage.success("发布成功");
router.back();
} else {
ElMessage.error(resp.data.message);
}
});
});
};
</script>
<style scoped lang="scss">
.add {
background-color: #ffffff;
padding: 10px;
.block {
padding: 10px 0;
border-bottom: 1px solid #0000001a;
&:last-child {
border-bottom: none;
}
.title {
font-size: 18px;
font-weight: 400;
color: #000000b2;
margin: 20px 0;
}
.content {
.el-input-width-auto,
:deep(.cascader-city .el-input) {
width: auto;
}
.tags {
padding-left: 40px;
margin: 20px 0;
> * {
margin-right: 20px;
&:last-child {
margin-right: 0;
}
}
.el-tag {
width: 100px;
height: 40px;
font-size: 16px;
}
.new-sku-input {
width: 100px;
height: 40px;
font-size: 16px;
}
.new-sku-btn {
width: 100px;
height: 40px;
font-size: 16px;
}
}
.table {
width: 600px;
}
}
}
.footer {
margin-top: 30px;
width: 100%;
text-align: center;
.el-button {
width: 160px;
}
}
}
</style>

View File

@ -0,0 +1,115 @@
<template>
<el-card class="product-mgr">
<template #header>
<span>商品管理</span>
</template>
<div>
<el-form inline>
<el-form-item label="商品分类">
<el-select v-model="searchValue.classifyId" clearable>
<el-option v-for="item in classifyList" :key="item.id" :label="item.names" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="商品状态">
<el-select v-model="searchValue.status" clearable>
<el-option label="上架" :value="1" />
<el-option label="下架" :value="2" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadData"> </el-button>
<el-button type="primary" @click="add"></el-button>
</el-form-item>
</el-form>
</div>
<el-table :data="tableData" class-name="table" v-loading="tableLoading" row-key="id">
<!--<el-table-column prop="id" label="ID" header-align="center" />-->
<el-table-column prop="names" label="商品名称" header-align="center" align="center" />
<el-table-column prop="cover" label="商品图片" header-align="center" align="center">
<template #default="scope">
<el-image :src="scope.row.cover" preview-teleported :initial-index="4" :preview-src-list="[scope.row.cover]">
<template #placeholder>
<div class="image-slot">加载中<span class="dot">...</span></div>
</template>
</el-image>
</template>
</el-table-column>
<el-table-column prop="price" label="商品售价" header-align="center" align="center" />
<el-table-column prop="createDate" label="添加时间" width="170" header-align="center" align="center" />
<el-table-column prop="status" label="状态" header-align="center" align="center">
<template #default="scope">
<el-tag v-if="scope.row.status === 1"></el-tag>
<el-tag v-else type="danger">下架</el-tag>
</template>
</el-table-column>
<!-- 操作 -->
<el-table-column label="操作" width="140" header-align="center" align="center">
<template #default="scope">
<el-button-group>
<el-button type="primary" @click="edit(scope.row)" text>编辑</el-button>
<el-button type="primary" @click="del(scope.row)" text>删除</el-button>
</el-button-group>
</template>
</el-table-column>
</el-table>
<el-pagination
background
layout="sizes,prev, pager, next, total"
:total="pageInfo.total"
:current-page="pageInfo.pageNum"
:page-size="pageInfo.pageSize"
@size-change="pageInfo.handleSizeChange"
@current-change="pageInfo.handleCurrentChange"
></el-pagination>
</el-card>
</template>
<script setup lang="ts">
import { queryTopAll } from "@/api/classify";
import { deleteProduct, productPage } from "@/api/product";
import { Classify, Product } from "@/api/types/types";
import { useTableStatus } from "@/hooks/tableState";
import { ElMessage, ElMessageBox } from "element-plus";
const router = useRouter();
const classifyList = ref<Classify.ClassifyInfo[]>([]);
const { searchValue, tableData, pageInfo, tableLoading, loadData } = useTableStatus<Product.ProductInfo, Product.PageSearch>(
productPage,
{}
);
//
onMounted(() => {
//
queryTopAll().then(res => {
classifyList.value = res.data.data;
});
});
//
const add = () => {
router.push({ path: "/center/product-mgr-edit" });
};
//
const edit = (item: Product.ProductInfo) => {
router.push({ path: "/center/product-mgr-edit", query: { id: item.id } });
};
//
const del = (item: Product.ProductInfo) => {
ElMessageBox.confirm("此操作将永久删除该商品, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
//
deleteProduct(item.id).then(() => {
loadData();
ElMessage.success("删除成功");
});
});
};
</script>
<style scoped lang="scss">
.product-mgr {
background-color: #ffffff;
padding: 10px;
}
</style>

View File

@ -0,0 +1,53 @@
<template>
<el-card class="statistics-mgr">
<template #header>
<span>统计管理</span>
</template>
<div>
<el-form inline>
<el-form-item label="时间范围">
<el-date-picker type="daterange" v-model="dateRange" value-format="YYYY-MM-DD" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadData"></el-button>
</el-form-item>
</el-form>
</div>
<el-table :data="tableData" class-name="table" v-loading="tableLoading" row-key="id">
<el-table-column prop="names" label="商品名称" header-align="center" align="center" />
<el-table-column prop="orderNumber" label="下单数" header-align="center" align="center" />
<el-table-column prop="orderPrices" label="下单金额" header-align="center" align="center" />
<el-table-column prop="orderUserNumber" label="下单用户" header-align="center" align="center" />
<el-table-column prop="orderNumberToDay" label="今日下单数" header-align="center" align="center" />
</el-table>
<el-pagination
background
layout="sizes,prev, pager, next, total"
:total="pageInfo.total"
:current-page="pageInfo.pageNum"
:page-size="pageInfo.pageSize"
@size-change="pageInfo.handleSizeChange"
@current-change="pageInfo.handleCurrentChange"
></el-pagination>
</el-card>
</template>
<script setup lang="ts">
import { statisticsPage } from "@/api/statistics";
import { Statistics } from "@/api/types/types";
import { useTableStatus } from "@/hooks/tableState";
const { searchValue, tableData, pageInfo, tableLoading, loadData } = useTableStatus<
Statistics.StatisticsInfo,
Statistics.PageSearch
>(statisticsPage, {});
const dateRange = ref<[string, string]>(["", ""]);
watchImmediate(dateRange, newValue => {
searchValue.value.beginData = newValue[0];
searchValue.value.endData = newValue[1];
});
</script>
<style scoped lang="scss">
.statistics-mgr {
padding: 10px;
}
</style>

View File

@ -0,0 +1,239 @@
<template>
<div class="step1">
<!-- 基本资料 -->
<div class="block">
<div class="title">用户信息</div>
<div class="content">
<el-form label-width="140px" label-suffix=":" ref="ruleForm1Ref" :model="formValue" :rules="rules">
<el-form-item label="店铺名称" prop="storeName">
<el-input v-model="formValue.storeName" placeholder="请输入店铺名称" />
</el-form-item>
<el-form-item label="店铺Logo" prop="avatar">
<upload-img v-model:image-url="formValue.avatar" />
</el-form-item>
</el-form>
</div>
</div>
<!-- 负责人信息 -->
<div class="block">
<div class="title">负责人信息</div>
<div class="content">
<el-form inline label-suffix=":" label-width="60px" ref="ruleForm2Ref" :model="formValue" :rules="rules">
<el-row>
<el-col :span="8">
<el-form-item label="姓名" prop="chargeNames">
<el-input class="el-input" v-model="formValue.chargeNames" placeholder="请输入负责人姓名" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="电话" prop="chargePhone">
<el-input class="el-input" v-model="formValue.chargePhone" placeholder="请输入负责人电话" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="邮箱" prop="chargeEmail">
<el-input class="el-input" v-model="formValue.chargeEmail" placeholder="请输入负责人邮箱" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</div>
<!-- 法人信息 -->
<div class="block">
<div class="title">法人信息</div>
<div class="content">
<el-form inline label-width="100px" label-suffix=":" ref="ruleForm3Ref" :model="formValue" :rules="rules">
<el-row>
<el-col :span="8">
<el-form-item label="姓名" prop="legalPersonNames">
<el-input class="el-input" v-model="formValue.legalPersonNames" placeholder="请输入法人姓名" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="电话" prop="legalPersonPhone">
<el-input class="el-input" v-model="formValue.legalPersonPhone" placeholder="请输入法人电话" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="邮箱" prop="legalPersonEmail">
<el-input class="el-input" v-model="formValue.legalPersonEmail" placeholder="请输入法人邮箱" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="店铺地址" prop="city">
<cascader-city
class="cascader-city"
v-model:province="formValue.province"
v-model:city="formValue.city"
placeholder="请选择省/市/区"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="详细地址" prop="address">
<el-input class="el-input" v-model="formValue.address" placeholder="请输入详细地址" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<!-- 身份证 -->
<el-form label-position="top" label-suffix=":" ref="ruleForm4Ref" :model="formValue" :rules="rules">
<el-row>
<el-col :span="12">
<el-form-item label="法人身份证-正面" prop="legalPersonIdcardFront">
<upload-img width="300px" v-model:image-url="formValue.legalPersonIdcardFront" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="法人身份证-反面" prop="legalPersonIdcardBack">
<upload-img width="300px" v-model:image-url="formValue.legalPersonIdcardBack" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</div>
<!-- 企业信息 -->
<div class="block">
<div class="title">企业信息</div>
<div class="content">
<el-form inline label-suffix=":" label-width="140px" ref="ruleForm5Ref" :model="formValue" :rules="rules">
<el-form-item label="企业名称" prop="companyName">
<el-input class="el-input" v-model="formValue.companyName" placeholder="请输入企业名称" />
</el-form-item>
<el-form-item label="统一社会信用号码" prop="companyCodes">
<el-input class="el-input" v-model="formValue.companyCodes" placeholder="请输入统一社会信用号码" />
</el-form-item>
<el-form-item label="行业类别" prop="profession">
<el-input class="el-input" v-model="formValue.profession" placeholder="请输入行业类别" />
</el-form-item>
</el-form>
<el-form label-position="top" label-suffix=":" ref="ruleForm6Ref" :model="formValue" :rules="rules">
<el-col :offset="2">
<el-form-item label="店铺证件" prop="companyImages">
<upload-img width="400px" height="200px" v-model:image-url="formValue.companyImages" />
</el-form-item>
</el-col>
</el-form>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { createStore } from "@/api/store";
import { Store } from "@/api/types/types";
import CascaderCity from "@/component/CascaderCity/CascaderCity.vue";
import UploadImg from "@/component/Upload/Img.vue";
import { validateEmailRule, validatePhoneRule } from "@/utils/validate";
import { FormInstance, FormRules } from "element-plus";
//
const ruleForm1Ref = ref<FormInstance>();
const ruleForm2Ref = ref<FormInstance>();
const ruleForm3Ref = ref<FormInstance>();
const ruleForm4Ref = ref<FormInstance>();
const ruleForm5Ref = ref<FormInstance>();
const ruleForm6Ref = ref<FormInstance>();
onMounted(() => {
console.log("onMounted", ruleForm1Ref);
});
//
const rules = reactive<FormRules<any>>({
storeName: [{ required: true, message: "请输入店铺名称", trigger: "change" }],
avatar: [{ required: true, message: "请选择店铺Logo", trigger: "change" }],
province: [{ required: true, message: "请选择省", trigger: "change" }],
city: [{ required: true, message: "请选择市", trigger: "change" }],
address: [{ required: true, message: "请输入详细地址", trigger: "blur" }],
manageMode: [{ required: true, message: "请选择店铺类型", trigger: "change" }],
chargeNames: [{ required: true, message: "请输入负责人姓名", trigger: "blur" }],
chargePhone: [
{ required: true, message: "请输入负责人电话", trigger: "blur" },
{ validator: validatePhoneRule, trigger: "blur" }
],
chargeEmail: [
{ required: true, message: "请输入负责人邮箱", trigger: "blur" },
{ validator: validateEmailRule, trigger: "blur" }
],
legalPersonNames: [{ required: true, message: "请输入法人姓名", trigger: "blur" }],
legalPersonPhone: [
{ required: true, message: "请输入法人电话", trigger: "blur" },
{ validator: validatePhoneRule, trigger: "blur" }
],
legalPersonEmail: [
{ required: true, message: "请输入法人邮箱", trigger: "blur" },
{ validator: validateEmailRule, trigger: "blur" }
],
legalPersonIdcardFront: [{ required: true, message: "请输入法人身份证-正面", trigger: "blur" }],
legalPersonIdcardBack: [{ required: true, message: "请输入法人身份证-反面", trigger: "blur" }],
companyName: [{ required: true, message: "请输入企业名称", trigger: "blur" }],
companyCodes: [{ required: true, message: "请输入统一社会信用号码", trigger: "blur" }],
profession: [{ required: true, message: "请输入行业类别", trigger: "blur" }],
companyImages: [{ required: true, message: "请输入店铺证件", trigger: "blur" }]
});
//
const formValue = reactive<Store.createStoreReq>({
storeName: "",
avatar: "",
province: "",
city: "",
address: "",
manageMode: "",
chargeNames: "",
chargePhone: "",
chargeEmail: "",
legalPersonNames: "",
legalPersonPhone: "",
legalPersonEmail: "",
legalPersonIdcardFront: "",
legalPersonIdcardBack: "",
companyName: "",
companyCodes: "",
profession: "",
companyImages: ""
});
defineExpose({
//
setFormValue(value: Store.createStoreReq) {
formValue.storeName = Object.assign(formValue.storeName, value);
},
//
submit() {
return Promise.all([
ruleForm1Ref.value?.validate(),
ruleForm2Ref.value?.validate(),
ruleForm3Ref.value?.validate(),
ruleForm4Ref.value?.validate(),
ruleForm5Ref.value?.validate(),
ruleForm6Ref.value?.validate()
]).then(() => {
console.log("submit", "发起提交");
return createStore(toRaw(formValue));
});
}
});
</script>
<style scoped lang="scss">
.step1 {
.block {
padding: 10px 0;
border-bottom: 1px solid #0000001a;
&:last-child {
border-bottom: none;
}
.title {
font-size: 18px;
font-weight: 400;
color: #000000b2;
margin: 20px 0;
}
.el-input,
:deep(.cascader-city .el-input) {
width: auto;
}
}
}
</style>

View File

@ -0,0 +1,59 @@
<template>
<div class="step2">
<div class="block">
<div class="title">缴纳保证金</div>
<div class="content">
<div class="img" v-loading="qrCodeUrl === ''">
<el-image :src="qrCodeUrl" alt="支付二维码" />
</div>
<div class="tip">手机扫码进行付款</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { payDeposit } from "@/api/store";
const qrCodeUrl = ref("");
onMounted(() => {
payDeposit().then(resp => {
console.log(resp.data.data, "数钱吧");
qrCodeUrl.value = resp.data.data.qrCodeImageUrl;
});
});
</script>
<style scoped lang="scss">
.step2 {
.block {
padding: 10px 0;
border-bottom: 1px solid #0000001a;
&:last-child {
border-bottom: none;
}
.title {
font-size: 18px;
font-weight: 400;
color: #000000b2;
margin: 20px 0;
}
.content {
text-align: center;
.img {
width: 400px;
height: 400px;
display: inline-block;
img {
width: 100%;
height: 100%;
}
}
.tip {
font-size: 20px;
color: #000000b2;
}
}
}
}
</style>

View File

@ -0,0 +1,96 @@
<template>
<div class="step2">
<div class="block">
<div class="title">等待审核通过</div>
<div class="content">
<div class="item">
<div class="label">保证金金额</div>
<div class="value">{{ storeInfoRef?.guaranteePrice }}</div>
</div>
<div class="item">
<div class="label">保证金缴纳时间</div>
<div class="value">{{ storeInfoRef?.guaranteeDate?.substring(0, 10) }}</div>
</div>
<div class="item">
<div class="label">店铺审核状态</div>
<div class="value">
<el-text :type="auditType">{{ auditStr }}</el-text>
</div>
</div>
<div class="item">
<div class="label">店铺到期日期</div>
<div class="value">{{ storeInfoRef?.expireDate?.substring(0, 10) }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Store } from "@/api/types/types";
//
const props = defineProps<{
storeInfoRef?: Store.getStoreEdit;
}>();
const auditType = computed(() => {
//0 1 2 3
switch (props.storeInfoRef?.audit) {
case 0:
return "";
case 1:
return "info";
case 2:
return "warning";
case 3:
return "success";
default:
return "";
}
});
const auditStr = computed(() => {
//0 1 2 3
switch (props.storeInfoRef?.audit) {
case 0:
return "创建中";
case 1:
return "审核中";
case 2:
return "审核失败";
case 3:
return "审核成功";
default:
return "未知";
}
});
</script>
<style scoped lang="scss">
.step2 {
.block {
padding: 10px 0;
border-bottom: 1px solid #0000001a;
&:last-child {
border-bottom: none;
}
.title {
font-size: 18px;
font-weight: 400;
color: #000000b2;
margin: 20px 0;
}
.content {
display: flex;
width: 100%;
flex-wrap: wrap;
font-size: 14px;
.item {
display: flex;
flex: 0 0 50%;
.label {
flex: 0 0 160px;
color: #00000080;
}
}
}
}
}
</style>

View File

@ -0,0 +1,39 @@
<template>
<div class="step2">
<div class="block">
<div class="title">已完成入驻</div>
<div class="content">
<el-result v-if="storeInfoRef?.audit == 3" icon="success" title="入驻成功" sub-title="" />
<el-result v-if="storeInfoRef?.audit == 2" icon="warning" title="入驻失败" sub-title="" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Store } from "@/api/types/types";
//
defineProps<{
storeInfoRef?: Store.getStoreEdit;
}>();
</script>
<style scoped lang="scss">
.step2 {
.block {
padding: 10px 0;
border-bottom: 1px solid #0000001a;
&:last-child {
border-bottom: none;
}
.title {
font-size: 18px;
font-weight: 400;
color: #000000b2;
margin: 20px 0;
}
.content {
text-align: center;
}
}
}
</style>

View File

@ -0,0 +1,99 @@
<template>
<el-card class="store-mgr" v-loading="loading">
<template #header>
<span>店铺入驻</span>
</template>
<!-- 进度 -->
<div class="step">
<div class="title">商家入驻流程</div>
<el-steps :active="stepActive" align-center>
<el-step title="填写基本资料" />
<el-step title="缴纳保证金" />
<el-step title="审核通过" />
</el-steps>
</div>
<store-mgr-step1 v-if="stepActive === 0" ref="storeMgrStep1Ref" />
<store-mgr-step2 v-if="stepActive === 1" ref="storeMgrStep2Ref" />
<store-mgr-step3 v-if="stepActive === 2" :store-info-ref="storeInfoRef" />
<store-mgr-step4 v-if="stepActive === 3" :store-info-ref="storeInfoRef" />
<!-- 提交 -->
<div class="block footer">
<el-button type="primary" class="submit-btn" @click="nextStep" v-if="stepActive === 0"></el-button>
<el-button type="primary" class="submit-btn" @click="nextStep" v-if="stepActive === 1"></el-button>
<el-button type="primary" class="submit-btn" @click="nextStep" v-if="stepActive === 2"></el-button>
<!-- todo 审核未通过的化需要回到第一状态 -->
</div>
</el-card>
</template>
<script setup lang="ts">
import { getStoreEdit } from "@/api/store";
import { Store } from "@/api/types/types";
import StoreMgrStep1 from "./StoreMgrStep1.vue";
import StoreMgrStep2 from "./StoreMgrStep2.vue";
import StoreMgrStep3 from "./StoreMgrStep3.vue";
import StoreMgrStep4 from "./StoreMgrStep4.vue";
//
const loading = ref(false);
//
const storeInfoRef = ref<Store.getStoreEdit>();
//
const stepActive = ref(0);
// 1
const storeMgrStep1Ref = ref<InstanceType<typeof StoreMgrStep1>>();
//
const updateStore = () => {
loading.value = true;
getStoreEdit()
.then(resp => {
storeInfoRef.value = resp.data.data;
stepActive.value = storeInfoRef.value.step;
if (storeMgrStep1Ref.value) {
storeMgrStep1Ref.value.setFormValue(storeInfoRef.value);
}
})
.finally(() => (loading.value = false));
};
onMounted(() => {
updateStore();
});
//
const nextStep = () => {
switch (stepActive.value) {
case 0:
storeMgrStep1Ref.value?.submit().then(() => {
stepActive.value++;
console.log("通过验证11111");
});
break;
case 1:
case 2:
updateStore();
break;
}
};
</script>
<style scoped lang="scss">
.store-mgr {
background-color: #ffffff;
padding: 10px;
.step {
padding: 10px 0;
border-bottom: 1px solid #0000001a;
.title {
font-size: 20px;
font-weight: 400;
color: #000000b2;
margin: 20px 0;
}
}
.footer {
text-align: center;
margin: 30px 0;
.submit-btn {
width: 300px;
}
}
}
</style>

View File

@ -0,0 +1,173 @@
<template>
<el-card class="store-order-mgr">
<template #header>
<span>订单管理</span>
</template>
<div>
<el-form inline>
<el-form-item label="手机号">
<el-input v-model="searchValue.phones" clearable />
</el-form-item>
<el-form-item label="订单状态">
<el-select v-model="searchValue.states" clearable>
<!-- 2 支付成功 3 支付失败 4 发货 5 签收 7 退款中 8 退款成功 9 退款失败 -->
<el-option label="支付成功" :value="2" />
<el-option label="支付失败" :value="3" />
<el-option label="发货" :value="4" />
<el-option label="签收" :value="5" />
<el-option label="退款中" :value="7" />
<el-option label="退款成功" :value="8" />
<el-option label="退款失败" :value="9" />
</el-select>
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker v-model="searchValue.beginData" type="date" placeholder="选择日期" />
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker v-model="searchValue.endData" type="date" placeholder="选择日期" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadData"> </el-button>
</el-form-item>
</el-form>
</div>
<el-table :data="tableData" class-name="table" v-loading="tableLoading" row-key="id">
<!--<el-table-column prop="tradeId" label="订单号" header-align="center" align="center" />-->
<el-table-column prop="userName" label="收货人" header-align="center" align="center" width="100px">
<template #default="scope">
收货人: {{ scope.row.userName }} <br />
手机号: {{ scope.row.userPhones }}
</template>
</el-table-column>
<el-table-column prop="storeName" label="店铺名称" header-align="center" align="center" />
<el-table-column prop="productName" label="商品名称" header-align="center" align="center" />
<el-table-column prop="price" label="实际支付" header-align="center" align="center" />
<el-table-column prop="states" label="状态" header-align="center" align="center">
<template #default="scope">
<el-tag type="success" v-if="scope.row.states == 2"></el-tag>
<el-tag type="danger" v-if="scope.row.states == 3"></el-tag>
<el-tag type="warning" v-if="scope.row.states == 4"></el-tag>
<el-tag type="info" v-if="scope.row.states == 5"></el-tag>
<el-tag type="info" v-if="scope.row.states == 7">退</el-tag>
<el-tag type="info" v-if="scope.row.states == 8">退</el-tag>
<el-tag type="info" v-if="scope.row.states == 9">退</el-tag>
</template>
</el-table-column>
<el-table-column prop="logisticsCode" label="物流编号" header-align="center" align="center">
<template #default="scope">
物流编号:{{ scope.row.logisticsCode }} <br />
发货时间:{{ scope.row.deliveryTime }}
</template>
</el-table-column>
<!-- <el-table-column prop="deliveryTime" label="发货时间" header-align="center" align="center" /> -->
<el-table-column prop="payTime" label="下单时间" header-align="center" align="center" />
<el-table-column prop="logisticsStates" label="订单状态" header-align="center" align="center">
<template #default="scope">
<!--0 不可发货 1 待发货 2 待收货 3 签收-->
<el-tag type="danger" v-if="scope.row.logisticsStates == 0"></el-tag>
<el-tag type="info" v-if="scope.row.logisticsStates == 1"></el-tag>
<el-tag type="info" v-if="scope.row.logisticsStates == 2"></el-tag>
<el-tag type="success" v-if="scope.row.logisticsStates == 3"></el-tag>
</template>
</el-table-column>
<!-- 操作 -->
<el-table-column label="操作" header-align="center" align="center">
<template #default="scope">
<el-button-group>
<el-button type="primary" text v-if="scope.row.logisticsStates == 1"
@click="logisticsAction(scope.row)">发货</el-button>
<el-button type="primary" text @click="toDetail(scope.row)"></el-button>
</el-button-group>
</template>
</el-table-column>
</el-table>
<el-pagination background layout="sizes,prev, pager, next, total" :total="pageInfo.total"
:current-page="pageInfo.pageNum" :page-size="pageInfo.pageSize" @size-change="pageInfo.handleSizeChange"
@current-change="pageInfo.handleCurrentChange"></el-pagination>
</el-card>
</template>
<script setup lang="tsx">
import { orderLogistics, storeOrderPage } from "@/api/order";
import { Order } from "@/api/types/types";
import { useTableStatus } from "@/hooks/tableState";
import { ElForm, ElFormItem, ElInput } from "element-plus";
//
const router = useRouter();
const toDetail = (item: Order.StoreOrderInfo) => {
router.push({
path: `/center/order-detail/${item.id}`
});
};
//
const orderLogisticsForm = ref<Order.LogisticsReq>({});
const logisticsAction = async (item: Order.StoreOrderInfo) => {
orderLogisticsForm.value.id = item.id;
//
const result = await ElMessageBox({
title: "请输入物流信息",
showCancelButton: true,
showConfirmButton: true,
showClose: true,
draggable: true,
customStyle: {
width: "600px",
maxWidth: "100%"
},
message() {
return (
<ElForm labelSuffix=":">
<ElFormItem label="物流名称">
<ElInput
modelValue={orderLogisticsForm.value.logisticsNames}
onUpdate:modelValue={val => (orderLogisticsForm.value.logisticsNames = val)}
placeholder="请输入物流名称"
/>
</ElFormItem>
<ElFormItem label="物流单号">
<ElInput
modelValue={orderLogisticsForm.value.logisticsCode}
onUpdate:modelValue={val => (orderLogisticsForm.value.logisticsCode = val)}
placeholder="请输入物流单号" />
</ElFormItem>
</ElForm>
);
}
});
//
if (result === "confirm") {
if (!orderLogisticsForm.value.logisticsCode) {
ElMessage.error("请输入物流单号");
return;
}
if (!orderLogisticsForm.value.logisticsNames) {
ElMessage.error("请输入物流名称");
return;
}
orderLogistics(orderLogisticsForm.value).then(resp => {
if (resp.data.data) {
ElMessage.success("发货成功");
orderLogisticsForm.value = {};
loadData();
} else {
ElMessage.error(resp.data.message);
}
});
}
};
//
const { searchValue, tableData, pageInfo, tableLoading, loadData } = useTableStatus<Order.StoreOrderInfo, Order.StorePageSearch>(
storeOrderPage,
{}
);
</script>
<style scoped lang="scss">
.store-order-mgr {
padding: 10px;
}
</style>

View File

@ -0,0 +1,58 @@
<template>
<el-card class="store-user-mgr">
<template #header>
<span>用户管理</span>
</template>
<div>
<el-form inline>
<el-form-item label="收货电话">
<el-input v-model="searchValue.phones" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadData"> </el-button>
</el-form-item>
</el-form>
</div>
<el-table :data="tableData" class-name="table" v-loading="tableLoading" row-key="id">
<el-table-column prop="id" label="ID" header-align="center" align="center" />
<el-table-column prop="avatar " label="头像" header-align="center" align="center">
<template #default="scope">
<el-image :src="scope.row.avatar" preview-teleported :initial-index="4" :preview-src-list="[scope.row.cover]">
<template #placeholder>
<div class="image-slot">加载中<span class="dot">...</span></div>
</template>
</el-image>
</template>
</el-table-column>
<el-table-column prop="codes" label="账户" header-align="center" align="center" />
<el-table-column prop="loginName" label="手机号码" header-align="center" align="center" />
<!--<el-table-column prop="adress" label="收货地址" header-align="center" align="center" />-->
</el-table>
<el-pagination
background
layout="sizes,prev, pager, next, total"
:total="pageInfo.total"
:current-page="pageInfo.pageNum"
:page-size="pageInfo.pageSize"
@size-change="pageInfo.handleSizeChange"
@current-change="pageInfo.handleCurrentChange"
></el-pagination>
</el-card>
</template>
<script setup lang="ts">
import { StoreUser } from "@/api/types/types";
import { useTableStatus } from "@/hooks/tableState";
import { statisticsUserPage } from "@/api/statistics";
const { searchValue, tableData, pageInfo, tableLoading, loadData } = useTableStatus<StoreUser.StoreUser, StoreUser.PageSearch>(
statisticsUserPage,
{
phones: ""
}
);
</script>
<style scoped lang="scss">
.store-user-mgr {
padding: 10px;
}
</style>

View File

@ -0,0 +1,88 @@
<template>
<div class="user-message" v-loading="tableLoading">
<!-- 内容 -->
<div class="item-wrap" v-if="!tableLoading">
<el-card v-for="item in tableData" :key="item.id">
<div class="item">
<div class="icon">
<svg-icon class="svg" name="message" />
</div>
<div class="info">
<div class="title">{{ item.titles }}</div>
<div class="content">{{ item.contents }}</div>
<div class="date">{{ item.createDate }}</div>
</div>
</div>
</el-card>
<el-card v-if="tableData.length == 0" class="no-message"></el-card>
</div>
<el-skeleton v-if="tableLoading" :rows="5" animated />
<!-- 分页 -->
<el-pagination
background
layout="sizes,prev, pager, next, total"
:total="pageInfo.total"
:current-page="pageInfo.pageNum"
:page-size="pageInfo.pageSize"
@size-change="pageInfo.handleSizeChange"
@current-change="pageInfo.handleCurrentChange"
></el-pagination>
</div>
</template>
<script setup lang="ts">
import { getMessageList } from "@/api/message";
import { Message } from "@/api/types/types";
import SvgIcon from "@/component/SvgIcon/SvgIcon.vue";
import { useTableStatus } from "@/hooks/tableState";
const { tableData, pageInfo, tableLoading } = useTableStatus<Message.MessageInfo, string>(getMessageList, "");
</script>
<style scoped lang="scss">
.user-message {
padding: 10px;
.item-wrap {
min-height: 200px;
.item {
margin-bottom: 10px;
display: flex;
align-items: center;
width: 100%;
.icon {
margin-right: 10px;
border-radius: 70px;
width: 80px;
height: 80px;
background-color: #0068b70f;
display: flex;
justify-content: center;
align-items: center;
svg {
width: 80%;
height: 80%;
}
}
.info {
flex: 1;
.title {
font-size: 18px;
font-weight: 400;
margin-bottom: 10px;
}
.content {
font-size: 14px;
color: #606266;
margin-bottom: 10px;
}
.date {
font-size: 14px;
color: #606266;
}
}
}
.no-message {
text-align: center;
font-size: 16px;
color: #606266;
}
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More