feat: 新建专辑

main
fadeaway 4 months ago
parent 72c4fa2c77
commit 6c9b39f5c7

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -1,13 +1,13 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<link rel="shortcut icon" type="image/x-icon" href="https://unpkg.byted-static.com/latest/byted/arco-config/assets/favicon.ico"> <link rel="shortcut icon" type="image/x-icon" href="./favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Arco Design Pro - 开箱即用的中台前端/设计解决方案</title> <title>雀乐音乐人</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
</body> </body>
</html> </html>

@ -0,0 +1,51 @@
import React from 'react';
import { Image, Space, Button } from '@arco-design/web-react';
import {
IconHeart,
IconThumbUp,
IconMessage,
} from '@arco-design/web-react/icon';
import styles from './style/index.module.less';
export interface AlbumItemProps {
data: any;
}
function AlbumItem(props?: AlbumItemProps) {
const { data } = props || {};
return (
<div className={styles.wrap}>
<div className={styles.contWrap}>
<Image className={styles.img} src={data?.pic} alt="专辑封面" />
<h5 className={styles.name}>{data.name}</h5>
<p className={styles.desc}>{data.desc}</p>
<p className={styles.income}>
&nbsp;<span className={styles.incomeMoney}>{data.income}</span>
</p>
<div className={styles.other}>
<Space className={styles.otherItem} size={3}>
<IconHeart />
<span>{data.collection}</span>
</Space>
<Space className={styles.otherItem} size={3}>
<IconThumbUp />
<span>{data.like}</span>
</Space>
<Space className={styles.otherItem} size={3}>
<IconMessage />
<span>{data.comment}</span>
</Space>
</div>
</div>
<Space className={styles.status}>
<Button type="text" size="small">
</Button>
</Space>
</div>
);
}
export default AlbumItem;

