Compare commits
3 Commits
348d23f72c
...
a5b8e9cd87
Author | SHA1 | Date | |
---|---|---|---|
a5b8e9cd87 | |||
ae132cb274 | |||
6d63bdf1a2 |
@ -1,6 +1,6 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="100" height="100">
|
||||
<path d="M671.584 201.142c22.034 0 39.896 17.398 39.896 38.858v163.2l207.98-141.828a20.4 20.4 0 0 1 20.666-1.322c6.602 3.35 10.732 10.002 10.732 17.252V746.7c0 7.25-4.13 13.902-10.732 17.252a20.4 20.4 0 0 1-20.668-1.32L711.48 620.8V784c0 21.46-17.86 38.858-39.896 38.858H113.04c-22.034 0-39.896-17.398-39.896-38.858V240c0-21.46 17.862-38.858 39.896-38.858h558.546z"
|
||||
fill="#9C34FE"></path>
|
||||
<path d="M328.478 388.784c-7.584 0-14.122 5.196-15.64 12.434l-0.32 3.07v215.346c0 5.11 2.58 9.894 6.896 12.796a16.32 16.32 0 0 0 14.73 1.736l2.912-1.398 173.748-107.712c4.106-2.56 6.786-6.806 7.276-11.532 0.49-4.724-1.264-9.408-4.764-12.714l-2.512-1.944-173.748-107.712a16.28 16.28 0 0 0-8.578-2.332v-0.038z"
|
||||
fill="#FFFFFF"></path>
|
||||
<svg width="37" height="34" viewBox="0 0 37 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M28.9241 2.04763C29.2793 1.00838 30.7152 0.976901 31.1363 1.95321L31.1719 2.04823L31.6512 3.44974C31.761 3.77115 31.9385 4.06528 32.1716 4.31227C32.4048 4.55927 32.6882 4.75339 33.0028 4.88154L33.1316 4.92964L34.5332 5.40829C35.5724 5.76342 35.6039 7.19937 34.6282 7.62042L34.5332 7.65605L33.1316 8.1353C32.8101 8.24506 32.5159 8.42251 32.2688 8.65567C32.0217 8.88884 31.8275 9.1723 31.6993 9.48693L31.6512 9.6152L31.1725 11.0173C30.8174 12.0566 29.3814 12.088 28.961 11.1123L28.9241 11.0173L28.4455 9.61579C28.3357 9.29427 28.1583 9.00003 27.9251 8.75292C27.6919 8.50582 27.4085 8.31161 27.0939 8.1834L26.9656 8.1353L25.5641 7.65665C24.5242 7.30152 24.4928 5.86556 25.4691 5.44511L25.5641 5.40829L26.9656 4.92964C27.287 4.81981 27.5811 4.64233 27.8281 4.40916C28.0751 4.176 28.2692 3.89257 28.3974 3.57801L28.4455 3.44974L28.9241 2.04763ZM34.7992 1.07303e-07C34.9103 -1.40135e-07 35.0192 0.0311648 35.1135 0.089953C35.2077 0.148741 35.2836 0.232795 35.3325 0.332562L35.361 0.402044L35.5689 1.01135L36.1787 1.2192C36.2901 1.25702 36.3877 1.32705 36.4592 1.42041C36.5307 1.51376 36.5729 1.62624 36.5804 1.74359C36.5879 1.86094 36.5604 1.97787 36.5013 2.07957C36.4423 2.18128 36.3544 2.26317 36.2488 2.31487L36.1787 2.34337L35.5694 2.55123L35.3616 3.16112C35.3237 3.27243 35.2536 3.36998 35.1602 3.44142C35.0668 3.51286 34.9543 3.55496 34.837 3.56241C34.7197 3.56985 34.6027 3.54229 34.5011 3.48322C34.3994 3.42415 34.3176 3.33623 34.2659 3.2306L34.2374 3.16112L34.0296 2.55182L33.4197 2.34397C33.3083 2.30614 33.2107 2.23611 33.1392 2.14276C33.0677 2.0494 33.0256 1.93693 33.0181 1.81958C33.0105 1.70223 33.038 1.58529 33.0971 1.48359C33.1561 1.38189 33.244 1.3 33.3496 1.2483L33.4197 1.21979L34.029 1.01194L34.2368 0.402044C34.2769 0.284712 34.3526 0.182855 34.4535 0.110754C34.5543 0.0386538 34.6752 -7.43997e-05 34.7992 1.07303e-07Z" fill="#9C34FE"/>
|
||||
<path d="M18.481 20.1202C19.2177 19.3857 19.8018 18.5128 20.2 17.5517C20.5981 16.5906 20.8023 15.5604 20.8009 14.5201C20.8009 10.1463 17.2548 6.6001 12.8809 6.6001C8.50712 6.6001 4.96094 10.1463 4.96094 14.5201C4.96094 16.7073 5.84732 18.6873 7.28084 20.1202" stroke="black" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M1 32.9999L1.66 29.6999L8.92 24.4199L12.88 28.3799L16.84 24.4199L24.1 29.6999L24.76 32.9999" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.61523 17.8246C5.62403 15.112 6.06557 13.1305 6.93985 11.88C8.25193 10.0049 9.17527 10.1429 9.84715 10.4346C10.519 10.7263 10.915 12.635 12.0377 13.1855C13.1597 13.7353 16.0333 13.8032 17.0174 14.4652C18.0008 15.1265 20.2514 16.3574 19.6904 18.4589" stroke="black" stroke-width="2"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 859 B After Width: | Height: | Size: 2.7 KiB |
@ -40,7 +40,7 @@ body {
|
||||
}
|
||||
|
||||
.app-header {
|
||||
@apply w-full navigation-container flex justify-between items-center p-basic fixed top-0 inset-x-0;
|
||||
@apply w-full navigation-container flex justify-between items-center p-basic fixed top-0 inset-x-0 bg-white/90;
|
||||
z-index: var(--header-z-index);
|
||||
height: var(--app-header-header);
|
||||
}
|
||||
@ -66,6 +66,16 @@ body {
|
||||
.article-action-icon {
|
||||
@apply cursor-pointer text-gray-500 hover:text-blue-500 block;
|
||||
}
|
||||
.news-detail-modal{
|
||||
.ant-modal-content{
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
.news-detail-content-container{
|
||||
margin-right: -30px;
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
|
@ -1,13 +1,13 @@
|
||||
import {Input} from "antd";
|
||||
import {useBoolean, useLocalStorageState, useSetState} from "ahooks";
|
||||
import {useMemo, useState} from "react";
|
||||
import {useBoolean, useLocalStorageState, useSetState,useClickAway} from "ahooks";
|
||||
import {useCallback, useMemo, useRef, useState} from "react";
|
||||
import {clsx} from "clsx";
|
||||
import useArticleTags from "@/hooks/useArticleTags.ts";
|
||||
|
||||
import {CloseOutlined, MenuOutlined, SearchOutlined} from "@ant-design/icons";
|
||||
import {UpOutlined, MenuOutlined, SearchOutlined} from "@ant-design/icons";
|
||||
import TimeSelect from "@/components/form/time-select.tsx";
|
||||
|
||||
import styles from './style.module.scss'
|
||||
import {clsx} from "clsx";
|
||||
import {IconPin} from "@/components/icons";
|
||||
|
||||
type SearchPanelProps = {
|
||||
@ -23,9 +23,10 @@ const DEFAULT_STATE = {
|
||||
}
|
||||
export default function SearchPanel({onSearch}: SearchPanelProps) {
|
||||
const tags = useArticleTags();
|
||||
const [panelVisible, {setTrue, setFalse}] = useBoolean(false)
|
||||
const [panelVisible, {set}] = useBoolean(false)
|
||||
const [params, setParams] = useSetState<ApiArticleSearchParams>({
|
||||
pagination,
|
||||
time_flag:1
|
||||
});
|
||||
const [prevSearchName, setPrevSearchName] = useState<string>()
|
||||
|
||||
@ -88,6 +89,29 @@ export default function SearchPanel({onSearch}: SearchPanelProps) {
|
||||
}
|
||||
return [] as OptionItem[];
|
||||
}, [pinnedTag, tags])
|
||||
const pinnedManagePanel = useRef<HTMLDivElement|null>(null)
|
||||
|
||||
const togglePinnedManagePanel = useCallback((visible: boolean) => {
|
||||
if(!pinnedManagePanel.current){
|
||||
return;
|
||||
}
|
||||
const _target = pinnedManagePanel.current!;
|
||||
if(visible){
|
||||
_target.style.height = 'auto'
|
||||
const {height} = _target.getBoundingClientRect()
|
||||
_target.style.height = '38px'
|
||||
requestAnimationFrame(()=>{
|
||||
_target.style.height = `${height}px`
|
||||
})
|
||||
}else{
|
||||
requestAnimationFrame(()=>{
|
||||
_target.style.height = '0'
|
||||
})
|
||||
}
|
||||
},[pinnedManagePanel])
|
||||
const setTrue = ()=> togglePinnedManagePanel(true)
|
||||
const setFalse = ()=>togglePinnedManagePanel(false)
|
||||
useClickAway(() => setFalse(), pinnedManagePanel)
|
||||
|
||||
return (<div className={`${styles.searchPanel} pt-8 pb-2`}>
|
||||
<div className="flex justify-between items-center">
|
||||
@ -103,23 +127,22 @@ export default function SearchPanel({onSearch}: SearchPanelProps) {
|
||||
/>
|
||||
<TimeSelect
|
||||
className="w-[120px] ml-1"
|
||||
value={params.time_flag || 0}
|
||||
value={params.time_flag || 1}
|
||||
onChange={handleTimeFilter}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="filter-container mt-5 h-[40px]">
|
||||
<div className="filter-container mt-5">
|
||||
<div className="list-container relative">
|
||||
<div className="justify-between flex items-center border-b pb-2 overflow-hidden">
|
||||
<div
|
||||
className={`filter-item whitespace-nowrap px-2 py-1 mt-1 text-sm mr-1 cursor-pointer rounded ${state.tag_level_1_id == -1 ? 'selected' : ''}`}
|
||||
onClick={() => {
|
||||
handleFilter({tag_level_1_id: -1, tag_level_2_id: -1})
|
||||
setSubOptions([])
|
||||
}}>全部
|
||||
</div>
|
||||
|
||||
<div className="pinned-tag-list flex flex-wrap overflow-hidden flex-1 min-w-0 h-[32px]">
|
||||
<div className="justify-between flex items-start border-b pb-2 overflow-hidden">
|
||||
<div className="pinned-tag-list flex flex-wrap flex-1 min-w-0">
|
||||
<div
|
||||
className={`filter-item whitespace-nowrap px-2 py-1 mt-1 text-sm mr-1 cursor-pointer rounded ${state.tag_level_1_id == -1 ? 'selected' : ''}`}
|
||||
onClick={() => {
|
||||
handleFilter({tag_level_1_id: -1, tag_level_2_id: -1})
|
||||
setSubOptions([])
|
||||
}}>全部
|
||||
</div>
|
||||
{pinnedList.filter(s => (Number(s.value) !== 999999)).map(it => (
|
||||
<span
|
||||
className={`filter-item whitespace-nowrap px-2 py-1 mt-1 text-sm mr-1 cursor-pointer rounded ${state.tag_level_1_id == it.value ? 'selected' : ''}`}
|
||||
@ -130,18 +153,25 @@ export default function SearchPanel({onSearch}: SearchPanelProps) {
|
||||
}}>{it.label}</span>)
|
||||
)}
|
||||
</div>
|
||||
<div className="pinned-menu ">
|
||||
<span className={'cursor-pointer block hover:text-blue-500'} onClick={setTrue}>
|
||||
<div className="pinned-menu mt-2">
|
||||
<span className={'cursor-pointer block hover:text-blue-500'} onClick={e=>{
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setTrue();
|
||||
}}>
|
||||
<MenuOutlined style={{fontSize: 20}}/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ref={pinnedManagePanel} className={clsx(styles.pinnedManagePanelContainer)}>
|
||||
{/* 固定新闻来源 */}
|
||||
<div className={clsx(styles.pinnedManagePanel, panelVisible ? 'block' : 'hidden')}>
|
||||
<div className={clsx(styles.pinnedManagePanel)}>
|
||||
<div className="header flex justify-between">
|
||||
<div className="title font-bold">新闻来源</div>
|
||||
<span className={'cursor-pointer block hover:text-blue-500'} onClick={setFalse}><CloseOutlined
|
||||
style={{fontSize: 20}}/></span>
|
||||
<div className={'cursor-pointer block hover:text-blue-500'} onClick={setFalse}>
|
||||
<UpOutlined style={{fontSize: 20}}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="tags-list-container">
|
||||
{
|
||||
@ -165,6 +195,7 @@ export default function SearchPanel({onSearch}: SearchPanelProps) {
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 二级目录 */}
|
||||
{state.tag_level_1_id != -1 && subOptions.length > 0 &&
|
||||
<div
|
||||
|
@ -14,10 +14,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pinnedManagePanel {
|
||||
@apply absolute bg-white top-0 px-4 pt-2 pb-4 rounded shadow-md z-10;
|
||||
.pinnedManagePanelContainer {
|
||||
@apply absolute bg-white top-0 rounded shadow-md z-10;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
transition: height 0.2s ease-in-out;
|
||||
inset-inline: -20px;
|
||||
}
|
||||
.pinnedManagePanel {
|
||||
@apply px-4 pt-2 pb-4 grid;
|
||||
|
||||
:global {
|
||||
.btn-panel {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, {useRef, useState} from "react";
|
||||
import {Checkbox, Divider, Empty, Modal, Pagination, Space} from "antd";
|
||||
import {Checkbox, Divider, Empty, Modal, Space} from "antd";
|
||||
import {useRequest} from "ahooks";
|
||||
import {CloseOutlined} from "@ant-design/icons"
|
||||
|
||||
import SearchPanel from "@/pages/news/components/search-panel.tsx";
|
||||
import {getById, getList} from "@/service/api/news.ts";
|
||||
@ -15,7 +16,7 @@ import ButtonToTop from "@/components/scoller/button-to-top.tsx";
|
||||
export default function NewsIndex() {
|
||||
|
||||
const [params, setParams] = useState<ApiArticleSearchParams>({
|
||||
pagination: {page: 1, limit: 12}
|
||||
pagination: {page: 1, limit: 12},time_flag:1
|
||||
})
|
||||
const [checkedId, setCheckedId] = useState<Id[]>([])
|
||||
const [activeNews, setActiveNews] = useState<NewsInfo>()
|
||||
@ -46,7 +47,7 @@ export default function NewsIndex() {
|
||||
const {update, close} = showLoading('获取新闻详情...')
|
||||
getById(id).then(res => {
|
||||
close()
|
||||
setActiveNews(res)
|
||||
setActiveNews({...res, id})
|
||||
}).catch(() => {
|
||||
update('获取新闻详情失败', 'info')
|
||||
})
|
||||
@ -60,23 +61,46 @@ export default function NewsIndex() {
|
||||
setCheckedId([])
|
||||
}
|
||||
}
|
||||
const scrollerRef = useRef<InfiniteScrollerRef|null>(null)
|
||||
|
||||
const scrollerRef = useRef<InfiniteScrollerRef | null>(null)
|
||||
const handleCheckChange = (id: number) => {
|
||||
if (checkedId.includes(id)) {
|
||||
setCheckedId(checkedId.filter(id => id != id))
|
||||
} else {
|
||||
setCheckedId([...checkedId, id])
|
||||
}
|
||||
}
|
||||
|
||||
return (<div className={'container pb-5'}>
|
||||
<SearchPanel onSearch={setParams}/>
|
||||
{activeNews && <Modal open={true} centered width={1000} footer={null} onCancel={() => setActiveNews(undefined)}>
|
||||
<div className="news-detail px-3 pb-5">
|
||||
{activeNews && <Modal
|
||||
rootClassName={'news-detail-modal'}
|
||||
closeIcon={null} open={true} width={1000}
|
||||
maskClosable={false}
|
||||
footer={null} onCancel={() => setActiveNews(undefined)}
|
||||
>
|
||||
<div className="news-detail pl-16 pr-1 flex pb-5">
|
||||
<div className="px-4 py-6 bg-white">
|
||||
<div className="new-title text-2xl">{activeNews?.title}</div>
|
||||
<Divider className={'my-4'}/>
|
||||
<div className="info mt-2 mb-5 text-sm flex gap-3">
|
||||
<div className="info mt-2 mb-2 text-sm flex gap-3">
|
||||
<span className="source text-blue-400">{activeNews?.media_name}</span>
|
||||
<span className="create-time text-gray-400">{formatTime(activeNews?.publish_time)}</span>
|
||||
</div>
|
||||
<div className="overflow-auto leading-7 text-base"
|
||||
<Divider className={'my-2'}/>
|
||||
<div className="overflow-auto leading-7 text-base news-detail-content-container"
|
||||
style={{maxHeight: 500}} dangerouslySetInnerHTML={{__html: activeNews?.content || ''}}></div>
|
||||
</div>
|
||||
<div className="actions ml-2">
|
||||
<div className="close">
|
||||
<CloseOutlined className="text-xl text-gray-400 hover:text-gray-800"
|
||||
onClick={() => setActiveNews(undefined)}/>
|
||||
</div>
|
||||
<div className="whitespace-nowrap text-sm mt-2">
|
||||
<Checkbox
|
||||
checked={checkedId.includes(activeNews!.id)}
|
||||
onChange={() => handleCheckChange(activeNews!.id)}
|
||||
><span>选择</span></Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>}
|
||||
<div className="news-list-container">
|
||||
@ -99,7 +123,7 @@ export default function NewsIndex() {
|
||||
pagination={data?.pagination}
|
||||
loading={loading}
|
||||
ref={scrollerRef}
|
||||
onScroll={(top)=> setState({showToTop: top > 30})}
|
||||
onScroll={(top) => setState({showToTop: top > 30})}
|
||||
onCallback={(page) => {
|
||||
setParams({...params, pagination: {...params.pagination, page}})
|
||||
}}
|
||||
@ -130,19 +154,15 @@ export default function NewsIndex() {
|
||||
</div>}
|
||||
</div>
|
||||
<div className="info text-gray-400 mt-4 text-sm">
|
||||
<div>来源: <span>{item.media_name}</span></div>
|
||||
<div className="line-clamp-1">来源: <span>{item.data_source_name}</span></div>
|
||||
<div className="extras flex items-center justify-between gap-3">
|
||||
<div><span>{formatTime(item.publish_time)}</span></div>
|
||||
<div><span>{formatTime(item.publish_time,'min')}</span></div>
|
||||
<div
|
||||
className={`checkbox mt-[2px]`}>
|
||||
className={`checkbox mt-1`}>
|
||||
{item.internal_article_id > 0 ?
|
||||
<span className={"inline-block w-[16px] "}></span> :
|
||||
<span className={"inline-block text-gray-600"}>已推送</span> :
|
||||
<Checkbox checked={checkedId.includes(item.id)} onChange={() => {
|
||||
if (checkedId.includes(item.id)) {
|
||||
setCheckedId(checkedId.filter(id => id != item.id))
|
||||
} else {
|
||||
setCheckedId([...checkedId, item.id])
|
||||
}
|
||||
handleCheckChange(item.id)
|
||||
}}/>}
|
||||
</div>
|
||||
</div>
|
||||
@ -155,7 +175,7 @@ export default function NewsIndex() {
|
||||
|
||||
</div>
|
||||
<div className="page-action">
|
||||
<ButtonToTop visible={state.showToTop} onClick={()=>scrollerRef.current?.scrollToPosition(0)} />
|
||||
<ButtonToTop visible={state.showToTop} onClick={() => scrollerRef.current?.scrollToPosition(0)}/>
|
||||
<div>
|
||||
<ButtonNewsDownload ids={checkedId}/>
|
||||
</div>
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 447 KiB |
@ -1,12 +1,14 @@
|
||||
.main {
|
||||
@apply py-10;
|
||||
background-color: rgb(244, 247, 252);
|
||||
background: url(./components/main-bg.jpg) no-repeat center;
|
||||
background-size: cover;
|
||||
height: calc(100vh - var(--app-header-header));
|
||||
min-height: 500px;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
justify-content: right;
|
||||
|
||||
}
|
||||
|
||||
.boxLogin {
|
||||
|
1
src/types/core.d.ts
vendored
1
src/types/core.d.ts
vendored
@ -35,6 +35,7 @@ declare interface ArticleDetail {
|
||||
}
|
||||
|
||||
declare interface NewsInfo {
|
||||
id: number;
|
||||
title: string;
|
||||
content: string;
|
||||
media_name: string;
|
||||
|
@ -55,8 +55,13 @@ function getDayjs(time:any){
|
||||
return dayjs(time);
|
||||
}
|
||||
|
||||
export function formatTime(time:any,template = 'YYYY-MM-DD HH:mm:ss') {
|
||||
if(!time) return '-';
|
||||
export function formatTime(time: any, template: 'min' | 'date' | string = 'YYYY-MM-DD HH:mm:ss') {
|
||||
if (!time) return '-';
|
||||
if (template == 'min') {
|
||||
template = 'YYYY-MM-DD HH:mm'
|
||||
} else if (template == 'date') {
|
||||
template = 'YYYY-MM-DD'
|
||||
}
|
||||
return getDayjs(time).format(template)
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ import dayjs from "dayjs";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(({mode}) => {
|
||||
const devServerHost = mode == 'test' ? '124.220.14.192' : '192.168.0.231:9999'
|
||||
const devServerHost = mode == 'test' ? 'https://fm-admin.starbitech.com' : 'http://192.168.0.231:9999'
|
||||
const AUTH_TOKEN_KEY = mode == 'production' ? 'digital-person-token' : `digital-person-token_${mode}`
|
||||
|
||||
if (mode !== 'production') {
|
||||
@ -41,7 +41,7 @@ export default defineConfig(({mode}) => {
|
||||
port: 10021,
|
||||
proxy: {
|
||||
'/mgmt': {
|
||||
target: `http://${devServerHost}`, // http://124.220.14.192/ 192.168.0.231:9999
|
||||
target: `${devServerHost}`, // http://124.220.14.192/ 192.168.0.231:9999
|
||||
changeOrigin: true,
|
||||
// rewrite: (path) => path.replace(/^\/api/, '')
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user