提交 08c2d1a8 作者: 翁进城

论坛开发

上级 28d362de
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
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
"passport": "^0.6.0", "passport": "^0.6.0",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"pinyin-pro": "^3.14.0", "pinyin-pro": "^3.14.0",
"react-infinite-scroll-component": "^6.1.0",
"styled-components": "^6.0.0-rc.1", "styled-components": "^6.0.0-rc.1",
"swiper": "^9.3.2", "swiper": "^9.3.2",
"swr": "^2.1.5", "swr": "^2.1.5",
......
import request, { Response } from "~/api/request"; import request, { Response } from "~/api/request";
export interface DynamicListParams { export interface DynamicListParams {
pageNo: number, pageNo: number;
pageSize: number, pageSize: number;
userId: number userId: number;
} }
export interface Dynamic { export interface Dynamic {
...@@ -31,6 +31,24 @@ export interface DynamicListResp { ...@@ -31,6 +31,24 @@ export interface DynamicListResp {
totalPage: 0; 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 { export default {
/** /**
* 论坛动态列表 * 论坛动态列表
...@@ -40,4 +58,14 @@ export default { ...@@ -40,4 +58,14 @@ export default {
dynamicList(params: DynamicListParams): Promise<Response<DynamicListResp>> { dynamicList(params: DynamicListParams): Promise<Response<DynamicListResp>> {
return request("/release/dynamic/dynamicList", "get", params); 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 { 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 { RcFile, UploadProps } from "antd/es/upload";
import type { UploadFile } from "antd/es/upload/interface"; import type { UploadFile } from "antd/es/upload/interface";
import { useState } from "react"; import { useState } from "react";
import Image from "next/image"; import gApi from "~/api";
import NImage from "next/image";
const getBase64 = (file: RcFile): Promise<string> => import api from "./api";
new Promise((resolve, reject) => { import { useGeolocation, useUser } from "~/lib/hooks";
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result as string);
reader.onerror = (error) => reject(error);
});
type Props = { type Props = {
open: boolean; open: boolean;
onCancel: () => void;
onOk?: () => 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) { export default function PublishMessage(props: Props) {
...@@ -24,98 +27,110 @@ export default function PublishMessage(props: Props) { ...@@ -24,98 +27,110 @@ export default function PublishMessage(props: Props) {
const [previewOpen, setPreviewOpen] = useState(false); const [previewOpen, setPreviewOpen] = useState(false);
const [previewImage, setPreviewImage] = useState(""); const [previewImage, setPreviewImage] = useState("");
const [previewTitle, setPreviewTitle] = useState(""); const [previewTitle, setPreviewTitle] = useState("");
const [fileList, setFileList] = useState<UploadFile[]>([ const [fileList, setFileList] = useState<UploadFile[]>([]);
{ const [showLoading, setShowLoad] = useState(false);
uid: "-1", const [form] = Form.useForm();
name: "image.png", const user = useUser();
status: "done", const position = useGeolocation();
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 handlePreviewCancel = () => setPreviewOpen(false); const handlePreviewCancel = () => setPreviewOpen(false);
//图片预览
const handlePreview = async (file: UploadFile) => { const handlePreview = async (file: UploadFile) => {
if (!file.url && !file.preview) { if (file.url) {
file.preview = await getBase64(file.originFileObj as RcFile); setPreviewImage(file.url);
setPreviewOpen(true);
setPreviewTitle(
file.name || file.url!.substring(file.url!.lastIndexOf("/") + 1)
);
} }
};
setPreviewImage(file.url || (file.preview as string)); //图片上传
setPreviewOpen(true); const handleChange: UploadProps["onChange"] = (info) => {
setPreviewTitle( console.log("uploadChange", info);
file.name || file.url!.substring(file.url!.lastIndexOf("/") + 1) 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 ( return (
<Modal <Modal
title="" title=""
open={props.open} open={props.open}
onOk={handleOk} onCancel={props.onCancel}
onCancel={handleCancel}
width={500} width={500}
confirmLoading={confirmLoading} confirmLoading={confirmLoading}
okText="发布" footer={null}
okButtonProps={{ style: { height: 37, padding: "0 32px", fontSize: 16 } }} okButtonProps={{ style: { height: 37, padding: "0 32px", fontSize: 16 } }}
cancelButtonProps={{ style: { display: "none" } }} cancelButtonProps={{ style: { display: "none" } }}
maskClosable={false} maskClosable={false}
> >
<Form style={{ paddingTop: 32 }}> <Form style={{ paddingTop: 32 }} onFinish={onFinish} form={form}>
<Form.Item> <Form.Item
<Input placeholder="输入标题" style={{ height: 44 }}></Input> name="description"
</Form.Item> rules={[{ required: true }]}
<Form.Item> help="请输入内容"
>
<Input.TextArea <Input.TextArea
allowClear allowClear
showCount showCount
...@@ -124,25 +139,56 @@ export default function PublishMessage(props: Props) { ...@@ -124,25 +139,56 @@ export default function PublishMessage(props: Props) {
style={{ height: 120, resize: "none" }} style={{ height: 120, resize: "none" }}
></Input.TextArea> ></Input.TextArea>
</Form.Item> </Form.Item>
<Form.Item> <Form.Item valuePropName="picture" getValueFromEvent={normFile}>
<Upload <Upload
action="https://www.mocky.io/v2/5cc8019d300000980a055e76" name="uploadFile"
action={gApi.imgOss}
listType="picture-card" listType="picture-card"
fileList={fileList} fileList={fileList}
onPreview={handlePreview} onPreview={handlePreview}
onChange={handleChange} onChange={handleChange}
maxCount={1}
> >
{fileList.length >= 8 ? null : uploadButton} {fileList.length >= 8 ? null : (
<div>
<PlusOutlined />
<div style={{ marginTop: 8 }}>Upload</div>
</div>
)}
</Upload> </Upload>
<Modal <Modal
open={previewOpen} open={previewOpen}
title={previewTitle} title={previewTitle}
footer={null} footer={null}
onCancel={handlePreviewCancel} onCancel={handlePreviewCancel}
bodyStyle={{ textAlign: "center" }}
> >
<Image alt="example" style={{ width: "100%" }} src={previewImage} /> <Image alt="example" src={previewImage} preview={false} />
</Modal> </Modal>
</Form.Item> </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> </Form>
</Modal> </Modal>
); );
......
...@@ -2,7 +2,9 @@ import { MessageInstance } from 'antd/es/message/interface'; ...@@ -2,7 +2,9 @@ import { MessageInstance } from 'antd/es/message/interface';
declare global { declare global {
interface Window { interface Window {
messageApi: MessageInstance; messageApi: MessageInstance; //全局消息提示api
WxLogin: any; WxLogin: any; //微信登录对象
setUserId: (number) => void; //获取用户信息需要的userId
_AMapSecurityConfig: { securityJsCode: string }; //高德地图api密钥配置
} }
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论