提交 1535e021 作者: 余乾开

Merge branch 'master' into feature/chuck

import request, { Response } from "~/api/request";
import config from "./config";
export interface RegionResp {
childInfo?: RegionResp[] | null;
......@@ -38,4 +39,8 @@ export default {
userInfo: (params: UserInfoParams): Promise<Response<UserInfoResp>> => {
return request("/userapp/user-account/info", "get", params, {});
},
//图片上传地址
imgOss: () => {
return config.baseUrl + "/pms/upload/imgOss";
}
};
......@@ -93,7 +93,11 @@ export default function NavHeader() {
</Space>
{user ? (
<div className={styles.haedImg}>
<Avatar size={36} style={{ background: "#bdbdbd" }}></Avatar>
<Avatar
size={36}
style={{ background: "#bdbdbd" }}
src={user.userImg}
></Avatar>
</div>
) : (
<Button
......
......@@ -4,6 +4,7 @@ import styles from "./index.module.scss";
import img from "./assets/img.png";
import { useEffect, useState } from "react";
import api, { ListTagResp } from "./api";
import { useRouter } from "next/router";
type Props = {
open?: boolean;
......@@ -11,12 +12,7 @@ type Props = {
onCancel?: () => void;
};
export default function JoinModal(props: Props) {
const test = [
{
name: '飞手培训机构',
id: 0
}
];
const router = useRouter();
const [tagList, setTagList] = useState<ListTagResp[]>([]);
useEffect(() => {
......@@ -24,6 +20,11 @@ export default function JoinModal(props: Props) {
setTagList(res.result || []);
})
}, [])
const onClickTag = (item: ListTagResp) => {
router.replace("/JoinPolicy?tagId=" + item.id);
props.onCancel && props.onCancel();
};
return (
<Modal
title="申请合作加盟"
......@@ -40,9 +41,13 @@ export default function JoinModal(props: Props) {
<Col
key={item.id}
style={{ cursor: "pointer", height: 100, padding: 0 }}
onClick={() => onClickTag(item)}
>
<Image src={img} width={100} height={100} alt=""></Image>
<div className={styles.identityBtn}>{item.tagName}{">"}</div>
<div className={styles.identityBtn}>
{item.tagName}
{">"}
</div>
</Col>
);
})}
......
import React from "react";
import React, { useEffect } from "react";
import { Empty } from "antd";
import { Box , WaterfallBox } from "./styled";
import { LeftBox , Box , WaterfallBox } from "./styled";
import { leftBoxProps } from "../interface";
export default function Left(props: leftBoxProps) {
const { boxIndex, leftRenderDom, leftcontentstyle, leftWaterfallDom } = props;
const { boxIndex, leftRenderDom, leftcontentstyle, leftWaterfallDom } = props;
return (
<div>
<LeftBox>
{leftRenderDom?.columns.map((item) => {
if (item.noFor) {
return item.element;
......@@ -30,7 +30,7 @@ export default function Left(props: leftBoxProps) {
return null;
})}
{
<WaterfallBox index={boxIndex} leftcontentstyle={leftcontentstyle}>
leftWaterfallDom?.columns.length ? <WaterfallBox index={boxIndex} leftcontentstyle={leftcontentstyle}>
{ <div className="left-columns">
{leftWaterfallDom?.columns.map((item) => {
if (!item.noFor && item.type === "left") {
......@@ -47,12 +47,12 @@ export default function Left(props: leftBoxProps) {
return null
})}
</div>}
</WaterfallBox>
</WaterfallBox> : null
}
{leftRenderDom?.pagination ? leftRenderDom?.pagination : null}
{!leftRenderDom?.columns.length && !leftWaterfallDom?.columns.length? (
<Empty description={"暂无数据"} />
) : null}
</div>
</LeftBox>
);
}
......@@ -12,6 +12,9 @@ export interface BoxProps {
}
}
export const LeftBox = styled.div`
box-sizing: border-box;
`
export const Box = styled.div<BoxProps>`
box-sizing: border-box;
......@@ -39,7 +42,7 @@ export const WaterfallBox = styled.div<BoxProps>`
box-sizing: border-box;
display: flex;
width: ${props => props.leftcontentstyle?.width ? props.leftcontentstyle?.width : "790px"};
.item{
.item{
// 每个元素都要设置右边距margin-right(每个元素的左右间隙)
// 同时设置下边距margin-bottom(每个元素的上下间隙)
/* margin: 0 24px 15px 0; */
......@@ -49,15 +52,13 @@ export const WaterfallBox = styled.div<BoxProps>`
// 这里的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});
// 每行最右侧的那个不设置右外边距
&:nth-child(${props => props.index}n + ${props => props.index}) {
margin-right: 0;
}
}
.left-columns{
}
.right-columns{
.item{
margin-right: 0;
}
}
`
\ No newline at end of file
`
components/footer/assets/fuwuhao.png

14.1 KB | W: | H:

components/footer/assets/fuwuhao.png

23.9 KB | W: | H:

components/footer/assets/fuwuhao.png
components/footer/assets/fuwuhao.png
components/footer/assets/fuwuhao.png
components/footer/assets/fuwuhao.png
  • 2-up
  • Swipe
  • Onion skin
components/footer/assets/mmc.png

3.8 KB | W: | H:

components/footer/assets/mmc.png

11.7 KB | W: | H:

components/footer/assets/mmc.png
components/footer/assets/mmc.png
components/footer/assets/mmc.png
components/footer/assets/mmc.png
  • 2-up
  • Swipe
  • Onion skin
components/footer/assets/shequn.png

14.8 KB | W: | H:

components/footer/assets/shequn.png

16.6 KB | W: | H:

components/footer/assets/shequn.png
components/footer/assets/shequn.png
components/footer/assets/shequn.png
components/footer/assets/shequn.png
  • 2-up
  • Swipe
  • Onion skin
import React, { useEffect, useState } from "react";
import { AutoComplete, Modal } from "antd";
import Image from "next/image";
import api from "~/api";
type Props = {
open: boolean;
......@@ -20,6 +21,8 @@ export default function LoginModal(props: Props) {
style: "",
href: "",
});
window.setUserId(1);
}
}, [props.open]);
......
import { serialize, parse } from "cookie";
const TOKEN_NAME = "token";
export const MAX_AGE = 60 * 60 * 8; // 8 hours
export function setTokenCookie(res, token) {
const cookie = serialize(TOKEN_NAME, token, {
maxAge: MAX_AGE,
expires: new Date(Date.now() + MAX_AGE * 1000),
httpOnly: true,
secure: process.env.NODE_ENV === "production",
path: "/",
sameSite: "lax",
});
res.setHeader("Set-Cookie", cookie);
}
export function removeTokenCookie(res) {
const cookie = serialize(TOKEN_NAME, "", {
maxAge: -1,
path: "/",
});
res.setHeader("Set-Cookie", cookie);
}
export function parseCookies(req) {
// For API Routes we don't need to parse the cookies.
if (req.cookies) return req.cookies;
// For pages we do need to parse the cookies.
const cookie = req.headers?.cookie;
return parse(cookie || "");
}
export function getTokenCookie(req) {
const cookies = parseCookies(req);
return cookies[TOKEN_NAME];
}
import Iron from "@hapi/iron";
import { MAX_AGE, setTokenCookie, getTokenCookie } from "./auth-cookies";
const TOKEN_SECRET = process.env.TOKEN_SECRET;
export async function setLoginSession(res, session) {
const createdAt = Date.now();
// Create a session object with a max age that we can validate later
const obj = { ...session, createdAt, maxAge: MAX_AGE };
const token = await Iron.seal(obj, TOKEN_SECRET, Iron.defaults);
setTokenCookie(res, token);
}
export async function getLoginSession(req) {
const token = getTokenCookie(req);
if (!token) return;
const session = await Iron.unseal(token, TOKEN_SECRET, Iron.defaults);
const expiresAt = session.createdAt + session.maxAge * 1000;
// Validate the expiration date of the session
if (Date.now() > expiresAt) {
throw new Error("Session expired");
}
return session;
}
import { useEffect, useState } from "react";
import api, { UserInfoResp } from "~/api";
/*
const fetcher = (url) =>
fetch(url)
.then((r) => r.json())
.then((data) => {
return { user: data?.user || null };
}); */
export function useUser() {
const [user, setUser] = useState(null);
useEffect(() => {
api
.userInfo({
userAccountId: 0,
})
.then((res) => {
setUser(res.result);
});
}, []);
return user;
}
import { useEffect, useState } from "react";
import api, { UserInfoResp } from "~/api";
/*
const fetcher = (url) =>
fetch(url)
.then((r) => r.json())
.then((data) => {
return { user: data?.user || null };
}); */
export function useUser() {
const [user, setUser] = useState<UserInfoResp | null>(null);
const [userAccountId, setUserAccountId] = useState<number | "">('');
useEffect(() => {
setUserAccountId(Number(window.localStorage.getItem('userId')));
window.setUserId = (id) => {
setUserAccountId(id);
window.localStorage.setItem('userId', id);
};
try {
let userInfo = JSON.parse(window.localStorage.getItem('userInfo') || '') || null;
setUser(userInfo);
} catch (e) { }
}, [])
useEffect(() => {
if (!user && userAccountId) {
api
.userInfo({
userAccountId: userAccountId,
})
.then((res) => {
setUser(res.result || null);
window.localStorage.setItem('userInfo', JSON.stringify(res.result || ''));
});
}
}, [userAccountId]);
return user;
}
export function useGeolocation() {
const [position, setPosition] = useState<{
position?: any,
address?: any
} | null>(null);
useEffect(() => {
const AMapLoader = require("@amap/amap-jsapi-loader");
window._AMapSecurityConfig = {
securityJsCode: 'b00440e4bf3989bb2481297acaa05908',
}
AMapLoader.load({
key: "826769d41d66ebd005ffa0b3e0013781", // 申请好的Web端开发者Key,首次调用 load 时必填
version: "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
plugins: ['AMap.Geolocation', "AMap.Geocoder"], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
})
.then(async (AMap: any) => {
//用户定位
const geolocation = new AMap.Geolocation({
enableHighAccuracy: true,//是否使用高精度定位,默认:true
timeout: 10000, //超过10秒后停止定位,默认:5s
position: 'RB', //定位按钮的停靠位置
offset: [10, 20], //定位按钮与设置的停靠位置的偏移量,默认:[10, 20]
zoomToAccuracy: true, //定位成功后是否自动调整地图视野到定位点
});
geolocation.getCurrentPosition(function (status: string, result: any) {
if (status == 'complete') {
onComplete(result)
} else {
onError(result)
}
});
//解析定位结果
async function onComplete(data: any) {
console.log('定位成功', data.position);
setPosition({
position: data.position,
address: null
})
var geocoder = new AMap.Geocoder({
city: '全国' // city 指定进行编码查询的城市,支持传入城市名、adcode 和 citycode
})
geocoder.getAddress([data.position.lng, data.position.lat], function (status: string, result: {info: string, regeocode?: {
formattedAddress: string,
addressComponent: {
adcode: string
}
}}) {
console.log('获取地址结果', result)
if (status === 'complete' && result.info === 'OK') {
// result为对应的地理位置详细信息
setPosition({
...position,
address: result.regeocode
})
}else{
setPosition({
...position,
address: null
})
}
})
}
//解析定位错误信息
async function onError(data: any) {
// message.error(`定位失败
// 失败原因排查信息:${data.message}
// 浏览器返回信息:${data.originMessage}
// `)
}
})
.catch((e: any) => {
console.log(e);
});
}, [])
return position
}
\ No newline at end of file
import Local from "passport-local";
import { findUser, validatePassword } from "./user";
export const localStrategy = new Local.Strategy(function (
username,
password,
done
) {
findUser({ username })
.then((user) => {
if (user && validatePassword(user, password)) {
done(null, user);
} else {
done(new Error("Invalid username and password combination"));
}
})
.catch((error) => {
done(error);
});
});
import crypto from "crypto";
import { v4 as uuidv4 } from "uuid";
/**
* User methods. The example doesn't contain a DB, but for real applications you must use a
* db here, such as MongoDB, Fauna, SQL, etc.
*/
const users = [];
export async function createUser({ username, password }) {
// Here you should create the user and save the salt and hashed password (some dbs may have
// authentication methods that will do it for you so you don't have to worry about it):
const salt = crypto.randomBytes(16).toString("hex");
const hash = crypto
.pbkdf2Sync(password, salt, 1000, 64, "sha512")
.toString("hex");
const user = {
id: uuidv4(),
createdAt: Date.now(),
username,
hash,
salt,
};
// This is an in memory store for users, there is no data persistence without a proper DB
users.push(user);
return { username, createdAt: Date.now() };
}
// Here you should lookup for the user in your DB
export async function findUser({ username }) {
// This is an in memory store for users, there is no data persistence without a proper DB
return users.find((user) => user.username === username);
}
// Compare the password of an already fetched user (using `findUser`) and compare the
// password for a potential match
export function validatePassword(user, inputPassword) {
const inputHash = crypto
.pbkdf2Sync(inputPassword, user.salt, 1000, 64, "sha512")
.toString("hex");
const passwordsMatch = user.hash === inputHash;
return passwordsMatch;
}
......@@ -35,10 +35,12 @@
"@types/styled-components": "^5.1.26",
"babel-plugin-styled-components": "^2.1.1",
"cookie": "^0.5.0",
"moment": "^2.29.4",
"next-connect": "^1.0.0",
"passport": "^0.6.0",
"passport-local": "^1.0.0",
"pinyin-pro": "^3.14.0",
"react-infinite-scroll-component": "^6.1.0",
"styled-components": "^6.0.0-rc.1",
"swiper": "^9.3.2",
"swr": "^2.1.5",
......
import request, { Response } from "~/api/request"
export interface CooperationApplyParams {
applyName: string,
applyPhone: string,
remark?: string,
userAccountId: number,
cooperationTagId: number
}
export default {
//请加盟
cooperationApply(params: CooperationApplyParams):Promise<Response<string>>{
return request('/userapp/cooperation/apply', 'post', params)
}
}
\ No newline at end of file
.banner {
width: 100vw;
height: 200px;
background: #f25834;
position: absolute;
left: 50%;
top: 0;
transform: translate(-50%, 0);
}
.font1 {
font-size: 20px;
font-family: MicrosoftYaHeiUI-Bold, MicrosoftYaHeiUI;
font-weight: bold;
color: #000000;
line-height: 25px;
}
.font2 {
font-size: 18px;
font-family: MicrosoftYaHei;
color: #333333;
line-height: 32px;
letter-spacing: 1px;
}
import { Button, Col, Divider, Form, Input, Row } from "antd";
import { useRouter } from "next/router";
import LayoutView from "~/components/layout";
import api from "./api";
import styles from "./index.module.scss";
export default function JoinPolicy() {
const router = useRouter();
//提交
const onFinish = (values: any) => {
console.log(values);
api
.cooperationApply({
...values,
userAccountId: 1,
cooperationTagId: router.query.tagId,
})
.then((res) => {
console.log("提交结果", res);
if (res.result === "已通过") {
window.messageApi.success(res.result);
setTimeout(() => {
router.push("/");
}, 1500);
} else {
window.messageApi.error(res.message);
}
});
};
return (
<LayoutView>
<div className={styles.banner}></div>
<div
className="page"
style={{
background: "#fff",
position: "relative",
zIndex: 1,
marginTop: 60,
paddingBottom: 63,
}}
>
<div
className={styles.font1}
style={{ textAlign: "center", paddingTop: 40, paddingBottom: 30 }}
>
加盟入驻政策
</div>
<div
className={styles.font2}
style={{ paddingLeft: 50, paddingRight: 50 }}
>
1.每月自动获取一张高额度优惠券
<br /> 2.流量扶持和店铺曝光度
<br />
3成为科比特指定官方生态合作伙伴
<br /> 4.享受不低于1亿制造采购补贴
<br /> 5.无人机产业链资源共享
<br /> 6.享受价值5亿产业链订单
<br /> 7.享受一对一代运营服务
<br /> 8.享受无人机生态圈资源
<br /> 9.成为科比特指定官方生态
<br />
10.享受无人机生态圈资源合作伙伴
<br />
11.受价值5亿产业链订单
<br />
12.成为科比特指定官方生态合作伙伴
</div>
<Divider />
<div
className={styles.font1}
style={{ textAlign: "center", marginBottom: 28 }}
>
申请加盟入驻
</div>
<Row justify={"center"}>
<Col style={{ width: 400 }}>
<Form labelAlign="left" labelCol={{ span: 5 }} onFinish={onFinish}>
<Form.Item
label="姓名"
name="applyName"
rules={[{ required: true }]}
>
<Input placeholder="请输入姓名"></Input>
</Form.Item>
<Form.Item
label="联系方式"
name="applyPhone"
rules={[{ required: true }]}
>
<Input placeholder="请输入手机号"></Input>
</Form.Item>
<Form.Item name="remark">
<Input.TextArea placeholder="其它信息"></Input.TextArea>
</Form.Item>
<Row justify={"center"}>
<Button
type="primary"
htmlType="submit"
style={{ marginTop: 11, width: 200 }}
size="large"
>
提交申请
</Button>
</Row>
</Form>
</Col>
</Row>
</div>
</LayoutView>
);
}
import request, { Response } from "~/api/request"
export interface CompanyAuthParams {
companyName: string;
creditCode: string;
licenseImg: string;
userAccountId: number;
authStatus: 1 | 0
}
export default {
//提交企业认证
companyAuth(params: CompanyAuthParams):Promise<Response<string>>{
return request('/userapp/company-auth/add', 'post', params)
}
}
\ No newline at end of file
.font1 {
font-size: 20px;
font-family: MicrosoftYaHeiUI-Bold;
font-weight: bold;
color: #000000;
line-height: 25px;
}
.font2 {
font-size: 14px;
font-family: MicrosoftYaHei;
color: #989ca7;
line-height: 19px;
}
.banner {
width: 100vw;
height: 200px;
background: #1e3d7d;
position: absolute;
left: 50%;
top: 0;
transform: translate(-50%, 0);
}
.upload {
text-align: center;
margin-top: 40px;
:global .ant-upload {
width: 260px !important;
height: 172px !important;
}
}
\ No newline at end of file
import { LoadingOutlined, PlusOutlined } from "@ant-design/icons";
import { Col, Form, Input, Row, Upload, message, Button, Image } from "antd";
import type { UploadChangeParam } from "antd/es/upload";
import type { RcFile, UploadFile, UploadProps } from "antd/es/upload/interface";
import { useState } from "react";
import config from "~/api/config";
import Layout from "~/components/layout";
import api from "./api";
import styles from "./index.module.scss";
import gApi from '~/api';
const beforeUpload = (file: RcFile) => {
const isJpgOrPng = file.type === "image/jpeg" || file.type === "image/png";
if (!isJpgOrPng) {
message.error("You can only upload JPG/PNG file!");
}
//限制上传10M
const isLt2M = file.size / 1024 / 1024 < 10;
if (!isLt2M) {
message.error("Image must smaller than 2MB!");
}
return isJpgOrPng && isLt2M;
};
const normFile = (e: any) => {
console.log("Upload event:", e);
if (Array.isArray(e)) {
return e;
}
return e?.fileList;
};
export default function Certification() {
const [loading, setLoading] = useState(false);
const [imageUrl, setImageUrl] = useState<string>();
//上传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,
userAccountId: 1
})
.then((res) => {
console.log('提交结果', res);
if(res.result === '已通过'){
window.messageApi.success(res.result);
}
});
};
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
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)" }}
>
<Input bordered={false} placeholder="请输入企业名称"></Input>
</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}
>
{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>
);
}
......@@ -3,10 +3,11 @@ import {useRouter} from 'next/router';
import Layout from "~/components/layout";
import {Box} from './styled';
import ImagePreview from './components/picture-preview';
import { Button , Image as AImage , Divider } from 'antd';
import { Button , Image as AImage , Divider , Select,Modal ,Tag,Space,Form,message} from 'antd';
import Image from 'next/image';
import errImg from "~/assets/errImg";
import api,{GetWebDeviceDetailResult} from './api';
import api,{GetWebDeviceDetailResult,GetWebDeviceWareSkuById} from './api';
const {CheckableTag } = Tag
export default function EquipmentLeasingDetail() {
const router = useRouter();
......@@ -14,6 +15,7 @@ export default function EquipmentLeasingDetail() {
const [id, setId] = useState<number | null>(null);
const [detail,setDetail] = useState<GetWebDeviceDetailResult | null>()
const [wareSkuList,setWareSkuList] = useState<GetWebDeviceWareSkuById[] | undefined>()
useEffect(()=>{
setId(Number(router.query.id))
......@@ -28,9 +30,98 @@ export default function EquipmentLeasingDetail() {
.then((res) => {
setDetail(res.result || null);
});
api
.listWareSkuById({
id:id
})
.then((res) => {
res.result?.map(item=>{
return item
})
setWareSkuList(res.result || undefined);
});
}
},[id])
//租赁弹框
const [isModalOpen, setIsModalOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
const tagsData = ['3-7天', '8-15天', '16-30天', '30天以上'];
const [selectedTags, setSelectedTags] = useState<number>();
const [selectedTagsData, setSelectedTagsData] = useState<string>();
const showModal = () => {
setIsModalOpen(true);
if (wareSkuList?.length) {
setSelectedTags(wareSkuList[0].id);
form.setFieldValue("id",wareSkuList[0].id)
setSelectedTagsData("3-7天")
form.setFieldValue("date","3-7天")
}
};
const handleOk = () => {
setLoading(true);
form
.validateFields()
.then(async (values) => {
form.resetFields()
message.success("租赁成功")
setLoading(false);
setIsModalOpen(false);
// try{
// const res = await api.listWareSkuUpdate(values)
// if (res.code === "200") {
// setLoading(false);
// setIsModalOpen(false);
// form.resetFields()
// message.success('租赁成功')
// }else{
// setLoading(false);
// message.error(res.message)
// }
// }catch(e:any){
// message.error(e.message)
// }
}).catch((err) => {
message
.warning({
content: err.errorFields[0].errors[0],
})
.then();
setLoading(false);
});
};
const handleCancel = () => {
setIsModalOpen(false);
};
const handleChange = (tag: number, checked: boolean) => {
if (checked) {
const nextWareSkuList = checked
? tag
: wareSkuList?.filter((t) => t.id !== tag)[0].id;
console.log('You are interested in: ', nextWareSkuList);
setSelectedTags(nextWareSkuList);
form.setFieldValue("id",tag)
}
};
const handleChangeDate = (tag: string, checked: boolean) => {
if (checked) {
const nextSelectedTags = checked
? tag
: tagsData.filter((t) => t !== tag)[0];
console.log('You are interested in: ', nextSelectedTags);
setSelectedTagsData(nextSelectedTags);
form.setFieldValue("date",tag)
}
};
return (
<Layout>
<Box>
......@@ -45,13 +136,36 @@ export default function EquipmentLeasingDetail() {
</div>) : (<div className='function not'></div>)
}
<div className='menoy'>
<span className='menoy-left'>¥{detail?.minRent}</span>
<span className='menoy-left'>{`¥${detail?.minRent}`}</span>
<span className='menoy-right'>/天起</span>
</div>
<div className='classification'></div>
<div className='classification'>
<div className='top'>
<div className='left'>
<span className='label'>选择</span>
<span className='value'>商品分类</span>
</div>
<div className='right'>
<Select
className="selectItem"
defaultActiveFirstOption
defaultValue={wareSkuList}
style={{ width: 120 }}
bordered={false}
options={wareSkuList}
fieldNames={{label:"skuTitle",value:"id"}}
placeholder="选择商品"
/>
</div>
</div>
<div className='bottom'>
<span className='label'>发货</span>
<span className='value'>顺丰到付</span>
</div>
</div>
<div className='botton-btn'>
<Button className='btn-left' size='small' type="primary">成为渠道商</Button>
<Button className='btn-right' size='small' type="primary">立即租赁</Button>
<Button className='btn-right' size='small' type="primary" onClick={showModal}>立即租赁</Button>
</div>
</div>
</div>
......@@ -66,6 +180,71 @@ export default function EquipmentLeasingDetail() {
detail?.wareDetailContent ? <div style={{ textAlign: "center" }} dangerouslySetInnerHTML={{ __html: detail?.wareDetailContent}}>
</div> : <div style={{ textAlign: "center" }} ></div>
}
{/* 立即租赁 */}
<Modal
wrapClassName='application'
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
getContainer={false}
footer={[
<Button
style={{ width: "100%" ,height: 44 }}
key="submit"
type="primary"
loading={loading}
onClick={handleOk}
>
立即租赁
</Button>,
]}
>
<div className='title'>
<div className="left"></div>
<div className="right">
<div className="top">
<span className='tag'>¥</span>
<span className='money'>{detail?.minRent}</span>
<span className='unit'>/天</span>
</div>
<div className="bottom">渠道免押金</div>
</div>
</div>
<Form
form={form}
layout="vertical"
name="application"
initialValues={{ modifier: 'public' }}
>
<Form.Item style={{flex:1,marginRight:16}} name="id" label="选择商品">
<Space size={[0, 8]} wrap>
{wareSkuList?.map((tag) => (
<CheckableTag
style={{height:28,lineHeight:"28px"}}
key={tag.id}
checked={wareSkuList?.some(item=>tag.id === selectedTags)}
onChange={(checked) => handleChange(tag.id, checked)}
>
{tag.skuTitle}
</CheckableTag>
))}
</Space>
</Form.Item>
<Form.Item style={{flex:1,marginRight:16}} name="date" label="租期天数(拿到和归还当天不算入租期)">
<Space size={[0, 8]} wrap>
{tagsData.map((tag) => (
<CheckableTag
key={tag}
checked={tag === selectedTagsData}
onChange={(checked) => handleChangeDate(tag, checked)}
>
{tag}
</CheckableTag>
))}
</Space>
</Form.Item>
</Form>
</Modal>
</Box>
</Layout>
)
......
......@@ -34,9 +34,48 @@ export interface GetWebDeviceDetailResult {
wareDetailContent: string | TrustedHTML
}
export interface PriceList {
id: number,
wareInfoId: number,
skuInfoId: number,
rentPrice: number,
minDay: number,
maxDay: number,
createTime: null
}
export interface GetWebDeviceWareSkuById {
id: number,
wareInfoId: number,
skuTitle: string,
rentPrice: number | null,
rentDeposit: number,
stockNum: number,
saleNum: number,
createTime: string,
updateTime: null,
skuPriceDTOList: Array<PriceList>,
}
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/webDevice/detail', 'get', params)
},
//web-设备租赁-商品
listWareSkuById: (params: GetWebDeviceDetailParams): Promise<Response<GetWebDeviceWareSkuById[]>> => {
return request('/pms/appDevice/listWareSkuById', 'get', params)
},
//web-设备租赁-立即租赁
listWareSkuUpdate: (params: WebDeviceUpdateParams): Promise<Response<number>> => {
return request('/pms/appDevice/update', 'post', params)
}
}
\ No newline at end of file
......@@ -56,9 +56,40 @@ export const Box = styled.div`
}
.classification{
margin-top: 28px;
width: 300px;
width: 375px;
height: 50px;
background-color: pink;
.label{
height: 21px;
font-size: 16px;
font-family: MicrosoftYaHei;
color: #9A9A9A;
line-height: 21px;
margin-right: 36px;
}
.value{
height: 21px;
font-size: 16px;
font-family: MicrosoftYaHei;
color: #151515;
line-height: 21px;
}
.top{
display: flex;
justify-content: space-between;
align-items: center;
.left{
}
.right{
.selectItem{
.ant-select-selection-placeholder {
color: #000;
}
}
}
}
.bottom{
margin-top: 5px;
}
}
.botton-btn{
margin-top: 30px;
......@@ -123,4 +154,49 @@ export const Box = styled.div`
color: #989898;
}
}
.application{
.title{
display: flex;
align-items: center;
padding-bottom: 25px;
.left{
width: 58px;
height: 58px;
background: #D8D8D8;
border-radius: 2px;
}
.right{
margin-left: 15px;
.top{
.tag{
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #FF0F0F;
}
.money{
font-size: 22px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #FF0F0F;
}
.unit{
font-size: 12px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #FF0F0F;
}
}
.bottom{
width: 65px;
height: 18px;
font-size: 13px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #121212;
line-height: 18px;
}
}
}
}
`
\ No newline at end of file
......@@ -59,7 +59,7 @@ export default function EquipmentLeasing(props: Props) {
const rightDom = (item: Advertisement) => {
return (
<div key={item.id} className="right-box-item right-item">
<Image src={item.imageUrl} alt="error" width={270} height={422} />
<Image src={item.imageUrl} alt="error" width={180} height={295} />
</div>
);
};
......@@ -69,7 +69,7 @@ export default function EquipmentLeasing(props: Props) {
const [abort, setAbort] = useState<AbortController | null>(null); //请求中断
const [pageParams, setPageParams] = useState({
pageNo: 1,
pageSize: 16,
pageSize: 15,
}); //分页器对象
const onPageChange = (page: number, pageSize: number) => {
......@@ -141,9 +141,9 @@ export default function EquipmentLeasing(props: Props) {
></Filter>
<div style={{ paddingTop: 13 }}>
<ContentBox
boxIndex={4}
boxIndex={5}
leftcontentstyle={{
width: "916px",
width: "1020px",
margin: { top: 0, right: "12px", bottom: "12px", left: 0 },
}}
leftRenderDom={{
......
......@@ -10,16 +10,17 @@ export const Box = styled.div`
width: 1200px;
.item {
width: 220px;
height: 205px;
background: #ffffff;
border-radius: 6px;
cursor: pointer;
transition: all 0.5s;
&-top {
display: flex;
justify-content: center;
align-items: center;
height: 145px;
background: #ffffff;
border-radius: 6px 6px 0px 0px;
padding: 19px 52px 10px 52px;
&-image {
width: 116px;
height: 116px;
......@@ -27,9 +28,8 @@ export const Box = styled.div`
}
}
&-bottom {
padding: 10px 13px 14px 18px;
padding: 0px 13px 0px 18px;
&-title {
width: 189px;
height: 24px;
font-size: 14px;
font-family: MicrosoftYaHeiUI-Bold, MicrosoftYaHeiUI;
......@@ -65,8 +65,8 @@ export const Box = styled.div`
}
}
.right-item {
width: 270px;
height: 422px;
width: 180px;
height: 295px;
background: #d8d8d8;
border-radius: 6px;
overflow: hidden;
......
import React from "react";
import React, { useEffect, useState } from "react";
import Layout from "~/components/layout";
import { Box } from "./styled";
import { Button } from "antd";
import { useRouter } from "next/router";
import { ParsedUrlQuery } from "querystring";
interface RouterDetail {
videoUrl:string | '',
curriculumName:string
}
export default function FlyingDetail() {
const router = useRouter();
const [detail,setDetail] =useState<ParsedUrlQuery | RouterDetail>()
useEffect(()=>{
setDetail(router.query)
},[router])
return (
<Layout>
<Box>
<div className="box-top">
<div className="left">科比特行业课程1111</div>
<div className="left">{detail?.curriculumName}</div>
<div className="right">
<Button
type="primary"
......@@ -24,7 +35,7 @@ export default function FlyingDetail() {
</div>
</div>
<div className="box-body">
<video className="body-video" src="" />
<video className="body-video" controls src={detail?.videoUrl as string} />
</div>
</Box>
</Layout>
......
......@@ -39,7 +39,15 @@ export default function FlyingHandService() {
<div
className="item"
key={item.id}
onClick={() => router.push(`/flyingHandService/detail/${item.id}`)}
onClick={() => {
router.push({
pathname: `/flyingHandService/detail/${item.id}`,
query: {
videoUrl: item.videoUrl ,
curriculumName: item.curriculumName
},
})
} }
>
<div className="item-top">
<Image
......@@ -193,17 +201,34 @@ export default function FlyingHandService() {
const [isModalOpen, setIsModalOpen] = useState(false);
const [loading, setLoading] = useState(false);
const handleOk = async (values: any) => {
const value = form.getFieldsValue()
setLoading(true);
try{
const res = await api.PilotRegistrations(value)
form
.validateFields()
.then(async (values) => {
setLoading(true);
try{
const res = await api.PilotRegistrations(values)
if (res.code === "200") {
setLoading(false);
setIsModalOpen(false);
form.resetFields()
message.success('报名成功')
}else{
setLoading(false);
message.error(res.message)
}
}catch(e:any){
message.error(e.message)
}
}).catch((err) => {
message
.warning({
content: err.errorFields[0].errors[0],
})
.then();
});
};
const handleCancel = () => {
......@@ -309,21 +334,21 @@ export default function FlyingHandService() {
<div style={{display:"flex",justifyContent:"space-between"}}>
<Form.Item style={{flex:1,marginRight:16}}
name="name"
rules={[{ required: true, message: 'Please input the title of collection!' }]}
rules={[{ required: true, message: '请输入姓名!' }]}
>
<Input placeholder="姓名" />
</Form.Item>
<Form.Item style={{flex:1}} name="telephone">
<Form.Item style={{flex:1}} name="telephone" rules={[{ required: true, message: '请输入手机号!' }]}>
<Input placeholder="手机号" />
</Form.Item>
</div>
<Form.Item
name="city"
rules={[{ required: true, message: 'Please select gender!' }]}
rules={[{ required: true, message: '请选择城市!' }]}
>
<Cascader
allowClear
placeholder="地域"
placeholder="城市"
className="selectItem"
size="large"
fieldNames={{
......@@ -338,16 +363,14 @@ export default function FlyingHandService() {
<Form.Item
name="drivingLicense"
rules={[{ required: true, message: 'Please select gender!' }]}
>
<Select placeholder="是否有驾照">
<Select allowClear placeholder="是否有驾照">
<Option value="0"></Option>
<Option value="1"></Option>
</Select>
</Form.Item>
<Form.Item
name="uavLicenseLevelOne"
rules={[{ required: true, message: 'Please select gender!' }]}
>
<Cascader
allowClear
......
import request, { Response } from "~/api/request";
export interface DynamicListParams {
pageNo: number,
pageSize: number,
userId: number
pageNo: number;
pageSize: number;
userId: number;
}
export interface Dynamic {
......@@ -31,6 +31,24 @@ export interface DynamicListResp {
totalPage: 0;
}
export interface CommentParams {
content: string; //评论内容
dynamicId: number; //动态id
parentId?: number; //父级评论
userId: number; //用户id
}
export interface ByDynamicParams {
id: number;
dynamicId: number;
parentId: number;
userId: number;
content: string;
likesCount: number;
createTime: string;
children: ByDynamicParams[];
}
export default {
/**
* 论坛动态列表
......@@ -40,4 +58,14 @@ export default {
dynamicList(params: DynamicListParams): Promise<Response<DynamicListResp>> {
return request("/release/dynamic/dynamicList", "get", params);
},
//评论
comment(params: CommentParams): Promise<Response<null>> {
return request("/release/dynamic/comment", "post", params);
},
//根据动态查看评论
byDynamic(params: { dynamicId: number }): Promise<Response<Array<ByDynamicParams>>> {
return request("/release/dynamic/byDynamic", "get", params);
},
};
import request, { Response } from "~/api/request";
export interface PublishParams {
lat?: number; //纬度
lon?: number; //经度
description: string; //描述
userId: number; //用户id
mediaVO: {
//发布图片
picture: Array<string>;
};
}
export default {
//动态发布
publish(params: PublishParams): Promise<Response<null>> {
return request("/release/dynamic/publish", "post", params);
},
};
import { PlusOutlined } from "@ant-design/icons";
import { Form, Input, Modal, Upload } from "antd";
import { Form, Input, Modal, Upload, Image, Button, Row, Col } from "antd";
import type { RcFile, UploadProps } from "antd/es/upload";
import type { UploadFile } from "antd/es/upload/interface";
import { useState } from "react";
import Image from "next/image";
const getBase64 = (file: RcFile): Promise<string> =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result as string);
reader.onerror = (error) => reject(error);
});
import gApi from "~/api";
import NImage from "next/image";
import api from "./api";
import { useGeolocation, useUser } from "~/lib/hooks";
type Props = {
open: boolean;
onCancel: () => void;
onOk?: () => void;
onCancel?: () => void;
};
const normFile = (e: any) => {
console.log("Upload event:", e);
if (Array.isArray(e)) {
return e;
}
return e?.fileList;
};
export default function PublishMessage(props: Props) {
......@@ -24,98 +27,110 @@ export default function PublishMessage(props: Props) {
const [previewOpen, setPreviewOpen] = useState(false);
const [previewImage, setPreviewImage] = useState("");
const [previewTitle, setPreviewTitle] = useState("");
const [fileList, setFileList] = useState<UploadFile[]>([
{
uid: "-1",
name: "image.png",
status: "done",
url: "https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png",
},
{
uid: "-2",
name: "image.png",
status: "done",
url: "https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png",
},
{
uid: "-3",
name: "image.png",
status: "done",
url: "https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png",
},
{
uid: "-4",
name: "image.png",
status: "done",
url: "https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png",
},
{
uid: "-xxx",
percent: 50,
name: "image.png",
status: "uploading",
url: "https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png",
},
{
uid: "-5",
name: "image.png",
status: "error",
},
]);
const handleOk = () => {
setConfirmLoading(true);
setTimeout(() => {
handleCancel();
setConfirmLoading(false);
}, 2000);
};
const handleCancel = () => {
props.onCancel && props.onCancel();
};
const [fileList, setFileList] = useState<UploadFile[]>([]);
const [showLoading, setShowLoad] = useState(false);
const [form] = Form.useForm();
const user = useUser();
const position = useGeolocation();
//预览关闭
const handlePreviewCancel = () => setPreviewOpen(false);
//图片预览
const handlePreview = async (file: UploadFile) => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj as RcFile);
if (file.url) {
setPreviewImage(file.url);
setPreviewOpen(true);
setPreviewTitle(
file.name || file.url!.substring(file.url!.lastIndexOf("/") + 1)
);
}
};
setPreviewImage(file.url || (file.preview as string));
setPreviewOpen(true);
setPreviewTitle(
file.name || file.url!.substring(file.url!.lastIndexOf("/") + 1)
);
//图片上传
const handleChange: UploadProps["onChange"] = (info) => {
console.log("uploadChange", info);
if (info.file.status === "uploading") {
let find = fileList.find((item) => {
return item.uid === info.file.uid;
});
if (!find) {
setFileList([...fileList, info.file]);
}
return;
}
if (info.file.status === "done") {
// Get this url from response in real world.
if (info.file.response.code === "200" && info.file.response.result) {
let fileList1 = fileList.map((item) => {
if (item.uid === info.file.uid) {
info.file.url = info.file.response.result?.[0];
return info.file;
}
return item;
});
setFileList([...fileList1]);
} else {
window.messageApi.error(info.file.response?.message || "上传失败");
info.fileList = fileList.filter((item) => {
return item.uid !== info.file.uid;
});
setFileList([...info.fileList]);
}
}
};
const handleChange: UploadProps["onChange"] = ({ fileList: newFileList }) =>
setFileList(newFileList);
//提交
const onFinish = (values: any) => {
console.log(values);
setShowLoad(true);
api
.publish({
lat: position?.position?.lat, //纬度
lon: position?.position?.lng, //经度
title: '', //标题
description: values.description, //描述
userId: user!.id, //用户id
mediaVO: {
//发布图片
//@ts-ignore
picture: fileList.filter((item) => item.url).map((item) => item.url),
},
})
.then((res) => {
console.log("提交结果", res);
setShowLoad(false);
if (res.code === "200") {
window.messageApi.success("发布成功");
props.onCancel();
props.onOk && props.onOk();
setTimeout(() => {
form.resetFields(["title", "description"]);
setFileList([]);
}, 500);
}
});
};
const uploadButton = (
<div>
<PlusOutlined />
<div style={{ marginTop: 8 }}>Upload</div>
</div>
);
return (
<Modal
title=""
open={props.open}
onOk={handleOk}
onCancel={handleCancel}
onCancel={props.onCancel}
width={500}
confirmLoading={confirmLoading}
okText="发布"
footer={null}
okButtonProps={{ style: { height: 37, padding: "0 32px", fontSize: 16 } }}
cancelButtonProps={{ style: { display: "none" } }}
maskClosable={false}
>
<Form style={{ paddingTop: 32 }}>
<Form.Item>
<Input placeholder="输入标题" style={{ height: 44 }}></Input>
</Form.Item>
<Form.Item>
<Form style={{ paddingTop: 32 }} onFinish={onFinish} form={form}>
<Form.Item
name="description"
rules={[{ required: true }]}
help="请输入内容"
>
<Input.TextArea
allowClear
showCount
......@@ -124,25 +139,56 @@ export default function PublishMessage(props: Props) {
style={{ height: 120, resize: "none" }}
></Input.TextArea>
</Form.Item>
<Form.Item>
<Form.Item valuePropName="picture" getValueFromEvent={normFile}>
<Upload
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
name="uploadFile"
action={gApi.imgOss}
listType="picture-card"
fileList={fileList}
onPreview={handlePreview}
onChange={handleChange}
maxCount={1}
>
{fileList.length >= 8 ? null : uploadButton}
{fileList.length >= 8 ? null : (
<div>
<PlusOutlined />
<div style={{ marginTop: 8 }}>Upload</div>
</div>
)}
</Upload>
<Modal
open={previewOpen}
title={previewTitle}
footer={null}
onCancel={handlePreviewCancel}
bodyStyle={{ textAlign: "center" }}
>
<Image alt="example" style={{ width: "100%" }} src={previewImage} />
<Image alt="example" src={previewImage} preview={false} />
</Modal>
</Form.Item>
<Row justify="space-between" align="middle">
<Col>
<NImage
style={{ verticalAlign: "text-top", marginRight: 7 }}
alt=""
src={require("./assets/position.png")}
width={14}
height={17}
/>
{position?.address?.formattedAddress || "位置"}
</Col>
<Col>
<Button
type="primary"
htmlType="submit"
size="large"
style={{ width: 95 }}
loading={showLoading}
>
发布
</Button>
</Col>
</Row>
</Form>
</Modal>
);
......
......@@ -174,15 +174,27 @@ interface ListTenderNewsInfoParams {
provinceCode?: number;
}
export interface TenderApplyType{
tenderInfoId: number,
tenderNewsId: number,
userAccountId: number
}
export const listNewsApi = {
//新闻列表
listNewsPage: (
params: ListPageNewsInfoParams
): Promise<Response<ListPageNewsInfoResp>> => {
return request("/release/industry-news/listNewsPage", "post", params);
},
//招标列表
listNewTenderInfo: (
params: ListTenderNewsInfoParams
): Promise<Response<ListTenderNewsInfoResp>> => {
return request("/release/tender/listNewTenderInfo", "post", params);
},
};
//web-招标-合作申请提交
tenderApply: (params: TenderApplyType): Promise<Response<number>> => {
return request('/release/tender/apply', 'post', params)
}
}
......@@ -63,10 +63,10 @@ export default function MapComponent() {
}
//解析定位错误信息
async function onError(data:any) {
message.error(`定位失败
失败原因排查信息:${data.message}
浏览器返回信息:${data.originMessage}
`)
// message.error(`定位失败
// 失败原因排查信息:${data.message}
// 浏览器返回信息:${data.originMessage}
// `)
}
await mapEntiy(0)
})
......@@ -85,7 +85,7 @@ export default function MapComponent() {
const list = res.result
?.map((item) => item.locationList)
.flat()
.filter((item: { dizhi: string }) => item.dizhi.includes("广东省"));
.filter((item: { dizhi: string }) => item.dizhi.includes("省"));
const markerList: any = [];
if (list?.length) {
list?.map((item) => {
......@@ -100,7 +100,7 @@ export default function MapComponent() {
lon: userPositioning?.lon || data?.lon || 113.93029,
lat: userPositioning?.lat || data?.lat || 22.53291,
pageNo:1,
pageSize: pageSize || 10
pageSize: pageSize || 40
});
const list = res.result?.list
const markerList: any = [];
......@@ -118,7 +118,7 @@ export default function MapComponent() {
lon: userPositioning?.lon || data?.lon || 113.93029,
lat: userPositioning?.lat || data?.lat || 22.53291,
pageNo:1,
pageSize: 10
pageSize: 40
});
const list = res.result?.list
const markerList: any = [];
......@@ -137,7 +137,7 @@ export default function MapComponent() {
if (index === 0) {
showPositioningInfo(index,data)
}else if (index === 1) {
showFlyerBitmap(index,data)
showFlyerBitmap(index,data,30)
} else if(index === 2) {
showUavBitmap(index,data)
}else{
......
import React, { useEffect, useState } from "react";
import { Space, Select, Button } from "antd";
import { Space, Select, Button, message } from "antd";
import Image from "next/image";
import { useRouter } from "next/router";
import { Box } from "./styled";
......@@ -266,6 +266,31 @@ export default function WaterfallFlowBody() {
}
};
const handleTenderApply = async (item:NewsTenderType)=>{
if (item.apply) return;
message.success("申请成功")
item.apply = 1
// let res = await listNewsApi.tenderApply({
// tenderInfoId: item.id,
// tenderNewsId: item.tenderNewsId,
// userAccountId: 0,
// })
// try{
// if (res.code==="200") {
// message.success("申请成功")
// let res8 = await listNewsApi.listNewTenderInfo({
// pageNo: 1,
// pageSize: 6,
// });
// setRightBottomDomList(rightDom2(res8.result?.list!));
// }else{
// message.error(res.message)
// }
// }catch(e){
// console.log(e);
// }
}
const leftDom = (
item: ColumnsType,
index: number,
......@@ -273,7 +298,7 @@ export default function WaterfallFlowBody() {
option: []
) => {
return (
<div key={item.title} className="item">
<div key={item.title} className={`item ${index>=3?'right-box':''}`}>
<div className="item-title">
<div className="item-left">
<div className="item-left-label" onClick={() => routerPath(index)}>
......@@ -355,7 +380,7 @@ export default function WaterfallFlowBody() {
</div>
<div className="body">
{list?.map((item, index) => (
<div key={item.id} className="body-item">
<div key={item.id} className="body-item" onClick={()=>router.push(`/projectInfo/newsArticle/${item.id}`)}>
<div
className={`item-ranking ${index === 0 ? "one" : ""} ${
index === 1 ? "two" : ""
......@@ -374,7 +399,7 @@ export default function WaterfallFlowBody() {
};
const rightDom2 = (list: Array<NewsTenderType>) => {
if(!list.length) return;
if(!list?.length) return;
return (
<div key={1008} className="right-box-item right-item-second">
<div className="item-box">
......@@ -393,9 +418,12 @@ export default function WaterfallFlowBody() {
{item.tenderContent}
<div className="label-bottom">{item.tenderPrice}</div>
</div>
<div className="item-right">
<div className="left">{`${item.tenderPrice}W`}</div>
<div className="right">申请合作</div>
<div className={`item-right ${item.apply ? 'apply' : ''}`} onClick={()=>handleTenderApply(item)}>
<div className="left">{`${item.tenderPrice}W`}</div>
{
item.apply ? <div className="right">已申请</div> : <>
<div className="right">申请合作</div></>
}
</div>
</div>
))}
......
......@@ -13,7 +13,7 @@ export const Box = styled.div`
margin: 0 auto;
.item {
transition: all 0.5s;
border-radius: 6px;
border-radius: 6px;
&-title {
display: flex;
justify-content: space-between;
......@@ -117,7 +117,7 @@ export const Box = styled.div`
&-label {
cursor: pointer;
font-size: 16px;
font-weight: 100;
font-weight: bold;
&:hover {
color: #ff552d;
}
......@@ -162,6 +162,7 @@ export const Box = styled.div`
align-items: center;
padding: 0 23px 0 19px;
height: 32px;
cursor: pointer;
.item-ranking {
color: #9295a3;
&.one {
......@@ -184,6 +185,9 @@ export const Box = styled.div`
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
&:hover {
color: #ff552d;
}
}
}
}
......@@ -200,13 +204,12 @@ export const Box = styled.div`
height: 48px;
font-size: 16px;
font-family: MicrosoftYaHeiUI-Bold, MicrosoftYaHeiUI;
font-weight: bold;
color: #000000;
line-height: 25px;
&-label {
cursor: pointer;
font-size: 16px;
font-weight: 100;
font-weight: bold;
&:hover {
color: #ff552d;
}
......@@ -282,6 +285,11 @@ export const Box = styled.div`
height: 22px;
background: url(${button.src}) no-repeat;
background-size: contain;
cursor: pointer;
&.apply{
opacity: 0.5;
cursor: not-allowed;
}
.left {
width: 35px;
height: 22px;
......
......@@ -7,6 +7,7 @@ import Layout from "~/components/layout";
import newsApi, { Item } from "../components/news/api";
import api, { DetailsResp } from "./api";
import styles from "./index.module.scss";
import Moment from 'moment';
export default function CaseArticle() {
const router = useRouter();
......@@ -32,13 +33,36 @@ export default function CaseArticle() {
newsApi
.listNewsPage({
pageNo: 1,
pageSize: 5,
pageSize: 10,
})
.then((res) => {
setNewList(res.result?.list || []);
});
}, []);
const fontColor = (i: number) => {
switch (i) {
case 0:
return {
color: "#ff2c46",
};
case 1:
return {
color: "#FF6602",
};
case 2:
return {
color: "#FAA910",
};
default:
return {
color: "#9295A3",
};
}
};
return (
<Layout layoutStyle={{ backgroundColor: "#fff" }}>
<div style={{ paddingTop: 29 }}>
......@@ -49,7 +73,7 @@ export default function CaseArticle() {
className={styles.font2}
style={{ marginTop: 18, marginBottom: 41 }}
>
{data?.createTime} · {data?.caseAuthor}
{data?.createTime && Moment(data?.createTime).format('YYYY-MM-DD')} · {data?.caseAuthor}
</div>
<div
style={{ lineHeight: 1.5 }}
......@@ -61,14 +85,14 @@ export default function CaseArticle() {
<Row
className={styles.font4}
align="middle"
style={{ paddingTop: 24, paddingLeft: 24 }}
style={{ paddingTop: 16, paddingLeft: 20 }}
>
行业新闻
<RightOutlined style={{ fontSize: 16, marginLeft: 15 }} />
<RightOutlined style={{ fontSize: 16, marginLeft: 9 }} />
</Row>
<Row gutter={10} style={{ marginTop: 18 }}>
<Row gutter={10} style={{ marginTop: 8 }}>
<Col span={24}>
{newsList.map((item) => {
{newsList.map((item, i) => {
return (
<Row
key={item.id}
......@@ -80,20 +104,20 @@ export default function CaseArticle() {
}}
>
<Col
className={`${styles.font3} ${styles.ellipse2}`}
style={{ width: 217, padding: "13px 0 17px 20px" }}
className={`${styles.font3}`}
style={{ margin: "6px 23px 6px 19px", width: "100%" }}
>
{item.newsTitle}
</Col>
<Col style={{ paddingRight: 16 }}>
<Image
src={item.surfaceImg}
width={90}
height={60}
preview={false}
fallback={errImg}
style={{ borderRadius: 6 }}
></Image>
<Row wrap={false}>
<Col
span={2}
style={{ textAlign: "center", ...fontColor(i) }}
>
{i + 1}
</Col>
<Col span={22} className={styles.ellipse1}>
{item.newsTitle}
</Col>
</Row>
</Col>
</Row>
);
......
......@@ -16,27 +16,24 @@
}
.font3 {
font-size: 16px;
font-size: 14px;
font-family: MicrosoftYaHei;
color: #323232;
line-height: 21px;
line-height: 19px;
}
.font4 {
font-size: 20px;
font-size: 16px;
font-family: MicrosoftYaHeiUI-Bold, MicrosoftYaHeiUI;
font-weight: bold;
color: #000000;
line-height: 25px;
line-height: 20px;
}
.newsBox {
width: 384px;
height: 491px;
background-image: url("./assets/bk.png");
background-size: 100% 100%;
}
.ellipse2 {
@include ellipsis(2);
.ellipse1 {
@include ellipsis(1);
}
import { Button } from "antd";
import { Button, Empty, Pagination, Spin } from "antd";
import { useState, useEffect } from "react";
import api, { Item } from "./api";
import styles from "./index.module.scss";
......@@ -16,10 +16,11 @@ export default function Bids(props: Props) {
const [list, setList] = useState<Array<Item>>([]);
const [pageParams, setPageParams] = useState({
pageNo: 1,
pageSize: 5,
pageSize: 10,
});
const [count, setCount] = useState(0);
const [abort, setAbort] = useState<AbortController | null>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
//中断前一次请求
......@@ -31,6 +32,7 @@ export default function Bids(props: Props) {
if (!abort) {
return;
}
setLoading(true);
api
.listNewTenderInfo(
{
......@@ -44,35 +46,58 @@ export default function Bids(props: Props) {
.then((res) => {
setList(res.result?.list || []);
setCount(res.result?.totalCount || 0);
setLoading(false);
});
}, [abort]);
const onPageChange = (page: number, pageSize: number) => {
setPageParams({
...pageParams,
pageNo: page,
});
};
return (
<div className={styles.bids}>
{list.map((item) => {
return (
<div className={styles.item} key={item.id}>
<div className={styles.info}>
<div className={styles.title}>项目需求:{item.tenderContent}</div>
<Spin spinning={loading} delay={500}>
<div className={styles.bids} style={{ height: 610 }}>
{list.map((item) => {
return (
<div className={styles.item} key={item.id}>
<div className={styles.info}>
<div className={styles.title}>
{item.tenderContent}
</div>
</div>
{item.apply ? (
<Button
type="primary"
disabled
className={`${styles.btn} ${styles.disabled}`}
>
<div className={styles.text1}>{item.tenderPrice}</div>
<div className={styles.text2}>已申请</div>
</Button>
) : (
<Button type="primary" className={styles.btn}>
<div className={styles.text1}>{item.tenderPrice}</div>
<div className={styles.text2}>申请合作</div>
</Button>
)}
</div>
{item.apply ? (
<Button
type="primary"
disabled
className={`${styles.btn} ${styles.disabled}`}
>
<div className={styles.text1}>{item.tenderPrice}</div>
<div className={styles.text2}>已申请</div>
</Button>
) : (
<Button type="primary" className={styles.btn}>
<div className={styles.text1}>{item.tenderPrice}</div>
<div className={styles.text2}>申请合作</div>
</Button>
)}
</div>
);
})}
</div>
);
})}
{list.length === 0 && <Empty></Empty>}
</div>
<Pagination
current={pageParams.pageNo}
defaultPageSize={pageParams.pageSize}
showSizeChanger={false}
showQuickJumper
total={count}
onChange={onPageChange}
hideOnSinglePage={true}
style={{ marginTop: 20 }}
/>
</Spin>
);
}
......@@ -2,14 +2,13 @@
//项目需求
.casas {
padding-top: 20px;
.item {
padding: 39px 14px 39px 16px;
padding: 7px 14px 9px 16px;
display: flex;
border-bottom: 1px dashed RGBA(222, 222, 222, 1);
border-bottom: 1px dashed RGBA(222, 222, 222, 1);
justify-content: space-between;
align-items: center;
.info {
.title {
......@@ -30,12 +29,10 @@
.btn {
width: 120px;
height: 40px;
height: 34px;
border-radius: 6px;
font-size: 14px;
font-family: MicrosoftYaHei;
color: #ff552d;
border: 1px solid #ff552d;
}
.btnDisabled {
......
import { Button } from "antd";
import { Button, Empty, Pagination, Spin } from "antd";
import { useRouter } from "next/router";
import { useState, useEffect } from "react";
import api, { Item } from "./api";
......@@ -17,10 +17,12 @@ export default function Cases(props: Props) {
const [list, setList] = useState<Array<Item>>([]);
const [pageParams, setPageParams] = useState({
pageNo: 1,
pageSize: 5,
pageSize: 12,
});
const [count, setCount] = useState(0);
const [abort, setAbort] = useState<AbortController | null>(null);
const [loading, setLoading] = useState(false);
const Router = useRouter();
useEffect(() => {
......@@ -33,6 +35,7 @@ export default function Cases(props: Props) {
if (!abort) {
return;
}
setLoading(true);
api
.listCasePage(
{
......@@ -46,26 +49,50 @@ export default function Cases(props: Props) {
.then((res) => {
setList(res.result?.list || []);
setCount(res.result?.totalCount || 0);
setLoading(false);
});
}, [abort]);
const onPageChange = (page: number, pageSize: number) => {
setPageParams({
...pageParams,
pageNo: page,
});
};
return (
<div className={styles.casas}>
{list.map((item) => {
return (
<div className={styles.item} key={item.id}>
<div className={styles.info}>
<div className={styles.title}>{item.caseTitle}</div>
<Spin spinning={loading} delay={500}>
<div className={styles.casas} style={{ height: 612 }}>
{list.map((item) => {
return (
<div className={styles.item} key={item.id}>
<div className={styles.info}>
<div className={styles.title}>{item.caseTitle}</div>
</div>
<Button
type="primary"
className={styles.btn}
onClick={() =>
Router.push("/projectInfo/caseArticle/" + item.id)
}
>
申请合作
</Button>
</div>
<Button
className={styles.btn}
onClick={() => Router.push("/projectInfo/caseArticle/" + item.id)}
>
查看案例
</Button>
</div>
);
})}
</div>
);
})}
{list.length === 0 && <Empty></Empty>}
</div>
<Pagination
current={pageParams.pageNo}
defaultPageSize={pageParams.pageSize}
showSizeChanger={false}
showQuickJumper
total={count}
onChange={onPageChange}
hideOnSinglePage={true}
style={{ marginTop: 20 }}
/>
</Spin>
);
}
......@@ -5,11 +5,8 @@
}
.new {
padding-top: 20px;
width: 750px;
.item {
cursor: pointer;
padding: 10px 12px;
display: flex;
border-bottom: 1px dashed RGBA(222, 222, 222, 1);
......@@ -20,19 +17,39 @@
height: 80px;
background: #d8d8d8;
border-radius: 6px;
margin-right: 22px;
margin-right: 17px;
@include ellipsis(1);
flex-shrink: 0;
}
.info {
padding: 3px 0 2px;
padding-left: 3px;
padding-right: 83px;
.title {
font-size: 16px;
font-family: MicrosoftYaHeiUI-Bold, MicrosoftYaHeiUI;
font-weight: bold;
color: #000000;
line-height: 22px;
@include ellipsis(1);
}
.desc {
font-size: 14px;
font-family: MicrosoftYaHei;
color: #878787;
line-height: 22px;
margin-top: 6px;
@include ellipsis(1);
}
.date {
font-size: 12px;
font-family: MicrosoftYaHei;
color: #adadad;
line-height: 16px;
margin-top: 5px;
}
}
}
......
import { RightOutlined } from "@ant-design/icons";
import { Col, Row } from "antd";
import { Button, Col, Empty, Pagination, Row, Spin } from "antd";
import styles from "./index.module.scss";
import Image from "next/image";
import { useState, useEffect } from "react";
import api, { Item } from "./api";
import { useRouter } from "next/router";
import Moment from "moment";
type Props = {
params?: {
......@@ -16,10 +17,11 @@ type Props = {
};
export default function News(props: Props) {
const [loading, setLoading] = useState(false);
const [list, setList] = useState<Array<Item>>([]);
const [pageParams, setPageParams] = useState({
pageNo: 1,
pageSize: 5,
pageSize: 6,
});
const [count, setCount] = useState(0);
const [abort, setAbort] = useState<AbortController | null>(null);
......@@ -35,6 +37,7 @@ export default function News(props: Props) {
if (!abort) {
return;
}
setLoading(true);
api
.listNewsPage(
{
......@@ -48,33 +51,63 @@ export default function News(props: Props) {
.then((res) => {
setList(res.result?.list || []);
setCount(res.result?.totalCount || 0);
setLoading(false);
});
}, [abort]);
const onPageChange = (page: number, pageSize: number) => {
setPageParams({
...pageParams,
pageNo: page,
});
};
return (
<Row justify="space-between">
<Col className={styles.new}>
{list.map((item) => {
return (
<div
className={styles.item}
key={item.id}
onClick={() => router.push("/projectInfo/newsArticle/" + item.id)}
>
<Image
className={styles.logo}
src={item.surfaceImg}
alt=""
width={120}
height={80}
></Image>
<div className={styles.info}>
<div className={styles.title}>{item.newsTitle}</div>
<Spin spinning={loading} delay={500}>
<Row justify="space-between" style={{ height: 606 }}>
<Col className={styles.new}>
{list.map((item) => {
return (
<div className={styles.item} key={item.id}>
<Image
className={styles.logo}
src={item.surfaceImg}
alt=""
width={120}
height={80}
></Image>
<div className={styles.info}>
<div className={styles.title}>{item.newsTitle}</div>
<div className={styles.desc}>{item.newsContents}</div>
<div className={styles.date}>
{Moment().format("yyyy-MM-DD")} · {item.newsAuthor}
</div>
</div>
<Button
type="primary"
style={{ width: 120, height: 40, flexShrink: 0 }}
onClick={() =>
router.push("/projectInfo/newsArticle/" + item.id)
}
>
申请合作
</Button>
</div>
</div>
);
})}
</Col>
</Row>
);
})}
</Col>
</Row>
{list.length === 0 && <Empty></Empty>}
<Pagination
current={pageParams.pageNo}
defaultPageSize={pageParams.pageSize}
showSizeChanger={false}
showQuickJumper
total={count}
onChange={onPageChange}
hideOnSinglePage={true}
style={{ marginTop: 20 }}
/>
</Spin>
);
}
......@@ -29,6 +29,12 @@ export interface Item {
updateTime?: string;
}
export interface SolveRequireParams {
requirementsInfoId: number, //需求id
userAccountId: number //用户id
}
export default {
/**
* 需求发布列表
......@@ -37,5 +43,13 @@ export default {
*/
listPublishPage(params: ListPublishPageParams, options = {}): Promise<Response<ListPublishPageResp>> {
return request('/release/requirements/listPublishPage', 'post', params, options);
},
/**
* 需求已解决
* @param params
* @returns
*/
solveRequire(params: SolveRequireParams): Promise<Response<null>>{
return request('/release/requirements/solveRequire', 'get', params);
}
}
\ No newline at end of file
@import "~/styles/mixins.scss";
//项目需求
.requirements {
.item {
cursor: pointer;
padding: 15px 15px;
display: flex;
border-bottom: 1px dashed RGBA(222, 222, 222, 1);
align-items: center;
.logo {
width: 50px;
......@@ -13,10 +15,12 @@
margin-right: 24px;
background: url('./assets/resolved.png') no-repeat;
background-size: 100% 100%;
flex-shrink: 0;
}
.info {
padding: 3px 0 2px;
flex: auto;
.title {
white-space: nowrap; /* 防止换行 */
......@@ -27,6 +31,7 @@
font-weight: bold;
margin-bottom: 6px;
color: RGBA(0, 0, 0, 0.4);
@include ellipsis(1);
}
.desc {
......@@ -37,6 +42,7 @@
font-family: MicrosoftYaHei;
color: RGBA(135, 135, 135, 0.4);
line-height: 1;
@include ellipsis(1);
}
}
......
import { Empty, Pagination, Spin } from "antd";
import { Button, Empty, Pagination, Popconfirm, Spin } from "antd";
import router from "next/router";
import React, { useState, useEffect } from "react";
import api, { Item } from "./api";
import styles from "./index.module.scss";
......@@ -21,12 +22,18 @@ export default function Requirements(props: Props) {
});
const [count, setCount] = useState(0);
const [abort, setAbort] = useState<AbortController | null>(null);
const [userId, setUserId] = useState(-1);
const [reload, setReload] = useState(false);
useEffect(() => {
setUserId(Number(window.localStorage.getItem("userId") || -1));
}, []);
useEffect(() => {
//中断前一次请求
abort?.abort();
setAbort(new AbortController());
}, [pageParams, props.params]);
}, [pageParams, props.params, reload]);
useEffect(() => {
if (!abort) {
......@@ -57,6 +64,24 @@ export default function Requirements(props: Props) {
});
};
/**
* 确认解决
* @param e
*/
const confirmSolved = (
item: Item
) => {
api.solveRequire({
requirementsInfoId: item.id,
userAccountId: userId
}).then(res => {
if(res.code === '200'){
window.messageApi.success('提交完成');
setReload(!reload);
}
})
};
return (
<Spin spinning={loading} delay={500}>
<div className={styles.requirements} style={{ height: 635 }}>
......@@ -75,6 +100,24 @@ export default function Requirements(props: Props) {
具体需求:{item.requireDescription}
</div>
</div>
{item.userAccountId === userId && (
<Popconfirm
title="提示"
description="确认该需求已经解决了吗?"
onConfirm={() => confirmSolved(item)}
okText="是"
cancelText="否"
disabled={!!item.solved}
>
<Button
type="primary"
style={{ width: 120, height: 40, flexShrink: 0 }}
disabled={!!item.solved}
>
已解决
</Button>
</Popconfirm>
)}
</div>
);
})}
......
......@@ -7,6 +7,7 @@ import Layout from "~/components/layout";
import newsApi, { Item } from "../components/news/api";
import api, { DetailsResp } from "./api";
import styles from "./index.module.scss";
import Moment from "moment";
export default function CaseArticle() {
const router = useRouter();
......@@ -32,13 +33,36 @@ export default function CaseArticle() {
newsApi
.listNewsPage({
pageNo: 1,
pageSize: 5,
pageSize: 10,
})
.then((res) => {
setNewList(res.result?.list || []);
});
}, []);
const fontColor = (i: number) => {
switch (i) {
case 0:
return {
color: "#ff2c46",
};
case 1:
return {
color: "#FF6602",
};
case 2:
return {
color: "#FAA910",
};
default:
return {
color: "#9295A3",
};
}
};
return (
<Layout layoutStyle={{ backgroundColor: "#fff" }}>
<div style={{ paddingTop: 29 }}>
......@@ -49,10 +73,12 @@ export default function CaseArticle() {
className={styles.font2}
style={{ marginTop: 18, marginBottom: 41 }}
>
{data?.createTime} · {data?.newsAuthor}
{data?.createTime &&
Moment(data?.createTime).format("YYYY-MM-DD")}{" "}
· {data?.newsAuthor}
</div>
<div
style={{lineHeight: 1.5}}
style={{ lineHeight: 1.5 }}
dangerouslySetInnerHTML={{ __html: data?.newsContents || "" }}
></div>
</Col>
......@@ -61,14 +87,14 @@ export default function CaseArticle() {
<Row
className={styles.font4}
align="middle"
style={{ paddingTop: 24, paddingLeft: 24 }}
style={{ paddingTop: 16, paddingLeft: 20 }}
>
行业新闻
<RightOutlined style={{ fontSize: 16, marginLeft: 15 }} />
<RightOutlined style={{ fontSize: 16, marginLeft: 9 }} />
</Row>
<Row gutter={10} style={{ marginTop: 18 }}>
<Row gutter={10} style={{ marginTop: 8 }}>
<Col span={24}>
{newsList.map((item) => {
{newsList.map((item, i) => {
return (
<Row
key={item.id}
......@@ -80,20 +106,20 @@ export default function CaseArticle() {
}}
>
<Col
className={`${styles.font3} ${styles.ellipse2}`}
style={{ width: 217, padding: "13px 0 17px 20px" }}
className={`${styles.font3}`}
style={{ margin: "6px 23px 6px 19px", width: "100%" }}
>
{item.newsTitle}
</Col>
<Col style={{ paddingRight: 16 }}>
<Image
src={item.surfaceImg}
width={90}
height={60}
preview={false}
fallback={errImg}
style={{ borderRadius: 6 }}
></Image>
<Row wrap={false}>
<Col
span={2}
style={{ textAlign: "center", ...fontColor(i) }}
>
{i + 1}
</Col>
<Col span={22} className={styles.ellipse1}>
{item.newsTitle}
</Col>
</Row>
</Col>
</Row>
);
......
......@@ -16,27 +16,24 @@
}
.font3 {
font-size: 16px;
font-size: 14px;
font-family: MicrosoftYaHei;
color: #323232;
line-height: 21px;
line-height: 19px;
}
.font4 {
font-size: 20px;
font-size: 16px;
font-family: MicrosoftYaHeiUI-Bold, MicrosoftYaHeiUI;
font-weight: bold;
color: #000000;
line-height: 25px;
line-height: 20px;
}
.newsBox {
width: 384px;
height: 491px;
background-image: url("./assets/bk.png");
background-size: 100% 100%;
}
.ellipse2 {
@include ellipsis(2);
}
.ellipse1 {
@include ellipsis(1);
}
\ No newline at end of file
......@@ -2,7 +2,9 @@ import { MessageInstance } from 'antd/es/message/interface';
declare global {
interface Window {
messageApi: MessageInstance;
WxLogin: any;
messageApi: MessageInstance; //全局消息提示api
WxLogin: any; //微信登录对象
setUserId: (number) => void; //获取用户信息需要的userId
_AMapSecurityConfig: { securityJsCode: string }; //高德地图api密钥配置
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论