@ -0,0 +1,108 @@
.wrap {
width: 175px;
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
border-radius: 2px;
&:hover .contWrap {
border-color: #165dff;
}
}
.contWrap {
width: 100%;
padding: 15px;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #f7f8fa;
transition: border-color 0.3s;
border: 1px solid #f7f8fa;
border-radius: 2px;
}
.img {
width: 145px;
height: 145px;
}
.name {
width: 100%;
font-family: PingFang SC;
font-size: 14px;
font-weight: 500;
line-height: 19.6px;
text-align: left;
color: #1d2129;
margin-top: 5px;
margin-bottom: 0;
}
.desc {
width: 100%;
font-family: PingFang SC;
font-size: 12px;
font-weight: 400;
line-height: 16.8px;
text-align: left;
color: #4e5969;
margin-top: 5px;
margin-bottom: 0;
}
.income {
width: 100%;
font-family: PingFang SC;
font-size: 12px;
font-weight: 400;
line-height: 16.8px;
text-align: left;
color: #86909c;
margin-top: 5px;
margin-bottom: 0;
.incomeMoney {
font-family: PingFang SC;
font-size: 12px;
font-weight: 400;
line-height: 16.8px;
text-align: left;
color: #1d2129;
}
}
.other {
width: 100%;
color: #86909c;
font-size: 12px;
margin-top: 5px;
margin-bottom: 0;
display: flex;
align-items: center;
justify-content: space-between;
.otherItem {
display: flex;
align-items: center;
}
}
.status {
width: 100%;
box-sizing: border-box;
// padding-left: 15px;
// padding-right: 15px;
text-align: left;
margin-top: 9px;
:global(.arco-btn) {
font-family: PingFang SC;
font-size: 14px;
font-weight: 400;
color: #4e5969;
}
}

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import AvatarPng from '@/assets/avatar.png'; import AvatarPng from '@/assets/avatar.png';
import UploadForm from './UploadForm'; import UploadForm from '@/components/UploadForm';
// 艺人头像 // 艺人头像
const FormArtistAvatar = () => { const FormArtistAvatar = () => {

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import UploadForm from './UploadForm'; import UploadForm from '@/components/UploadForm';
// 后台截图 // 后台截图
const FormBackendScreenshot = () => { const FormBackendScreenshot = () => {

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import UploadForm from './UploadForm'; import UploadForm from '@/components/UploadForm';
// 主页背景 // 主页背景
const FormHomepageBackground = () => { const FormHomepageBackground = () => {

@ -21,7 +21,7 @@ import './mock';
const store = createStore(rootReducer); const store = createStore(rootReducer);
function Index() { function Index() {
const [lang, setLang] = useStorage('arco-lang', 'en-US'); const [lang, setLang] = useStorage('arco-lang', 'zh-CN');
const [theme, setTheme] = useStorage('arco-theme', 'light'); const [theme, setTheme] = useStorage('arco-theme', 'light');
const isLogin = useMemo(() => checkLogin(), []); const isLogin = useMemo(() => checkLogin(), []);

@ -109,6 +109,23 @@ function PageLayout() {
const flattenRoutes = useMemo(() => getFlattenRoutes(routes) || [], [routes]); const flattenRoutes = useMemo(() => getFlattenRoutes(routes) || [], [routes]);
const layoutBgc = useMemo(
() => ({ backgroundColor: showMenu ? '#fff' : '#f2f3f7' }),
[showMenu]
);
const layoutMainContentPadding = useMemo(
() =>
showMenu
? {
paddingTop: '15px',
paddingLeft: '30px',
paddingBottom: '50px',
}
: {},
[showMenu]
);
function renderRoutes(locale) { function renderRoutes(locale) {
routeMap.current.clear(); routeMap.current.clear();
return function travel(_routes: IRoute[], level, parentNode = []) { return function travel(_routes: IRoute[], level, parentNode = []) {
@ -197,7 +214,7 @@ function PageLayout() {
}, [pathname]); }, [pathname]);
return ( return (
<Layout className={styles.layout}> <Layout className={styles.layout} style={layoutBgc}>
<Header mode="light" /> <Header mode="light" />
<Layout className={styles.layoutContent}> <Layout className={styles.layoutContent}>
{showMenu && ( {showMenu && (
@ -226,7 +243,10 @@ function PageLayout() {
</div> </div>
</Sider> </Sider>
)} )}
<Layout className={styles.layoutMainContent}> <Layout
className={styles.layoutMainContent}
style={{ ...layoutMainContentPadding, ...layoutBgc }}
>
{!!breadcrumb.length && ( {!!breadcrumb.length && (
<div className={styles.layoutBreadcrumb}> <div className={styles.layoutBreadcrumb}>
<Breadcrumb> <Breadcrumb>
@ -243,6 +263,7 @@ function PageLayout() {
{flattenRoutes.map((route, index) => { {flattenRoutes.map((route, index) => {
return ( return (
<Route <Route
exact
key={index} key={index}
path={`/${route.key}`} path={`/${route.key}`}
component={route.component} component={route.component}

@ -3,6 +3,7 @@ const i18n = {
'menu.index': 'Index', 'menu.index': 'Index',
'menu.artists': 'Artists', 'menu.artists': 'Artists',
'menu.albumWorks': 'Album Works', 'menu.albumWorks': 'Album Works',
'menu.albumWorks.create': 'Create an Album',
'menu.financial': 'Financial', 'menu.financial': 'Financial',
'menu.settings': 'Settings', 'menu.settings': 'Settings',
'menu.dashboard': 'Dashboard', 'menu.dashboard': 'Dashboard',
@ -46,6 +47,7 @@ const i18n = {
'menu.index': '首页', 'menu.index': '首页',
'menu.artists': '厂牌艺人', 'menu.artists': '厂牌艺人',
'menu.albumWorks': '专辑作品', 'menu.albumWorks': '专辑作品',
'menu.albumWorks.create': '新建专辑',
'menu.financial': '财务报表', 'menu.financial': '财务报表',
'menu.settings': '个人设置', 'menu.settings': '个人设置',
'menu.dashboard': '仪表盘', 'menu.dashboard': '仪表盘',

@ -0,0 +1,65 @@
import React, { useMemo, useState } from 'react';
import { Breadcrumb, Steps } from '@arco-design/web-react';
import Step1 from './step1';
import Step2 from './step2';
import Step3, { AgreeStep3 } from './step3';
import styles from './style/index.module.less';
const Step = Steps.Step;
const AllSteps = [
{ title: '专辑信息', desc: '请填写专辑基本信息' },
{ title: '上传歌曲', desc: '上传歌曲并填写信息' },
{ title: '签约授权', desc: '对专辑进行签约授权' },
];
function CreateAlbum() {
const [curStep, setCurStep] = useState(1);
const steps = useMemo(() => AllSteps.slice(0, curStep), [curStep]);
const handleStep1Next = (step1Values) => {
console.log(step1Values);
setCurStep(2);
};
const handleStep2Pre = () => {
setCurStep(1);
};
const handleStep2Next = () => {
setCurStep(3);
};
const handleStep3Next = () => {
// TOOD:
};
return (
<div className={styles.warp}>
<Breadcrumb className={styles.breadcrumbWrap}>
{steps.map((node, index) => (
<Breadcrumb.Item key={index}>{node.title}</Breadcrumb.Item>
))}
</Breadcrumb>
<section className={styles.contWrap}>
<Steps
className={styles.stepsWrap}
current={curStep}
style={{ maxWidth: 780, marginBottom: 40 }}
>
{AllSteps.map(({ title, desc }) => (
<Step title={title} description={desc} key={title} />
))}
</Steps>
{curStep === 1 && <Step1 onNext={handleStep1Next} />}
{curStep === 2 && (
<Step2 onPre={handleStep2Pre} onNext={handleStep2Next} />
)}
{curStep === 3 && <Step3 />}
</section>
{curStep === 3 && <AgreeStep3 onNext={handleStep3Next} />}
</div>
);
}
export default CreateAlbum;

@ -0,0 +1,117 @@
import React, { useMemo, useState } from 'react';
import {
Form,
Input,
Select,
DatePicker,
Button,
} from '@arco-design/web-react';
import UploadForm from '@/components/UploadForm';
import stylesIndex from './style/index.module.less';
import styles from './style/step1.module.less';
export interface CreateAlbumStep1Props {
onNext?: (arg0: any) => void;
}
const { Item: FormItem, useForm } = Form;
const { TextArea } = Input;
const AlbumVersionOptions = [{ label: '录音室版本', value: '1' }];
const AlbumTypeOptions = [{ label: 'EP', value: '1' }];
const AlbumStypeOptions = [
{ label: '后摇', value: '1' },
{ label: '后朋克', value: '2' },
];
function CreateAlbumStep1(props: CreateAlbumStep1Props) {
const [form] = useForm();
const { onNext } = props || {};
const handleNext = () => {
form.validate().then((values) => {
console.log(values);
onNext?.(values);
});
};
return (
<Form
form={form}
labelCol={{ flex: '100px' }}
wrapperCol={{ flex: '468px' }}
>
<FormItem
label="专辑名称"
field="albumName"
rules={[{ required: true, message: '输入专辑名称' }]}
>
<Input placeholder="输入专辑名称" allowClear />
</FormItem>
<FormItem label="专辑版本" field="albumVersion">
<Select
placeholder="选择专辑版本"
allowClear
options={AlbumVersionOptions}
/>
</FormItem>
<FormItem
label="专辑类型"
field="albumType"
rules={[{ required: true, message: '选择专辑类型' }]}
>
<Select
placeholder="选择专辑类型"
allowClear
options={AlbumTypeOptions}
/>
</FormItem>
<FormItem
label="专辑风格"
field="albumStyle"
rules={[{ required: true, message: '选择发行日期' }]}
>
<Select
mode="multiple"
placeholder="选择专辑风格"
allowClear
options={AlbumStypeOptions}
/>
</FormItem>
<FormItem
label="发行日期"
field="date"
rules={[{ required: true, message: '选择发行日期' }]}
>
<DatePicker
placeholder="选择发行日期"
allowClear
style={{ width: '100%' }}
/>
</FormItem>
<FormItem label="专辑条码" field="albumCode">
<Input placeholder="输入专辑条码" allowClear />
</FormItem>
<FormItem label="专辑描述" field="albumDesc">
<TextArea placeholder="简单描述专辑" allowClear />
</FormItem>
<UploadForm
label="专辑封面"
field="albumCover"
rules={[{ required: true, message: '上传专辑封面' }]}
tip="上传高清专辑封面"
subTip="支持格式:.jpg .png尺寸大于640*6402M以内"
/>
<div className={stylesIndex.submitWrap}>
<Button type="primary" onClick={handleNext}>
</Button>
</div>
</Form>
);
}
export default CreateAlbumStep1;

@ -0,0 +1,117 @@
import React, { useMemo, useState } from 'react';
import {
Form,
Input,
Select,
DatePicker,
Button,
Upload,
Message,
Space,
} from '@arco-design/web-react';
import stylesIndex from './style/index.module.less';
import styles from './style/step2.module.less';
export interface CreateAlbumStep1Props {
onPre?: () => void;
onNext?: (arg0: any) => void;
}
const { Item: FormItem, useForm } = Form;
const isAcceptFile = (file, accept) => {
if (accept && file) {
const accepts = Array.isArray(accept)
? accept
: accept
.split(',')
.map((x) => x.trim())
.filter((x) => x);
const fileExtension =
file.name.indexOf('.') > -1 ? file.name.split('.').pop() : '';
return accepts.some((type) => {
const text = type && type.toLowerCase();
const fileType = (file.type || '').toLowerCase();
if (text === fileType) {
// 类似excel文件这种
// 比如application/vnd.ms-excel和application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
// 本身就带有.字符的,不能走下面的.jpg等文件扩展名判断处理
// 所以优先对比input的accept类型和文件对象的type值
return true;
}
if (new RegExp('/*').test(text)) {
// image/* 这种通配的形式处理
const regExp = new RegExp('/.*$');
return fileType.replace(regExp, '') === text.replace(regExp, '');
}
if (new RegExp('..*').test(text)) {
// .jpg 等后缀名
return text === `.${fileExtension && fileExtension.toLowerCase()}`;
}
return false;
});
}
return !!file;
};
function CreateAlbumStep2(props: CreateAlbumStep1Props) {
const [form] = useForm();
const { onPre, onNext } = props || {};
const handlePre = () => {
onPre?.();
};
const handleNext = () => {
form.validate().then((values) => {
console.log(values);
onNext?.(values);
});
};
return (
<>
<Upload
className={styles.uploadArea}
drag
multiple
accept="image/*"
action="/"
onDrop={(e) => {
const uploadFile = e.dataTransfer.files[0];
if (isAcceptFile(uploadFile, 'image/*')) {
return;
} else {
Message.info('不接受的文件类型,请重新上传指定文件类型~');
}
}}
tip={
<div className={styles.uploadAreaTip}>
{
'歌曲须为MP3/WAV建议优先上传WAV播放效果更佳音质≥320kbps采样率≥44.1kHz,文件大小<200MB'
}
</div>
}
/>
<Form
form={form}
labelCol={{ flex: '100px' }}
wrapperCol={{ flex: '468px' }}
>
{/* <FormItem field="albumName">
</FormItem> */}
<div className={stylesIndex.submitWrap}>
<Space size={24}>
<Button onClick={handlePre}></Button>
<Button type="primary" onClick={handleNext}>
</Button>
</Space>
</div>
</Form>
</>
);
}
export default CreateAlbumStep2;

@ -0,0 +1,62 @@
import React, { useState } from 'react';
import { Radio, Button, Message } from '@arco-design/web-react';
import styles from './style/step3.module.less';
function CreateAlbumStep3() {
return (
<>
<h3 className={styles.title}></h3>
<br />
<article className={styles.article}>
使线使
<br />
<br />
<h5 className={styles.contTitle}></h5>
/使
<br />
<br />
<h5 className={styles.contTitle}>使</h5>
1
<br />
2使
<br />
3使
<br />
4使
</article>
</>
);
}
export interface AgreeStep3Props {
onNext?: () => void;
}
function AgreeStep3(props: AgreeStep3Props) {
const { onNext } = props || {};
const [agree, setAgree] = useState(false);
const handleNext = () => {
if (!agree) {
Message.warning('请阅读协议并同意向雀乐音乐人授权');
} else {
onNext?.();
}
};
return (
<div className={styles.agreeWrap}>
<Radio checked={agree} onChange={(checked: boolean) => setAgree(checked)}>
</Radio>
<Button type="primary" onClick={handleNext}>
</Button>
</div>
);
}
export default CreateAlbumStep3;
export { AgreeStep3 };

@ -0,0 +1,41 @@
.warp {
width: 800px;
min-height: 100%;
margin: auto;
padding-top: 40px;
padding-bottom: 67px;
display: flex;
flex-direction: column;
box-sizing: border-box;
}
.breadcrumbWrap {
margin-bottom: 14px;
}
.contWrap {
flex: 1;
width: 100%;
background: #fff;
border-radius: 16px;
padding: 50px 40px 80px;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
}
.stepsWrap {
:global(.arco-steps-item) {
width: 232px;
}
}
.submitWrap {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
margin-top: 40px;
}

@ -0,0 +1,20 @@
.uploadArea {
width: 494px;
height: 224px;
margin: auto;
:global(.arco-upload-trigger),
:global(.arco-upload-trigger .arco-upload-trigger-drag) {
width: 100%;
height: 100%;
box-sizing: border-box;
}
.uploadAreaTip {
width: 462px;
white-space: pre-wrap;
text-align: center;
margin-left: auto;
margin-right: auto;
}
}

@ -0,0 +1,36 @@
.title {
font-family: PingFang SC;
font-size: 17px;
font-weight: 600;
line-height: 20px;
text-align: left;
color: #1d2129;
width: 100%;
margin: 0;
}
.contTitle {
font-family: PingFang SC;
font-size: 12px;
font-weight: 500;
line-height: 20px;
text-align: left;
margin: 0;
}
.article {
font-family: PingFang SC;
font-size: 12px;
font-weight: 400;
line-height: 20px;
text-align: left;
color: #4e5969;
margin: 0;
}
.agreeWrap {
margin-top: 30px;
display: flex;
align-items: center;
justify-content: space-between;
}

@ -1,12 +1,180 @@
import React from 'react'; import React, { useState, useEffect } from 'react';
import { Typography, Card } from '@arco-design/web-react'; import { useHistory } from 'react-router-dom';
import {
Typography,
Card,
Tabs,
Space,
Button,
Select,
Grid,
} from '@arco-design/web-react';
import AlbumItem from '@/components/AlbumItem';
const { Row, Col } = Grid;
const TabPane = Tabs.TabPane;
const options = [
{ label: '全部时间', value: 'all' },
{ label: '近一个月', value: 'lastAMonth' },
{ label: '近三个月', value: 'last3Month' },
{ label: '近半年', value: 'lastHaltYear' },
{ label: '近一年', value: 'lastYear' },
];
function AlbumWorks() { function AlbumWorks() {
const history = useHistory();
const [activeTab, setActiveTab] = useState('1');
const [albumList, setAlbumList] = useState([]);
const handleCreate = () => {
history.push('/albumWorks/create?menu=false');
};
useEffect(() => {
setAlbumList([
{
pic: '',
name: 'Ring of Past',
desc: 'Real Life Listening Party 2019.09.23',
income: '1341',
collection: 300,
like: 300,
comment: 300,
status: 1,
},
{
pic: '',
name: 'Ring of Past2',
desc: 'Real Life Listening Party 2019.09.23',
income: '1341',
collection: 300,
like: 300,
comment: 300,
status: 1,
},
{
pic: '',
name: 'Ring of Past3',
desc: 'Real Life Listening Party 2019.09.23',
income: '1341',
collection: 300,
like: 300,
comment: 300,
status: 1,
},
{
pic: '',
name: 'Ring of Past4',
desc: 'Real Life Listening Party 2019.09.23',
income: '1341',
collection: 300,
like: 300,
comment: 300,
status: 1,
},
{
pic: '',
name: 'Ring of Past5',
desc: 'Real Life Listening Party 2019.09.23',
income: '1341',
collection: 300,
like: 300,
comment: 300,
status: 1,
},
{
pic: '',
name: 'Ring of Past6',
desc: 'Real Life Listening Party 2019.09.23',
income: '1341',
collection: 300,
like: 300,
comment: 300,
status: 1,
},
{
pic: '',
name: 'Ring of Past7',
desc: 'Real Life Listening Party 2019.09.23',
income: '1341',
collection: 300,
like: 300,
comment: 300,
status: 1,
},
]);
}, []);
const handleTabsChange = (tabKey) => {
setActiveTab(tabKey);
};
const tabs = [
{
title: '上架中',
key: '1',
content: '上架中',
},
{
title: '已新建',
key: '2',
content: '已新建',
},
{
title: '待审核',
key: '3',
content: '待审核',
},
{
title: '待上架',
key: '4',
content: '待上架',
},
{
title: '已驳回',
key: '5',
content: '已驳回',
},
];
return ( return (
<Card> <>
<Typography.Title heading={6}></Typography.Title> <Row style={{ marginBottom: '15px' }}>
<Typography.Text>You can add content here :)</Typography.Text> <Col flex={1}>
</Card> <Tabs
type="card-gutter"
activeTab={activeTab}
onChange={handleTabsChange}
>
{tabs.map((x) => (
<TabPane destroyOnHide key={x.key} title={x.title} />
))}
</Tabs>
</Col>
<Col flex={500} style={{ textAlign: 'right' }}>
<Space size={12}>
<Button type="primary" onClick={handleCreate}>
</Button>
<Select
style={{ width: '200px' }}
defaultValue="all"
options={options}
/>
</Space>
</Col>
</Row>
<Row gutter={[28, 15]}>
{albumList?.map((album: any, index) => {
return (
<Col key={`${index}-${album.name}`} flex="20%">
<AlbumItem data={album} />
</Col>
);
})}
</Row>
</>
); );
} }

@ -16,13 +16,21 @@ export const routes: IRoute[] = [
name: 'menu.index', name: 'menu.index',
key: 'index', key: 'index',
}, },
{ // {
name: 'menu.artists', // name: 'menu.artists',
key: 'artists', // key: 'artists',
}, // },
{ {
name: 'menu.albumWorks', name: 'menu.albumWorks',
key: 'albumWorks', key: 'albumWorks',
children: [
{
name: 'menu.albumWorks.create',
key: 'albumWorks/create',
ignore: true,
breadcrumb: false,
},
],
}, },
{ {
name: 'menu.financial', name: 'menu.financial',

@ -10,6 +10,22 @@ body {
background-color: var(--color-bg-1); background-color: var(--color-bg-1);
} }
::-webkit-scrollbar {
width: 16px;
height: 4px;
}
::-webkit-scrollbar-thumb {
border: 4px solid transparent;
background-clip: padding-box;
border-radius: 7px;
background-color: var(--color-text-4);
}
::-webkit-scrollbar-thumb:hover {
background-color: var(--color-text-3);
}
.chart-wrapper { .chart-wrapper {
.bizcharts-tooltip { .bizcharts-tooltip {
background: linear-gradient( background: linear-gradient(

@ -121,7 +121,4 @@
background-color: #fff; background-color: #fff;
transition: padding-left 0.2s; transition: padding-left 0.2s;
box-sizing: border-box; box-sizing: border-box;
padding-top: 15px;
padding-left: 30px;
padding-bottom: 50px;
} }

Loading…
Cancel
Save