提交 bc8b8fa0 作者: ZhangLingKun

测试:代码清理

上级 ab6b3300
.DS_Store
.next
node_modules
Dockerfile
.dist
\ No newline at end of file
node_modules
\ No newline at end of file
{
"env": {
"es6": true,
"node": true
},
"plugins": ["prettier"],
"extends": [
"airbnb-base",
"next/core-web-vitals",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:jsx-a11y/recommended",
"plugin:prettier/recommended"
],
"rules": {
"prettier/prettier": [
"error",
{
"singleQuote": true
}
]
},
"overrides": [
// Configuration for TypeScript files
{
"files": ["**/*.ts", "**/*.tsx"],
"plugins": ["@typescript-eslint", "unused-imports"],
"extends": [
"airbnb-typescript",
"next/core-web-vitals",
"plugin:prettier/recommended"
],
"parserOptions": {
"project": "./tsconfig.json"
},
"rules": {
"prettier/prettier": [
"error",
{
"singleQuote": true
}
],
"react/destructuring-assignment": "off", // Vscode doesn't support automatically destructuring, it's a pain to add a new variable
"jsx-a11y/anchor-is-valid": "off", // Next.js use his own internal link system
"react/require-default-props": "off", // Allow non-defined react props as undefined
"react/jsx-props-no-spreading": "off", // _app.tsx uses spread operator and also, react-hook-form
"@next/next/no-img-element": "off", // We currently not using next/image because it isn't supported with SSG mode
"import/order": [
"error",
{
"groups": ["builtin", "external", "internal"],
"pathGroups": [
{
"pattern": "react",
"group": "external",
"position": "before"
}
],
"pathGroupsExcludedImportTypes": ["react"],
"newlines-between": "always",
"alphabetize": {
"order": "asc",
"caseInsensitive": true
}
}
],
"@typescript-eslint/no-use-before-define": "warn",
"@typescript-eslint/comma-dangle": "off", // Avoid conflict rule between Eslint and Prettier
"import/prefer-default-export": "off", // Named export is easier to refactor automatically
"class-methods-use-this": "off", // _document.tsx use render method without `this` keyword
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports": "warn",
"unused-imports/no-unused-vars": [
"warn",
{ "argsIgnorePattern": "^_" }
],
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/no-static-element-interactions": "off",
"eqeqeq": "warn",
"no-unsafe-optional-chaining": "warn",
"no-param-reassign": "warn"
}
}
]
}
name: Build and Push to ACR
on:
push:
branches: ["develop"]
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: WeChat Work notification by markdown
uses: chf007/action-wechat-work@master
env:
WECHAT_WORK_BOT_WEBHOOK: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=9be1b073-1760-442d-8e3d-faa0fd32ea16
with:
msgtype: markdown
content: "
### GitHub提交信息 \n
> - 提交人: ${{github.actor}} \n
> - 提交信息: ${{ github.event.head_commit.message }} \n
> - 提交到仓库: ${{github.repository}} \n
> - 提交到分支: ${{github.ref}} \n
即将开始更新,请关注Argocd同步状态...
"
- 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 "Update Image Tag"
git tag -a $TAG -m "日常迭代"
git push origin "develop" --tags
- name: Send Error Notification by WeChat
if: ${{ failure() }}
run: |
curl 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=9be1b073-1760-442d-8e3d-faa0fd32ea16' -H 'Content-Type: application/json' -d '
{
"msgtype": "markdown",
"markdown": {
"content": "### `GitHub构建并推送镜像失败` \n
> - 提交人: ${{github.actor}} \n
> - 提交信息: ${{github.event.head_commit.message}} \n
> - 提交到仓库: ${{github.repository}} \n
> - 提交到分支: ${{github.ref}} \n 请修复错误后重新提交..."
}'
\ No newline at end of file
name: Build and Push to ACR
on:
push:
### Production
branches: ["master"]
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: WeChat Work notification by markdown
uses: chf007/action-wechat-work@master
env:
WECHAT_WORK_BOT_WEBHOOK: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=9be1b073-1760-442d-8e3d-faa0fd32ea16
with:
msgtype: markdown
content: "
### GitHub提交信息 \n
> - 提交人: ${{github.actor}} \n
> - 提交信息: ${{ github.event.head_commit.message }} \n
> - 提交到仓库: ${{github.repository}} \n
> - 提交到分支: ${{github.ref}} \n
即将开始更新,请关注Argocd同步状态...
"
- 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 "Update Image Tag"
git tag -a $TAG -m "日常迭代"
git push origin "master" --tags
- name: Send Error Notification by WeChat
if: ${{ failure() }}
run: |
curl 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=9be1b073-1760-442d-8e3d-faa0fd32ea16' -H 'Content-Type: application/json' -d '
{
"msgtype": "markdown",
"markdown": {
"content": "### `GitHub构建并推送镜像失败` \n
> - 提交人: ${{github.actor}} \n
> - 提交信息: ${{github.event.head_commit.message}} \n
> - 提交到仓库: ${{github.repository}} \n
> - 提交到分支: ${{github.ref}} \n 请修复错误后重新提交..."
}'
\ No newline at end of file
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
/.dev/
/.dist/
# misc
.DS_Store
*.pem
.idea/
.vscode/
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# lock files
package-lock.json
yarn.lock
#pnpm-lock.yaml
public/antd.min.css
stages:
- dockerbuild
- kustomize_tag_push
variables:
REGION_id: cn-shenzhen
REGISTRY: mmc-registry.cn-shenzhen.cr.aliyuncs.com
IMAGE: web
TAG: $CI_COMMIT_SHA
ACR_EE_USERNAME: QD--KeBiTeHangKong@1354706964800968
ACR_EE_PASSWORD: MMC@2023&ACR
ACR_EE_REGISTRY: mmc-registry.cn-shenzhen.cr.aliyuncs.com
ACR_EE_INSTANCE_ID: cri-yhk5zgfc2v1sia6l
ACR_EE_IMAGE: web
ACR_EE_TAG: $CI_COMMIT_SHA
JAVA_VERSION: '8'
GITLAB_URL: https://oauth2:MjVJKxB7m4tCy7symBzn@git.mmcuav.cn/iuav/csf-web.git
docker_build_dev:
stage: dockerbuild
variables:
NAMESPACE: sharefly-dev
ACR_EE_NAMESPACE: sharefly-dev
only:
- develop
script:
- echo "dev docker build"
- echo $ACR_EE_USERNAME
- echo $ACR_EE_REGISTRY
- docker login -u $ACR_EE_USERNAME -p 'MMC@2023&ACR' $ACR_EE_REGISTRY
- 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}"
- docker logout
docker_build_prod:
stage: dockerbuild
variables:
NAMESPACE: sharefly
ACR_EE_NAMESPACE: sharefly
only:
- master
script:
- echo "prod docker build"
- echo $ACR_EE_USERNAME
- echo $ACR_EE_REGISTRY
- docker login -u $ACR_EE_USERNAME -p 'MMC@2023&ACR' $ACR_EE_REGISTRY
- 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}"
- docker logout
kustomize_set_image_dev:
stage: kustomize_tag_push
variables:
NAMESPACE: sharefly-dev
ACR_EE_NAMESPACE: sharefly-dev
only:
- develop
before_script:
- echo "dev set image"
- git config --global user.name "bax" #配置本地仓库用户名信息
- git config --global user.email "baoaxin1999@163.com" #配置本地仓库邮箱信息
script:
- git remote -v
- git checkout -B develop
- cd kustomization/overlays/dev
- kustomize edit set image REGISTRY/NAMESPACE/IMAGE:TAG=$REGISTRY/$NAMESPACE/$IMAGE:$TAG
- cat kustomization.yaml
- git remote set-url origin "$GITLAB_URL"
- git commit -am '[skip ci] DEV image update' #git 本地提交,注意“skip ci”为gitlab流水线文件内置关键字,作用为跳过ci流水线操作,未设置可能导致流水线进入死循环
- git push --set-upstream origin develop #重新提交修改镜像版本后的代码
kustomize_set_image_prod:
stage: kustomize_tag_push
variables:
NAMESPACE: sharefly
ACR_EE_NAMESPACE: sharefly
only:
- master
before_script:
- echo "prod set image"
- echo "master"
- git config --global user.name "bax" #配置本地仓库用户名信息
- git config --global user.email "baoaxin1999@163.com" #配置本地仓库邮箱信息
script:
- git remote -v
- git checkout -B master
- cd kustomization/overlays/prod
- kustomize edit set image REGISTRY/NAMESPACE/IMAGE:TAG=$REGISTRY/$NAMESPACE/$IMAGE:$TAG
- cat kustomization.yaml
- git remote set-url origin "$GITLAB_URL"
- git commit -am '[skip ci] DEV image update' #git 本地提交,注意“skip ci”为gitlab流水线文件内置关键字,作用为跳过ci流水线操作,未设置可能导致流水线进入死循环
- git push --set-upstream origin master #重新提交修改镜像版本后的代码
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
gitlab: http://git.mmcuav.cn/iuav/csf-web
github: https://github.com/sharefly-iuav/web
# 发布流程
1. 先本地npm run build, 确认构建前ts检查无错误, 可构建成功.
## 构建测试服
1. 切换到develop分支下, 先提交gitlab, 再提交github, 自动触发构建测试服
## 构建正式服
1. ssh连接到远程服务器登录(ip:120.77.247.178 账号: root 密码: YXF&mmc@m2023)
2. 顺序执行以下命令
```bash
cd /var/www/html/sharefly-web-nextjs
# master分支下
git pull
npm run build
pm2 reload all
# 若pm2未启动任务,则手动启动
pm2 start npm -- run start -- -p 5001
```
注意git pull可能提示账号登录, 使用自已账号即可
\ No newline at end of file
const dev = {
baseUrl: '/local',
};
const prod = {
baseUrl: '',
};
export default process.env.NODE_ENV === 'development' ? dev : prod;
import request, { Response } from '~/api/request';
import config from './config';
export interface RegionResp {
childInfo?: RegionResp[] | null;
id: number;
level: number;
name: string;
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;
cooperationTagVOS: {
createTime: string;
id: number;
tagDescription: string;
tagImg: string;
tagName: string;
tagRequire: string;
}[];
}
export interface TestAppletLoginResp {
userAccountId: number;
token: string;
uid: string;
phoneNum?: string;
nickName: string;
sessionKey?: any;
}
export default {
// 获取区域数据
region: (): Promise<Response<Array<RegionResp>>> => {
return request('/pms/webDevice/getSecondDistrictInfo');
},
// 测试-小程序unionId登录-注册
testAppletLogin: (): Promise<Response<TestAppletLoginResp>> => {
const 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`;
},
// 文件上传地址
fileUpload: () => {
return `${config.baseUrl}/pms/upload/breakpoint`;
},
// 宣传中心
listBannerImg: (
moduleCode: string,
): Promise<
Response<
{
id: number;
bannerImg: string;
}[]
>
> => {
return request('/release/module/listBannerImg', 'get', {
moduleCode,
});
},
};
import config from './config';
let loginTimeout: NodeJS.Timeout | undefined;
/**
* 请求封装
* @param url 请求url
* @param method 请求方法类型
* @param data 请求的参数
* @param options 额外参数
* @returns Promise<Response>
*/
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()) {
case 'get':
let params = new URLSearchParams();
if (data) {
Object.keys(data).forEach((key) => {
params.append(key, data[key]);
});
url += '?' + params;
}
break;
case 'post':
options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
...options,
};
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)
.then((r) => {
try {
return r.json();
} catch (e) {
console.error(e);
}
return {
code: '-1',
message: '请求失败',
result: null,
};
})
.then((data) => {
if (data.errors) {
//全局消息提示
errMsg('请求出错');
if (Array.isArray(data.errors)) {
data.errors.forEach((item: any) => {
if (item.defaultMessage) {
errMsg(item.defaultMessage);
}
});
}
return {
code: '-1',
message: '请求失败',
result: null,
};
}
if (data.code !== '200') {
//未登录判断
if (data.code === '5008' || data.code === '2014') {
loginErrorMsg();
window.logout();
} else {
errMsg(data.message || '请求出错');
}
}
return data;
})
.catch((error) => {
if (error.name === 'AbortError') {
console.log('请求已中断');
console.log(error);
} else {
console.error('请求出错', error);
}
return {
code: '-1',
message: '请求失败',
result: null,
};
});
}
//准备响应结构
export interface Response<T> {
code: string;
message: string;
result?: T | null;
}
import useSWR, { SWRResponse } from "swr";
import config from './config';
/**
* 请求封装
* @param url 请求url
* @param method 请求方法类型
* @param data 请求的参数
* @returns Promise<Response>
*/
export default function request(url: string, method: String = 'get', data?: any): SWRResponse {
let options = {};
switch (method.toLowerCase()) {
case 'get':
let params = new URLSearchParams();
if (data) {
Object.keys(data).forEach((key) => {
params.append(key, data[key]);
})
url += params;
}
break;
case 'post':
options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
}
break;
}
const fetcher = (url:string) => fetch(config.baseUrl + url, options)
.then((r) => r.json())
.then((data) => {
return data;
});
return useSWR(url, fetcher)
}
\ No newline at end of file
export default "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg==";
{
"presets": ["next/babel"],
"plugins": [
[
"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
.navHeader {
padding-top: 48px;
}
.navHeaderContent{
width: 100%;
height: 48px;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
position: fixed;
top: 0;
left: 0;
z-index: 999;
}
.nav {
width: 1200px;
display: flex;
align-items: center;
}
.logo {
width: 90px;
height: 48px;
background-image: url(./assets/logo.png);
background-size: 100% 100%;
}
.tabs {
height: 100%;
margin-right: 58px;
:global .ant-tabs-nav {
height: 100%;
margin-bottom: 0;
&::before {
border: 0;
}
}
:global .ant-tabs-tab {
& + .ant-tabs-tab {
margin-left: 0px;
}
.ant-tabs-tab-btn {
font-size: 14px;
color: #424242;
width: 100px;
text-align: center;
font-family: MicrosoftYaHei;
font-weight: bold;
}
}
:global .ant-tabs-ink-bar {
display: none;
}
}
.btns {
margin-right: 25px;
}
.btn1 {
width: 120px;
height: 35px;
border-radius: 0px;
font-size: 16px;
font-family: MicrosoftYaHeiUI-Bold, MicrosoftYaHeiUI;
font-weight: bold;
color: #ffffff;
}
.btn2 {
box-sizing: border-box;
width: 120px;
height: 35px;
border-radius: 0px;
border: 1px solid rgba(255, 85, 45, 1);
font-size: 16px;
font-family: MicrosoftYaHeiUI-Bold, MicrosoftYaHeiUI;
font-weight: bold;
color: rgba(255, 85, 45, 1);
background: none;
}
import React, { useContext, useEffect, useState } from 'react';
import { Avatar, Button, Dropdown, Space, Tabs } from 'antd';
import type { TabsProps } from 'antd';
import { useRouter } from 'next/router';
import LoginModal from '~/components/loginModal';
import { UserContext } from '~/lib/userProvider';
import styles from './index.module.scss';
import JoinModal from './joinModal';
import PublishModal from './publishModal';
const items: TabsProps['items'] = [
{
key: '/home',
label: ` 首页 `,
},
{
key: '/mall',
label: `产品商城`,
},
{
key: '/equipmentLeasing',
label: `设备租赁`,
},
{
key: '/flyingHandService',
label: `飞手培训`,
},
{
key: '/jobServices',
label: `作业服务`,
},
{
key: '/projectInfo',
label: `项目资讯`,
},
{
key: '/forum',
label: `社区论坛`,
},
];
type Props = {
style?: React.CSSProperties;
};
export default function NavHeader(props: Props) {
const router = useRouter();
const [currentPath, setCurrentPath] = useState('');
const { userInfo, 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);
}
}, [router.route]);
// 导航更改
const onChange = (key: string) => {
router.push(key);
};
// 退出登录
const onLogout = () => {
logout();
};
const [openLoginModal, setOpenLoginModal] = useState(false); // 登录modal
const [openPublishModal, setOpenPublishModal] = useState(false); // 发布modal
const [openJoinModal, setOpenJoinModal] = useState(false); // 加盟modal
// 发布按钮事件
function onPublish() {
// 登录判断
if (!userInfo) {
setOpenLoginModal(true);
} else {
setOpenPublishModal(true);
}
}
// 加盟按钮事件
function onJoin() {
// 登录判断
if (!userInfo) {
setOpenLoginModal(true);
} else {
setOpenJoinModal(true);
}
}
// 从其它组件通知需要登录
useEffect(() => {
if (needLogin) {
setOpenLoginModal(true);
}
}, [needLogin]);
return (
<div className={styles.navHeader} style={props.style}>
<div className={styles.navHeaderContent}>
<div className={styles.nav}>
<div className={styles.logo}></div>
<Tabs
className={styles.tabs}
activeKey={currentPath}
items={items}
onChange={onChange}
onTabClick={onChange}
/>
<Space size={16} className={styles.btns}>
<Button type='primary' className={styles.btn1} onClick={onPublish}>
+ 发布需求
</Button>
<Button className={styles.btn2} onClick={onJoin}>
加盟入驻
</Button>
</Space>
{userInfo ? (
<div className={styles.haedImg}>
<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>
) : (
<Button
type='text'
onClick={() => setOpenLoginModal(true)}
style={{ fontWeight: 'bold', fontSize: 16 }}
>
登录
</Button>
)}
</div>
</div>
<LoginModal
open={openLoginModal}
onCancel={() => {
setOpenLoginModal(false);
setNeedLogin(false);
}}
></LoginModal>
<PublishModal
open={openPublishModal}
onCancel={() => {
setOpenPublishModal(false);
}}
></PublishModal>
<JoinModal
open={openJoinModal}
onCancel={() => {
setOpenJoinModal(false);
}}
></JoinModal>
</div>
);
}
import request, { Response } from '~/api/request';
export interface ListTagResp {
id: number;
tagName: string;
tagImg?: string;
tagDescription: string;
createTime: string;
}
// eslint-disable-next-line import/no-anonymous-default-export
export default {
// 加盟标签列表
listTag: (): Promise<Response<Array<ListTagResp>>> => {
return request('/userapp/cooperation/listTag');
},
};
.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;
}
}
}
/* eslint-disable */
import { useContext, useEffect, useState } from 'react';
import { Col, Modal, Row } from 'antd';
import Image from 'next/image';
import { useRouter } from 'next/router';
import commonApi from '~/api';
import { UserContext } from '~/lib/userProvider';
import api, { ListTagResp } from './api';
import styles from './index.module.scss';
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, setUserInfo } = useContext(UserContext);
useEffect(() => {
api.listTag().then((res) => {
setTagList(res.result || []);
});
}, []);
const onClickTag = (item: ListTagResp) => {
commonApi.userInfo().then((res) => {
setUserInfo(res.result);
if (res.result!.companyAuthStatus) {
router.replace({ pathname: '/JoinPolicy', query: { 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>
);
}
import request, { Response } from '~/api/request';
export interface TypeResp {
id: number;
typeName: string;
}
export interface PublishParams {
publishPhone: number; // 手机号
publishName: string; // 发布名称
requirementTypeId: number; // 需求类型
requireDescription: string; // 需求描述
provinceCode?: string; // 省编码
}
export default {
/**
* 需求类型
* @returns
*/
listType(): Promise<Response<Array<TypeResp>>> {
return request('/release/requirements/listType');
},
/**
* 需求发布
* @param params
* @returns
*/
publish(params: PublishParams): Promise<Response<any>> {
return request('/release/requirements/publish', 'post', params);
},
};
.modal {
:global .ant-modal-content {
border-radius: 0;
.ant-modal-title{
text-align: center;
}
}
}
import { useContext, useEffect, useState } from 'react';
import { Button, Form, Input, Modal, Select } from 'antd';
import { CommonContext } from '~/lib/commonProvider';
import { useGeolocation } from '~/lib/hooks';
import { phoneNumber } from '~/lib/validateUtils';
import api, { PublishParams, TypeResp } from './api';
import styles from './index.module.scss';
type Props = {
open?: boolean;
onOk?: () => void;
onCancel?: () => void;
};
export default function PublishModal(props: Props) {
const [types, setTypes] = useState<Array<TypeResp>>([]); // 需求类型
const [params, setParams] = useState<PublishParams>({
publishName: '',
publishPhone: -1,
requireDescription: '',
requirementTypeId: -1,
});
const [form] = Form.useForm();
const position = useGeolocation();
const { reloadRequirements, setReloadRequirements } = useContext(CommonContext);
useEffect(() => {
api.listType().then((res) => {
setTypes(res.result || []);
});
}, []);
const onFinish = (values: any) => {
console.log('Success:', values);
// eslint-disable-next-line no-param-reassign
values.publishPhone = Number(values.publishPhone);
api
.publish({
...params,
...values,
provinceCode: position?.address?.addressComponent?.adcode,
})
.then((res) => {
if (res.code === '200') {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
props.onCancel && props.onCancel();
window.messageApi.success('发布成功');
setReloadRequirements(!reloadRequirements);
setTimeout(() => {
form.resetFields();
}, 500);
}
});
};
const onFinishFailed = (errorInfo: any) => {
console.log('Failed:', errorInfo);
};
return (
<Modal
open={props.open}
onOk={props.onOk}
onCancel={props.onCancel}
title='需求发布'
className={styles.modal}
width={460}
footer={null}
>
<Form
labelCol={{ span: 5 }}
labelAlign='left'
onFinish={onFinish}
onFinishFailed={onFinishFailed}
form={form}
>
<Form.Item
label='姓名'
name='publishName'
rules={[{ required: true, message: '请输入姓名!' }]}
>
<Input placeholder='输入姓名'></Input>
</Form.Item>
<Form.Item
label='手机号'
name='publishPhone'
rules={[
{ required: true, message: '请输入手机号!' },
{
pattern: /^1\d{10}$/,
message: '很输入11位手机号',
},
]}
>
<Input onInput={phoneNumber} maxLength={11} allowClear placeholder='输入手机号'></Input>
</Form.Item>
<Form.Item
label='需求类型'
name='requirementTypeId'
rules={[{ required: true, message: '请选择需求类型!' }]}
>
<Select
placeholder='选择需求类型'
options={types}
fieldNames={{ label: 'typeName', value: 'id' }}
></Select>
</Form.Item>
<Form.Item
name='requireDescription'
rules={[{ required: true, message: '请输入需求描述!' }]}
>
<Input.TextArea
placeholder='项目需求描述'
style={{ height: 162 }}
maxLength={256}
showCount
></Input.TextArea>
</Form.Item>
<Form.Item>
<Button
type='primary'
htmlType='submit'
style={{ width: '100%', height: 40, borderRadius: 0 }}
>
立即发布
</Button>
</Form.Item>
</Form>
</Modal>
);
}
// 颜色变量
$color-primary: #f4b700;
$color-primary-hover: #f7c800;
$color-primary-active: #ffcc32;
$color-primary-border: #f4b700;
.brand-select-search-view {
position: relative;
width: 100%;
box-sizing: border-box;
.brand-select-item {
min-height: 50px;
background: #fff;
display: flex;
align-items: center;
justify-content: flex-start;
box-sizing: border-box;
position: relative;
padding: 12px 20px 12px 20px;
border-bottom: 1px solid #f2f4fa;
.item-label {
font-size: 14px;
color: #979aa8;
margin-right: 16px;
min-width: 32px;
}
.item-content {
display: flex;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
width: 100%;
.alphabet-list, .select-list {
width: 100%;
display: flex;
align-items: center;
justify-content: flex-start;
margin-bottom: 10px;
flex-wrap: wrap;
.list-item {
position: relative;
height: 24px;
width: 20px;
font-size: 14px;
line-height: 20px;
margin: 0 16px 0 0;
border-radius: 2px;
text-align: center;
border: 1px solid transparent;
cursor: pointer;
user-select: none;
}
.list-item:hover {
color: $color-primary;
}
.list-item:hover:after {
position: absolute;
content: '';
width: 35%;
height: 1px;
background: $color-primary;
left: calc((100% - 35%) / 2);
bottom: 0;
}
.item-active {
background: $color-primary-active;
border: 1px solid $color-primary;
}
.item-active:hover {
color: #000000;
}
.item-active:hover:after {
display: none;
}
}
.select-list {
margin-bottom: 0;
.list-item {
width: 80px;
margin: 0 10px 0 0;
}
}
.selected {
margin-bottom: -10px;
.list-item {
border: 1px solid $color-primary;
color: $color-primary;
margin-bottom: 10px;
}
.list-item:hover {
background: #f7f8fc;
}
.list-item:hover:after {
display: none;
}
}
.alphabet-content {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
padding: 2px 12px 12px;
background-color: #f7f8fc;
width: 100%;
min-height: 50px;
box-sizing: border-box;
.content-item {
position: relative;
width: 80px;
margin: 10px 8px 0 0;
font-size: 14px;
overflow: hidden;
background: #ffffff;
text-align: center;
height: 28px;
line-height: 26px;
border: 1px solid transparent;
user-select: none;
}
.content-item:hover {
color: #f4b700;
}
.content-item:hover:after {
position: absolute;
content: '';
width: 35%;
height: 1px;
background: #f4b700;
left: calc((100% - 35%) / 2);
bottom: 0;
}
.item-active {
background: $color-primary-active;
border: 1px solid $color-primary;
}
.item-active:hover {
color: #000000;
}
.item-active:hover:after {
display: none;
}
}
.select-region {
.region-label {
margin: 0 12px;
}
.region-label:first-child{
margin-left: 0;
}
}
}
}
.brand-select-item:last-child {
border-bottom: none;
}
}
/* eslint-disable */
import React, { useEffect, useState } from 'react';
import './index.scss';
import { Select } from 'antd';
import { pinyin } from 'pinyin-pro';
// 搜索列表的类型
interface searchColumns {
type: 'Alphabet' | 'Brand' | 'Price' | 'Industry' | 'Product';
options: { label: string; value: any; children?: { label: string; value: any }[] }[];
name: string;
label: string;
subLabel?: string;
}
// 传参类型
interface propType {
columns: searchColumns[];
selected: boolean;
region: boolean;
}
const BrandSelectItem: React.FC<{ label: string; children: any }> = (props) => {
const { label, children } = props;
return (
<div className={'brand-select-item'}>
<div className={'item-label'}>{label}</div>
<div className={'item-content'}>{children}</div>
</div>
);
};
const BrandSelectSearch: React.FC<propType> = (props) => {
const { columns, selected, region } = props;
// 字母列表转换
const [alphabetList, setAlphabetList] = useState<{ label?: string; children: any }[]>([]);
// 字母列表索引
const [alphabetIndex, setAlphabetIndex] = useState<number>(0);
// 字母列表详情索引
const [alphabetContentIndex, setAlphabetContentIndex] = useState<number>(0);
// 普通筛选索引
const [optionsIndex, setOptionsIndex] = useState<{ index: number; subIndex?: number }[]>([]);
// 转换过后的普通筛选列表
const [columnsList, setColumnsList] = useState<searchColumns[]>([]);
// 获取字母列表 (将传入的列表转成以拼音开头的数组)
const getAlphabetList = () => {
// 如果没有字母列表则不执行
if (!columns.some((i) => i.type === 'Alphabet')) return;
// 获取字母列表
const options = columns.find((i) => i.type === 'Alphabet')?.options;
let _options;
const temp = new Set(
(_options = options) === null || _options === void 0
? void 0
: _options.map(function (i) {
let _pinyin;
let _pinyin$at;
let _pinyin$at$at;
return (_pinyin = pinyin(i.label, {
toneType: 'none',
type: 'array',
})) === null || _pinyin === void 0
? void 0
: (_pinyin$at = _pinyin.at(0)) === null || _pinyin$at === void 0
? void 0
: (_pinyin$at$at = _pinyin$at.at(0)) === null || _pinyin$at$at === void 0
? void 0
: _pinyin$at$at.toUpperCase();
}),
);
// 获取开头字母列表(去重)
const arr = [
...new Set(
options?.map((i) =>
pinyin(i.label, { toneType: 'none', type: 'array' })?.at(0)?.at(0)?.toUpperCase(),
),
),
].sort();
// 转换成展示列表
setAlphabetList(
arr?.map((i) => {
const children =
options?.filter(
(j) =>
pinyin(j.label, { toneType: 'none', type: 'array' })?.at(0)?.at(0)?.toUpperCase() ===
i,
) || [];
return {
label: i,
children: [{ label: '不限', value: 'all' }, ...children],
};
}),
);
};
// 获取普通列表
const getOptionsList = (options: { label: string; value: any }[]) => {
return [{ label: '不限', value: 'all' }, ...options];
};
// componentDidMount
useEffect(() => {
if (!columns) return;
getAlphabetList();
// 初始化索引
setOptionsIndex(columns.map((i) => (i.subLabel ? { index: 0, subIndex: 0 } : { index: 0 })));
// 初始化列表
setColumnsList(columns?.map((i) => ({ ...i, options: getOptionsList(i.options) })));
}, [columns]);
// componentDidUpdate
useEffect(() => {
// 如果没有普通筛选列表则不执行
if (!optionsIndex.length) return;
// 如果普通筛选列表全为0则不执行
if (!optionsIndex.some((i) => i.index !== 0)) return;
console.log('optionsIndex --->', optionsIndex);
console.log(
'componentDidUpdate -->',
optionsIndex.map((i, j) => columnsList[j].options[i.index]),
);
}, [optionsIndex]);
return (
<div className={'brand-select-search-view'}>
{region && (
<BrandSelectItem label={'地区'}>
<div className={'select-region'}>
<span className={'region-label'}></span>
<Select placeholder={'请选择省份'} options={[]} size={'small'} />
<span className={'region-label'}></span>
<Select placeholder={'请选择城市'} options={[]} size={'small'} />
</div>
</BrandSelectItem>
)}
{columnsList.map((i, j) => {
if (i.type === 'Alphabet') {
return (
<BrandSelectItem label={'品牌'} key={j}>
<div className='alphabet-list'>
{alphabetList.map((n, m) => (
<div
className={`list-item ${alphabetIndex === m && 'item-active'}`}
key={m}
onClick={() => {
setAlphabetIndex(m);
setAlphabetContentIndex(0);
}}
>
{n.label}
</div>
))}
</div>
<div className='alphabet-content'>
{alphabetList[alphabetIndex]?.children?.map((n: any, m: number) => (
<div
className={`content-item ${alphabetContentIndex === m && 'item-active'}`}
key={m}
onClick={() => {
setAlphabetContentIndex(alphabetContentIndex === m ? 0 : m);
const index =
columnsList
.find((i) => i.type === 'Alphabet')
?.options?.findIndex((i) => i.label === n.label) || 0;
// setOptionsIndex(optionsIndex?.map((i, k) => (k === j ? index : i)));
setOptionsIndex(optionsIndex?.map((i, k) => (k === j ? { index } : i)));
}}
>
{n.label}
</div>
))}
</div>
</BrandSelectItem>
);
}
if (['Industry', 'Brand', 'Price', 'Product'].includes(i.type)) {
return (
<div key={j}>
<BrandSelectItem label={i.label}>
<div className='select-list'>
{i.options?.map((n, m) => (
<div
className={`list-item ${optionsIndex[j].index === m && 'item-active'}`}
key={m}
onClick={() => {
setOptionsIndex(
optionsIndex.map((l, k) =>
k === j
? optionsIndex[j].index === m
? i.subLabel
? { index: 0, subIndex: 0 }
: { index: 0 }
: i.subLabel
? { index: m, subIndex: 0 }
: { index: m }
: l,
),
);
}}
>
{n.label}
</div>
))}
</div>
</BrandSelectItem>
{!!i.subLabel && (
<BrandSelectItem label={i.subLabel}>
<div className='select-list'>
{getOptionsList(i.options[optionsIndex[j].index]?.children || [])?.map(
(n, m) => (
<div
className={`list-item ${optionsIndex[j].subIndex === m && 'item-active'}`}
key={m}
onClick={() => {
setOptionsIndex(
optionsIndex.map((i, k) =>
k === j ? { ...i, subIndex: m === i.subIndex ? 0 : m } : i,
),
);
}}
>
{n.label}
</div>
),
)}
</div>
</BrandSelectItem>
)}
</div>
);
}
})}
{selected && (
<BrandSelectItem label={'已选'}>
<div className='select-list selected'>
{optionsIndex.map(
(n, m) =>
columnsList[m].options[n.index].label !== '不限' && (
<div
className={`list-item`}
key={m}
onClick={() => {
// setOptionsIndex(
// optionsIndex.map((i, k) => (k === j ? (optionsIndex[j] === m ? 0 : m) : i)),
// );
}}
>
{columnsList[m].options[n.index].label}
</div>
),
)}
</div>
</BrandSelectItem>
)}
</div>
);
};
export default BrandSelectSearch;
import React from 'react';
import { BoxProps } from './interface';
import Left from './left';
import Right from './right';
import { Box } from './styled';
export default function ContentBox(props: BoxProps) {
return (
<Box>
<Left
boxIndex={props.boxIndex}
leftRenderDom={props.leftRenderDom}
leftcontentstyle={props.leftcontentstyle}
leftWaterfallDom={props.leftWaterfallDom}
/>
<Right rightRenderDom={props.rightRenderDom} />
</Box>
);
}
export interface DomType {
columns: {
noFor?: boolean;
element: JSX.Element;
}[];
pagination?: JSX.Element;
}
export interface WaterfallType {
columns: {
noFor?: boolean;
type?: string;
element: JSX.Element;
}[];
pagination?: JSX.Element;
}
// eslint-disable-next-line @typescript-eslint/naming-convention
export interface leftBoxProps {
boxIndex: number;
leftRenderDom?: DomType;
leftWaterfallDom?: WaterfallType;
leftcontentstyle?: {
width?: string;
margin?: {
top: number | string;
right: number | string;
bottom: number | string;
left: number | string;
};
};
}
// eslint-disable-next-line @typescript-eslint/naming-convention
export interface rightBoxProps {
rightRenderDom: DomType;
}
export interface BoxProps {
boxIndex: number;
leftRenderDom?: DomType;
leftWaterfallDom?: WaterfallType;
leftcontentstyle?: {
width?: string;
margin?: {
top: number | string;
right: number | string;
bottom: number | string;
left: number | string;
};
};
rightRenderDom: DomType;
}
import React from 'react';
import { Empty } from 'antd';
import { LeftBox, Box, WaterfallBox } from './styled';
import { leftBoxProps } from '../interface';
export default function Left(props: leftBoxProps) {
const { boxIndex, leftRenderDom, leftcontentstyle, leftWaterfallDom } = props;
return (
<LeftBox>
{leftRenderDom?.columns.map((item) => {
if (item.noFor) {
return item.element;
}
return null;
})}
{
<Box index={boxIndex} leftcontentstyle={leftcontentstyle}>
{leftRenderDom?.columns.map((item) => {
if (!item.noFor) {
return item.element;
}
return null;
})}
</Box>
}
{leftWaterfallDom?.columns.map((item) => {
if (item.noFor) {
return item.element;
}
return null;
})}
{leftWaterfallDom?.columns.length ? (
<WaterfallBox index={boxIndex} leftcontentstyle={leftcontentstyle}>
{
<div className='left-columns'>
{leftWaterfallDom?.columns.map((item) => {
if (!item.noFor && item.type === 'left') {
return item.element;
}
return null;
})}
</div>
}
{
<div className='right-columns'>
{leftWaterfallDom?.columns.map((item) => {
if (!item.noFor && item.type === 'right') {
return item.element;
}
return null;
})}
</div>
}
</WaterfallBox>
) : null}
{leftRenderDom?.pagination ? leftRenderDom?.pagination : null}
{!leftRenderDom?.columns.length && !leftWaterfallDom?.columns.length ? (
<Empty description={'暂无数据'} />
) : null}
</LeftBox>
);
}
import styled from 'styled-components';
export interface BoxProps {
index: number;
leftcontentstyle?: {
width?: string;
margin?: {
top: number | string;
right: number | string;
bottom: number | string;
left: number | string;
};
};
}
export const LeftBox = styled.div`
box-sizing: border-box;
`;
export const Box = styled.div<BoxProps>`
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
width: ${(props) => (props.leftcontentstyle?.width ? props.leftcontentstyle?.width : '790px')};
.item {
// 每个元素都要设置右边距margin-right(每个元素的左右间隙)
// 同时设置下边距margin-bottom(每个元素的上下间隙)
/* 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'};
width: calc(
(
100% - ${(props) => props.index - 1} *
${(props) =>
props.leftcontentstyle?.margin ? props.leftcontentstyle?.margin.right : '24px'}
) / ${(props) => props.index}
);
// 这里一行显示index个,所以是/index,一行显示几个就除以几
// 这里的72px = (分布个数index-1)*间隙20px, 可以根据实际的分布个数和间隙区调整
min-width: calc(
(
100% - ${(props) => props.index - 1} *
${(props) =>
props.leftcontentstyle?.margin ? props.leftcontentstyle?.margin.right : '24px'}
) / ${(props) => props.index}
);
max-width: calc(
(
100% - ${(props) => props.index - 1} *
${(props) =>
props.leftcontentstyle?.margin ? props.leftcontentstyle?.margin.right : '24px'}
) / ${(props) => props.index}
);
// 每行最右侧的那个不设置右外边距
&:nth-child(${(props) => props.index}n + ${(props) => props.index}) {
margin-right: 0;
}
}
`;
export const WaterfallBox = styled.div<BoxProps>`
box-sizing: border-box;
display: flex;
width: ${(props) => (props.leftcontentstyle?.width ? props.leftcontentstyle?.width : '790px')};
.item {
// 每个元素都要设置右边距margin-right(每个元素的左右间隙)
// 同时设置下边距margin-bottom(每个元素的上下间隙)
/* 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'};
/* width: calc(( 100% - ${(props) => props.index} * ${(props) =>
props.leftcontentstyle?.margin ? props.leftcontentstyle?.margin.right : '24px'}) / ${(
props,
) => props.index});
// 这里一行显示index个,所以是/index,一行显示几个就除以几
// 这里的72px = (分布个数index-1)*间隙20px, 可以根据实际的分布个数和间隙区调整
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}); */
}
.left-columns {
}
.right-columns {
.item {
margin-right: 0;
}
}
`;
import React from 'react';
import { Box } from './styled';
import { rightBoxProps } from '../interface';
export default function Right(props: rightBoxProps) {
const { rightRenderDom } = props;
return <Box>{rightRenderDom.columns.map((item) => item.element)}</Box>;
}
import styled from 'styled-components';
export const Box = styled.div`
box-sizing: border-box;
.right-box-item {
margin-bottom: 10px;
}
`;
import styled from 'styled-components';
export const Box = styled.div`
box-sizing: border-box;
display: flex;
justify-content: space-between;
width: 100%;
`;
import request, { Response } from '~/api/request';
export interface FilterOptionResp {
id: number;
name?: string;
appName?: string;
children?: FilterOptionResp[];
}
export interface RegionResp {
childInfo: RegionResp[] | null;
id: number;
level: number;
name: string;
pid: number;
}
export interface InfoList {
id: number;
directoryId: number;
name: string;
icon: string;
children: InfoList[];
}
export interface TypesResp {
directoryId: number;
name: string;
categoriesInfoListDTO: InfoList[];
}
export default {
category: (): Promise<Response<Array<FilterOptionResp>>> => {
return request('/pms/webProductMall/category');
},
categoryId: (): Promise<Response<Array<FilterOptionResp>>> => {
return request('/pms/webDevice/category');
},
brand: (): Promise<Response<Array<FilterOptionResp>>> => {
return request('/pms/webDevice/brand');
},
model: (): Promise<Response<Array<FilterOptionResp>>> => {
return request('/pms/webDevice/model');
},
part: (): Promise<Response<Array<FilterOptionResp>>> => {
return request('/pms/webProductMall/parts');
},
quality: (): Promise<Response<Array<FilterOptionResp>>> => {
return request('/pms/webProductMall/quality');
},
region: (): Promise<Response<Array<RegionResp>>> => {
return request('/pms/webDevice/getSecondDistrictInfo');
},
industry: (): Promise<Response<Array<RegionResp>>> => {
return request('/release/work/listAllIndustry');
},
appType: (): Promise<Response<Array<RegionResp>>> => {
return request('/release/work/listAllAppType');
},
deviceBrand: (): Promise<Response<Array<RegionResp>>> => {
return request('/pms/webDevice/deviceBrand');
},
deviceModel: (): Promise<Response<Array<RegionResp>>> => {
return request('/pms/webDevice/deviceModel');
},
infoByType: (params: { type: number }): Promise<Response<Array<TypesResp>>> => {
return request('/pms/classify/queryCategoryInfoByType', 'get', params);
},
};
import { useEffect, useState } from 'react';
import { Space, Select } from 'antd';
import api, { RegionResp } from '../../api';
import styles from '../../index.module.scss';
type Props = {
onChange: (item: RegionResp) => void;
};
export default function RegionItem(props: Props) {
const [provinceList, setProvinceList] = useState<RegionResp[]>([]);
const [cityList, setCityList] = useState<RegionResp[]>([]);
const [selectCity, setSelectCity] = useState<number>();
useEffect(() => {
api.region().then((res) => {
setProvinceList(res?.result || []);
});
}, []);
const onProvinceChange = (value: number, item: any) => {
console.log('省', value, item);
setCityList(item.childInfo || []);
setSelectCity(undefined);
props.onChange(item);
};
const onCityChange = (value: number, item: any) => {
console.log('市', value);
setSelectCity(value);
props.onChange(item);
};
return (
<div className={styles.filterItem}>
<div className={styles.filterItemTitle}>地域:</div>
<div className={styles.filterItemMain}>
<Space size={40}>
<Select
bordered={false}
popupMatchSelectWidth={false}
placeholder='选择省'
onChange={onProvinceChange}
options={provinceList.map((item) => {
return {
...item,
value: item.id,
label: item.name,
};
})}
/>
{/* <Select
value={selectCity}
bordered={false}
popupMatchSelectWidth={false}
placeholder="选择市"
onChange={onCityChange}
options={cityList.map((item) => {
return {
...item,
value: item.id,
label: item.name,
};
})}
/> */}
</Space>
</div>
</div>
);
}
import React from 'react';
import { Space, Tag } from 'antd';
// eslint-disable-next-line import/no-cycle
import { FilterResult } from '../..';
import { InfoList } from '../../api';
import styles from '../../index.module.scss';
type Props = {
data: FilterResult;
onDel: (key: string | number) => void;
};
export default function ResultItem({ data, onDel }: Props) {
return (
<div className={styles.filterItem}>
<div className={styles.filterItemTitle}>已选:</div>
<div className={styles.filterItemMain}>
<Space size={10}>
{data.provinceId && (
// Object.keys(data).map((key) => {
// //@ts-ignore
// 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 (
<Tag
closable
onClose={(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
onDel(item.id);
}}
key={item.name}
>
{item?.name}
</Tag>
);
})}
</Space>
</div>
</div>
);
}
import { useState, useEffect } from 'react';
import { Space, Button, Collapse } from 'antd';
import Image from 'next/image';
import downArrowImg from '~/assets/images/down-arrow.png';
import { FilterOptionResp, InfoList } from '../../api';
import styles from '../../index.module.scss';
type Props = {
onChange: (id: FilterOptionResp) => void;
typeName: string;
dataValue: InfoList[];
categoryMouseEnter: (item: FilterOptionResp) => void;
changeCurrentItemIndex: (index: number) => void;
currentItemIndex: number;
categoryMouseLeave: () => void;
};
export default function CategoryItem(props: Props) {
const [data, setData] = useState<FilterOptionResp[]>([]);
useEffect(() => {
setData(props.dataValue || []);
}, []);
const onClick = (item: FilterOptionResp) => {
if (!item.children) {
props.onChange({
id: item.id,
name: `${props.typeName}${item.name}`,
});
}
};
const onMouseEnter = (item: FilterOptionResp, index: number) => {
if (item.children) {
props.changeCurrentItemIndex(index);
} else {
props.changeCurrentItemIndex(-1);
}
props.categoryMouseEnter({
id: item.id,
name: `${props.typeName}${item.name}`,
children: item.children,
});
};
const showCount = 10; // 展示数量
return (
<div className={styles.filterItem}>
<div className={styles.filterItemTitle}>{props.typeName}</div>
<div className={`${styles.filterItemMain} ${data.length <= showCount && styles.disabled}`}>
<Collapse ghost collapsible='icon' expandIconPosition='end' style={{ width: '100%' }}>
<Collapse.Panel
header={
<Space size={[10, 0]}>
{data.slice(0, showCount).map((item, index) => {
return (
<div
key={item.id}
className={`${styles.filterItemContent} ${
props.currentItemIndex === index ? styles.filterItemContentHover : ''
}`}
onMouseEnter={() => onMouseEnter(item, index)}
onMouseLeave={props.categoryMouseLeave}
>
<Button type='link' onClick={(e) => onClick(item)}>
{item.name}
</Button>
{item.children ? (
<Image
src={downArrowImg}
className={styles.filterItemIcon}
width={14}
height={14}
alt='展开图标'
/>
) : (
''
)}
</div>
);
})}
</Space>
}
key='1'
>
<Space size={[10, 0]} wrap>
{data.slice(showCount).map((item, index) => {
return (
<div
key={item.id}
className={`${styles.filterItemContent} ${
props.currentItemIndex === index ? styles.filterItemContentHover : ''
}`}
onMouseEnter={() => onMouseEnter(item, index)}
onMouseLeave={props.categoryMouseLeave}
>
<Button type='link' onClick={(e) => onClick(item)}>
{item.name}
</Button>
{item.children ? (
<Image
src={downArrowImg}
className={styles.filterItemIcon}
width={14}
height={14}
alt='展开图标'
/>
) : (
''
)}
</div>
);
})}
</Space>
</Collapse.Panel>
</Collapse>
</div>
</div>
);
}
.filterWrap {
padding: 0px 32px;
background: #ffffff;
border-radius: 6px;
position: relative;
* {
font-size: 12px !important;
}
}
.filterItem {
min-height: 26px;
border-bottom: 1px dashed RGBA(222, 222, 222, 1);
display: flex;
&:nth-last-child(1) {
border-bottom: none;
}
.filterItemTitle {
flex-shrink: 0;
width: 80px;
margin-right: 8px;
color: rgba(153, 153, 153, 1);
line-height: 26px;
height: auto;
}
.filterItemMain {
flex: 1;
display: flex;
justify-content: space-between;
overflow: hidden;
&.disabled {
:global .ant-collapse-expand-icon {
display: none;
}
}
:global .ant-collapse-item {
transform: translateY(-2px);
.ant-collapse-expand-icon {
margin-top: 6px;
}
.ant-collapse-header {
padding: 0;
height: 26px;
line-height: 26px;
}
.ant-collapse-content-box {
padding: 0;
}
}
.filterItemContent{
display: flex;
align-items: center;
cursor: pointer;
padding: 0 15px;
.filterItemIcon{
transform: rotateZ(180deg);
margin-left: 5px;
transition: transform 1s;
}
&:hover{
background: #ECECEC;
.filterItemIcon{
transform: rotateZ(0deg);
transition: transform 0.168s;
}
}
button:hover{
color: #000;
}
}
}
:global .ant-select-selector {
padding: 0 12px 0 0;
.ant-select-selection-item,
.ant-select-selection-placeholder {
font-size: 16px;
color: #3e4149;
}
}
:global .ant-select-arrow {
color: #3e4149;
}
:global .ant-btn-link {
font-size: 16px;
color: #3e4149;
padding: 0;
}
:global .ant-tag {
padding: 4px 9px;
}
}
.filterCategorySecond{
position: absolute;
top: 26px;
left: 0;
width: 100%;
min-height: 60px;
background: #FFFFFF;
box-shadow: 0px 4px 10px 0px rgba(0,0,0,0.08);
border: 1px solid #EBEBEB;
display: none;
z-index: 9999;
}
.filterCategorySecond:hover ~ .filterItem{
.filterItemContentHover{
background: #ECECEC;
.filterItemIcon{
transform: rotateZ(0deg) !important;
transition: transform 0.168s !important;
}
}
}
import React, { useEffect, useState, forwardRef, useImperativeHandle, Ref, useRef } from 'react';
import { Button } from 'antd';
import { useRouter } from 'next/router';
import api, { FilterOptionResp, TypesResp, InfoList } from './api';
import RegionItem from './compoents/regionItem';
// eslint-disable-next-line import/no-cycle
import ResultItem from './compoents/resultItem';
import TypeInfo from './compoents/typeInfo';
import styles from './index.module.scss';
export type AdapterResult = {
categoryId?: any[];
provinceId?: number;
};
export type FilterResult = {
categoryId?: InfoList[];
provinceId?: FilterOptionResp;
};
type Props = {
types: string[]; // 需要包含的筛选条件项
showResultItem: Boolean; // 显示结果栏
onChange: (
filterResult: FilterResult,
adapterFilterResult: AdapterResult, // 适配器,直接用于接口请求
) => void; // 筛选条件更改事件
};
const Filter = (props: Props, ref: Ref<any>) => {
const categorySecondRef = useRef<any>();
const router = useRouter();
useImperativeHandle(ref, () => ({
clearRouter,
}));
const [result, setResult] = useState<FilterResult>({});
const onChange = (item: FilterOptionResp, type: string) => {
clearRouter();
const data: { [key: string]: FilterOptionResp[] | FilterOptionResp } = {};
if (type === 'categoryId') {
if (result.categoryId) {
data[type] = [item, ...result.categoryId];
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(() => {
props.onChange(result, {
categoryId: result.categoryId,
provinceId: result.provinceId?.id,
});
}, [result]);
const clearRouter = () => {
if (Object.keys(router.query).length) {
router.query = {};
router.replace(router.pathname);
}
};
const onDel = (key: string | number) => {
clearRouter();
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?.forEach((item, index) => {
if (item.id === key) {
result.categoryId?.splice(index, 1);
}
});
}
setResult({
...result,
});
};
const routerList = ['/jobServices', '/equipmentLeasing', '/flyingHandService', '/mall'];
const [typeInfo, setTypeInfo] = useState<Array<TypesResp> | null>();
const [currentDicIndex, setCurrentDicIndex] = useState<number>(-1);
const [currentItemIndex, setCurrentItemIndex] = useState<number>(-1); // 当前分类移入的下标
const [categoryObj, setCategoryObj] = useState<FilterOptionResp>();
// 分类移入
const categoryMouseEnter = (item: FilterOptionResp) => {
if (item.children) {
setCategoryObj(item);
categorySecondRef.current.style.display = 'block';
if (typeInfo) {
const index: number = typeInfo.findIndex((v) =>
v.categoriesInfoListDTO.some((i) => i.id === item.id),
);
setCurrentDicIndex(index);
categorySecondRef.current.style.top = `${(index + 1) * 26}px`;
}
} else {
categorySecondRef.current.style.display = 'none';
}
};
// 分类移出
const categoryMouseLeave = () => {
categorySecondRef.current.style.display = 'none';
};
// 二级菜单移入
const categorySecondMouseEnter = () => {
if (currentItemIndex !== -1) {
categorySecondRef.current.style.display = 'block';
}
};
// 二级菜单移出
const onMouseLeave = () => {
setCurrentItemIndex(-1);
categorySecondRef.current.style.display = 'none';
};
// 修改移入的下标
const changeCurrentItemIndex = (index: number) => {
setCurrentItemIndex(index);
};
// 二级分类选中
const categorySecondSelect = (v: FilterOptionResp) => {
const obj = JSON.parse(JSON.stringify(categoryObj));
if (obj?.children) {
obj.children = obj.children.filter((item: FilterOptionResp) => item.id === v.id);
obj.name += `/${v.name}`;
onChange(obj, 'categoryId');
}
};
useEffect(() => {
if (routerList.indexOf(router.pathname) > -1) {
(async () => {
const res = await api.infoByType({
type: routerList.indexOf(router.pathname) + 1,
});
setTypeInfo(res.result);
// 首页跳转自定筛选选中
const 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 (
<>
{props.types.includes('地域') && (
<div
className={styles.filterWrap}
style={{
marginBottom: 10,
}}
>
<RegionItem
onChange={(item: FilterOptionResp) => onChange(item, 'provinceId')}
></RegionItem>
</div>
)}
<div className={styles.filterWrap} onMouseLeave={onMouseLeave}>
<div
className={styles.filterCategorySecond}
ref={categorySecondRef}
onMouseLeave={onMouseLeave}
onMouseEnter={categorySecondMouseEnter}
>
{categoryObj?.children &&
categoryObj?.children.map((v) => (
<Button type='link' key={v.id} onClick={() => categorySecondSelect(v)}>
{v.name}
</Button>
))}
</div>
{typeInfo?.length &&
typeInfo?.map((item, index) => (
<TypeInfo
key={item.directoryId}
typeName={item.name}
dataValue={item.categoriesInfoListDTO}
onChange={(e: FilterOptionResp) => onChange(e, 'categoryId')}
categoryMouseEnter={categoryMouseEnter}
changeCurrentItemIndex={changeCurrentItemIndex}
currentItemIndex={currentDicIndex === index ? currentItemIndex : -1}
categoryMouseLeave={categoryMouseLeave}
></TypeInfo>
))}
{props.showResultItem && <ResultItem data={result} onDel={onDel}></ResultItem>}
</div>
</>
);
};
export default forwardRef(Filter);
.footer {
height: 110px;
background: #ffffff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.footerBox {
width: 1200px;
display: flex;
justify-content: space-evenly;
}
.logo {
width: 90px;
height: 48px;
background: url("./assets/logo.png") no-repeat;
background-size: 100% 100%;
margin-right: 155px;
}
.qrcodeList {
flex: 1;
display: flex;
align-items: center;
justify-content: space-evenly;
.qrcodeItem {
margin-right: 111px;
display: flex;
align-items: center;
&:nth-last-child(1) {
margin-right: 0;
}
.qrcodeContent{
margin-left: 10px;
.qrcodeTitle {
margin-top: 4px;
font-size: 12px;
color: #959595;
}
}
.qrcodeImg {
width: 60px;
height: 60px;
border-radius: 8px;
overflow: hidden;
background-color: #fff;
}
}
}
.rightText {
.number {
text-align: center;
font-size: 58px;
font-family: Arial-Black, Arial;
font-weight: 900;
color: RGBA(255, 176, 45, 1);
line-height: 82px;
}
.text {
font-size: 16px;
font-family: MicrosoftYaHei;
color: #fff;
line-height: 21px;
}
}
.filingNumber{
font-size: 12px;
font-weight: 400;
color: #959595;
margin-top: 15px;
}
import { Image } from 'antd';
import errImg from '~/assets/errImg';
import styles from './index.module.scss';
const qrcodeList = [
{
img: '/assets/xiaochengxu.png',
title: '轻松玩转',
meta: '云享飞小程序',
},
{
img: '/assets/fuwuhao.png',
title: '云享飞服务号',
meta: '',
},
{
img: '/assets/mmc.png',
title: '科比特官网',
url: 'http://www.mmcuav.cn/',
meta: '',
},
{
img: '/assets/shequn.png',
title: '官方社群',
meta: '',
},
];
export default function Footer() {
return (
<div className={styles.footer}>
<div className={styles.footerBox}>
{/* <div className={styles.logo}></div> */}
<div className={styles.qrcodeList}>
{qrcodeList.map((item, i) => {
return (
<div className={styles.qrcodeItem} key={i}>
<Image
style={{
cursor: item.title === '科比特官网' ? 'pointer' : '',
}}
onClick={() => {
if (item.title === '科比特官网') window.open(item.url, '_blank');
}}
alt=''
className={styles.qrcodeImg}
src={item.img}
fallback={errImg}
preview={item.title !== '科比特官网'}
></Image>
<div className={styles.qrcodeContent}>
<div className={styles.qrcodeTitle}>{item.title}</div>
{item.meta ? <div className={styles.qrcodeTitle}>{item.meta}</div> : ''}
</div>
</div>
);
})}
</div>
</div>
<a className={styles.filingNumber} href='https://beian.miit.gov.cn' target='_blank'>
浙ICP备2023014395号
</a>
</div>
);
}
.content{
min-height: 120px;
line-height: 1;
width: 1200px;
position: relative;
margin: 0 auto;
}
import React from 'react';
import { Layout, Space } from 'antd';
import { useRouter } from 'next/router';
import FooterView from '~/components/footer';
import NavHeader from '~/components/NavHeader';
import styles from './index.module.scss';
const { Header, Footer, Content } = Layout;
// 底部栏固定定位
const includesPage = ['/home', '/flyingHandService/detail/[id]'];
const homeStyle: React.CSSProperties = {
marginTop: 10,
};
const headerStyle: React.CSSProperties = {
height: 'auto',
background: 'none',
padding: 0,
lineHeight: '1',
position: 'relative',
};
const contentStyle: React.CSSProperties = {
minHeight: 120,
lineHeight: '1',
color: '',
backgroundColor: '',
width: 1200,
position: 'relative',
margin: '0 auto',
};
const footerStyle: React.CSSProperties = {
lineHeight: '1',
padding: 0,
position: 'relative',
marginTop: 60,
};
type Props = {
children?: React.ReactNode;
layoutStyle?: React.CSSProperties;
contentStyle?: React.CSSProperties;
hideFooter?: boolean;
headerStyle?: React.CSSProperties;
};
export default function LayoutView(props: Props) {
const router = useRouter();
return (
<Space direction='vertical' style={{ minWidth: '100%' }} size={[0, 48]}>
<Layout style={{ minHeight: '100vh', backgroundColor: '#F8F8F8', ...props.layoutStyle }}>
<Header style={headerStyle}>
<NavHeader style={props.headerStyle} />
</Header>
<Content className={styles.content} style={props.contentStyle}>
{props.children}
</Content>
{!props.hideFooter && (
<Footer
style={
includesPage.includes(router.pathname)
? { ...footerStyle, ...homeStyle }
: footerStyle
}
>
<FooterView></FooterView>
</Footer>
)}
</Layout>
</Space>
);
}
import React, { useContext, useEffect, useState } from 'react';
import { Modal, Image } from 'antd';
import api from '~/api';
import errImg from '~/assets/errImg';
import { UserContext } from '~/lib/userProvider';
type Props = {
open: boolean;
onCancel: () => void;
};
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,
})
.then((res) => {
if (res.code === '200') {
clearInterval(handle);
setTimeHandle(null);
window.localStorage.setItem('token', res.result.token);
api.userInfo().then((userRes) => {
setUserInfo(userRes.result);
window.messageApi.success('登录成功');
props.onCancel();
window.location.reload();
});
}
});
}, 1000);
setTimeHandle(handle);
}
}, [randomLoginCode]);
useEffect(() => {
if (!props.open && timeHandle) {
clearTimeout(timeHandle);
}
}, [timeHandle, props.open]);
return (
<>
<Modal open={props.open} onCancel={props.onCancel} width={400} footer={null}>
<div
style={{
fontSize: 20,
fontFamily: 'MicrosoftYaHeiUI-Bold, MicrosoftYaHeiUI',
fontWeight: 'bold',
color: '#000',
marginTop: 17,
marginBottom: 48,
textAlign: 'center',
}}
>
欢迎来到云享飞
</div>
<div id='login_container' style={{ margin: 'auto', display: 'table' }}>
<Image src={qrCode} width={150} height={150} fallback={errImg}></Image>
</div>
<div
style={{
// marginTop: -120,
marginBottom: 52,
fontSize: 14,
fontFamily: 'MicrosoftYaHei',
color: '#3E454D',
textAlign: 'center',
}}
>
打开微信扫一扫
</div>
</Modal>
</>
);
}
import request, { Response } from '~/api/request';
// eslint-disable-next-line import/no-anonymous-default-export
export default {
uploadFile: (data: FormData): Promise<Response<string>> =>
request('/pms/upload/breakpoint', 'post', data, { headers: {}, body: data }),
};
import React, { useEffect, useState } from 'react';
import { message, Upload, UploadProps } from 'antd';
import uploadApi from './api';
interface PropsType {
listType?: 'text' | 'picture' | 'picture-card'; // 上传列表的内建样式
fileSize?: number; // 文件大小
fileType?: string[]; // 上传文件类型
fileUpload: boolean; // 是否上传到服务器(返回文件流还是返回上传后的地址)
fileLength?: number; // 最大上传文件数量
children: React.ReactNode; // 上传按钮
showUploadList?: boolean; // 是否隐藏上传文件列表
onChange?: (
fileList: {
id: number;
name: string;
uid: number;
url: string;
}[],
) => void; // 上传文件改变时的状态
defaultFileList?: any[]; // 默认文件列表
}
export const Uploader: React.FC<PropsType> = (props) => {
Uploader.defaultProps = {
listType: 'picture-card',
fileSize: 2,
fileLength: 1,
fileType: ['image/png', 'image/jpeg', 'image/jpg', 'image/gif'],
onChange: () => {},
defaultFileList: [],
showUploadList: true,
};
const {
fileType = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/bmp'],
children,
listType,
fileSize,
fileUpload,
fileLength,
onChange,
defaultFileList,
showUploadList,
} = props;
const [fileList, setFileList] = useState<any[]>([]);
// 上传文件配置
const uploadProps: UploadProps = {
listType,
fileList,
showUploadList,
beforeUpload: (res) => {
const isType = fileType?.includes(res.type);
const isSize = res.size / 1024 / 1024 < (fileSize || 2);
const isLength = fileList.length < (fileLength || 1);
if (!isType) {
message.error('上传文件格式错误!').then();
}
if (!isSize) {
message.error(`文件最大${props.fileSize}M,请压缩后上传!`).then();
}
if (!isLength) {
message.error(`最多上传${fileLength || 1}个文件`).then();
}
return isType && isSize && isLength;
},
customRequest: (res) => {
if (fileUpload) {
setFileList([
...fileList,
{
id: new Date().getTime(),
uid: new Date().getTime(),
name: (res.file as any).name,
type: (res.file as any).type,
url: (res.file as any).url,
status: 'uploading',
},
]);
// 上传到服务器
const formData = new FormData();
formData.append('uploadFile', res.file);
uploadApi.uploadFile(formData).then(({ result, code }) => {
if (code === '200') {
setFileList([
...fileList,
{
id: new Date().getTime(),
uid: new Date().getTime(),
name: (res.file as any).name || '',
url: result,
type: (res.file as any).type,
status: 'done',
},
]);
onChange?.([
...fileList,
{
id: new Date().getTime(),
uid: new Date().getTime(),
name: (res.file as any).name || '',
url: result,
type: (res.file as any).type,
status: 'done',
},
]);
}
});
} else {
setFileList([...fileList, res.file]);
onChange?.([...fileList, res.file]);
}
},
onRemove: (res) => {
const newFileList = fileList.filter((item) => item.uid !== res.uid);
setFileList(newFileList);
onChange?.(newFileList);
},
// onPreview: { onPreview },
};
useEffect(() => {
// 如果有默认文件列表
if (defaultFileList?.length) {
setFileList(defaultFileList);
} else {
setFileList([]);
}
}, [defaultFileList]);
return (
<div className='uploader-view'>
<Upload {...uploadProps} style={{ width: '100%' }}>
{children}
</Upload>
</div>
);
};
import { FC } from 'react';
import { Modal, ModalProps, Image } from 'antd';
import { Box } from './styled';
interface SelfProps {
wxCodeImg: string;
}
const WxCodeModal: FC<ModalProps & SelfProps> = ({ open, onCancel, wxCodeImg }) => {
return (
<Modal open={open} onCancel={onCancel} width={400} footer={null}>
<Box>
<div className='title'>立即申请合作</div>
<div className='img'>
<Image src={wxCodeImg} width={160} height={160} alt={'图片'} />
</div>
<div className='meta'>打开微信扫一扫</div>
</Box>
</Modal>
);
};
export default WxCodeModal;
import styled from 'styled-components';
export const Box = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.title {
font-size: 20px;
font-family: MicrosoftYaHeiUI-Bold, MicrosoftYaHeiUI;
font-weight: bold;
color: #ff5a33;
line-height: 25px;
}
.img {
margin: 48px 0 40px 0;
}
.meta {
font-size: 14px;
font-family: MicrosoftYaHei;
color: #3e454d;
line-height: 19px;
}
`;
apiVersion: v1
kind: ConfigMap
metadata:
name: web-map
namespace: default
data:
NODE_ENV: default
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deployment
namespace: default
spec:
minReadySeconds: 10
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
name: web-port
env:
- name: NODE_ENV
valueFrom:
configMapKeyRef:
name: web-map
key: NODE_ENV
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
ports:
- protocol: TCP
port: 3000
apiVersion: v1
kind: ConfigMap
metadata:
name: web-map
data:
NODE_ENV: dev
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deployment
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
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
images:
- name: REGISTRY/NAMESPACE/IMAGE:TAG
newName: mmc-registry.cn-shenzhen.cr.aliyuncs.com/sharefly-dev/web
newTag: 07a1a7386929697a1d7fa2923e5de5c267fe0079
- op: replace
path: /spec/ports/0/nodePort
value: 31001
\ No newline at end of file
apiVersion: v1
kind: ConfigMap
metadata:
name: web-map
data:
NODE_ENV: prod
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deployment
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
\ 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
images:
- name: REGISTRY/NAMESPACE/IMAGE:TAG
newName: mmc-registry.cn-shenzhen.cr.aliyuncs.com/sharefly/web
newTag: 7110f0af1a0657f441ec58e77895eda43be109c5
- op: replace
path: /spec/ports/0/nodePort
value: 31000
\ No newline at end of file
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;
// @ts-nocheck
/* eslint-disable */
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,
});
const 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;
}
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) {
/* empty */
}
}, []);
useEffect(() => {
if (userInfo !== null) {
localStorage.setItem('userInfo', JSON.stringify(userInfo || ''));
}
}, [userInfo]);
// 测试登录
function testLogin() {
api.testAppletLogin().then((res) => {
if (res && res.code === '200') {
window.localStorage.setItem('token', res.result?.token || '');
api.userInfo().then((userRes) => {
setUserInfo(userRes.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;
// @ts-nocheck
/* eslint-disable */
// 不能输入数字,其他可惜输入
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} */
let distDir = '.dev'; // 默认输出目录
if (process.env.NODE_ENV === 'production') {
// 生产环境用另一个目录构建,防止与dev冲突
distDir = '.next';
}
const nextConfig = {
distDir,
reactStrictMode: true,
transpilePackages: ['antd'],
output: 'standalone',
compiler: {
styledComponents: true,
},
redirects() {
return [
{
source: '/',
destination: '/home',
permanent: true,
},
];
},
async rewrites() {
return [
{
source: '/local/:path*',
// destination: 'https://www.iuav.com/:path*',
destination: 'https://test.iuav.com/:path*',
},
];
},
images: {
remotePatterns: [
{
protocol: 'http',
hostname: '**',
},
{
protocol: 'https',
hostname: '**',
},
],
},
pageExtensions: ['page.tsx', 'page.ts', 'page.jsx', 'page.js'],
};
module.exports = nextConfig;
{
"name": "create-next-app-antd",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint:next": "next lint",
"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",
"lint": "npx eslint pages",
"lint:fix": "npm run lint -- --fix",
"prettier": "npx prettier pages --check",
"prettier:fix": "npm run prettier -- --write",
"format": "npm run prettier:fix && npm run lint:fix"
},
"devDependencies": {
"@ant-design/cssinjs": "^1.3.0",
"@ant-design/static-style-extract": "~1.0.1",
"@types/node": "^18.11.17",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.10",
"@typescript-eslint/eslint-plugin": "^5.10.1",
"@typescript-eslint/parser": "^5.10.1",
"cross-env": "^7.0.3",
"eslint": "^8.42.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^16.1.0",
"eslint-config-next": "^13.1.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-unused-imports": "^2.0.0",
"prettier": "^2.8.8",
"sass": "^1.62.1",
"ts-node": "^10.9.1",
"tslib": "^2.5.0",
"typescript": "^4.9.4"
},
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@ant-design/icons": "^5.0.1",
"@hapi/iron": "^7.0.1",
"@types/styled-components": "^5.1.26",
"antd": "^5.6.4",
"babel-plugin-styled-components": "^2.1.1",
"babel-plugin-styled-components-px2rem": "^1.5.5",
"cookie": "^0.5.0",
"dayjs": "^1.11.8",
"moment": "^2.29.4",
"next-connect": "^1.0.0",
"passport": "^0.6.0",
"passport-local": "^1.0.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",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"swiper": "^9.3.2",
"swr": "^2.1.5",
"uuid": "^9.0.0",
"next": "^13.1.1"
}
}
import request, { Response } from '~/api/request';
export interface CooperationApplyParams {
applyName: string;
applyPhone: string;
remark?: string;
cooperationTagId: number;
attachmentList?: { type: number; url: string }[];
}
export interface GetTagIdResp {
id: number;
tagName: string;
tagImg: string;
tagDescription: string;
createTime: string;
tagRequire: 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);
},
};
.headerLine{
height: 8px;
background: linear-gradient(90deg, #F5B351 0%, #FF552D 100%);
}
.font1 {
font-size: 20px;
font-weight: bold;
color: #000000;
line-height: 25px;
}
.font2 {
font-size: 14px;
color: #1A1B1C;
line-height: 19px;
letter-spacing: 1px;
}
.infoContent{
padding: 29px 0 0 50px;
box-sizing: border-box;
.tip{
.tipTitle{
font-size: 14px;
font-weight: bold;
color: #000000;
line-height: 18px;
margin-bottom: 17px;
}
}
.formWrap{
margin-top: 35px;
.formTitle{
font-size: 14px;
font-weight: bold;
color: #000000;
line-height: 18px;
margin-bottom: 17px;
}
}
.uploadTip{
font-size: 12px;
color: #858687;
line-height: 18px;
margin-bottom: 12px;
}
.uploadOperate{
}
}
.submitOperate{
text-align: center;
margin-top: 69px;
button{
width: 200px;
height: 40px;
border-radius: 6px;
background: linear-gradient(90deg, #FF552D 0%, #FF812D 100%);
font-size: 16px;
color: #FEFFFE;
}
}
import { useEffect, useState } from 'react';
import { Button, Col, Divider, Form, Input, message, Row } from 'antd';
import { useRouter } from 'next/router';
import LayoutView from '~/components/layout';
import { Uploader } from '~/components/uploader';
import api from './api';
import styles from './index.module.scss';
export default function JoinPolicy() {
const [form] = Form.useForm();
const router = useRouter();
const [content, setContent] = useState(''); // 福利内容
const [tagId, setTagId] = useState<number>(-1);
useEffect(() => {
if (router.query.tagId) {
setTagId(Number(router.query.tagId));
api
.getTagById({
id: Number(router.query.tagId),
})
.then((res) => {
setContent(res.result?.tagRequire.replaceAll('\n', '<br/>') || '');
});
}
}, [router.query.tagId]);
// 提交
const submitApply = () => {
form
.validateFields()
.then((valid) => {
api
.cooperationApply({
...valid,
cooperationTagId: tagId,
})
.then((res) => {
if (res.code === '200') {
window.messageApi.success('提交成功');
form.resetFields();
setTimeout(() => {
router.push('/');
}, 1500);
}
});
})
.catch((err: any) => {
message.warning(err.errorFields[0].errors[0]);
});
};
// 上传变更
const uploadChange = (value: any) => {
const attachmentList = value.map((v: any) => ({
type: v.type.includes('image') ? 0 : 1,
url: v.url,
}));
form.setFieldValue('attachmentList', attachmentList);
};
return (
<LayoutView>
<div
className='page'
style={{
background: '#fff',
position: 'relative',
zIndex: 1,
marginTop: 20,
paddingBottom: 63,
}}
>
<div className={styles.headerLine}></div>
<div className={styles.font1} style={{ textAlign: 'center', marginTop: '22px' }}>
加盟入驻
</div>
<Divider />
<div className={styles.infoContent}>
<div className={styles.tip}>
<div className={styles.tipTitle}>需准备上传资料</div>
<div className={styles.font2} dangerouslySetInnerHTML={{ __html: content }}></div>
</div>
<div className={styles.formWrap}>
<div className={styles.formTitle}>填写资料</div>
<div className={styles.form}>
<Form labelCol={{ span: 2 }} wrapperCol={{ span: 10 }} labelAlign='left' form={form}>
<Form.Item
label='联系人'
name='applyName'
rules={[{ required: true, message: '请输入联系人' }]}
>
<Input placeholder='请输入联系人' maxLength={30} />
</Form.Item>
<Form.Item
label='联系方式'
name='applyPhone'
rules={[
{ required: true, message: '请输入联系方式' },
{ pattern: /^1\d{10}$/, message: '请输入正确的手机号码' },
]}
>
<Input placeholder='请输入联系方式' />
</Form.Item>
<Row>
<Col span={2}>
<div>
<span style={{ color: 'red' }}>*</span>上传资料:
</div>
</Col>
<Col span={10}>
<div className={styles.uploadTip}>
<div>1. 图片格式为JPG、JPEG、BMP、GIF、PNG</div>
<div>2. 文档格式为word、PDF、excel</div>
<div> 3. 文件大小不超过10M</div>
</div>
<div className={styles.uploadOperate}>
<Form.Item
name='attachmentList'
rules={[{ required: true, message: '请上传资料' }]}
>
<Uploader
fileUpload
listType='text'
fileLength={10}
fileSize={10}
onChange={uploadChange}
fileType={[
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/pdf',
'application/msword',
'image/png',
'image/jpeg',
'image/jpg',
'image/gif',
'image/bmp',
]}
>
<Button>上传文件</Button>
</Uploader>
</Form.Item>
</div>
</Col>
</Row>
<Form.Item label='备注(选填)' name='remark'>
<Input placeholder='请输入备注' maxLength={60} />
</Form.Item>
</Form>
</div>
</div>
</div>
<div className={styles.submitOperate}>
<Button type='primary' onClick={submitApply}>
提交申请
</Button>
</div>
</div>
</LayoutView>
);
}
JoinPolicy.getInitialProps = async () => {
return {};
};
import '../public/antd.min.css';
import '../styles/index.scss';
import { useEffect } from 'react';
import { message } from 'antd';
import type { AppProps } from 'next/app';
import Head from 'next/head';
import Script from 'next/script';
import CommonProvider from '~/lib/commonProvider';
import UserProvider from '~/lib/userProvider';
import withTheme from '../theme';
export default function App({ Component, pageProps }: AppProps) {
const [messageApi, contextHolder] = message.useMessage();
useEffect(() => {
// 全局消息提示
window.messageApi = messageApi;
// @ts-ignore
window.onresize = function () {
// window.document.querySelector('html')!.style.fontSize = (window.innerWidth / 1920) + 'PX';
};
}, []);
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}
<CommonProvider>
<UserProvider>
<Component {...pageProps} />
</UserProvider>
</CommonProvider>
</>,
);
}
import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document';
import { ServerStyleSheet } from 'styled-components';
export default class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const sheet = new ServerStyleSheet();
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
}
render() {
return (
<Html lang='en' style={{ fontSize: 1 }}>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
type Data = {
name: string;
};
export default function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
res.status(200).json({ name: 'John Doe' });
}
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;
}
// eslint-disable-next-line import/no-anonymous-default-export
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);
},
};
.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 { useContext, useEffect, useState } from 'react';
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 Router from 'next/router';
import gApi from '~/api';
import Layout from '~/components/layout';
import { UserContext } from '~/lib/userProvider';
import api from './api';
import styles from './index.module.scss';
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 }}
>
{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';
export interface ListPageDeviceInfoParams {
brandId?: number;
districtId?: number;
modelId?: number;
pageNo: number;
pageSize: number;
partsId?: number;
productCategoryId?: number;
qualityId?: number;
}
export interface Device {
id: number;
goodsName: string;
images: string;
price: number | null;
}
export interface ListPageDeviceInfoResp {
pageNo: 1;
pageSize: 10;
list: Array<Device>;
totalCount: 0;
totalPage: 0;
}
export default {
// web-设备租赁-分页
listPageDeviceInfo: (
params: ListPageDeviceInfoParams,
options = {},
): Promise<Response<ListPageDeviceInfoResp>> => {
return request('/pms/product/mall/deviceList', 'post', params, options);
},
};
import request, { Response } from '~/api/request';
export interface GetWebDeviceDetailParams {
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 {
id: number;
imgUrl: string;
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 {
id: number;
images: {
id: number;
imgUrl: string;
imgType: number;
}[];
goodsVideo: string;
goodsVideoId: number;
goodsName: string;
goodsDetail: {
id: number;
goodsDesc: string;
content: string | null;
remark: string | null;
};
directoryId: number;
categoryByOne: number;
categoryByTwo: null;
tag: null;
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 {
// web-设备租赁-详情
listDetailDeviceInfo: (
params: GetWebDeviceDetailParams,
): Promise<Response<GetWebDeviceDetailResult>> => {
return request('/pms/product/mall/getLeaseGoodsDetail', 'get', params);
},
// 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;
}
}
`;
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论