feat(landing): 预测案例管理员可编辑 + 图片上传修复 + 构建hash
- 后端新增 GET/PUT /api/cfg/landing-cases 接口存取案例数据 - 上传接口自动创建 uploads 目录,修复目录不存在导致上传失败 - 前端预测案例支持管理员编辑标题/副标题/案例内容 - hero 图片初始不设默认值,configReady 门控渲染,消除刷新闪烁 - .umirc.ts 开启 hash:true 避免浏览器缓存旧 JS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T71oHHhfhLAWAL9EuoFuYh
This commit is contained in:
parent
b26546d459
commit
1d987277c6
File diff suppressed because it is too large
Load Diff
|
|
@ -128,6 +128,7 @@ export default defineConfig({
|
|||
mfsu: {
|
||||
strategy: 'normal'
|
||||
},
|
||||
hash: true,
|
||||
esbuildMinifyIIFE: true,
|
||||
npmClient: 'pnpm'
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { useEffect, useRef, useState } from 'react'
|
||||
import { history, useModel } from '@umijs/max'
|
||||
import { Modal, Input, Upload, message, Checkbox } from 'antd'
|
||||
import { EditOutlined, UploadOutlined, PlusOutlined, DeleteOutlined, SearchOutlined } from '@ant-design/icons'
|
||||
import { Modal, Input, InputNumber, Upload, message, Checkbox } from 'antd'
|
||||
import { EditOutlined, UploadOutlined, PlusOutlined, DeleteOutlined, SearchOutlined, SaveOutlined } from '@ant-design/icons'
|
||||
import { LOC_STOR_KEY } from '@/common/constants'
|
||||
import PhoneAuthModal from '@/components/phone-auth-modal'
|
||||
import api from '@/services/api'
|
||||
import type { LandingHero, LandingNewsItem } from '@/services/api/config'
|
||||
import type { LandingHero, LandingNewsItem, LandingCaseItem } from '@/services/api/config'
|
||||
import type { EcoMaterial } from '@/services/api/material'
|
||||
import { articles } from '@/common/articles'
|
||||
import './landing.css'
|
||||
|
|
@ -23,10 +23,10 @@ const ChevronIcon = ({ dir }: { dir: 'left' | 'right' }) => (
|
|||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.4} strokeLinecap="round" strokeLinejoin="round"><path d={dir === 'left' ? 'M15 5l-7 7 7 7' : 'M9 5l7 7-7 7'} /></svg>
|
||||
)
|
||||
|
||||
const FALLBACK_HERO_IMAGE = '/iapip-web/images/hero-interior.jpg'
|
||||
const DEFAULT_HERO: LandingHero = {
|
||||
title: '装修住得安心,\n先把污染源头挡在门外',
|
||||
description: '输入房间、环境与所用材料,系统用稳态质量平衡公式预测甲醛、苯、TVOC 等 6 项污染物浓度,判定是否超标,并溯源到具体污染材料,给出整改建议。',
|
||||
image: '/iapip-web/images/hero-interior.jpg'
|
||||
description: '输入房间、环境与所用材料,系统用稳态质量平衡公式预测甲醛、苯、TVOC 等 6 项污染物浓度,判定是否超标,并溯源到具体污染材料,给出整改建议。'
|
||||
}
|
||||
|
||||
const DEFAULT_NEWS_TITLE = '读懂装修污染,从知道哪些是健康材料开始'
|
||||
|
|
@ -40,11 +40,13 @@ const stats = [
|
|||
{ n: '6 项', t: '污染物 · 甲醛/苯/TVOC/氨/氡/VOC' },
|
||||
{ n: '2 部', t: '国标依据 · 18883 / 50325' }
|
||||
]
|
||||
const cases = [
|
||||
const DEFAULT_CASES: LandingCaseItem[] = [
|
||||
{ type: '住宅 · I类民用建筑', name: '锦绣华庭 · 主卧', meta: '主源:多层实木复合地板 · 人造板衣柜', w1: 82, v1: '0.18', w2: 36, v2: '0.08' },
|
||||
{ type: '住宅 · I类民用建筑', name: '翠湖天地 · 儿童房', meta: '主源:人造板衣柜 · 壁纸基膜', w1: 95, v1: '0.21', w2: 32, v2: '0.07' },
|
||||
{ type: '酒店客房 · II类民用建筑', name: '云栖精选酒店 · 标准间', meta: '主源:木器漆饰面 · 软包', w1: 90, v1: '0.72', w2: 58, v2: '0.46' }
|
||||
]
|
||||
const DEFAULT_CASES_TITLE = '预测 → 识别 → 优化,你家的装修健康卫士'
|
||||
const DEFAULT_CASES_SUB = '真实流程演示:从预测超标,到识别主要污染材料,再到优化复测达标。'
|
||||
const steps = [
|
||||
{ h: '录入房间与材料', p: '选择房间、填写面积层高与通风换气率,勾选所用装修材料及用量。' },
|
||||
{ h: '预测', p: '按稳态质量平衡 C = Σ(EFᵢ·Aᵢ)/(n·V) 计算 6 项污染物浓度,对照国标判定达标。' },
|
||||
|
|
@ -124,6 +126,60 @@ export default function Landing() {
|
|||
} catch { message.error('上传失败') }
|
||||
}
|
||||
|
||||
// --- 预测案例编辑 ---
|
||||
const [cases, setCases] = useState<LandingCaseItem[]>(DEFAULT_CASES)
|
||||
const [casesTitle, setCasesTitle] = useState(DEFAULT_CASES_TITLE)
|
||||
const [casesSub, setCasesSub] = useState(DEFAULT_CASES_SUB)
|
||||
const [caseEditOpen, setCaseEditOpen] = useState(false)
|
||||
const [caseEditForm, setCaseEditForm] = useState<{ title: string; sub: string; items: LandingCaseItem[] }>({ title: '', sub: '', items: [] })
|
||||
const [caseSaving, setCaseSaving] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
api.config.getLandingCases({ skipErrorHandler: true }).then(data => {
|
||||
if (data) {
|
||||
if (data.sectionTitle) setCasesTitle(data.sectionTitle)
|
||||
if (data.sectionSub) setCasesSub(data.sectionSub)
|
||||
if (data.items?.length) setCases(data.items)
|
||||
}
|
||||
}).catch(() => {})
|
||||
}, [])
|
||||
|
||||
const openCaseEdit = () => {
|
||||
setCaseEditForm({ title: casesTitle, sub: casesSub, items: cases.map(c => ({ ...c })) })
|
||||
setCaseEditOpen(true)
|
||||
}
|
||||
const updateCaseField = (idx: number, field: keyof LandingCaseItem, val: any) => {
|
||||
setCaseEditForm(prev => ({
|
||||
...prev,
|
||||
items: prev.items.map((c, i) => i === idx ? { ...c, [field]: val } : c)
|
||||
}))
|
||||
}
|
||||
const addCase = () => {
|
||||
setCaseEditForm(prev => ({
|
||||
...prev,
|
||||
items: [...prev.items, { type: '住宅 · I类民用建筑', name: '新案例', meta: '', w1: 50, v1: '0.10', w2: 30, v2: '0.05' }]
|
||||
}))
|
||||
}
|
||||
const removeCase = (idx: number) => {
|
||||
setCaseEditForm(prev => ({ ...prev, items: prev.items.filter((_, i) => i !== idx) }))
|
||||
}
|
||||
const saveCases = async () => {
|
||||
setCaseSaving(true)
|
||||
try {
|
||||
await api.config.updateLandingCases({
|
||||
sectionTitle: caseEditForm.title,
|
||||
sectionSub: caseEditForm.sub,
|
||||
items: caseEditForm.items
|
||||
})
|
||||
setCasesTitle(caseEditForm.title)
|
||||
setCasesSub(caseEditForm.sub)
|
||||
setCases(caseEditForm.items)
|
||||
setCaseEditOpen(false)
|
||||
message.success('预测案例已保存')
|
||||
} catch { message.error('保存失败') }
|
||||
finally { setCaseSaving(false) }
|
||||
}
|
||||
|
||||
const openPredict = () => {
|
||||
if (hasToken()) history.push('/home')
|
||||
else setAuthOpen(true)
|
||||
|
|
@ -135,14 +191,23 @@ export default function Landing() {
|
|||
|
||||
// --- Hero 编辑 ---
|
||||
const [hero, setHero] = useState<LandingHero>(DEFAULT_HERO)
|
||||
const [configReady, setConfigReady] = useState(false)
|
||||
const [editOpen, setEditOpen] = useState(false)
|
||||
const [editForm, setEditForm] = useState<LandingHero>({})
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
api.config.getLandingConfig({ skipErrorHandler: true })
|
||||
.then(data => { if (data) setHero({ ...DEFAULT_HERO, ...data }) })
|
||||
.catch(() => {})
|
||||
.then(data => {
|
||||
setHero(prev => ({
|
||||
...DEFAULT_HERO,
|
||||
image: FALLBACK_HERO_IMAGE,
|
||||
...(data || {}),
|
||||
...(!data?.image ? { image: FALLBACK_HERO_IMAGE } : {})
|
||||
}))
|
||||
})
|
||||
.catch(() => { setHero(prev => ({ ...prev, image: FALLBACK_HERO_IMAGE })) }),
|
||||
api.config.getLandingNews({ skipErrorHandler: true })
|
||||
.then(data => {
|
||||
if (data) {
|
||||
|
|
@ -151,6 +216,7 @@ export default function Landing() {
|
|||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
]).finally(() => setConfigReady(true))
|
||||
}, [])
|
||||
|
||||
const openEdit = () => {
|
||||
|
|
@ -248,6 +314,8 @@ export default function Landing() {
|
|||
}
|
||||
}
|
||||
|
||||
if (!configReady) return <div className="lp" style={{ minHeight: '100vh' }} />
|
||||
|
||||
return (
|
||||
<div className="lp">
|
||||
<header className="nav">
|
||||
|
|
@ -288,7 +356,7 @@ export default function Landing() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="hero-visual">
|
||||
<img className="hero-img" src={hero.image} alt="室内装修实景" style={{ objectFit: 'cover', display: 'block' }} />
|
||||
{hero.image && <img className="hero-img" src={hero.image} alt="室内装修实景" style={{ objectFit: 'cover', display: 'block' }} />}
|
||||
{isAdmin && (
|
||||
<Upload accept="image/jpeg,image/png,image/webp,image/gif" showUploadList={false} beforeUpload={handleImageUpload}>
|
||||
<button className="edit-icon hero-img-edit" title="更换图片"><UploadOutlined /></button>
|
||||
|
|
@ -348,7 +416,7 @@ export default function Landing() {
|
|||
<section className="sec" id="cases" style={{ background: 'var(--bg2)' }}>
|
||||
<div className="wrap">
|
||||
<div className="sec-head">
|
||||
<div><div className="sec-tag">预测案例</div><h2 className="sec-h">预测 → 识别 → 优化,你家的装修健康卫士</h2><p className="sec-sub">真实流程演示:从预测超标,到识别主要污染材料,再到优化复测达标。</p></div>
|
||||
<div><div className="sec-tag">预测案例</div><h2 className="sec-h">{casesTitle}{isAdmin && <button className="edit-icon" title="编辑预测案例" onClick={openCaseEdit}><EditOutlined /></button>}</h2><p className="sec-sub">{casesSub}</p></div>
|
||||
</div>
|
||||
<div className="cases-grid">
|
||||
{cases.map((c, i) => (
|
||||
|
|
@ -552,6 +620,78 @@ export default function Landing() {
|
|||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
{/* 预测案例编辑弹窗 */}
|
||||
<Modal
|
||||
title="编辑预测案例"
|
||||
open={caseEditOpen}
|
||||
onOk={saveCases}
|
||||
onCancel={() => setCaseEditOpen(false)}
|
||||
confirmLoading={caseSaving}
|
||||
okText="保存"
|
||||
cancelText="取消"
|
||||
width={720}
|
||||
styles={{ body: { maxHeight: '65vh', overflowY: 'auto' } }}
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
|
||||
<div>
|
||||
<div style={{ marginBottom: 6, fontWeight: 500 }}>栏目标题</div>
|
||||
<Input value={caseEditForm.title} onChange={e => setCaseEditForm(f => ({ ...f, title: e.target.value }))} />
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ marginBottom: 6, fontWeight: 500 }}>栏目副标题</div>
|
||||
<Input value={caseEditForm.sub} onChange={e => setCaseEditForm(f => ({ ...f, sub: e.target.value }))} />
|
||||
</div>
|
||||
|
||||
{caseEditForm.items.map((c, idx) => (
|
||||
<div key={idx} className="news-edit-card">
|
||||
<div className="news-edit-card-head">
|
||||
<span style={{ fontWeight: 600 }}>案例 {idx + 1}</span>
|
||||
{caseEditForm.items.length > 1 && (
|
||||
<button className="edit-icon" style={{ width: 28, height: 28 }} title="删除" onClick={() => removeCase(idx)}>
|
||||
<DeleteOutlined style={{ color: '#bf4a30' }} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="news-edit-row">
|
||||
<div style={{ flex: 1 }}>
|
||||
<div className="news-edit-label">建筑类型</div>
|
||||
<Input value={c.type} onChange={e => updateCaseField(idx, 'type', e.target.value)} placeholder="如: 住宅 · I类民用建筑" />
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div className="news-edit-label">案例名称</div>
|
||||
<Input value={c.name} onChange={e => updateCaseField(idx, 'name', e.target.value)} placeholder="如: 锦绣华庭 · 主卧" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="news-edit-label">污染源描述</div>
|
||||
<Input value={c.meta} onChange={e => updateCaseField(idx, 'meta', e.target.value)} placeholder="如: 主源:多层实木复合地板 · 人造板衣柜" />
|
||||
</div>
|
||||
<div className="news-edit-row">
|
||||
<div style={{ flex: 1 }}>
|
||||
<div className="news-edit-label">优化前浓度</div>
|
||||
<Input value={c.v1} onChange={e => updateCaseField(idx, 'v1', e.target.value)} placeholder="如: 0.18" />
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div className="news-edit-label">优化前进度 %</div>
|
||||
<InputNumber min={0} max={100} value={c.w1} onChange={v => updateCaseField(idx, 'w1', v ?? 0)} style={{ width: '100%' }} />
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div className="news-edit-label">优化后浓度</div>
|
||||
<Input value={c.v2} onChange={e => updateCaseField(idx, 'v2', e.target.value)} placeholder="如: 0.08" />
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div className="news-edit-label">优化后进度 %</div>
|
||||
<InputNumber min={0} max={100} value={c.w2} onChange={v => updateCaseField(idx, 'w2', v ?? 0)} style={{ width: '100%' }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<button className="news-add-btn" onClick={addCase}>
|
||||
<PlusOutlined /> 添加案例
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
{/* 环保建材编辑弹窗 */}
|
||||
<Modal
|
||||
title="选择展示的环保建材"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,252 @@
|
|||
import { HTTP_HEADER } from '@/common/constants'
|
||||
import { AnyObj, ApcLimitStandard, CityInformation, MaterialCategory, ProjectSpaceCategory } from '@/common/types'
|
||||
import { getLocalToken } from '@/common/utils'
|
||||
import { request } from '@umijs/max'
|
||||
|
||||
export async function getProjSpaceCatList(query?: { project_category?: string }, options?: AnyObj) {
|
||||
return request<ProjectSpaceCategory[]>(`/api/cfg/proj-space-cat`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
[HTTP_HEADER.AUTHORIZATION]: getLocalToken()
|
||||
},
|
||||
params: query,
|
||||
...(options || {})
|
||||
})
|
||||
}
|
||||
|
||||
export async function getProjSpaceCat(project_category: string, options?: AnyObj) {
|
||||
return request<ProjectSpaceCategory | null>(`/api/cfg/proj-space-cat/by`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
[HTTP_HEADER.AUTHORIZATION]: getLocalToken()
|
||||
},
|
||||
params: {
|
||||
project_category
|
||||
},
|
||||
...(options || {})
|
||||
})
|
||||
}
|
||||
|
||||
export async function getMtrlCatList(options?: AnyObj) {
|
||||
return request<MaterialCategory[]>(`/api/cfg/mtrl-cat`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
[HTTP_HEADER.AUTHORIZATION]: getLocalToken()
|
||||
},
|
||||
...(options || {})
|
||||
})
|
||||
}
|
||||
|
||||
export type MtrlCatPayload = {
|
||||
pry_no_1?: number
|
||||
first_category: string
|
||||
second_categories: {
|
||||
second_category: string
|
||||
unit?: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export async function createMtrlCat(body: MtrlCatPayload, options?: AnyObj) {
|
||||
return request(`/api/cfg/mtrl-cat`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
[HTTP_HEADER.AUTHORIZATION]: getLocalToken()
|
||||
},
|
||||
data: body,
|
||||
...(options || {})
|
||||
})
|
||||
}
|
||||
|
||||
export async function updateMtrlCat(body: MtrlCatPayload & { pry_no_1: number }, options?: AnyObj) {
|
||||
return request(`/api/cfg/mtrl-cat`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
[HTTP_HEADER.AUTHORIZATION]: getLocalToken()
|
||||
},
|
||||
data: body,
|
||||
...(options || {})
|
||||
})
|
||||
}
|
||||
|
||||
export async function deleteMtrlCat(pry_no_1: number, options?: AnyObj) {
|
||||
return request(`/api/cfg/mtrl-cat/${pry_no_1}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
[HTTP_HEADER.AUTHORIZATION]: getLocalToken()
|
||||
},
|
||||
...(options || {})
|
||||
})
|
||||
}
|
||||
|
||||
export async function getCityInfoList(query?: { province?: string; city?: string }, options?: AnyObj) {
|
||||
return request<CityInformation[]>(`/api/cfg/city-info`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
[HTTP_HEADER.AUTHORIZATION]: getLocalToken()
|
||||
},
|
||||
params: query,
|
||||
...(options || {})
|
||||
})
|
||||
}
|
||||
|
||||
export async function getCityInfo(city: string, options?: AnyObj) {
|
||||
return request<{
|
||||
province: string
|
||||
city: string
|
||||
climatic_zone?: string
|
||||
env_temp: number
|
||||
env_hum: number
|
||||
} | null>(`/api/cfg/city-info/by`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
[HTTP_HEADER.AUTHORIZATION]: getLocalToken()
|
||||
},
|
||||
params: {
|
||||
city
|
||||
},
|
||||
...(options || {})
|
||||
})
|
||||
}
|
||||
|
||||
export async function getAPCLmtStdList(options?: AnyObj) {
|
||||
return request<ApcLimitStandard[]>(`/api/cfg/apc-lmt-std`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
[HTTP_HEADER.AUTHORIZATION]: getLocalToken()
|
||||
},
|
||||
...(options || {})
|
||||
})
|
||||
}
|
||||
|
||||
export type LandingHero = {
|
||||
title?: string
|
||||
description?: string
|
||||
image?: string
|
||||
}
|
||||
|
||||
export async function getLandingConfig(options?: AnyObj) {
|
||||
return request<LandingHero | null>('/api/cfg/landing', {
|
||||
method: 'GET',
|
||||
...(options || {})
|
||||
})
|
||||
}
|
||||
|
||||
export async function updateLandingConfig(body: LandingHero, options?: AnyObj) {
|
||||
return request('/api/cfg/landing', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
[HTTP_HEADER.AUTHORIZATION]: getLocalToken()
|
||||
},
|
||||
data: body,
|
||||
...(options || {})
|
||||
})
|
||||
}
|
||||
|
||||
export type LandingNewsItem = {
|
||||
id: string
|
||||
tag?: string
|
||||
date?: string
|
||||
title: string
|
||||
summary?: string
|
||||
image?: string
|
||||
}
|
||||
|
||||
export type LandingNews = {
|
||||
sectionTitle?: string
|
||||
items?: LandingNewsItem[]
|
||||
}
|
||||
|
||||
export async function getLandingNews(options?: AnyObj) {
|
||||
return request<LandingNews | null>('/api/cfg/landing-news', {
|
||||
method: 'GET',
|
||||
...(options || {})
|
||||
})
|
||||
}
|
||||
|
||||
export async function updateLandingNews(body: LandingNews, options?: AnyObj) {
|
||||
return request('/api/cfg/landing-news', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
[HTTP_HEADER.AUTHORIZATION]: getLocalToken()
|
||||
},
|
||||
data: body,
|
||||
...(options || {})
|
||||
})
|
||||
}
|
||||
|
||||
export async function getArticleContent(id: string, options?: AnyObj) {
|
||||
return request<{ html: string } | null>(`/api/cfg/article/${id}`, {
|
||||
method: 'GET',
|
||||
...(options || {})
|
||||
})
|
||||
}
|
||||
|
||||
export async function uploadArticleDoc(id: string, file: File) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return request<{ html: string; title?: string }>(`/api/cfg/article/${id}/upload-doc`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
[HTTP_HEADER.AUTHORIZATION]: getLocalToken()
|
||||
},
|
||||
data: formData
|
||||
})
|
||||
}
|
||||
|
||||
export async function getCaseImages(options?: any) {
|
||||
return request<Record<string, string>>('/api/cfg/case-images', { method: 'GET', ...(options || {}) })
|
||||
}
|
||||
|
||||
export async function setCaseImage(index: number, url: string) {
|
||||
return request<Record<string, string>>(`/api/cfg/case-images/${index}`, {
|
||||
method: 'PUT',
|
||||
headers: { [HTTP_HEADER.AUTHORIZATION]: getLocalToken() },
|
||||
data: { url }
|
||||
})
|
||||
}
|
||||
|
||||
export type LandingCaseItem = {
|
||||
type: string
|
||||
name: string
|
||||
meta?: string
|
||||
w1?: number
|
||||
v1?: string
|
||||
w2?: number
|
||||
v2?: string
|
||||
}
|
||||
|
||||
export type LandingCases = {
|
||||
sectionTitle?: string
|
||||
sectionSub?: string
|
||||
items?: LandingCaseItem[]
|
||||
}
|
||||
|
||||
export async function getLandingCases(options?: AnyObj) {
|
||||
return request<LandingCases | null>('/api/cfg/landing-cases', {
|
||||
method: 'GET',
|
||||
...(options || {})
|
||||
})
|
||||
}
|
||||
|
||||
export async function updateLandingCases(body: LandingCases, options?: AnyObj) {
|
||||
return request('/api/cfg/landing-cases', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
[HTTP_HEADER.AUTHORIZATION]: getLocalToken()
|
||||
},
|
||||
data: body,
|
||||
...(options || {})
|
||||
})
|
||||
}
|
||||
|
||||
export async function uploadLandingImage(file: File) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return request<{ url: string }>('/api/cfg/landing/upload', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
[HTTP_HEADER.AUTHORIZATION]: getLocalToken()
|
||||
},
|
||||
data: formData
|
||||
})
|
||||
}
|
||||
Loading…
Reference in New Issue