提交 44ca5201 作者: 龚洪江

Merge branch 'master' into develop

# Conflicts:
#	package.json
.DS_Store
.next
node_modules
Dockerfile
.dist
\ No newline at end of file
node_modules
\ No newline at end of file
{ {
"extends": "next/core-web-vitals" "extends": "next/core-web-vitals",
"env": {
"es6": true,
"node": true
},
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error"
}
} }
name: Build and Push to ACR
on:
push:
branches: ["feature/chuck"]
env:
REGION_ID: cn-shenzhen
REGISTRY: mmc-registry.cn-shenzhen.cr.aliyuncs.com
NAMESPACE: sharefly-dev
IMAGE: web
TAG: ${{ github.sha }}
ACR_EE_REGISTRY: mmc-registry.cn-shenzhen.cr.aliyuncs.com
ACR_EE_INSTANCE_ID: cri-yhk5zgfc2v1sia6l
ACR_EE_NAMESPACE: sharefly-dev
ACR_EE_IMAGE: web
ACR_EE_TAG: ${{ github.sha }}
JAVA_VERSION: "8"
GITLAB_URL: https://oauth2:MjVJKxB7m4tCy7symBzn@git.mmcuav.cn/iuav/csf-web.git
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
environment: dev
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Login to ACR EE with the AccessKey pair
uses: aliyun/acr-login@v1
with:
login-server: "https://${{ env.ACR_EE_REGISTRY }}"
region-id: "${{ env.REGION_ID }}"
username: "QD--KeBiTeHangKong@1354706964800968"
password: "MMC@2023&ACR"
instance-id: "${{ env.ACR_EE_INSTANCE_ID }}"
- name: Build and push image to ACR EE
run: |
docker build -t "$ACR_EE_REGISTRY/$ACR_EE_NAMESPACE/$ACR_EE_IMAGE:$TAG" .
docker push "$ACR_EE_REGISTRY/$ACR_EE_NAMESPACE/$ACR_EE_IMAGE:$TAG"
- name: Kustomize Set Image
run: |-
cd kustomization/overlays/dev
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash /dev/stdin 3.8.6
./kustomize edit set image REGISTRY/NAMESPACE/IMAGE:TAG=$REGISTRY/$NAMESPACE/$IMAGE:$TAG
- name: Commit and Push
run: |
git config user.name "Chuck"
git config user.email "Chuck@users.noreply.github.com"
git remote set-url origin "$GITLAB_URL"
git commit -am "Generated Image Update"
git push origin "feature/chuck"
name: Build and Push to ACR
on:
push:
### Production
branches: ["release/chuck"]
env:
REGION_ID: cn-shenzhen
REGISTRY: mmc-registry.cn-shenzhen.cr.aliyuncs.com
NAMESPACE: sharefly
IMAGE: web
TAG: ${{ github.sha }}
ACR_EE_REGISTRY: mmc-registry.cn-shenzhen.cr.aliyuncs.com
ACR_EE_INSTANCE_ID: cri-yhk5zgfc2v1sia6l
ACR_EE_NAMESPACE: sharefly
ACR_EE_IMAGE: web
ACR_EE_TAG: ${{ github.sha }}
JAVA_VERSION: "8"
GITLAB_URL: https://oauth2:MjVJKxB7m4tCy7symBzn@git.mmcuav.cn/iuav/csf-web.git
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
### Production
environment: prod
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Login to ACR EE with the AccessKey pair
uses: aliyun/acr-login@v1
with:
login-server: "https://${{ env.ACR_EE_REGISTRY }}"
region-id: "${{ env.REGION_ID }}"
username: "QD--KeBiTeHangKong@1354706964800968"
password: "MMC@2023&ACR"
instance-id: "${{ env.ACR_EE_INSTANCE_ID }}"
- name: Build and push image to ACR EE
run: |
docker build -t "$ACR_EE_REGISTRY/$ACR_EE_NAMESPACE/$ACR_EE_IMAGE:$TAG" .
docker push "$ACR_EE_REGISTRY/$ACR_EE_NAMESPACE/$ACR_EE_IMAGE:$TAG"
### Production
- name: Kustomize Set Image
run: |-
cd kustomization/overlays/prod
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash /dev/stdin 3.8.6
./kustomize edit set image REGISTRY/NAMESPACE/IMAGE:TAG=$REGISTRY/$NAMESPACE/$IMAGE:$TAG
### Production
- name: Commit and Push
run: |
git config user.name "Chuck"
git config user.email "Chuck@users.noreply.github.com"
git remote set-url origin "$GITLAB_URL"
git commit -am "generated Image update"
git push origin "release/chuck"
...@@ -11,8 +11,7 @@ ...@@ -11,8 +11,7 @@
# next.js # next.js
/.next/ /.next/
/out/ /out/
/.dev/
# production
/.dist/ /.dist/
# misc # misc
...@@ -39,7 +38,6 @@ next-env.d.ts ...@@ -39,7 +38,6 @@ next-env.d.ts
# lock files # lock files
package-lock.json package-lock.json
yarn.lock
pnpm-lock.yaml pnpm-lock.yaml
public/antd.min.css public/antd.min.css
\ No newline at end of file
node_modules
yarn.lock
\ No newline at end of file
{
"jsxSingleQuote": true,
"singleQuote": true,
"semi": true,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 100,
"bracketSameLine": false,
"useTabs": false,
"arrowParens": "always",
"endOfLine": "auto"
}
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN yarn build
# If using npm comment out above and use below instead
# RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
\ No newline at end of file
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). gitlab: http://git.mmcuav.cn/iuav/csf-web
github: https://github.com/sharefly-iuav/web
## Getting Started # 发布流程
1. 先本地npm run build, 确认构建前ts检查无错误, 可构建成功.
First, run the development server: ## 构建测试服
1. 切换到feature/chuck分支下, 先提交gitlab, 再提交github, 自动触发构建测试服
## 构建正式服
1. ssh连接到远程服务器登录(ip:120.77.247.178 账号: root 密码: YXF&mmc@m2023)
2. 顺序执行以下命令
```bash ```bash
npm run dev cd /var/www/html/sharefly-web-nextjs
# or # master分支下
yarn dev git pull
npm run build
pm2 reload all
# 若pm2未启动任务,则手动启动
pm2 start npm -- run start -- -p 5001
``` ```
注意git pull可能提示账号登录, 使用自已账号即可
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. \ No newline at end of file
You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
...@@ -3,7 +3,7 @@ const dev = { ...@@ -3,7 +3,7 @@ const dev = {
}; };
const prod = { const prod = {
baseUrl: "https://iuav.mmcuav.cn", baseUrl: "",
}; };
export default process.env.NODE_ENV === "development" ? dev : prod; export default process.env.NODE_ENV === "development" ? dev : prod;
import request, { Response } from "~/api/request"; import request, { Response } from '~/api/request'
import config from './config'
export interface RegionResp { export interface RegionResp {
childInfo?: RegionResp[] | null; childInfo?: RegionResp[] | null
id: number; id: number
level: number; level: number
name: string; name: string
pid: number; pid: number
}
export interface UserInfoResp {
id: number
accountType: number
uid: string
phoneNum: string
userName: string
nickName: string
userImg: string
userSex: number
email: string
source: number
accountStatus: number
remark: string
portType: number
createTime: string
companyAuthStatus: number
token: string
cooperationTagId: number | null
}
export interface TestAppletLoginResp {
userAccountId: number
token: string
uid: string
phoneNum?: string
nickName: string
sessionKey?: any
} }
export default { export default {
//获取区域数据 //获取区域数据
region: (): Promise<Response<Array<RegionResp>>> => { region: (): Promise<Response<Array<RegionResp>>> => {
return request("/pms/webDevice/getSecondDistrictInfo"); return request('/pms/webDevice/getSecondDistrictInfo')
},
//测试-小程序unionId登录-注册
testAppletLogin: (): Promise<Response<TestAppletLoginResp>> => {
let params = new URLSearchParams()
params.append('unionId', 'oQZEd5hy0Qrwaj10BGtP8xq8vH--s88888')
return request(
'/userapp/auth/testAppletLogin',
'post',
{},
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params,
}
)
},
//生成小程序码
getAppletQRCode: (params: { randomLoginCode: string }) => {
return request('/userapp/wx/getAppletQRCode', 'get', {
page: 'page-identity/identity-empower/index',
scene: 'randomLoginCode=' + params.randomLoginCode,
})
}, },
}; //查询登录信息
getLoginInfo: (params: { randomLoginCode: string }) => {
return request('/userapp/temp-auth/getLoginInfo', 'get', params, {
hideError: true, //隐藏错误提示
})
},
//获取用户基本信息
userInfo: (): Promise<Response<UserInfoResp>> => {
return request('/userapp/user-account/info', 'get')
},
//图片上传地址
imgOss: () => {
return config.baseUrl + '/pms/upload/imgOss'
},
}
import config from './config'; import config from './config';
let loginTimeout: NodeJS.Timeout | undefined;
/** /**
* 请求封装 * 请求封装
* @param url 请求url * @param url 请求url
...@@ -8,7 +10,8 @@ import config from './config'; ...@@ -8,7 +10,8 @@ import config from './config';
* @param options 额外参数 * @param options 额外参数
* @returns Promise<Response> * @returns Promise<Response>
*/ */
export default function request(url: string, method: String = 'get', data?: any, options = {}): Promise<Response<any>> { export default function request(url: string, method: String = 'get', data?: any, options: any & { hideError?: boolean, headers?: { token?: string } } = {}): Promise<Response<any>> {
let token = localStorage.getItem('token') || '';
switch (method.toLowerCase()) { switch (method.toLowerCase()) {
case 'get': case 'get':
...@@ -24,16 +27,49 @@ export default function request(url: string, method: String = 'get', data?: any, ...@@ -24,16 +27,49 @@ export default function request(url: string, method: String = 'get', data?: any,
case 'post': case 'post':
options = { options = {
...options,
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
}, },
body: JSON.stringify(data) body: JSON.stringify(data),
...options,
} }
break; break;
} }
if(options.headers){
options.headers.token = token;
}else{
options.headers = {
token
}
}
/**
* 错误消息
* @param msg
*/
function errMsg(msg: string) {
if (!options.hideError) {
window.messageApi.error(msg);
}
}
/**
* 未登录消息展示,1.5秒内限制只展示一次
* @returns
*/
function loginErrorMsg(){
console.log('loginTimeout', loginTimeout)
if(loginTimeout){
return;
}
loginTimeout = setTimeout(() => {
errMsg('请先登录');
loginTimeout = undefined;
}, 1500)
}
return fetch(config.baseUrl + url, options) return fetch(config.baseUrl + url, options)
.then((r) => { .then((r) => {
try { try {
...@@ -50,12 +86,12 @@ export default function request(url: string, method: String = 'get', data?: any, ...@@ -50,12 +86,12 @@ export default function request(url: string, method: String = 'get', data?: any,
.then((data) => { .then((data) => {
if (data.errors) { if (data.errors) {
//全局消息提示 //全局消息提示
window.messageApi.error('请求出错') errMsg('请求出错')
if (Array.isArray(data.errors)) { if (Array.isArray(data.errors)) {
data.errors.forEach((item: any) => { data.errors.forEach((item: any) => {
if (item.defaultMessage){ if (item.defaultMessage) {
window.messageApi.error(item.defaultMessage) errMsg(item.defaultMessage)
} }
}) })
} }
...@@ -66,6 +102,17 @@ export default function request(url: string, method: String = 'get', data?: any, ...@@ -66,6 +102,17 @@ export default function request(url: string, method: String = 'get', data?: any,
result: null result: null
} }
} }
if (data.code !== '200') {
//未登录判断
if(data.code === '5008' || data.code === '2014'){
loginErrorMsg();
window.logout();
}else{
errMsg(data.message || '请求出错');
}
}
return data; return data;
}) })
.catch(error => { .catch(error => {
......
...@@ -3,6 +3,18 @@ ...@@ -3,6 +3,18 @@
"plugins": [ "plugins": [
[ [
"styled-components",{ "ssr":true } "styled-components",{ "ssr":true }
],
[
"styled-components-px2rem",
{
"rootValue": 1,
"unitPrecision": 5,
"propList": ["*"],
"selectorBlackList": [], //排除html样式
"replace": true,
"mediaQuery": false,
"minPixelValue": 0
}
] ]
] ]
} }
\ No newline at end of file
...@@ -78,8 +78,4 @@ ...@@ -78,8 +78,4 @@
background: none; background: none;
} }
.headImg {
width: 48px;
height: 48px;
background: #ffffff;
}
import React, { useState } from "react"; import React, { useContext, useEffect, useState } from "react";
import { Avatar, Button, Space, Tabs } from "antd"; import { Avatar, Button, Dropdown, Space, Tabs } from "antd";
import type { TabsProps } from "antd"; import type { TabsProps } from "antd";
import styles from "./index.module.scss"; import styles from "./index.module.scss";
import { useRouter } from "next/router"; import { Router, useRouter } from "next/router";
import LoginModal from "~/components/loginModal"; import LoginModal from "~/components/loginModal";
import { useUser } from "~/lib/hooks";
import PublishModal from "./publishModal"; import PublishModal from "./publishModal";
import JoinModal from "./joinModal";
import { UserContext } from "~/lib/userProvider";
const items: TabsProps["items"] = [ const items: TabsProps["items"] = [
{ {
...@@ -38,63 +39,143 @@ const items: TabsProps["items"] = [ ...@@ -38,63 +39,143 @@ const items: TabsProps["items"] = [
}, },
]; ];
export default function NavHeader() { type Props = {
style?: React.CSSProperties;
};
export default function NavHeader(props: Props) {
const router = useRouter(); const router = useRouter();
const currentPath = router.asPath; const [currentPath, setCurrentPath] = useState("");
const user = useUser(); const { userInfo, testLogin, logout, setNeedLogin, needLogin } =
useContext(UserContext);
useEffect(() => {
const routerTo = items?.filter((item) => router.route == item.key)[0];
if (routerTo) {
setCurrentPath(routerTo?.key!);
} else {
setCurrentPath(router.route);
}
console.log("currentHash", currentPath);
}, [router.route]);
console.log("currentHash", currentPath); //导航更改
const onChange = (key: string) => { const onChange = (key: string) => {
router.push(key); router.push(key);
}; };
const [openLoginModal, setPpenLoginModal] = useState(false); //登录modal //退出登录
const onLogout = () => {
logout();
};
const [openLoginModal, setOpenLoginModal] = useState(false); //登录modal
const [openPublishModal, setOpenPublishModal] = useState(false); //发布modal const [openPublishModal, setOpenPublishModal] = useState(false); //发布modal
const [openJoinModal, setOpenJoinModal] = useState(false); //加盟modal
const showModal = () => { //发布按钮事件
setPpenLoginModal(true); function onPublish() {
}; //登录判断
if (!userInfo) {
setOpenLoginModal(true);
} else {
setOpenPublishModal(true);
}
}
const handleCancel = () => { //加盟按钮事件
setPpenLoginModal(false); function onJoin() {
}; //登录判断
if (!userInfo) {
setOpenLoginModal(true);
} else {
setOpenJoinModal(true);
}
}
//从其它组件通知需要登录
useEffect(() => {
if (needLogin) {
setOpenLoginModal(true);
}
}, [needLogin]);
return ( return (
<div className={styles.navHeader}> <div className={styles.navHeader} style={props.style}>
<div className={styles.nav}> <div className={styles.nav}>
<div className={styles.logo}></div> <div className={styles.logo}></div>
<Tabs <Tabs
className={styles.tabs} className={styles.tabs}
defaultActiveKey={currentPath} activeKey={currentPath}
items={items} items={items}
onChange={onChange} onChange={onChange}
onTabClick={onChange}
/> />
<Space size={16} className={styles.btns}> <Space size={16} className={styles.btns}>
<Button <Button type="primary" className={styles.btn1} onClick={onPublish}>
type="primary"
className={styles.btn1}
onClick={() => setOpenPublishModal(true)}
>
+ 发布需求 + 发布需求
</Button> </Button>
<Button className={styles.btn2}>加盟入驻</Button> <Button className={styles.btn2} onClick={onJoin}>
加盟入驻
</Button>
</Space> </Space>
{user ? ( {userInfo ? (
<div className={styles.haedImg}> <div className={styles.haedImg}>
<Avatar size={48} style={{ background: "#fff" }}></Avatar> <Dropdown
menu={{
items: [
{
key: "2",
label: (
<div
onClick={() =>
router.push("/personalCenter/servicesOrders")
}
>
我的订单
</div>
),
},
{ key: "1", label: <div onClick={onLogout}>退出登录</div> },
],
}}
>
<Avatar
size={36}
style={{ background: "#bdbdbd" }}
src={userInfo.userImg}
></Avatar>
</Dropdown>
</div> </div>
) : ( ) : (
<Button <Button
type="text" type="text"
onClick={showModal} onClick={() => setOpenLoginModal(true)}
style={{ fontWeight: "bold", fontSize: 16 }} style={{ fontWeight: "bold", fontSize: 16 }}
> >
登录 登录
</Button> </Button>
)} )}
</div> </div>
<LoginModal open={openLoginModal} onCancel={handleCancel}></LoginModal> <LoginModal
<PublishModal open={openPublishModal} onCancel={() => {setOpenPublishModal(false)}}></PublishModal> open={openLoginModal}
onCancel={() => {
setOpenLoginModal(false);
setNeedLogin(false);
}}
></LoginModal>
<PublishModal
open={openPublishModal}
onCancel={() => {
setOpenPublishModal(false);
}}
></PublishModal>
<JoinModal
open={openJoinModal}
onCancel={() => {
setOpenJoinModal(false);
}}
></JoinModal>
</div> </div>
); );
} }
import request, { Response } from "~/api/request"
export interface ListTagResp {
id: number;
tagName: string;
tagImg?: string;
tagDescription: string;
createTime: string;
}
export default {
//加盟标签列表
listTag: (): Promise<Response<Array<ListTagResp>>> => {
return request('/userapp/cooperation/listTag')
}
}
\ No newline at end of file
.identityBtn {
box-sizing: border-box;
padding: 0 5px;
min-width: 100%;
border-radius: 6px;
text-align: center;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #000000;
flex-wrap: nowrap;
}
.modal {
:global .ant-modal-content {
border-radius: 6px;
.ant-modal-title {
text-align: center;
}
}
}
import { Col, Modal, Row } from "antd";
import Image from "next/image";
import styles from "./index.module.scss";
import img from "./assets/img.png";
import { useContext, useEffect, useState } from "react";
import api, { ListTagResp } from "./api";
import { useRouter } from "next/router";
import { UserContext } from "~/lib/userProvider";
const imgs = [
require("./assets/生产制造商.png"),
require("./assets/品牌企业.png"),
require("./assets/商务公关机构.png"),
require("./assets/无人机自媒体.png"),
require("./assets/投资机构.png"),
require("./assets/飞手团队.png"),
require("./assets/二手服务商.png"),
require("./assets/飞手培训机构.png"),
require("./assets/推广合作商.png"),
];
type Props = {
open?: boolean;
onOk?: () => void;
onCancel?: () => void;
};
export default function JoinModal(props: Props) {
const router = useRouter();
const [tagList, setTagList] = useState<ListTagResp[]>([]);
const { userInfo } = useContext(UserContext);
useEffect(() => {
api.listTag().then((res) => {
setTagList(res.result || []);
});
}, []);
const onClickTag = (item: ListTagResp) => {
if (userInfo!.companyAuthStatus) {
router.replace("/JoinPolicy?tagId=" + item.id);
props.onCancel && props.onCancel();
} else {
router.push("/certification");
}
};
return (
<Modal
title="申请合作加盟"
open={props.open}
onOk={props.onOk}
onCancel={props.onCancel}
className={styles.modal}
width={460}
footer={null}
>
<Row style={{rowGap: 29, paddingTop: 21, paddingBottom: 21}}>
{tagList.map((item, i) => {
return (
<Col
key={item.id}
span={8}
style={{
cursor: "pointer",
padding: 0,
textAlign: "center",
}}
onClick={() => onClickTag(item)}
>
<Image src={imgs[i]} width={64} height={64} alt=""></Image>
<div className={styles.identityBtn}>
{item.tagName}
{">"}
</div>
</Col>
);
})}
</Row>
</Modal>
);
}
...@@ -6,11 +6,11 @@ export interface TypeResp { ...@@ -6,11 +6,11 @@ export interface TypeResp {
} }
export interface PublishParams { export interface PublishParams {
userAccountId: number; //账号id
publishPhone: number; //手机号 publishPhone: number; //手机号
publishName: string; //发布名称 publishName: string; //发布名称
requirementTypeId: number; //需求类型 requirementTypeId: number; //需求类型
requireDescription: string; //需求描述 requireDescription: string; //需求描述
provinceCode?: string; //省编码
} }
export default { export default {
......
.modal { .modal {
width: 460px;
:global .ant-modal-content { :global .ant-modal-content {
border-radius: 0; border-radius: 0;
.ant-modal-title{
text-align: center;
}
} }
} }
import { Button, Form, Input, Modal, Select } from "antd"; import { Button, Form, Input, Modal, Select } from "antd";
import { useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { CommonContext } from "~/lib/commonProvider";
import { useGeolocation } from "~/lib/hooks";
import api, { PublishParams, TypeResp } from "./api"; import api, { PublishParams, TypeResp } from "./api";
import styles from "./index.module.scss"; import styles from "./index.module.scss";
import {phoneNumber} from '~/lib/validateUtils'
type Props = { type Props = {
open?: boolean; open?: boolean;
...@@ -11,14 +14,15 @@ type Props = { ...@@ -11,14 +14,15 @@ type Props = {
export default function PublishModal(props: Props) { export default function PublishModal(props: Props) {
const [types, setTypes] = useState<Array<TypeResp>>([]); //需求类型 const [types, setTypes] = useState<Array<TypeResp>>([]); //需求类型
const [params, setParams] = useState<PublishParams>({ const [params, setParams] = useState<PublishParams>({
userAccountId: -1,
publishName: "", publishName: "",
publishPhone: -1, publishPhone: -1,
requireDescription: "", requireDescription: "",
requirementTypeId: -1, requirementTypeId: -1,
}); });
const [form] = Form.useForm(); const [form] = Form.useForm();
console.log("form", form); const position = useGeolocation();
const { reloadRequirements, setReloadRequirements } =
useContext(CommonContext);
useEffect(() => { useEffect(() => {
api.listType().then((res) => { api.listType().then((res) => {
...@@ -33,10 +37,13 @@ export default function PublishModal(props: Props) { ...@@ -33,10 +37,13 @@ export default function PublishModal(props: Props) {
.publish({ .publish({
...params, ...params,
...values, ...values,
provinceCode: position?.address?.addressComponent?.adcode
}) })
.then((res) => { .then((res) => {
if (res.code !== "-1") { if (res.code === "200") {
props.onCancel && props.onCancel(); props.onCancel && props.onCancel();
window.messageApi.success("发布成功");
setReloadRequirements(!reloadRequirements);
setTimeout(() => { setTimeout(() => {
form.resetFields(); form.resetFields();
}, 500); }, 500);
...@@ -75,9 +82,20 @@ export default function PublishModal(props: Props) { ...@@ -75,9 +82,20 @@ export default function PublishModal(props: Props) {
<Form.Item <Form.Item
label="手机号" label="手机号"
name="publishPhone" name="publishPhone"
rules={[{ required: true, message: "请输入手机号!" }]} rules={[
{ required: true, message: "请输入手机号!" },
{
pattern: /^1\d{10}$/,
message: "很输入11位手机号",
},
]}
> >
<Input placeholder="输入手机号"></Input> <Input
onInput={phoneNumber}
maxLength={11}
allowClear
placeholder="输入手机号"
></Input>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="需求类型" label="需求类型"
......
import React from "react"; import React, { useEffect } from "react";
import { Empty } from "antd"; import { Empty } from "antd";
import { Box , WaterfallBox } from "./styled"; import { LeftBox , Box , WaterfallBox } from "./styled";
import { leftBoxProps } from "../interface"; import { leftBoxProps } from "../interface";
export default function Left(props: leftBoxProps) { export default function Left(props: leftBoxProps) {
const { boxIndex, leftRenderDom, leftcontentstyle, leftWaterfallDom } = props; const { boxIndex, leftRenderDom, leftcontentstyle, leftWaterfallDom } = props;
return ( return (
<div> <LeftBox>
{leftRenderDom?.columns.map((item) => { {leftRenderDom?.columns.map((item) => {
if (item.noFor) { if (item.noFor) {
return item.element; return item.element;
...@@ -30,7 +30,7 @@ export default function Left(props: leftBoxProps) { ...@@ -30,7 +30,7 @@ export default function Left(props: leftBoxProps) {
return null; return null;
})} })}
{ {
<WaterfallBox index={boxIndex} leftcontentstyle={leftcontentstyle}> leftWaterfallDom?.columns.length ? <WaterfallBox index={boxIndex} leftcontentstyle={leftcontentstyle}>
{ <div className="left-columns"> { <div className="left-columns">
{leftWaterfallDom?.columns.map((item) => { {leftWaterfallDom?.columns.map((item) => {
if (!item.noFor && item.type === "left") { if (!item.noFor && item.type === "left") {
...@@ -47,12 +47,12 @@ export default function Left(props: leftBoxProps) { ...@@ -47,12 +47,12 @@ export default function Left(props: leftBoxProps) {
return null return null
})} })}
</div>} </div>}
</WaterfallBox> </WaterfallBox> : null
} }
{leftRenderDom?.pagination ? leftRenderDom?.pagination : null} {leftRenderDom?.pagination ? leftRenderDom?.pagination : null}
{!leftRenderDom?.columns.length && !leftWaterfallDom?.columns.length? ( {!leftRenderDom?.columns.length && !leftWaterfallDom?.columns.length? (
<Empty description={"暂无数据"} /> <Empty description={"暂无数据"} />
) : null} ) : null}
</div> </LeftBox>
); );
} }
...@@ -12,6 +12,9 @@ export interface BoxProps { ...@@ -12,6 +12,9 @@ export interface BoxProps {
} }
} }
export const LeftBox = styled.div`
box-sizing: border-box;
`
export const Box = styled.div<BoxProps>` export const Box = styled.div<BoxProps>`
box-sizing: border-box; box-sizing: border-box;
...@@ -39,25 +42,23 @@ export const WaterfallBox = styled.div<BoxProps>` ...@@ -39,25 +42,23 @@ export const WaterfallBox = styled.div<BoxProps>`
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
width: ${props => props.leftcontentstyle?.width ? props.leftcontentstyle?.width : "790px"}; width: ${props => props.leftcontentstyle?.width ? props.leftcontentstyle?.width : "790px"};
.item{ .item{
// 每个元素都要设置右边距margin-right(每个元素的左右间隙) // 每个元素都要设置右边距margin-right(每个元素的左右间隙)
// 同时设置下边距margin-bottom(每个元素的上下间隙) // 同时设置下边距margin-bottom(每个元素的上下间隙)
/* margin: 0 24px 15px 0; */ /* margin: 0 24px 15px 0; */
margin: ${props => props.leftcontentstyle?.margin ? (`${props.leftcontentstyle?.margin.top} ${props.leftcontentstyle?.margin.right} ${props.leftcontentstyle?.margin.bottom} ${props.leftcontentstyle?.margin.left}`) : "0 24px 15px 0"};; margin: ${props => props.leftcontentstyle?.margin ? (`${props.leftcontentstyle?.margin.top} ${props.leftcontentstyle?.margin.right} ${props.leftcontentstyle?.margin.bottom} ${props.leftcontentstyle?.margin.left}`) : "0 24px 15px 0"};;
width: calc(( 100% - ${props => props.index} * ${props => props.leftcontentstyle?.margin ? props.leftcontentstyle?.margin.right : "24px" }) / ${props => props.index}); /* width: calc(( 100% - ${props => props.index} * ${props => props.leftcontentstyle?.margin ? props.leftcontentstyle?.margin.right : "24px" }) / ${props => props.index});
// 这里一行显示index个,所以是/index,一行显示几个就除以几 // 这里一行显示index个,所以是/index,一行显示几个就除以几
// 这里的72px = (分布个数index-1)*间隙20px, 可以根据实际的分布个数和间隙区调整 // 这里的72px = (分布个数index-1)*间隙20px, 可以根据实际的分布个数和间隙区调整
min-width: calc(( 100% - ${props => props.index} * ${props => props.leftcontentstyle?.margin ? props.leftcontentstyle?.margin.right : "24px" }) / ${props => props.index}); min-width: calc(( 100% - ${props => props.index} * ${props => props.leftcontentstyle?.margin ? props.leftcontentstyle?.margin.right : "24px" }) / ${props => props.index});
max-width: calc(( 100% - ${props => props.index} * ${props => props.leftcontentstyle?.margin ? props.leftcontentstyle?.margin.right : "24px" }) / ${props => props.index}); max-width: calc(( 100% - ${props => props.index} * ${props => props.leftcontentstyle?.margin ? props.leftcontentstyle?.margin.right : "24px" }) / ${props => props.index}); */
// 每行最右侧的那个不设置右外边距
&:nth-child(${props => props.index}n + ${props => props.index}) {
margin-right: 0;
}
} }
.left-columns{ .left-columns{
} }
.right-columns{ .right-columns{
.item{
margin-right: 0;
}
} }
` `
\ No newline at end of file
import request, { Response } from "~/api/request"; import request, { Response } from '~/api/request'
export interface FilterOptionResp { export interface FilterOptionResp {
id: number; id: number
name?: string; name?: string
appName?: string; appName?: string
} }
export interface RegionResp { export interface RegionResp {
childInfo: RegionResp[] | null; childInfo: RegionResp[] | null
id: number; id: number
level: number; level: number
name: string; name: string
pid: number; pid: number
}
export interface InfoList {
id: number
directoryId: number
name: string
icon: string
}
export interface TypesResp {
directoryId: number
name: string
categoriesInfoListDTO: InfoList[]
} }
export default { export default {
category: (): Promise<Response<Array<FilterOptionResp>>> => { category: (): Promise<Response<Array<FilterOptionResp>>> => {
return request("/pms/webProductMall/category"); return request('/pms/webProductMall/category')
},
categoryId: (): Promise<Response<Array<FilterOptionResp>>> => {
return request('/pms/webDevice/category')
}, },
brand: (): Promise<Response<Array<FilterOptionResp>>> => { brand: (): Promise<Response<Array<FilterOptionResp>>> => {
return request("/pms/webDevice/brand"); return request('/pms/webDevice/brand')
}, },
model: (): Promise<Response<Array<FilterOptionResp>>> => { model: (): Promise<Response<Array<FilterOptionResp>>> => {
return request("/pms/webDevice/model"); return request('/pms/webDevice/model')
}, },
part: (): Promise<Response<Array<FilterOptionResp>>> => { part: (): Promise<Response<Array<FilterOptionResp>>> => {
return request("/pms/webProductMall/parts"); return request('/pms/webProductMall/parts')
}, },
quality: (): Promise<Response<Array<FilterOptionResp>>> => { quality: (): Promise<Response<Array<FilterOptionResp>>> => {
return request("/pms/webProductMall/quality"); return request('/pms/webProductMall/quality')
}, },
region: (): Promise<Response<Array<RegionResp>>> => { region: (): Promise<Response<Array<RegionResp>>> => {
return request("/pms/webDevice/getSecondDistrictInfo"); return request('/pms/webDevice/getSecondDistrictInfo')
}, },
industry: (): Promise<Response<Array<RegionResp>>> => { industry: (): Promise<Response<Array<RegionResp>>> => {
return request("/release/work/listAllIndustry"); return request('/release/work/listAllIndustry')
}, },
appType: (): Promise<Response<Array<RegionResp>>> => { appType: (): Promise<Response<Array<RegionResp>>> => {
return request("/release/work/listAllAppType"); return request('/release/work/listAllAppType')
}, },
deviceBrand: (): Promise<Response<Array<RegionResp>>> => { deviceBrand: (): Promise<Response<Array<RegionResp>>> => {
return request("/pms/webDevice/deviceBrand"); return request('/pms/webDevice/deviceBrand')
}, },
deviceModel: (): Promise<Response<Array<RegionResp>>> => { deviceModel: (): Promise<Response<Array<RegionResp>>> => {
return request("/pms/webDevice/deviceModel"); return request('/pms/webDevice/deviceModel')
},
infoByType: (params: {
type: number
}): Promise<Response<Array<TypesResp>>> => {
return request('/pms/classify/queryCategoryInfoByType', 'get', params)
}, },
}; }
import { Space, Button, Select, Collapse } from "antd";
import styles from "../../index.module.scss";
import api, { FilterOptionResp } from "../../api";
import { useState, useEffect } from "react";
type Props = {
onChange: (id: FilterOptionResp) => void;
};
export default function CategoryItem(props: Props) {
const [data, setData] = useState<FilterOptionResp[]>([]);
useEffect(() => {
api.appType().then((res) => {
setData(res?.result || []);
});
}, []);
const onClick = (item: FilterOptionResp) => {
props.onChange({
id: item.id,
appName: "应用:" + item.appName,
});
};
return (
<div className={styles.filterItem}>
<div className={styles.filterItemTitle}>应用:</div>
<div
className={`${styles.filterItemMain} ${
data.length <= 10 && styles.disabled
}`}
>
<Collapse ghost collapsible="icon" expandIconPosition="end">
<Collapse.Panel
header={
<Space size={40}>
{data.slice(0, 10).map((item) => {
return (
<Button
type="link"
key={item.id}
onClick={(e) => onClick(item)}
>
{item.appName}
</Button>
);
})}
</Space>
}
key="1"
>
<Space size={40}>
{data.slice(10).map((item) => {
return (
<Button
type="link"
key={item.id}
onClick={(e) => onClick(item)}
>
{item.appName}
</Button>
);
})}
</Space>
</Collapse.Panel>
</Collapse>
</div>
</div>
);
}
import { Space, Button, Select, Collapse } from "antd";
import styles from "../../index.module.scss";
import api, { FilterOptionResp } from "../../api";
import { useEffect, useState } from "react";
type Props = {
onChange: (id: FilterOptionResp) => void;
};
export default function BrandItem(props: Props) {
const [data, setData] = useState<FilterOptionResp[]>([]);
useEffect(() => {
api.brand().then((res) => {
setData(res?.result || []);
});
}, []);
const onClick = (item: FilterOptionResp) => {
props.onChange({
id: item.id,
name: "品牌:" + item.name,
});
};
return (
<div className={styles.filterItem}>
<div className={styles.filterItemTitle}>品牌:</div>
<div className={`${styles.filterItemMain} ${data.length <= 10 && styles.disabled}`}>
<Collapse
ghost
collapsible="icon"
expandIconPosition="end"
>
<Collapse.Panel
header={
<Space size={40}>
{data.slice(0, 10).map((item) => {
return (
<Button
type="link"
key={item.id}
onClick={(e) => onClick(item)}
>
{item.name}
</Button>
);
})}
</Space>
}
key="1"
>
<Space size={40}>
{data.slice(10).map((item) => {
return (
<Button
type="link"
key={item.id}
onClick={(e) => onClick(item)}
>
{item.name}
</Button>
);
})}
</Space>
</Collapse.Panel>
</Collapse>
</div>
</div>
);
}
import { Space, Button, Select, Collapse } from 'antd';
import styles from '../../index.module.scss';
import api, { FilterOptionResp } from "../../api";
import { useState, useEffect } from 'react';
type Props = {
onChange: (id: FilterOptionResp) => void;
};
export default function CategoryItem(props: Props) {
const [data, setData] = useState<FilterOptionResp[]>([]);
useEffect(() => {
api.deviceBrand().then((res) => {
setData(res?.result || []);
});
}, []);
const onClick = (item: FilterOptionResp) => {
props.onChange({
id: item.id,
name: "品牌:" + item.name,
});
};
return (
<div className={styles.filterItem}>
<div className={styles.filterItemTitle}>品牌:</div>
<div
className={`${styles.filterItemMain} ${
data.length <= 10 && styles.disabled
}`}
>
<Collapse ghost collapsible="icon" expandIconPosition="end">
<Collapse.Panel
header={
<Space size={40}>
{data.slice(0, 10).map((item) => {
return (
<Button
type="link"
key={item.id}
onClick={(e) => onClick(item)}
>
{item.name}
</Button>
);
})}
</Space>
}
key="1"
>
<Space size={40}>
{data.slice(10).map((item) => {
return (
<Button
type="link"
key={item.id}
onClick={(e) => onClick(item)}
>
{item.name}
</Button>
);
})}
</Space>
</Collapse.Panel>
</Collapse>
</div>
</div>
);
}
\ No newline at end of file
import { Space, Button, Select, Collapse } from 'antd';
import styles from '../../index.module.scss';
import api, { FilterOptionResp } from "../../api";
import { useState, useEffect } from 'react';
type Props = {
onChange: (id: FilterOptionResp) => void;
};
export default function CategoryItem(props: Props) {
const [data, setData] = useState<FilterOptionResp[]>([]);
useEffect(() => {
api.deviceModel().then((res) => {
setData(res?.result || []);
});
}, []);
const onClick = (item: FilterOptionResp) => {
props.onChange({
id: item.id,
name: "型号:" + item.name,
});
};
return (
<div className={styles.filterItem}>
<div className={styles.filterItemTitle}>型号:</div>
<div
className={`${styles.filterItemMain} ${
data.length <= 10 && styles.disabled
}`}
>
<Collapse ghost collapsible="icon" expandIconPosition="end">
<Collapse.Panel
header={
<Space size={40}>
{data.slice(0, 10).map((item) => {
return (
<Button
type="link"
key={item.id}
onClick={(e) => onClick(item)}
>
{item.name}
</Button>
);
})}
</Space>
}
key="1"
>
<Space size={40}>
{data.slice(10).map((item) => {
return (
<Button
type="link"
key={item.id}
onClick={(e) => onClick(item)}
>
{item.name}
</Button>
);
})}
</Space>
</Collapse.Panel>
</Collapse>
</div>
</div>
);
}
\ No newline at end of file
import { Space, Button, Select, Collapse } from 'antd';
import styles from '../../index.module.scss';
import api, { FilterOptionResp } from "../../api";
import { useState, useEffect } from 'react';
type Props = {
onChange: (id: FilterOptionResp) => void;
};
export default function CategoryItem(props: Props) {
const [data, setData] = useState<FilterOptionResp[]>([]);
useEffect(() => {
api.industry().then((res) => {
setData(res?.result || []);
});
}, []);
const onClick = (item: FilterOptionResp) => {
props.onChange({
id: item.id,
name: "行业:" + item.name,
});
};
return (
<div className={styles.filterItem}>
<div className={styles.filterItemTitle}>行业:</div>
<div
className={`${styles.filterItemMain} ${
data.length <= 10 && styles.disabled
}`}
>
<Collapse ghost collapsible="icon" expandIconPosition="end">
<Collapse.Panel
header={
<Space size={40}>
{data.slice(0, 10).map((item) => {
return (
<Button
type="link"
key={item.id}
onClick={(e) => onClick(item)}
>
{item.name}
</Button>
);
})}
</Space>
}
key="1"
>
<Space size={40}>
{data.slice(10).map((item) => {
return (
<Button
type="link"
key={item.id}
onClick={(e) => onClick(item)}
>
{item.name}
</Button>
);
})}
</Space>
</Collapse.Panel>
</Collapse>
</div>
</div>
);
}
\ No newline at end of file
import { Space, Button, Select, Collapse } from 'antd';
import styles from '../../index.module.scss';
import api, { FilterOptionResp } from "../../api";
import { useEffect, useState } from 'react';
type Props = {
onChange: (id: FilterOptionResp) => void;
};
export default function ModelItem(props: Props) {
const [data, setData] = useState<FilterOptionResp[]>([]);
useEffect(() => {
api.model().then((res) => {
setData(res?.result || []);
});
}, []);
const onClick = (item: FilterOptionResp) => {
props.onChange({
id: item.id,
name: "型号:" + item.name,
});
};
return (
<div className={styles.filterItem}>
<div className={styles.filterItemTitle}>型号:</div>
<div
className={`${styles.filterItemMain} ${
data.length <= 10 && styles.disabled
}`}
>
<Collapse ghost collapsible="icon" expandIconPosition="end">
<Collapse.Panel
header={
<Space size={40}>
{data.slice(0, 10).map((item) => {
return (
<Button
type="link"
key={item.id}
onClick={(e) => onClick(item)}
>
{item.name}
</Button>
);
})}
</Space>
}
key="1"
>
<Space size={40}>
{data.slice(10).map((item) => {
return (
<Button
type="link"
key={item.id}
onClick={(e) => onClick(item)}
>
{item.name}
</Button>
);
})}
</Space>
</Collapse.Panel>
</Collapse>
</div>
</div>
);
}
\ No newline at end of file
import { Space, Button, Select, Collapse } from 'antd';
import styles from '../../index.module.scss';
import api, { FilterOptionResp } from "../../api";
import { useState, useEffect } from 'react';
type Props = {
onChange: (id: FilterOptionResp) => void;
};
export default function PartItem(props: Props) {
const [data, setData] = useState<FilterOptionResp[]>([]);
useEffect(() => {
api.part().then((res) => {
setData(res?.result || []);
});
}, []);
const onClick = (item: FilterOptionResp) => {
props.onChange({
id: item.id,
name: "部件:" + item.name,
});
};
return (
<div className={styles.filterItem}>
<div className={styles.filterItemTitle}>部件:</div>
<div
className={`${styles.filterItemMain} ${
data.length <= 10 && styles.disabled
}`}
>
<Collapse ghost collapsible="icon" expandIconPosition="end">
<Collapse.Panel
header={
<Space size={40}>
{data.slice(0, 10).map((item) => {
return (
<Button
type="link"
key={item.id}
onClick={(e) => onClick(item)}
>
{item.name}
</Button>
);
})}
</Space>
}
key="1"
>
<Space size={40}>
{data.slice(10).map((item) => {
return (
<Button
type="link"
key={item.id}
onClick={(e) => onClick(item)}
>
{item.name}
</Button>
);
})}
</Space>
</Collapse.Panel>
</Collapse>
</div>
</div>
);
}
\ No newline at end of file
import { Space, Button, Select, Collapse } from 'antd';
import styles from '../../index.module.scss';
import api, { FilterOptionResp } from "../../api";
import { useState, useEffect } from 'react';
type Props = {
onChange: (id: FilterOptionResp) => void;
};
export default function QualityItem(props: Props) {
const [data, setData] = useState<FilterOptionResp[]>([]);
useEffect(() => {
api.quality().then((res) => {
setData(res?.result || []);
});
}, []);
const onClick = (item: FilterOptionResp) => {
props.onChange({
id: item.id,
name: "成色:" + item.name,
});
};
return (
<div className={styles.filterItem}>
<div className={styles.filterItemTitle}>成色:</div>
<div
className={`${styles.filterItemMain} ${
data.length <= 10 && styles.disabled
}`}
>
<Collapse ghost collapsible="icon" expandIconPosition="end">
<Collapse.Panel
header={
<Space size={40}>
{data.slice(0, 10).map((item) => {
return (
<Button
type="link"
key={item.id}
onClick={(e) => onClick(item)}
>
{item.name}
</Button>
);
})}
</Space>
}
key="1"
>
<Space size={40}>
{data.slice(10).map((item) => {
return (
<Button
type="link"
key={item.id}
onClick={(e) => onClick(item)}
>
{item.name}
</Button>
);
})}
</Space>
</Collapse.Panel>
</Collapse>
</div>
</div>
);
}
\ No newline at end of file
import { Space, Select } from "antd"; import { Space, Select } from 'antd'
import { useEffect, useState } from "react"; import { useEffect, useState } from 'react'
import styles from "../../index.module.scss"; import styles from '../../index.module.scss'
import api, { RegionResp } from "../../api"; import api, { RegionResp } from '../../api'
type Props = { type Props = {
onChange: (item: RegionResp) => void; onChange: (item: RegionResp) => void
}; }
export default function RegionItem(props: Props) { export default function RegionItem(props: Props) {
const [provinceList, setProvinceList] = useState<RegionResp[]>([]); const [provinceList, setProvinceList] = useState<RegionResp[]>([])
const [cityList, setCityList] = useState<RegionResp[]>([]); const [cityList, setCityList] = useState<RegionResp[]>([])
const [selectCity, setSelectCity] = useState<number>()
useEffect(() => { useEffect(() => {
api.region().then((res) => { api.region().then((res) => {
setProvinceList(res?.result || []); setProvinceList(res?.result || [])
}); })
}, []); }, [])
const onProvinceChange = (value: number, item: any) => { const onProvinceChange = (value: number, item: any) => {
console.log("省", value, item); console.log('省', value, item)
setCityList(item.childInfo || []); setCityList(item.childInfo || [])
props.onChange(item); setSelectCity(undefined)
}; props.onChange(item)
}
const onCityChange = (value: number, item: any) => { const onCityChange = (value: number, item: any) => {
console.log("市", value); console.log('市', value)
props.onChange(item); setSelectCity(value)
}; props.onChange(item)
}
return ( return (
<div className={styles.filterItem}> <div className={styles.filterItem}>
<div className={styles.filterItemTitle}>地域:</div> <div className={styles.filterItemTitle}>地域:</div>
...@@ -41,10 +44,11 @@ export default function RegionItem(props: Props) { ...@@ -41,10 +44,11 @@ export default function RegionItem(props: Props) {
...item, ...item,
value: item.id, value: item.id,
label: item.name, label: item.name,
}; }
})} })}
/> />
<Select {/* <Select
value={selectCity}
bordered={false} bordered={false}
popupMatchSelectWidth={false} popupMatchSelectWidth={false}
placeholder="选择市" placeholder="选择市"
...@@ -56,9 +60,9 @@ export default function RegionItem(props: Props) { ...@@ -56,9 +60,9 @@ export default function RegionItem(props: Props) {
label: item.name, label: item.name,
}; };
})} })}
/> /> */}
</Space> </Space>
</div> </div>
</div> </div>
); )
} }
import { Space, Tag } from "antd"; import { Space, Tag } from 'antd'
import { FilterResult } from "../.."; import { FilterResult } from '../..'
import styles from "../../index.module.scss"; import styles from '../../index.module.scss'
import { InfoList } from '../../api'
type Props = { type Props = {
data: FilterResult; data: FilterResult
onDel: (key: string) => void; onDel: (key: string | number) => void
}; }
export default function ResultItem({data, onDel}: Props) { export default function ResultItem({ data, onDel }: Props) {
return ( return (
<div className={styles.filterItem}> <div className={styles.filterItem}>
<div className={styles.filterItemTitle}>已选:</div> <div className={styles.filterItemTitle}>已选:</div>
<div className={styles.filterItemMain}> <div className={styles.filterItemMain}>
<Space size={10}> <Space size={10}>
{data && {data.provinceId && (
Object.keys(data).map((key) => { // Object.keys(data).map((key) => {
//@ts-ignore // //@ts-ignore
let item = data[key]; // let item = data[key]
// return (
// <Tag
// closable
// onClose={(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
// onDel(key)
// }}
// key={key}
// >
// {item?.name}
// </Tag>
// )
// })
<Tag
closable
onClose={(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
onDel('provinceId')
}}
key={data.provinceId.id}
>
{data.provinceId.name}
</Tag>
)}
{data.categoryId &&
data.categoryId.map((item: InfoList, index) => {
return ( return (
<Tag <Tag
closable closable
onClose={(e: React.MouseEvent<HTMLElement, MouseEvent>) => { onClose={(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
onDel(key); onDel(item.id)
}} }}
key={key} key={item.name}
> >
{item?.name} {item?.name}
</Tag> </Tag>
); )
})} })}
</Space> </Space>
</div> </div>
</div> </div>
); )
} }
import { Space, Button, Select, Collapse } from 'antd'; import { Space, Button, Select, Collapse } from 'antd'
import styles from '../../index.module.scss'; import styles from '../../index.module.scss'
import api, { FilterOptionResp } from "../../api"; import api, { FilterOptionResp, InfoList } from '../../api'
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react'
type Props = { type Props = {
onChange: (id: FilterOptionResp) => void; onChange: (id: FilterOptionResp) => void
}; typeName: string
dataValue: InfoList[]
}
export default function CategoryItem(props: Props) { export default function CategoryItem(props: Props) {
const [data, setData] = useState<FilterOptionResp[]>([]); const [data, setData] = useState<FilterOptionResp[]>([])
useEffect(() => { useEffect(() => {
api.category().then((res) => { setData(props.dataValue || [])
setData(res?.result || []); }, [])
});
}, []);
const onClick = (item: FilterOptionResp) => { const onClick = (item: FilterOptionResp) => {
props.onChange({ props.onChange({
id: item.id, id: item.id,
name: "类目:" + item.name, name: `${props.typeName}:` + item.name,
}); })
}; }
const showCount = 9 //展示数量
return ( return (
<div className={styles.filterItem}> <div className={styles.filterItem}>
<div className={styles.filterItemTitle}>类目</div> <div className={styles.filterItemTitle}>{props.typeName}</div>
<div <div
className={`${styles.filterItemMain} ${ className={`${styles.filterItemMain} ${
data.length <= 10 && styles.disabled data.length <= showCount && styles.disabled
}`} }`}
> >
<Collapse ghost collapsible="icon" expandIconPosition="end"> <Collapse
ghost
collapsible="icon"
expandIconPosition="end"
style={{ width: '100%' }}
>
<Collapse.Panel <Collapse.Panel
header={ header={
<Space size={40}> <Space size={[40, 0]}>
{data.slice(0, 10).map((item) => { {data.slice(0, showCount).map((item) => {
return ( return (
<Button <Button
type="link" type="link"
...@@ -44,14 +51,14 @@ export default function CategoryItem(props: Props) { ...@@ -44,14 +51,14 @@ export default function CategoryItem(props: Props) {
> >
{item.name} {item.name}
</Button> </Button>
); )
})} })}
</Space> </Space>
} }
key="1" key="1"
> >
<Space size={40}> <Space size={40}>
{data.slice(10).map((item) => { {data.slice(showCount).map((item) => {
return ( return (
<Button <Button
type="link" type="link"
...@@ -60,12 +67,12 @@ export default function CategoryItem(props: Props) { ...@@ -60,12 +67,12 @@ export default function CategoryItem(props: Props) {
> >
{item.name} {item.name}
</Button> </Button>
); )
})} })}
</Space> </Space>
</Collapse.Panel> </Collapse.Panel>
</Collapse> </Collapse>
</div> </div>
</div> </div>
); )
} }
\ No newline at end of file
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
flex: 1; flex: 1;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
overflow: hidden;
&.disabled { &.disabled {
:global .ant-collapse-expand-icon { :global .ant-collapse-expand-icon {
......
import CategoryItem from "./compoents/categoryItem";
import { FilterOptionResp, RegionResp } from "./api"; import { FilterOptionResp, RegionResp } from './api'
import ResultItem from "./compoents/resultItem"; import ResultItem from './compoents/resultItem'
import RegionItem from "./compoents/regionItem"; import RegionItem from './compoents/regionItem'
import styles from "./index.module.scss"; import styles from './index.module.scss'
import { useEffect, useState } from "react"; import React, {
import BrandItem from "./compoents/brandItem"; useEffect,
import ModelItem from "./compoents/modelItem"; useState,
import PartItem from "./compoents/partItem"; forwardRef,
import QualityItem from "./compoents/qualityItem"; useImperativeHandle,
import Industry from "./compoents/industry"; Ref,
import AppType from "./compoents/appType"; } from 'react'
import DeviceBrand from "./compoents/deviceBrand"; import { useRouter } from 'next/router'
import DeviceModel from "./compoents/deviceModel"; import TypeInfo from './compoents/typeInfo'
import api, { TypesResp, InfoList } from './api'
export type AdapterResult = { export type AdapterResult = {
brandId?: number; categoryId?: any[]
districtId?: number; provinceId?: number
modelId?: number; }
partsId?: number;
productCategoryId?: number;
qualityId?: number;
industryId?: number;
appTypeId?: number;
categoryId?: number;
};
export type FilterResult = { export type FilterResult = {
region?: RegionResp; categoryId?: InfoList[]
brand?: FilterOptionResp; provinceId?: FilterOptionResp
category?: FilterOptionResp; }
part?: FilterOptionResp;
model?: FilterOptionResp;
quality?: FilterOptionResp;
industryId?: FilterOptionResp;
appTypeId?: FilterOptionResp;
categoryId?: FilterOptionResp;
};
type itemType =
| "类目"
| "地域"
| "品牌"
| "部件"
| "型号"
| "成色"
| "行业"
| "应用"
| "设备品牌"
| "设备型号"
| "设备类目";
type Props = { type Props = {
types: itemType[]; //需要包含的筛选条件项 types: string[] //需要包含的筛选条件项
showResultItem: Boolean; //显示结果栏 showResultItem: Boolean //显示结果栏
onChange: ( onChange: (
filterResult: FilterResult, filterResult: FilterResult,
adapterFilterResult: AdapterResult //适配器,直接用于接口请求 adapterFilterResult: AdapterResult //适配器,直接用于接口请求
) => void; //筛选条件更改事件 ) => void //筛选条件更改事件
}; }
export default function Filter(props: Props) {
const [result, setResult] = useState<FilterResult>({});
const onChange = (item: FilterOptionResp, type: string) => {
console.log(item, type);
let data: { [key: string]: FilterOptionResp } = {}; const Filter = (props: Props, ref: Ref<any>) => {
data[type] = item; const router = useRouter()
console.log(data); useImperativeHandle(ref, () => ({
clearRouter: clearRouter,
}))
const [result, setResult] = useState<FilterResult>({})
setResult({ ...result, ...data }); const onChange = (item: FilterOptionResp, type: string) => {
}; clearRouter()
let data: { [key: string]: FilterOptionResp[] | FilterOptionResp } = {}
if (type === 'categoryId') {
if (result.categoryId) {
data[type] = [...result.categoryId, item]
const map = new Map()
//去重
data[type] = (data[type] as InfoList[]).filter(
(v) => !map.has(v.id) && map.set(v.id, 1)
)
} else {
data[type] = [item]
}
} else {
data[type] = item
}
setResult({ ...result, ...data })
}
useEffect(() => { useEffect(() => {
props.onChange(result, { props.onChange(result, {
brandId: result.brand?.id, categoryId: result.categoryId,
districtId: result.region?.id, provinceId: result.provinceId?.id,
modelId: result.model?.id, })
partsId: result.part?.id, }, [result])
productCategoryId: result.category?.id,
qualityId: result.quality?.id, const clearRouter = () => {
industryId: result.industryId?.id, if (Object.keys(router.query).length) {
appTypeId: result.appTypeId?.id, router.query = {}
categoryId: result.categoryId?.id, router.replace(router.pathname)
}); }
}, [result]); }
const onDel = (key: string) => { const onDel = (key: string | number) => {
//@ts-ignore clearRouter()
delete result[key]; console.log(key)
if (Object.prototype.toString.call(key) === '[object String]') {
//@ts-ignore
delete result[key]
} else {
if (result.categoryId?.length! === 1) {
result.categoryId = undefined
} else if (result.categoryId?.length! >= 2) {
result.categoryId?.map((item, index) => {
if (item.id === key) {
result.categoryId?.splice(index, 1)
}
})
}
}
setResult({ setResult({
...result, ...result,
}); })
}; }
const routerList = [
'/jobServices',
'/equipmentLeasing',
'/flyingHandService',
'/mall',
]
const [typeInfo, setTypeInfo] = useState<Array<TypesResp> | null>()
useEffect(() => {
if (routerList.indexOf(router.pathname) > -1) {
;(async () => {
const res = await api.infoByType({
type: routerList.indexOf(router.pathname) + 1,
})
setTypeInfo(res.result)
//首页跳转自定筛选选中
let queryVal = JSON.parse(JSON.stringify(router.query))
if (Object.keys(router.query).length) {
//获取类型的id
const idOfType = res.result
?.map((item) => item.categoriesInfoListDTO)
.flat()
.filter(
(item) => item && item.id === Number(queryVal['categoryId'])
)[0]?.directoryId
//获取类型的名称然后拼接
const TypeName = res.result?.filter(
(item) => item.directoryId === idOfType
)[0]?.name
onChange(
{
id: Number(queryVal['categoryId']),
name: `${
TypeName ? TypeName + ':' + queryVal.name : queryVal.name
}`,
},
'categoryId'
)
}
})()
}
}, [router])
return ( return (
<> <>
{props.types.includes("地域") && ( {props.types.includes('地域') && (
<div <div
className={styles.filterWrap} className={styles.filterWrap}
style={{ style={{
...@@ -100,66 +149,28 @@ export default function Filter(props: Props) { ...@@ -100,66 +149,28 @@ export default function Filter(props: Props) {
}} }}
> >
<RegionItem <RegionItem
onChange={(item: FilterOptionResp) => onChange(item, "region")} onChange={(item: FilterOptionResp) => onChange(item, 'provinceId')}
></RegionItem> ></RegionItem>
</div> </div>
)} )}
<div className={styles.filterWrap}> <div className={styles.filterWrap}>
{props.types.includes("品牌") && ( {typeInfo?.length &&
<BrandItem typeInfo?.map((item) => (
onChange={(item: FilterOptionResp) => onChange(item, "brand")} <TypeInfo
></BrandItem> key={item.directoryId}
)} typeName={item.name}
{props.types.includes("设备品牌") && ( dataValue={item.categoriesInfoListDTO}
<DeviceBrand onChange={(item: FilterOptionResp) =>
onChange={(item: FilterOptionResp) => onChange(item, "brand")} onChange(item, 'categoryId')
></DeviceBrand> }
)} ></TypeInfo>
{props.types.includes("类目") && ( ))}
<CategoryItem
onChange={(item: FilterOptionResp) => onChange(item, "category")}
></CategoryItem>
)}
{props.types.includes("设备类目") && (
<CategoryItem
onChange={(item: FilterOptionResp) => onChange(item, "categoryId")}
></CategoryItem>
)}
{props.types.includes("部件") && (
<PartItem
onChange={(item: FilterOptionResp) => onChange(item, "part")}
></PartItem>
)}
{props.types.includes("型号") && (
<ModelItem
onChange={(item: FilterOptionResp) => onChange(item, "model")}
></ModelItem>
)}
{props.types.includes("设备型号") && (
<DeviceModel
onChange={(item: FilterOptionResp) => onChange(item, "model")}
></DeviceModel>
)}
{props.types.includes("成色") && (
<QualityItem
onChange={(item: FilterOptionResp) => onChange(item, "quality")}
></QualityItem>
)}
{props.types.includes("行业") && (
<Industry
onChange={(item: FilterOptionResp) => onChange(item, "industryId")}
></Industry>
)}
{props.types.includes("应用") && (
<AppType
onChange={(item: FilterOptionResp) => onChange(item, "appTypeId")}
></AppType>
)}
{props.showResultItem && ( {props.showResultItem && (
<ResultItem data={result} onDel={onDel}></ResultItem> <ResultItem data={result} onDel={onDel}></ResultItem>
)} )}
</div> </div>
</> </>
); )
} }
export default forwardRef(Filter)
import styles from "./index.module.scss"; import styles from "./index.module.scss";
import Image from "next/image"; import { Image } from "antd";
import errImg from "~/assets/errImg";
import img_mmc from './assets/mmc.png';
const qrcodeList = [ const qrcodeList = [
{ {
img: require("./assets/mmc.png"), img: "/assets/mmc.png",
title: "科比特官网", title: "科比特官网",
url: "http://www.mmcuav.cn/", url: "http://www.mmcuav.cn/",
}, },
{ {
img: require("./assets/fuwuhao.png"), img: "/assets/fuwuhao.png",
title: "云享飞服务号", title: "云享飞服务号",
}, },
{ {
img: require("./assets/xiaochengxu.png"), img: "/assets/xiaochengxu.png",
title: "云享飞小程序", title: "云享飞小程序",
}, },
{ {
img: require("./assets/shequn.png"), img: "/assets/shequn.png",
title: "官方社群", title: "官方社群",
}, },
]; ];
...@@ -41,6 +43,8 @@ export default function Footer() { ...@@ -41,6 +43,8 @@ export default function Footer() {
alt="" alt=""
className={styles.qrcodeImg} className={styles.qrcodeImg}
src={item.img} src={item.img}
fallback={errImg}
preview={item.title !== "科比特官网"}
></Image> ></Image>
<div className={styles.qrcodeTitle}>{item.title}</div> <div className={styles.qrcodeTitle}>{item.title}</div>
</div> </div>
......
.content{
min-height: 120px;
line-height: 1;
width: 1200px;
position: relative;
margin: 0 auto;
}
\ No newline at end of file
...@@ -2,8 +2,16 @@ import React, { Suspense } from "react"; ...@@ -2,8 +2,16 @@ import React, { Suspense } from "react";
import { Layout, Space } from "antd"; import { Layout, Space } from "antd";
import NavHeader from "~/components/NavHeader"; import NavHeader from "~/components/NavHeader";
import FooterView from "~/components/footer"; import FooterView from "~/components/footer";
import {useRouter} from 'next/router'
import styles from './index.module.scss';
const { Header, Footer, Content } = Layout; const { Header, Footer, Content } = Layout;
//底部栏固定定位
const includesPage = ["/home","/flyingHandService/detail/[id]"]
const homeStyle: React.CSSProperties = {
marginTop:10
}
const headerStyle: React.CSSProperties = { const headerStyle: React.CSSProperties = {
height: "auto", height: "auto",
...@@ -24,8 +32,6 @@ const contentStyle: React.CSSProperties = { ...@@ -24,8 +32,6 @@ const contentStyle: React.CSSProperties = {
}; };
const footerStyle: React.CSSProperties = { const footerStyle: React.CSSProperties = {
color: "",
backgroundColor: "",
lineHeight: "1", lineHeight: "1",
padding: 0, padding: 0,
position: "relative", position: "relative",
...@@ -35,12 +41,15 @@ const footerStyle: React.CSSProperties = { ...@@ -35,12 +41,15 @@ const footerStyle: React.CSSProperties = {
type Props = { type Props = {
children?: React.ReactNode; children?: React.ReactNode;
layoutStyle?: React.CSSProperties; layoutStyle?: React.CSSProperties;
contentStyle?: React.CSSProperties;
hideFooter?: boolean; hideFooter?: boolean;
headerStyle?: React.CSSProperties
}; };
export default function LayoutView(props: Props) { export default function LayoutView(props: Props) {
const router = useRouter()
return ( return (
<Space direction="vertical" style={{ width: "100%" }} size={[0, 48]}> <Space direction="vertical" style={{ minWidth: "100%" }} size={[0, 48]}>
<Layout <Layout
style={Object.assign( style={Object.assign(
{ minHeight: "100vh", backgroundColor: "#F8F8F8" }, { minHeight: "100vh", backgroundColor: "#F8F8F8" },
...@@ -48,11 +57,19 @@ export default function LayoutView(props: Props) { ...@@ -48,11 +57,19 @@ export default function LayoutView(props: Props) {
)} )}
> >
<Header style={headerStyle}> <Header style={headerStyle}>
<NavHeader /> <NavHeader style={props.headerStyle} />
</Header> </Header>
<Content style={contentStyle}>{props.children}</Content> <Content className={styles.content} style={props.contentStyle}>
{props.children}
</Content>
{!props.hideFooter && ( {!props.hideFooter && (
<Footer style={footerStyle}> <Footer
style={
includesPage.includes(router.pathname)
? { ...footerStyle, ...homeStyle }
: footerStyle
}
>
<FooterView></FooterView> <FooterView></FooterView>
</Footer> </Footer>
)} )}
......
import React, { useState } from "react"; import React, { useContext, useEffect, useState } from "react";
import { Modal } from "antd"; import { Modal, Image } from "antd";
import Image from "next/image"; import api from "~/api";
import { UserContext } from "~/lib/userProvider";
import errImg from "~/assets/errImg";
type Props = { type Props = {
open: boolean; open: boolean;
onCancel: () => void; onCancel: () => void;
}; };
export default function loginModal(props: Props) { export default function LoginModal(props: Props) {
const [qrCode, setQrCode] = useState("");
const [randomLoginCode, setRandomLoginCode] = useState("");
const { userInfo, setUserInfo } = useContext(UserContext);
const [timeHandle, setTimeHandle] = useState<NodeJS.Timer | null>(null);
useEffect(() => {
/* if (props.open) {
new window.WxLogin({
self_redirect: true,
id: "login_container",
appid: "wx18b7883acd204278",
scope: "snsapi_login",
redirect_uri: encodeURIComponent("https://iuav.mmcuav.cn/"),
state: "",
style: "",
href: "",
});
} */
if (!props.open) {
setQrCode("");
return;
}
setRandomLoginCode(String(Date.now()));
}, [props.open]);
useEffect(() => {
if (randomLoginCode) {
//获取登录码
api
.getAppletQRCode({
randomLoginCode,
})
.then((res) => {
if (res.code == "200") {
setQrCode("data:image/png;base64," + res.result || "");
} else {
window.messageApi.error("获取登录二维码失败");
}
});
}
if (randomLoginCode && !userInfo) {
if (timeHandle) {
clearTimeout(timeHandle);
}
const handle = setInterval(() => {
api
.getLoginInfo({
randomLoginCode: randomLoginCode,
})
.then((res) => {
if (res.code === "200") {
clearInterval(handle);
setTimeHandle(null);
window.localStorage.setItem("token", res.result.token);
api.userInfo().then((res) => {
setUserInfo(res.result);
window.messageApi.success("登录成功");
props.onCancel();
window.location.reload();
});
}
});
}, 1000);
setTimeHandle(handle);
}
}, [randomLoginCode]);
useEffect(() => {
if (!props.open && timeHandle) {
clearTimeout(timeHandle);
}
}, [timeHandle, props.open]);
return ( return (
<> <>
<Modal <Modal
...@@ -30,16 +106,17 @@ export default function loginModal(props: Props) { ...@@ -30,16 +106,17 @@ export default function loginModal(props: Props) {
> >
欢迎来到云享飞 欢迎来到云享飞
</div> </div>
<Image <div id="login_container" style={{ margin: "auto", display: "table" }}>
alt="" <Image
src="" src={qrCode}
width={160} width={150}
height={160} height={150}
style={{ margin: "auto", display: "block" }} fallback={errImg}
></Image> ></Image>
</div>
<div <div
style={{ style={{
marginTop: 39, // marginTop: -120,
marginBottom: 52, marginBottom: 52,
fontSize: 14, fontSize: 14,
fontFamily: "MicrosoftYaHei", fontFamily: "MicrosoftYaHei",
......
apiVersion: v1
kind: ConfigMap
metadata:
name: web-map
namespace: default
data:
SPRING_PROFILES_ACTIVE: default
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deployment
namespace: default
spec:
minReadySeconds: 250
revisionHistoryLimit: 2
replicas: 1
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: REGISTRY/NAMESPACE/IMAGE:TAG
resources:
limits:
memory: 512Mi
cpu: 100m
ports:
- containerPort: 3000
\ No newline at end of file
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
commonLabels:
app: web
resources:
- ./deployment.yaml
- ./service.yaml
- ./configMap.yaml
\ No newline at end of file
apiVersion: v1
kind: Service
metadata:
name: web-svc
namespace: default
spec:
selector:
app: web
type: NodePort
ports:
- protocol: TCP
port: 3000
apiVersion: v1
kind: ConfigMap
metadata:
name: web-map
data:
SPRING_PROFILES_ACTIVE: dev
\ No newline at end of file
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deployment
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
\ No newline at end of file
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
#namePrefix: dev-
namespace: dev
commonLabels:
variant: dev
commonAnnotations:
note: This is dev!
patches:
- path: ./increase_replicas.yaml
- path: ./configMap.yaml
- path: ./service-patch.yaml
target:
kind: Service
name: web-svc
images:
- name: REGISTRY/NAMESPACE/IMAGE:TAG
newName: mmc-registry.cn-shenzhen.cr.aliyuncs.com/sharefly-dev/web
newTag: e820e7ccff1b734bbdf2460c9f4a3cd9edc99a76
- op: replace
path: /spec/ports/0/nodePort
value: 31001
\ No newline at end of file
apiVersion: v1
kind: ConfigMap
metadata:
name: web-map
data:
SPRING_PROFILES_ACTIVE: prod
\ No newline at end of file
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deployment
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
\ No newline at end of file
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namespace: prod
#namePrefix: prod-
commonLabels:
variant: prod
commonAnnotations:
note: This is prod!
patches:
- path: increase_replicas.yaml
- path: configMap.yaml
- path: service-patch.yaml
target:
kind: Service
name: web-svc
images:
- name: REGISTRY/NAMESPACE/IMAGE:TAG
newName: mmc-registry.cn-shenzhen.cr.aliyuncs.com/sharefly/web
newTag: 18a408695e671822e9b91f298e42cdb52a870360
- op: replace
path: /spec/ports/0/nodePort
value: 31000
\ No newline at end of file
import { serialize, parse } from "cookie";
const TOKEN_NAME = "token";
export const MAX_AGE = 60 * 60 * 8; // 8 hours
export function setTokenCookie(res, token) {
const cookie = serialize(TOKEN_NAME, token, {
maxAge: MAX_AGE,
expires: new Date(Date.now() + MAX_AGE * 1000),
httpOnly: true,
secure: process.env.NODE_ENV === "production",
path: "/",
sameSite: "lax",
});
res.setHeader("Set-Cookie", cookie);
}
export function removeTokenCookie(res) {
const cookie = serialize(TOKEN_NAME, "", {
maxAge: -1,
path: "/",
});
res.setHeader("Set-Cookie", cookie);
}
export function parseCookies(req) {
// For API Routes we don't need to parse the cookies.
if (req.cookies) return req.cookies;
// For pages we do need to parse the cookies.
const cookie = req.headers?.cookie;
return parse(cookie || "");
}
export function getTokenCookie(req) {
const cookies = parseCookies(req);
return cookies[TOKEN_NAME];
}
import Iron from "@hapi/iron";
import { MAX_AGE, setTokenCookie, getTokenCookie } from "./auth-cookies";
const TOKEN_SECRET = process.env.TOKEN_SECRET;
export async function setLoginSession(res, session) {
const createdAt = Date.now();
// Create a session object with a max age that we can validate later
const obj = { ...session, createdAt, maxAge: MAX_AGE };
const token = await Iron.seal(obj, TOKEN_SECRET, Iron.defaults);
setTokenCookie(res, token);
}
export async function getLoginSession(req) {
const token = getTokenCookie(req);
if (!token) return;
const session = await Iron.unseal(token, TOKEN_SECRET, Iron.defaults);
const expiresAt = session.createdAt + session.maxAge * 1000;
// Validate the expiration date of the session
if (Date.now() > expiresAt) {
throw new Error("Session expired");
}
return session;
}
import React, { createContext, Dispatch, SetStateAction, useState } from "react";
export const CommonContext = createContext<{
reloadRequirements: boolean; //更新项目需求列表
setReloadRequirements: Dispatch<SetStateAction<boolean>>;
}>({
reloadRequirements: false,
setReloadRequirements: () => {}
});
type Props = {
children: React.ReactNode;
};
const CommonProvider = ({ children }: Props) => {
const [reloadRequirements, setReloadRequirements] = useState(false);
return (
<CommonContext.Provider
value={{
reloadRequirements,
setReloadRequirements,
}}
>
{children}
</CommonContext.Provider>
);
};
export default CommonProvider;
/* import { useEffect } from "react";
import Router from "next/router";
import request from '~/api/request';
import useSWR, { SWRResponse } from "swr"; */
/*
const fetcher = (url) =>
fetch(url)
.then((r) => r.json())
.then((data) => {
return { user: data?.user || null };
}); */
export function useUser({ redirectTo, redirectIfFound } = {}) {
return {};
/* const { data, error } = useSWR("/api/user", request);
const user = data?.user;
const finished = Boolean(data);
const hasUser = Boolean(user);
useEffect(() => {
if (!redirectTo || !finished) return;
if (
// If redirectTo is set, redirect if the user was not found.
(redirectTo && !redirectIfFound && !hasUser) ||
// If redirectIfFound is also set, redirect if the user was found
(redirectIfFound && hasUser)
) {
Router.push(redirectTo);
}
}, [redirectTo, redirectIfFound, finished, hasUser]);
return error ? null : user; */
}
import { useEffect, useState } from "react";
export function useGeolocation() {
const [position, setPosition] = useState<{
position?: any,
address?: any
} | null>(null);
useEffect(() => {
const AMapLoader = require("@amap/amap-jsapi-loader");
window._AMapSecurityConfig = {
securityJsCode: 'd7492300c43c8d3737909b77f2b2c387',
}
AMapLoader.load({
key: "87b424e68754efc3ba9d11ae07475091", // 申请好的Web端开发者Key,首次调用 load 时必填
version: "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
plugins: ['AMap.Geolocation', "AMap.Geocoder"], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
})
.then(async (AMap: any) => {
//用户定位
const geolocation = new AMap.Geolocation({
enableHighAccuracy: true,//是否使用高精度定位,默认:true
timeout: 10000, //超过10秒后停止定位,默认:5s
position: 'RB', //定位按钮的停靠位置
offset: [10, 20], //定位按钮与设置的停靠位置的偏移量,默认:[10, 20]
zoomToAccuracy: true, //定位成功后是否自动调整地图视野到定位点
});
geolocation.getCurrentPosition(function (status: string, result: any) {
if (status == 'complete') {
onComplete(result)
} else {
onError(result)
}
});
//解析定位结果
async function onComplete(data: any) {
console.log('定位成功', data.position);
setPosition({
position: data.position,
address: null
})
var geocoder = new AMap.Geocoder({
city: '全国' // city 指定进行编码查询的城市,支持传入城市名、adcode 和 citycode
})
geocoder.getAddress([data.position.lng, data.position.lat], function (status: string, result: {info: string, regeocode?: {
formattedAddress: string,
addressComponent: {
adcode: string
}
}}) {
console.log('获取地址结果', result)
if (status === 'complete' && result.info === 'OK') {
// result为对应的地理位置详细信息
setPosition({
...position,
address: result.regeocode
})
}else{
setPosition({
...position,
address: null
})
}
})
}
//解析定位错误信息
async function onError(data: any) {
// message.error(`定位失败
// 失败原因排查信息:${data.message}
// 浏览器返回信息:${data.originMessage}
// `)
}
})
.catch((e: any) => {
console.log(e);
});
}, [])
return position
}
\ No newline at end of file
import Local from "passport-local";
import { findUser, validatePassword } from "./user";
export const localStrategy = new Local.Strategy(function (
username,
password,
done
) {
findUser({ username })
.then((user) => {
if (user && validatePassword(user, password)) {
done(null, user);
} else {
done(new Error("Invalid username and password combination"));
}
})
.catch((error) => {
done(error);
});
});
import crypto from "crypto";
import { v4 as uuidv4 } from "uuid";
/**
* User methods. The example doesn't contain a DB, but for real applications you must use a
* db here, such as MongoDB, Fauna, SQL, etc.
*/
const users = [];
export async function createUser({ username, password }) {
// Here you should create the user and save the salt and hashed password (some dbs may have
// authentication methods that will do it for you so you don't have to worry about it):
const salt = crypto.randomBytes(16).toString("hex");
const hash = crypto
.pbkdf2Sync(password, salt, 1000, 64, "sha512")
.toString("hex");
const user = {
id: uuidv4(),
createdAt: Date.now(),
username,
hash,
salt,
};
// This is an in memory store for users, there is no data persistence without a proper DB
users.push(user);
return { username, createdAt: Date.now() };
}
// Here you should lookup for the user in your DB
export async function findUser({ username }) {
// This is an in memory store for users, there is no data persistence without a proper DB
return users.find((user) => user.username === username);
}
// Compare the password of an already fetched user (using `findUser`) and compare the
// password for a potential match
export function validatePassword(user, inputPassword) {
const inputHash = crypto
.pbkdf2Sync(inputPassword, user.salt, 1000, 64, "sha512")
.toString("hex");
const passwordsMatch = user.hash === inputHash;
return passwordsMatch;
}
import React, {
createContext,
Dispatch,
SetStateAction,
useEffect,
useState,
} from "react";
import api, { UserInfoResp } from "~/api";
export const UserContext = createContext<{
testLogin: () => void;
logout: () => void;
userInfo: UserInfoResp | null | undefined;
setUserInfo: Dispatch<SetStateAction<UserInfoResp | null | undefined>>;
needLogin: Boolean;
setNeedLogin: Dispatch<SetStateAction<Boolean>>;
}>({
testLogin() {},
logout() {},
userInfo: null,
setUserInfo() {},
needLogin: false,
setNeedLogin() {},
});
type Props = {
children: React.ReactNode;
};
const UserProvider = ({ children }: Props) => {
const [userInfo, setUserInfo] = useState<UserInfoResp | null | undefined>(null);
const [needLogin, setNeedLogin] = useState<Boolean>(false); //用于通知登录modal需要打开
useEffect(() => {
try {
setUserInfo(
JSON.parse(window.localStorage.getItem("userInfo") || "") || undefined
);
window.setUserInfo = setUserInfo;
window.setNeedLogin = setNeedLogin;
window.logout = logout;
} catch (e) {}
}, []);
useEffect(() => {
if (userInfo !== null) {
localStorage.setItem("userInfo", JSON.stringify(userInfo || ""));
}
}, [userInfo]);
//测试登录
function testLogin() {
api.testAppletLogin().then((res) => {
if (res.code == "200") {
window.localStorage.setItem("token", res.result?.token || "");
api.userInfo().then((res) => {
setUserInfo(res.result || undefined);
});
}
});
}
//登出
function logout() {
localStorage.setItem("token", "");
setUserInfo(undefined);
}
return (
<UserContext.Provider
value={{
userInfo,
setUserInfo,
testLogin,
logout,
needLogin,
setNeedLogin,
}}
>
{children}
</UserContext.Provider>
);
};
export default UserProvider;
// 不能输入数字,其他可惜输入
export const exceptNumber = (val: any) => {
val.target.value = val.target.value
.replace(/1?(\d|([1-9]\d+))(.\d+)?$/g, "")
.replace(/\s/g, "");
};
// 只能输入正整数
export const onlyNumberPositive = (val: any) => {
// eslint-disable-next-line eqeqeq
if (val.target.value == 0) {
val.target.value = val.target.value.replace(/0/g, "");
}
val.target.value = val.target.value.replace(/\D/g, "");
};
// 不能输入汉字,其他可输入
export const exceptChinese = (val: any) => {
val.target.value = val.target.value
.replace(/[\u4E00-\u9FA5]|[\uFE30-\uFFA0]/g, "")
.replace(/\s/g, "");
};
// 只能输入字母和中文,不能输入数字和符号
export const onlyCharacter = (val: any) => {
val.target.value = val.target.value
.replace(/[^a-zA-Z\u4E00-\u9FA5]/g, "")
.replace(/\s/g, "");
};
// 手机号输入,限制11位
export const phoneNumber = (val: any) => {
if (val.target.value.length > 11) {
val.target.value = val.target.value.slice(0, 11);
} else {
val.target.value = val.target.value.replace(/\D/g, "");
}
};
// 开头不能输入空格
export const noSpaceFront = (val: any) => {
val.target.value = val.target.value.replace(/^\s/g, "");
};
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
let distDir = ".next"; //默认输出目录 let distDir = '.dev' //默认输出目录
if (process.env.NODE_ENV === "production") { if (process.env.NODE_ENV === 'production') {
//生产环境用另一个目录构建,防止与dev冲突 //生产环境用另一个目录构建,防止与dev冲突
distDir = ".dist"; distDir = '.next'
} }
const nextConfig = { const nextConfig = {
distDir, distDir,
reactStrictMode: true, reactStrictMode: true,
transpilePackages: ["antd"], transpilePackages: ['antd'],
output: 'standalone',
compiler: { compiler: {
styledComponents: true, styledComponents: true,
}, },
redirects() { redirects() {
return [ return [
{ {
source: "/", source: '/',
destination: "/home", destination: '/home',
permanent: true, permanent: true,
}, },
]; ]
}, },
async rewrites() { async rewrites() {
return [ return [
{ {
source: "/local/:path*", source: '/local/:path*',
destination: "https://iuav.mmcuav.cn/:path*", //destination: "https://iuav.mmcuav.cn/:path*",
destination: 'https://test.iuav.shop/:path*',
}, },
]; ]
}, },
images: { images: {
remotePatterns: [ remotePatterns: [
{ {
protocol: "http", protocol: 'http',
hostname: "**", hostname: '**',
}, },
{ {
protocol: "https", protocol: 'https',
hostname: "**", hostname: '**',
}, },
], ],
}, },
pageExtensions: ["page.tsx", "page.ts", "page.jsx", "page.js"], pageExtensions: ['page.tsx', 'page.ts', 'page.jsx', 'page.js'],
}; }
module.exports = nextConfig; module.exports = nextConfig
...@@ -6,9 +6,14 @@ ...@@ -6,9 +6,14 @@
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint:next": "next lint",
"predev": "ts-node --project ./tsconfig.node.json ./scripts/genAntdCss.tsx", "predev": "ts-node --project ./tsconfig.node.json ./scripts/genAntdCss.tsx",
"prebuild": "cross-env NODE_ENV=production ts-node --project ./tsconfig.node.json ./scripts/genAntdCss.tsx" "prebuild": "cross-env NODE_ENV=production ts-node --project ./tsconfig.node.json ./scripts/genAntdCss.tsx",
"lint": "npx eslint src",
"lint:fix": "npm run lint -- --fix",
"prettier": "npx prettier src --check",
"prettier:fix": "npm run prettier -- --write",
"format": "npm run prettier:fix && npm run lint:fix"
}, },
"devDependencies": { "devDependencies": {
"@ant-design/cssinjs": "^1.3.0", "@ant-design/cssinjs": "^1.3.0",
...@@ -18,9 +23,11 @@ ...@@ -18,9 +23,11 @@
"@types/react-dom": "^18.0.10", "@types/react-dom": "^18.0.10",
"antd": "^5.1.0", "antd": "^5.1.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.30.0", "eslint": "^8.42.0",
"eslint-config-next": "^13.1.1", "eslint-config-next": "^13.1.1",
"eslint-plugin-prettier": "^4.2.1",
"next": "^13.1.1", "next": "^13.1.1",
"prettier": "^2.8.8",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"sass": "^1.62.1", "sass": "^1.62.1",
...@@ -34,12 +41,16 @@ ...@@ -34,12 +41,16 @@
"@hapi/iron": "^7.0.1", "@hapi/iron": "^7.0.1",
"@types/styled-components": "^5.1.26", "@types/styled-components": "^5.1.26",
"babel-plugin-styled-components": "^2.1.1", "babel-plugin-styled-components": "^2.1.1",
"babel-plugin-styled-components-px2rem": "^1.5.5",
"cookie": "^0.5.0", "cookie": "^0.5.0",
"dayjs": "^1.11.8", "dayjs": "^1.11.8",
"moment": "^2.29.4",
"next-connect": "^1.0.0", "next-connect": "^1.0.0",
"passport": "^0.6.0", "passport": "^0.6.0",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"pinyin-pro": "^3.14.0", "pinyin-pro": "^3.14.0",
"postcss-pxtorem": "^6.0.0",
"react-infinite-scroll-component": "^6.1.0",
"styled-components": "^6.0.0-rc.1", "styled-components": "^6.0.0-rc.1",
"swiper": "^9.3.2", "swiper": "^9.3.2",
"swr": "^2.1.5", "swr": "^2.1.5",
......
import request, { Response } from "~/api/request"
export interface CooperationApplyParams {
applyName: string,
applyPhone: string,
remark?: string,
userAccountId: number,
cooperationTagId: number
}
export interface GetTagIdResp {
id: number,
tagName: string,
tagImg: string,
tagDescription: string,
createTime: string
}
export default {
//申请加盟
cooperationApply(params: CooperationApplyParams): Promise<Response<string>> {
return request('/userapp/cooperation/apply', 'post', params)
},
//加盟标签相关内容
getTagById(params: { id: number }): Promise<Response<GetTagIdResp>> {
return request('/userapp/cooperation/getTagById', 'get', params)
}
}
\ No newline at end of file
.banner {
width: 100vw;
height: 200px;
background: #f25834;
position: absolute;
left: 50%;
top: 0;
transform: translate(-50%, 0);
}
.font1 {
font-size: 20px;
font-family: MicrosoftYaHeiUI-Bold, MicrosoftYaHeiUI;
font-weight: bold;
color: #000000;
line-height: 25px;
}
.font2 {
font-size: 18px;
font-family: MicrosoftYaHei;
color: #333333;
line-height: 32px;
letter-spacing: 1px;
}
import { Button, Col, Divider, Form, Input, Row } from "antd";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import LayoutView from "~/components/layout";
import api from "./api";
import styles from "./index.module.scss";
import { phoneNumber } from "~/lib/validateUtils";
export default function JoinPolicy() {
const router = useRouter();
const [content, setContent] = useState(""); //福利内容
const tagId = Number(router.query.tagId);
useEffect(() => {
if (tagId) {
api
.getTagById({
id: tagId,
})
.then((res) => {
setContent(res.result?.tagDescription.replaceAll("\n", "<br/>") || "");
});
}
}, []);
//提交
const onFinish = (values: any) => {
console.log(values);
api
.cooperationApply({
...values,
cooperationTagId: tagId,
})
.then((res) => {
console.log("提交结果", res);
if (res.code === "200") {
window.messageApi.success('提交成功');
setTimeout(() => {
router.push("/");
}, 1500);
}
});
};
return (
<LayoutView>
<div className={styles.banner}></div>
<div
className="page"
style={{
background: "#fff",
position: "relative",
zIndex: 1,
marginTop: 60,
paddingBottom: 63,
}}
>
<div
className={styles.font1}
style={{ textAlign: "center", paddingTop: 40, paddingBottom: 30 }}
>
加盟入驻福利
</div>
<div
className={styles.font2}
style={{ paddingLeft: 50, paddingRight: 50 }}
dangerouslySetInnerHTML={{ __html: content }}
></div>
<Divider />
<div
className={styles.font1}
style={{ textAlign: "center", marginBottom: 28 }}
>
申请加盟入驻
</div>
<Row justify={"center"}>
<Col style={{ width: 400 }}>
<Form labelAlign="left" labelCol={{ span: 5 }} onFinish={onFinish}>
<Form.Item
label="姓名"
name="applyName"
rules={[{ required: true }]}
>
<Input placeholder="请输入姓名"></Input>
</Form.Item>
<Form.Item
label="联系方式"
name="applyPhone"
rules={[
{ required: true },
{
pattern: /^1\d{10}$/,
message: "很输入11位手机号",
},
]}
>
<Input
placeholder="请输入手机号"
maxLength={11}
onInput={phoneNumber}
></Input>
</Form.Item>
<Form.Item name="remark">
<Input.TextArea placeholder="其它信息"></Input.TextArea>
</Form.Item>
<Row justify={"center"}>
<Button
type="primary"
htmlType="submit"
style={{ marginTop: 11, width: 200 }}
size="large"
>
提交申请
</Button>
</Row>
</Form>
</Col>
</Row>
</div>
</LayoutView>
);
}
import '../public/antd.min.css'; import "../public/antd.min.css";
import '../styles/index.scss'; import "../styles/index.scss";
import type { AppProps } from 'next/app'; import type { AppProps } from "next/app";
import withTheme from '../theme'; import withTheme from "../theme";
import { message } from 'antd'; import { message } from "antd";
import { useEffect } from 'react'; import { useEffect } from "react";
import Head from "next/head";
import Script from "next/script";
import UserProvider from "~/lib/userProvider";
import CommonProvider from "~/lib/commonProvider";
export default function App({ Component, pageProps }: AppProps) { export default function App({ Component, pageProps }: AppProps) {
const [messageApi, contextHolder] = message.useMessage(); const [messageApi, contextHolder] = message.useMessage();
useEffect(() => { useEffect(() => {
//全局消息提示 //全局消息提示
window.messageApi = messageApi; window.messageApi = messageApi;
}, []) //@ts-ignore
window.onresize = function(){
// window.document.querySelector('html')!.style.fontSize = (window.innerWidth / 1920) + 'PX';
}
}, []);
return withTheme( return withTheme(
<> <>
<Head>
<title>云享飞</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
></meta>
</Head>
<Script src="https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"></Script>
{contextHolder} {contextHolder}
<Component {...pageProps} /> <CommonProvider>
<UserProvider>
<Component {...pageProps} />
</UserProvider>
</CommonProvider>
</> </>
); );
} }
...@@ -24,7 +24,7 @@ export default class MyDocument extends Document { ...@@ -24,7 +24,7 @@ export default class MyDocument extends Document {
render() { render() {
return ( return (
<Html lang='en'> <Html lang='en' style={{fontSize: 1}}>
<Head /> <Head />
<body> <body>
<Main /> <Main />
......
import request, { Response } from "~/api/request"
export interface CompanyAuthParams {
companyName: string;
creditCode: string;
licenseImg: string;
userAccountId: number;
authStatus: 1 | 0
}
export interface FuzzyQueryCompanyResp {
Status: string;
Message: string;
OrderNumber: string;
Paging: Paging;
Result: EnterpriseInfo[];
}
export interface EnterpriseInfo {
KeyNo: string;
Name: string;
CreditCode: string;
StartDate: string;
OperName: string;
Status: string;
No: string;
Address: string;
label: string;
value: string;
}
export interface Paging {
PageSize: number;
PageIndex: number;
TotalRecords: number;
}
export default {
//提交企业认证
companyAuth(params: CompanyAuthParams): Promise<Response<string>> {
return request('/userapp/company-auth/add', 'post', params)
},
//企业工商模糊搜索
fuzzyQueryCompany(params: { searchKey: string }): Promise<Response<FuzzyQueryCompanyResp>> {
return request('/userapp/company-auth/fuzzyQueryCompany', 'get', params);
}
}
\ No newline at end of file
.font1 {
font-size: 20px;
font-family: MicrosoftYaHeiUI-Bold;
font-weight: bold;
color: #000000;
line-height: 25px;
}
.font2 {
font-size: 14px;
font-family: MicrosoftYaHei;
color: #989ca7;
line-height: 19px;
}
.banner {
width: 100vw;
height: 200px;
background: #1e3d7d;
position: absolute;
left: 50%;
top: 0;
transform: translate(-50%, 0);
}
.upload {
text-align: center;
margin-top: 40px;
:global .ant-upload {
width: 260px !important;
height: 172px !important;
}
}
\ No newline at end of file
import { LoadingOutlined, PlusOutlined } from "@ant-design/icons";
import {
Col,
Form,
Input,
Row,
Upload,
message,
Button,
Image,
AutoComplete,
} from "antd";
import type { UploadChangeParam } from "antd/es/upload";
import type { RcFile, UploadFile, UploadProps } from "antd/es/upload/interface";
import { useContext, useEffect, useState } from "react";
import Layout from "~/components/layout";
import api from "./api";
import styles from "./index.module.scss";
import gApi from "~/api";
import Router from "next/router";
import { UserContext } from "~/lib/userProvider";
const beforeUpload = (file: RcFile) => {
const isJpgOrPng =
file.type === "image/jpeg" ||
file.type === "image/png" ||
file.type === "image/bmp" ||
file.type === "image/gif"
if (!isJpgOrPng) {
message.error("请上传10M以内的JPG、JPEG、BMP、GIF、PNG格式图片");
}
//限制上传10M
const isLt2M = file.size / 1024 / 1024 < 10;
if (!isLt2M) {
message.error("请上传10M以内的JPG、JPEG、BMP、GIF、PNG格式图片");
}
return isJpgOrPng && isLt2M;
};
const normFile = (e: any) => {
console.log("Upload event:", e);
if (Array.isArray(e)) {
return e;
}
return e?.fileList;
};
type EnterpriseOption = {
label: string;
value: string;
creditCode: string;
};
export default function Certification() {
const [loading, setLoading] = useState(false);
const [imageUrl, setImageUrl] = useState<string>();
const { userInfo, setUserInfo } = useContext(UserContext);
const [enterpriseOptions, setEnterpriseOptions] = useState<
Array<EnterpriseOption>
>([]);
const [form] = Form.useForm();
const [token, setToken] = useState("");
useEffect(() => {
setToken(window.localStorage.getItem("token") || "");
}, []);
//上传change事件
const handleChange: UploadProps["onChange"] = (
info: UploadChangeParam<UploadFile>
) => {
console.log("uploadChange", info);
if (info.file.status === "uploading") {
setLoading(true);
return;
}
if (info.file.status === "done") {
// Get this url from response in real world.
setLoading(false);
setImageUrl(info.file.response.result?.[0]);
}
};
//提交
const onFinish = (values: any) => {
console.log(values);
api
.companyAuth({
...values,
licenseImg: imageUrl,
})
.then((res) => {
console.log("提交结果", res);
if (res.code === "200") {
window.messageApi.success("提交成功,请等待审核");
if (userInfo) {
setUserInfo({
...userInfo,
companyAuthStatus: 1,
});
}
setTimeout(() => {
if (Router.query.type == "back") {
Router.back();
} else {
Router.push("/");
}
}, 1000);
}
});
};
let handle: NodeJS.Timeout;
//搜索企业
const onSearchEnterprise = (text: string) => {
if (handle) {
clearTimeout(handle);
}
handle = setTimeout(() => {
api
.fuzzyQueryCompany({
searchKey: text,
})
.then((res) => {
if (res.code === "200") {
setEnterpriseOptions(
res.result?.Result?.map((item) => {
return {
label: item.Name,
value: item.Name,
creditCode: item.CreditCode,
};
}) || []
);
} else {
setEnterpriseOptions([]);
}
});
}, 500);
};
//选择的企业
const onSelectEnterprise = (value: string, option: EnterpriseOption) => {
form.setFieldValue("creditCode", option.creditCode);
};
return (
<Layout>
<div className={styles.banner}></div>
<div
className="page"
style={{
background: "#fff",
position: "relative",
zIndex: 1,
marginTop: 60,
}}
>
<div
className={styles.font1}
style={{
padding: "30px 0 23px 31px",
borderBottom: "1px solid RGBA(231, 231, 231, 1)",
}}
>
企业认证{" "}
<span className={styles.font2} style={{ marginLeft: 28 }}>
发布信息需完成以下认证
</span>
</div>
<div>
<Form
form={form}
style={{ padding: "70px 170px 162px 170px" }}
onFinish={onFinish}
>
<Row justify="space-between">
<Col span={11}>
<Form.Item
label="企业名称"
name="companyName"
rules={[{ required: true }]}
style={{ borderBottom: "1px solid RGBA(243, 243, 243, 1)" }}
>
<AutoComplete
options={enterpriseOptions}
style={{ width: 200 }}
onSelect={onSelectEnterprise}
onSearch={onSearchEnterprise}
placeholder="请输入企业名称"
bordered={false}
/>
</Form.Item>
</Col>
<Col span={11}>
<Form.Item
label="企业信用代码"
name="creditCode"
rules={[{ required: true }]}
style={{ borderBottom: "1px solid RGBA(243, 243, 243, 1)" }}
>
<Input
bordered={false}
placeholder="请输入企业信用代码"
></Input>
</Form.Item>
</Col>
</Row>
<Form.Item
name="licenseImg"
rules={[{ required: true }]}
valuePropName="fileList"
getValueFromEvent={normFile}
help={<div style={{ textAlign: "center" }}>请上传营业执照</div>}
>
<Upload
name="uploadFile"
listType="picture-card"
className={styles.upload}
showUploadList={false}
action={gApi.imgOss}
beforeUpload={beforeUpload}
onChange={handleChange}
maxCount={1}
headers={{ token: token }}
>
{imageUrl ? (
<Image
src={imageUrl}
alt="uploadFile"
style={{ width: "100%" }}
preview={false}
/>
) : (
<div>
{loading ? <LoadingOutlined /> : <PlusOutlined />}
<div style={{ marginTop: 8 }}>上传营业执照</div>
</div>
)}
</Upload>
</Form.Item>
<div className={styles.font2} style={{ marginTop: 56 }}>
1. 请上传最新的营业执照,证件需加盖与主体一致的单位公章。 <br />
2.加盖公章的扫描件或复印件支持jpg.jpeg.bmp.gif.png格式图片,大小不超10M。
</div>
<Row justify="center">
<Button
type="primary"
htmlType="submit"
style={{ marginTop: 20, padding: "0 129px" }}
size="large"
>
提交认证
</Button>
</Row>
</Form>
</div>
</div>
</Layout>
);
}
import request, { Response } from '~/api/request'; import request, { Response } from '~/api/request'
export interface ListPageDeviceInfoParams { export interface ListPageDeviceInfoParams {
"brandId"?: number, brandId?: number
"districtId"?: number, districtId?: number
"modelId"?: number, modelId?: number
"pageNo": number, pageNo: number
"pageSize": number, pageSize: number
"partsId"?: number, partsId?: number
"productCategoryId"?: number, productCategoryId?: number
"qualityId"?: number qualityId?: number
} }
export interface Device { export interface Device {
id: number, id: number
wareNo: string, goodsName: string
wareTitle: string, images: string
wareTypeId: number, price: number | null
wareStatus: number,
minDeposit: number,
minRent: number,
totalStock: number,
totalSale: number,
propInfoId: null,
createTime: string,
wareImgs: [
{
id: number,
wareInfoId: number,
imgUrl: string,
imgType: number
}
],
tags: string[]
} }
export interface Advertisement { export interface Advertisement {
id:number, id: number
imageUrl:string imageUrl: string
} }
export interface ListPageDeviceInfoResp { export interface ListPageDeviceInfoResp {
"pageNo": 1, pageNo: 1
"pageSize": 10, pageSize: 10
"list": Array<Device>, list: Array<Device>
"totalCount": 0, totalCount: 0
"totalPage": 0 totalPage: 0
} }
export default { export default {
//web-设备租赁-分页 //web-设备租赁-分页
listPageDeviceInfo: (params: ListPageDeviceInfoParams,options = {}): Promise<Response<ListPageDeviceInfoResp>> => { listPageDeviceInfo: (
return request('/pms/webDevice/deviceList', 'post', params, options) params: ListPageDeviceInfoParams,
options = {}
): Promise<Response<ListPageDeviceInfoResp>> => {
return request('/pms/product/mall/deviceList', 'post', params, options)
}, },
//web-设备租赁-广告 //web-设备租赁-广告
listAdvertisementInfo: (): Promise<Response<Array<Advertisement>>> => { listAdvertisementInfo: (): Promise<Response<Array<Advertisement>>> => {
return request('/pms/webDevice/ad', 'get') return request('/pms/webDevice/ad', 'get')
} },
} }
\ No newline at end of file
import request, { Response } from '~/api/request'; import request, { Response } from '~/api/request'
export interface GetWebDeviceDetailParams { export interface GetWebDeviceDetailParams {
id:number goodsId: number
type: 1
}
export interface GetLeaseGoodsParams {
leaseTerm: number //租赁时限:(输入0:1-7天、输入1:8-15天、输入2:16-30天、输入3:30天以上)
productSpecId: number
} }
export interface WareImgsType { export interface WareImgsType {
id: number, id: number
wareInfoId: number | null, imgUrl: string
imgUrl: string,
imgType: number imgType: number
} }
export interface PriceType {
id: number
cooperationTag: number
price: number
productSpecId: number
leaseTerm: number
}
export interface GetLeaseGoodsResult {
productSpecId: number
type: number | null
leaseTerm: number
specPrice: PriceType[]
}
export interface GetWebDeviceDetailResult { export interface GetWebDeviceDetailResult {
id: number, id: number
wareNo: string, images: {
wareTitle: string, id: number
wareTypeId: number, imgUrl: string
wareStatus: number, imgType: number
payStatus: number, }[]
minDeposit: number, goodsVideo: string
maxDeposit: number, goodsVideoId: number
minRent: number, goodsName: string
maxRent: number, goodsDetail: {
totalStock: number, id: number
totalSale: number, goodsDesc: string
skuNum: number, content: string | null
tags: [ remark: string | null
string, }
string directoryId: number
], categoryByOne: number
wareImgs: Array<WareImgsType>, categoryByTwo: null
warePropDTO: number | null, tag: null
wareDetailContent: string | TrustedHTML shelfStatus: number
goodsSpec: {
productSpecList: GetWebDeviceWareSkuById[]
}[]
otherService?: {
id: number
saleServiceId: string
serviceName: string
}[]
price: number | null
goodsNo: string
}
export interface PriceList {
id: number
wareInfoId: number
skuInfoId: number
rentPrice: number
minDay: number
maxDay: number
createTime: null
}
export interface GetWebDeviceWareSkuById {
id: number
productSpec: number
productSkuId: number
specName: string
specImage: string
partNo: string
versionDesc: string
createTime: string | null
productSpecCPQVO: string | null
}
export interface WebDeviceUpdateParams {
id?: number
inventoryId?: number
inventoryUsage?: string
startDay?: string
endDay?: string
} }
export default { export default {
//web-设备租赁-详情 //web-设备租赁-详情
listDetailDeviceInfo: (params: GetWebDeviceDetailParams): Promise<Response<GetWebDeviceDetailResult>> => { listDetailDeviceInfo: (
return request('/pms/webDevice/detail', 'get', params) params: GetWebDeviceDetailParams
} ): Promise<Response<GetWebDeviceDetailResult>> => {
} return request('/pms/product/mall/getLeaseGoodsDetail', 'get', params)
\ No newline at end of file },
//web-设备租赁-立即租赁
listWareSkuUpdate: (
params: WebDeviceUpdateParams
): Promise<Response<number>> => {
return request('/pms/appDevice/update', 'post', params)
},
//web-设备租赁-详情-获取设备商品规格价格详情
GoodsPriceDetail: (
params: GetLeaseGoodsParams
): Promise<Response<GetLeaseGoodsResult>> => {
return request('/pms/product/mall/getLeaseGoodsPriceDetail', 'get', params)
},
}
import request, { Response } from '~/api/request'
export interface GetWebDeviceDetailParams {
actualPay: number
deposit: number
endDate: string
orderReceipt: {
detailAddress: string
receiptMethod: number
region: string
takeName: string
takePhone: number
}
rentPrice: number
returnDate: string
shouldPay: number
specsId: number
startDate: string
wareDescription: string
wareImg: string
wareInfoId: number
wareNo: string
wareNum: number
wareTitle: string
remark?: string
}
export interface WareImgsType {
id: number
imgUrl: string
imgType: number
}
export interface UserAddress {
id: number
takeName: string
takePhone: string
takeRegion: string
takeAddress: string
type: number
}
export interface GetOrderForGoods {
balance: number
nickName: string
orderNo: string
}
export default {
//web-地址管理-查询用户地址列表-条件查询
listUserAddress: (params: {}): Promise<Response<UserAddress[]>> => {
return request('/oms/user-address/selectList', 'POST', params)
},
//web-设备租赁-下单
FeignAddLease: (
params: GetWebDeviceDetailParams
): Promise<Response<GetOrderForGoods>> => {
return request('/oms/RentalOrders/feignAddLease', 'post', params)
},
//web-设备租赁-订单支付
OrderPayment: (params: {
orderNo: string
}): Promise<Response<GetOrderForGoods>> => {
return request(`/payment/repocash/orderPayment`, 'get', params)
},
}
import styled from 'styled-components'
export const OrderForGoodsBox = styled.div`
box-sizing: border-box;
width: 1000px;
.address {
.top {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #e6e6e6;
height: 30px;
line-height: 30px;
margin-top: 30px;
.left {
font-size: 14px;
font-family: MicrosoftYaHeiUI-Bold, MicrosoftYaHeiUI;
font-weight: bold;
color: #333333;
line-height: 18px;
}
.right {
.btn {
font-size: 14px;
font-family: MicrosoftYaHei;
color: #007aff;
line-height: 19px;
}
}
}
.bottom {
.item {
display: flex;
justify-content: space-between;
align-items: center;
width: 1000px;
height: 48px;
border: 1px solid transparent;
margin-top: 8px;
&.active {
background: #fff1e8;
border-radius: 6px;
border: 1px solid #ff552d;
}
.left {
display: flex;
align-items: center;
justify-content: space-around;
.active {
margin-right: 18px;
display: flex;
.icon {
width: 15px;
height: 22px;
background: #ff552d;
margin-left: 17px;
}
.label {
font-size: 14px;
font-family: MicrosoftYaHei;
color: #000000;
line-height: 19px;
margin-left: 18px;
}
}
}
.right {
margin-right: 22px;
}
}
}
}
.info {
margin-top: 30px;
.title {
font-size: 14px;
font-family: MicrosoftYaHeiUI-Bold, MicrosoftYaHeiUI;
font-weight: bold;
color: #333333;
line-height: 18px;
}
.table {
.table-title {
display: flex;
align-items: center;
width: 1000px;
border-bottom: 1px solid #e6e6e6;
padding: 10px 0;
margin-top: 20px;
.table-item {
text-align: center;
font-size: 14px;
font-family: MicrosoftYaHei;
color: #000000;
line-height: 19px;
}
}
.table-body {
display: flex;
align-items: center;
height: 100px;
margin-top: 10px;
.body-item {
text-align: center;
&.article {
display: flex;
justify-content: space-between;
.image {
margin-right: 10px;
.image-box {
width: 80px;
height: 80px;
}
}
.right {
.top {
width: 171px;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #141414;
line-height: 20px;
}
.bottom {
width: 171px;
font-size: 12px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #929295;
line-height: 17px;
}
}
}
&.lease-term {
display: flex;
align-items: center;
justify-content: center;
.num {
width: 62px;
height: 24px;
background: #ff552d;
border-radius: 2px;
position: relative;
margin: 0 15px;
line-height: 24px;
font-size: 14px;
font-family: MicrosoftYaHei;
color: #ffffff;
&::before {
content: '';
width: 10px;
height: 1px;
background-color: #ff552d;
position: absolute;
left: -10px;
top: 50%;
transform: translateY(-50%);
}
&::after {
content: '';
width: 10px;
height: 1px;
background-color: #ff552d;
position: absolute;
right: -10px;
top: 50%;
transform: translateY(-50%);
}
}
}
&.total-price {
font-size: 14px;
font-family: MicrosoftYaHeiUI-Bold, MicrosoftYaHeiUI;
font-weight: bold;
color: #ff3100;
line-height: 18px;
}
}
}
}
}
.notes {
display: flex;
align-items: center;
justify-content: space-between;
width: 1000px;
height: 110px;
background: #e1efff;
border: 1px solid #d0eaf5;
padding: 0 22px 0 16px;
.left {
display: flex;
align-items: top;
.label {
font-size: 14px;
font-family: MicrosoftYaHei;
color: #000000;
margin-top: 4px;
}
}
.right {
width: 430px;
.top {
display: flex;
align-items: center;
justify-content: space-between;
}
.font {
display: flex;
}
.bottom {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 18px;
}
.label {
font-size: 14px;
font-family: MicrosoftYaHei;
color: #000000;
line-height: 19px;
margin-right: 12px;
}
.value {
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #2b2b2b;
line-height: 20px;
}
.price {
font-size: 14px;
font-family: MicrosoftYaHeiUI-Bold, MicrosoftYaHeiUI;
font-weight: bold;
color: #ff3100;
line-height: 18px;
}
}
}
.detail-box {
display: flex;
justify-content: flex-end;
margin-top: 26px;
.right-box {
.detail {
width: 477px;
height: 110px;
border: 1px solid #ff5001;
padding: 16px 19px 19px 19px;
.top {
display: flex;
justify-content: flex-end;
align-items: center;
.label {
font-size: 14px;
font-family: MicrosoftYaHei;
color: #474747;
line-height: 19px;
margin-right: 10px;
}
.price {
font-size: 26px;
font-family: MicrosoftYaHeiUI-Bold, MicrosoftYaHeiUI;
font-weight: bold;
color: #ff552d;
line-height: 33px;
}
}
.bottom {
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 15px;
.value {
font-size: 12px;
font-family: MicrosoftYaHeiUI-Bold, MicrosoftYaHeiUI;
font-weight: bold;
color: #000000;
line-height: 15px;
margin-right: 10px;
}
.value-content {
font-size: 12px;
font-family: MicrosoftYaHei;
color: #333333;
line-height: 16px;
}
}
}
.detail-sumbit {
display: flex;
justify-content: flex-end;
.btn {
width: 182px;
height: 39px;
background: #ff552d;
border: 1px solid #ff5001;
border-radius: 0;
color: #ffffff;
}
}
}
}
.Payment {
.title {
text-align: center;
font-size: 26px;
font-family: MicrosoftYaHeiUI-Bold, MicrosoftYaHeiUI;
font-weight: bold;
color: #ff552d;
}
}
.addAddress {
.title {
text-align: center;
height: 25px;
font-size: 20px;
font-family: MicrosoftYaHeiUI-Bold, MicrosoftYaHeiUI;
font-weight: bold;
color: #000000;
line-height: 25px;
}
.image {
display: flex;
justify-content: center;
align-items: center;
padding: 48px 0 32px;
.addressImg {
width: 150px;
height: 150px;
}
}
.content {
text-align: center;
width: 311px;
height: 38px;
font-size: 14px;
font-family: MicrosoftYaHei;
color: #3e454d;
line-height: 19px;
}
}
`
import React , {useState,useRef} from 'react' import React, { useState, useRef } from 'react'
import {Box} from './styled'; import { Box } from './styled'
import { LeftOutlined , RightOutlined } from '@ant-design/icons'; import { LeftOutlined, RightOutlined } from '@ant-design/icons'
import {WareImgsType} from '../../api'; import { WareImgsType } from '../../api'
interface ImagesType{ interface ImagesType {
imgList: Array<WareImgsType> imgList: Array<WareImgsType>
} }
export default function PicturePreview(props:ImagesType) { export default function PicturePreview(props: ImagesType) {
const {imgList} = props const { imgList } = props
console.log(imgList);
const mask =useRef<HTMLDivElement>(null!)
const moveBox =useRef<HTMLDivElement>(null!)
const big =useRef<HTMLImageElement>(null!)
const [moveLeft,setMoveLeft] = useState(0) // 根据这个值设置图片列表向左偏移
// const imgList = [
// 'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
// 'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
// 'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg',
// 'https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg',
// 'https://fuss10.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg',
// 'https://fuss10.elemecdn.com/3/28/bbf893f792f03a54408b3b7a7ebf0jpeg.jpeg',
// 'https://fuss10.elemecdn.com/2/11/6535bcfb26e4c79b48ddde44f4b6fjpeg.jpeg'
// ]
const [activeImgIndex,setActiveImgIndex] = useState(0) const mask = useRef<HTMLDivElement>(null!)
const moveBox = useRef<HTMLDivElement>(null!)
const big = useRef<HTMLImageElement>(null!)
const [moveLeft, setMoveLeft] = useState(0) // 根据这个值设置图片列表向左偏移
// const imgList = [
// 'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
// 'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
// 'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg',
// 'https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg',
// 'https://fuss10.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg',
// 'https://fuss10.elemecdn.com/3/28/bbf893f792f03a54408b3b7a7ebf0jpeg.jpeg',
// 'https://fuss10.elemecdn.com/2/11/6535bcfb26e4c79b48ddde44f4b6fjpeg.jpeg'
// ]
// 改变预览图 const [activeImgIndex, setActiveImgIndex] = useState(0)
const handleChangeImg = (index:number) => {
if (index <= moveLeft + 3) setActiveImgIndex(index) // 改变预览图
} const handleChangeImg = (index: number) => {
// 移动缩略图 if (index <= moveLeft + 3) setActiveImgIndex(index)
const handleSlide = (direction:string) => { }
//左侧按钮 // 移动缩略图
if (direction == 'left') { const handleSlide = (direction: string) => {
moveLeft == 0 ? setMoveLeft(0) : setMoveLeft((props)=>props - 1) //左侧按钮
} else { // 右侧按钮 if (direction == 'left') {
if (imgList.length > 4) { moveLeft == 0 ? setMoveLeft(0) : setMoveLeft((props) => props - 1)
moveLeft >= imgList.length - 4 ? setMoveLeft(imgList.length - 4) : setMoveLeft((props)=>props + 1) } else {
} // 右侧按钮
} if (imgList.length > 4) {
} moveLeft >= imgList.length - 4
// 图片放大镜 ? setMoveLeft(imgList.length - 4)
const handleMouseMove = (event:React.MouseEvent<HTMLDivElement, MouseEvent>) => { : setMoveLeft((props) => props + 1)
let left = event.nativeEvent.offsetX - mask.current.offsetWidth / 2;
let top = event.nativeEvent.offsetY - mask.current.offsetHeight / 2;
// 最右侧和最下侧的临界值
const maxLeft = moveBox.current.offsetWidth - mask.current.offsetWidth
const maxTop = moveBox.current.offsetHeight - mask.current.offsetHeight
//约束范围
if (left <= 0) left = 0;
if (left >= maxLeft) left = maxLeft
if (top <= 0) top = 0;
if (top >= maxTop) top = maxTop
// 设置放大范围遮罩层位置
mask.current.style.left = left + "px";
mask.current.style.top = top + "px";
// 设置大图图片位置,可以用background代替这个方案,有兴趣可以尝试
big.current.style.left = -3 * left + "px"; // 3这个值是 大图除以小图算出来的比例 这里大图是900px 小图是300px
big.current.style.top = -3 * top + "px";
} }
}
}
// 图片放大镜
const handleMouseMove = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>
) => {
let left = event.nativeEvent.offsetX - mask.current.offsetWidth / 2
let top = event.nativeEvent.offsetY - mask.current.offsetHeight / 2
// 最右侧和最下侧的临界值
const maxLeft = moveBox.current.offsetWidth - mask.current.offsetWidth
const maxTop = moveBox.current.offsetHeight - mask.current.offsetHeight
//约束范围
if (left <= 0) left = 0
if (left >= maxLeft) left = maxLeft
if (top <= 0) top = 0
if (top >= maxTop) top = maxTop
// 设置放大范围遮罩层位置
mask.current.style.left = left + 'px'
mask.current.style.top = top + 'px'
// 设置大图图片位置,可以用background代替这个方案,有兴趣可以尝试
big.current.style.left = -3 * left + 'px' // 3这个值是 大图除以小图算出来的比例 这里大图是900px 小图是300px
big.current.style.top = -3 * top + 'px'
}
return ( return (
<Box> <Box>
<div className="img_wrapper"> <div className="img_wrapper">
<div className="img_content"> <div className="img_content">
{/* <!-- 蒙层,绑定鼠标事件 --> */} {/* <!-- 蒙层,绑定鼠标事件 --> */}
<div className="movebox" <div
onMouseMove={(e)=>handleMouseMove(e)} className="movebox"
ref={moveBox}> onMouseMove={(e) => handleMouseMove(e)}
</div> ref={moveBox}
{/* <!-- 主图 --> */} ></div>
<img src={imgList && imgList[activeImgIndex].imgUrl} {/* <!-- 主图 --> */}
<img
src={imgList && imgList[activeImgIndex].imgUrl}
className="img_small" className="img_small"
alt=""/> alt=""
{/* <!-- 放大区域 --> */} />
<div className="mask" {/* <!-- 放大区域 --> */}
ref={mask}></div> <div className="mask" ref={mask}></div>
{/* <!-- 大图预览图 --> */} {/* <!-- 大图预览图 --> */}
<div className="img_big"> <div className="img_big">
<img src={imgList && imgList[activeImgIndex].imgUrl} <img
ref={big} src={imgList && imgList[activeImgIndex].imgUrl}
alt=""/> ref={big}
</div> alt=""
/>
</div>
</div> </div>
{/* <!-- 缩略图列表 --> */} {/* <!-- 缩略图列表 --> */}
<div className="img_list_wrapper"> <div className="img_list_wrapper">
{imgList?.length>4 && <LeftOutlined className="el-icon-arrow-left" onClick={()=>handleSlide('left')}/>} {imgList?.length > 4 && (
<div className="img_list_content"> <LeftOutlined
<div className="img_list" className="el-icon-arrow-left"
style={{marginLeft: - moveLeft * 25 + '%'}}> onClick={() => handleSlide('left')}
{ />
imgList?.map((item,index)=>( )}
<img <div className="img_list_content">
onMouseOver={()=>handleChangeImg(index)} <div
key={index} className="img_list"
className={`${activeImgIndex === index ? 'activeImg' : ''}`} style={{ marginLeft: -moveLeft * 25 + '%' }}
src={item.imgUrl} >
alt="" /> {imgList?.map((item, index) => (
)) <img
} onMouseOver={() => handleChangeImg(index)}
</div> key={index}
</div> className={`${activeImgIndex === index ? 'activeImg' : ''}`}
{ imgList?.length>4 && <RightOutlined className="el-icon-arrow-right" onClick={()=>handleSlide('right')}/>} src={item.imgUrl}
</div> alt=""
</div> />
))}
</div>
</div>
{imgList?.length > 4 && (
<RightOutlined
className="el-icon-arrow-right"
onClick={() => handleSlide('right')}
/>
)}
</div>
</div>
</Box> </Box>
) )
} }
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论