1.录音文件加载时,显示模态页面防止用户快速点击。
BIN
public/logo.png
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 821 B |
30
src/App.js
@ -1,12 +1,32 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { Routes, Route, Navigate, useNavigate } from 'react-router-dom'
|
import { Routes, Route, Navigate, useNavigate } from 'react-router-dom'
|
||||||
import { useDispatch } from 'react-redux'
|
import { useDispatch } from 'react-redux'
|
||||||
|
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
||||||
import { setAccessToken, setUserInfo, setSelectInfo } from "./business/userSlice.js"
|
import { setAccessToken, setUserInfo, setSelectInfo } from "./business/userSlice.js"
|
||||||
import { useCookies } from 'react-cookie';
|
import { useCookies } from 'react-cookie';
|
||||||
import LoginPage from './LoginPage';
|
import LoginPage from './LoginPage';
|
||||||
import MainPage from './MainPage';
|
import MainPage from './MainPage';
|
||||||
import yzs from "./business/request.js";
|
import yzs from "./business/request.js";
|
||||||
|
|
||||||
|
const theme = createTheme({
|
||||||
|
status: {
|
||||||
|
danger: '#e53e3e',
|
||||||
|
},
|
||||||
|
palette: {
|
||||||
|
primary: {
|
||||||
|
main: '#FF595A',
|
||||||
|
darker: '#FF595A',
|
||||||
|
},
|
||||||
|
neutral: {
|
||||||
|
main: '#64748B',
|
||||||
|
contrastText: '#fff',
|
||||||
|
},
|
||||||
|
black: {
|
||||||
|
main: "#222222",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -32,10 +52,12 @@ function App() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Routes>
|
<ThemeProvider theme={theme}>
|
||||||
<Route exact path="/" element={cookies.accessToken ? <MainPage /> : <Navigate to="/login" />} />
|
<Routes>
|
||||||
<Route exact path="/login" element={<LoginPage />} />
|
<Route exact path="/" element={cookies.accessToken ? <MainPage /> : <Navigate to="/login" />} />
|
||||||
</Routes>
|
<Route exact path="/login" element={<LoginPage />} />
|
||||||
|
</Routes>
|
||||||
|
</ThemeProvider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import MenuItem from '@mui/material/MenuItem';
|
|||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import Toolbar from '@mui/material/Toolbar';
|
import Toolbar from '@mui/material/Toolbar';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import logo from './assets/logo.png';
|
import appbar_logo from './assets/appbar_logo.png';
|
||||||
import { Stack, CssBaseline } from '@mui/material';
|
import { Stack, CssBaseline } from '@mui/material';
|
||||||
import { useCookies } from 'react-cookie';
|
import { useCookies } from 'react-cookie';
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
@ -40,11 +40,11 @@ export default function () {
|
|||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<Container maxWidth={false} >
|
<Container maxWidth={false} >
|
||||||
<Toolbar disableGutters variant="dense">
|
<Toolbar disableGutters variant="dense">
|
||||||
<Stack direction="row" sx={{ flexGrow: 1 }}>
|
<Stack direction="row" sx={{ flexGrow: 1, alignItems: "center" }}>
|
||||||
<img src={logo} style={{
|
<img src={appbar_logo} style={{
|
||||||
width: 28,
|
width: 28,
|
||||||
height: 30,
|
height: 28,
|
||||||
marginRight: 24,
|
marginRight: 10,
|
||||||
}} />
|
}} />
|
||||||
<Typography variant='h6' sx={{ color: "#FFFFFF" }}>纽曼AI语记</Typography>
|
<Typography variant='h6' sx={{ color: "#FFFFFF" }}>纽曼AI语记</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -16,23 +16,6 @@ import TabContext from '@mui/lab/TabContext';
|
|||||||
import DynamicCodeForm from './components/DynamicCodeForm.js';
|
import DynamicCodeForm from './components/DynamicCodeForm.js';
|
||||||
import PasswordForm from './components/PasswordForm.js';
|
import PasswordForm from './components/PasswordForm.js';
|
||||||
import { useCookies } from 'react-cookie';
|
import { useCookies } from 'react-cookie';
|
||||||
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
|
||||||
|
|
||||||
const theme = createTheme({
|
|
||||||
status: {
|
|
||||||
danger: '#e53e3e',
|
|
||||||
},
|
|
||||||
palette: {
|
|
||||||
primary: {
|
|
||||||
main: '#FF595A',
|
|
||||||
darker: '#FF595A',
|
|
||||||
},
|
|
||||||
neutral: {
|
|
||||||
main: '#64748B',
|
|
||||||
contrastText: '#fff',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -116,48 +99,45 @@ export default function () {
|
|||||||
<img className={styles.titleIcon} src={logo} />
|
<img className={styles.titleIcon} src={logo} />
|
||||||
<h1 className={styles.titleText}>纽曼AI语记</h1>
|
<h1 className={styles.titleText}>纽曼AI语记</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.loginFrame}>
|
<div className={styles.loginFrame}>
|
||||||
<ThemeProvider theme={theme}>
|
<Container component="form" className={styles.form} onSubmit={handleSubmit}
|
||||||
<Container component="form" className={styles.form} onSubmit={handleSubmit}
|
sx={{
|
||||||
sx={{
|
width: 360,
|
||||||
width: 360,
|
height: 418,
|
||||||
height: 418,
|
backgroundColor: 'white',
|
||||||
backgroundColor: 'white',
|
display: "flex",
|
||||||
display: "flex",
|
flexDirection: "column",
|
||||||
flexDirection: "column",
|
justifyContent: "center",
|
||||||
justifyContent: "center",
|
alignItems: "center",
|
||||||
alignItems: "center",
|
boxShadow: "0px 5px 20px 0px rgba(146,0,1,0.1)",
|
||||||
boxShadow: "0px 5px 20px 0px rgba(146,0,1,0.1)",
|
borderRadius: 4,
|
||||||
borderRadius: 4,
|
|
||||||
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TabContext value={value}>
|
<TabContext value={value}>
|
||||||
<Box>
|
<Box>
|
||||||
<TabList
|
<TabList
|
||||||
aria-label="basic tabs example" value={value} onChange={handleChange} >
|
aria-label="basic tabs example" value={value} onChange={handleChange} >
|
||||||
<Tab label="手机动态码登录" value="1" />
|
<Tab label="手机动态码登录" value="1" />
|
||||||
<Tab label="账号密码登录" value="2" />
|
<Tab label="账号密码登录" value="2" />
|
||||||
</TabList>
|
</TabList>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<TabPanel value="1" >
|
<TabPanel value="1" >
|
||||||
<DynamicCodeForm udid={yzs.uniqueDeviceIdentifier()}
|
<DynamicCodeForm udid={yzs.uniqueDeviceIdentifier()}
|
||||||
firstEnter={firstEnter}
|
firstEnter={firstEnter}
|
||||||
agreeAgreement={agreeAgreement} onAgreeChange={onAgreeChange}
|
agreeAgreement={agreeAgreement} onAgreeChange={onAgreeChange}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel value="2" >
|
<TabPanel value="2" >
|
||||||
<PasswordForm firstEnter={firstEnter}
|
<PasswordForm firstEnter={firstEnter}
|
||||||
agreeAgreement={agreeAgreement} onAgreeChange={onAgreeChange}
|
agreeAgreement={agreeAgreement} onAgreeChange={onAgreeChange}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabContext>
|
</TabContext>
|
||||||
</Container>
|
</Container>
|
||||||
</ThemeProvider >
|
|
||||||
{/* <Button variant="contained" onClick={debug_test}>测试</Button> */}
|
{/* <Button variant="contained" onClick={debug_test}>测试</Button> */}
|
||||||
</div>
|
</div>
|
||||||
<Snackbar
|
<Snackbar
|
||||||
|
122
src/MainPage.js
@ -3,65 +3,25 @@ import { useSelector, useDispatch } from 'react-redux'
|
|||||||
import AppBar from './AppBar';
|
import AppBar from './AppBar';
|
||||||
import RecordList from './components/RecordList';
|
import RecordList from './components/RecordList';
|
||||||
import PlayerBar from './PlayerBar';
|
import PlayerBar from './PlayerBar';
|
||||||
import store from './business/store';
|
|
||||||
import yzs from "./business/request.js";
|
import yzs from "./business/request.js";
|
||||||
import { setList, setCurrentLyric, setCurrentBlob, setCurrentWaveData } from "./business/recorderSlice.js"
|
import { setList, fetchRecord } from "./business/recorderSlice.js"
|
||||||
import { CssBaseline, Box } from '@mui/material';
|
import { CssBaseline, Box, Typography } from '@mui/material';
|
||||||
import MainSkeleton from './MainSkeleton';
|
import Backdrop from '@mui/material/Backdrop';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
import RecordLyrics from './RecordLyrics';
|
import RecordLyrics from './RecordLyrics';
|
||||||
import { createTheme, ThemeProvider, styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import expand from './assets/expand.png';
|
import expand from './assets/expand.png';
|
||||||
import close from './assets/close.png';
|
import close from './assets/close.png';
|
||||||
|
import empty_hint from './assets/empty_hint.png';
|
||||||
|
|
||||||
const drawerWidth = 240;
|
const drawerWidth = 240;
|
||||||
|
|
||||||
const theme = createTheme({
|
|
||||||
status: {
|
|
||||||
danger: '#e53e3e',
|
|
||||||
},
|
|
||||||
palette: {
|
|
||||||
primary: {
|
|
||||||
main: '#FF595A',
|
|
||||||
darker: '#FF595A',
|
|
||||||
},
|
|
||||||
neutral: {
|
|
||||||
main: '#64748B',
|
|
||||||
contrastText: '#fff',
|
|
||||||
},
|
|
||||||
black: {
|
|
||||||
main: "#222222",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const lyricsBrowserStyle = {
|
const lyricsBrowserStyle = {
|
||||||
marginTop: 16,
|
marginTop: 16,
|
||||||
paddingBottom: 40,
|
paddingBottom: 40,
|
||||||
padding: 24,
|
padding: 24,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function fetchRecord(accessToken, record) {
|
|
||||||
if (record.transResultUrl) {
|
|
||||||
yzs.download(accessToken, record.transResultUrl).then(
|
|
||||||
blob => blob.text()
|
|
||||||
).then(text => {
|
|
||||||
// console.log("type", record.type, text);
|
|
||||||
let payload = null;
|
|
||||||
if (record.type === 1 || record.type === 3) {
|
|
||||||
payload = JSON.parse(text)
|
|
||||||
} else {
|
|
||||||
payload = text;
|
|
||||||
}
|
|
||||||
store.dispatch(setCurrentLyric(payload));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
yzs.download(accessToken, record.audioUrl).then(blob => {
|
|
||||||
store.dispatch(setCurrentBlob(URL.createObjectURL(blob)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const ClickHanlde = styled('div', { shouldForwardProp: (prop) => prop !== 'open' })(
|
const ClickHanlde = styled('div', { shouldForwardProp: (prop) => prop !== 'open' })(
|
||||||
({ theme, open }) => ({
|
({ theme, open }) => ({
|
||||||
width: 18,
|
width: 18,
|
||||||
@ -105,15 +65,43 @@ const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const RecordPlayer = ({ loading, playerBarWidth, currentTime, hasLyric, currentLyric }) => {
|
const LyricItem = ({ empty, hasLyric, lyricsBrowserStyle, currentLyric, currentTime }) => {
|
||||||
|
if (empty) {
|
||||||
|
return <div style={{
|
||||||
|
height: "calc(100vh - 250px)",
|
||||||
|
marginTop: 48,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}} >
|
||||||
|
<div>
|
||||||
|
<img style={{ maxWidth: "100%", marginBottom: 40, }} src={empty_hint} />
|
||||||
|
<Typography align='center' color="#929292">这里空空如也,添加些东西吧</Typography>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
} else {
|
||||||
|
return hasLyric ? <RecordLyrics style={lyricsBrowserStyle} currentLyric={currentLyric} currentTime={currentTime} /> :
|
||||||
|
<div style={lyricsBrowserStyle}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const RecordPlayer = ({ loading, empty, playerBarWidth, currentTime, hasLyric, currentLyric }) => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <MainSkeleton />
|
return <Backdrop
|
||||||
|
sx={{
|
||||||
|
color: '#fff',
|
||||||
|
zIndex: (theme) => theme.zIndex.drawer + 1,
|
||||||
|
// marginLeft: "240px",
|
||||||
|
marginTop: "45px",
|
||||||
|
}}
|
||||||
|
open >
|
||||||
|
<CircularProgress color="inherit" />
|
||||||
|
</Backdrop>
|
||||||
} else {
|
} else {
|
||||||
return <div>
|
return <div>
|
||||||
<PlayerBar width={playerBarWidth} currentTime={currentTime} />
|
<PlayerBar width={playerBarWidth} currentTime={currentTime} lyric={currentLyric} />
|
||||||
{hasLyric ? <RecordLyrics style={lyricsBrowserStyle} currentLyric={currentLyric} currentTime={currentTime} /> :
|
<LyricItem empty={empty} />
|
||||||
<div style={lyricsBrowserStyle}
|
|
||||||
/>}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -135,7 +123,7 @@ export default function () {
|
|||||||
yzs.get_record_list(accessToken, passportId).then(list => {
|
yzs.get_record_list(accessToken, passportId).then(list => {
|
||||||
dispatch(setList(list));
|
dispatch(setList(list));
|
||||||
if (list.length > 0) {
|
if (list.length > 0) {
|
||||||
fetchRecord(accessToken, list.at(0));
|
dispatch(fetchRecord(accessToken, 0, list.at(0)));
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.log("get list failed", error);
|
console.log("get list failed", error);
|
||||||
@ -156,7 +144,7 @@ export default function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
// console.log("innerWidth", document.documentElement.clientWidth, document.documentElement.clientWidth - (open ? 240 : 0) - 48)
|
// let scrollBarWidth = window.innerWidth - document.documentElement.clientWidth;
|
||||||
setPlayerBarWidth(document.documentElement.clientWidth - (open ? 240 : 0) - 48);
|
setPlayerBarWidth(document.documentElement.clientWidth - (open ? 240 : 0) - 48);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,20 +160,20 @@ export default function () {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => { handleResize(); }, [currentLyric]);
|
useEffect(() => {
|
||||||
|
if (!loading) handleResize();
|
||||||
|
}, [loading]);
|
||||||
|
|
||||||
return <Box sx={{ display: 'flex' }}>
|
return <Box sx={{ display: 'flex' }}>
|
||||||
<ThemeProvider theme={theme}>
|
<CssBaseline />
|
||||||
<CssBaseline />
|
<AppBar />
|
||||||
<AppBar />
|
<RecordList open={open} recordList={recordList} currentIndex={currentIndex} />
|
||||||
<RecordList open={open} recordList={recordList} currentIndex={currentIndex} fetchRecord={fetchRecord} />
|
<ClickHanlde open={open} onClick={onClick} />
|
||||||
<ClickHanlde open={open} onClick={onClick} />
|
<Main open={open}
|
||||||
<Main open={open}
|
onTransitionEnd={onTransitionEnd}
|
||||||
onTransitionEnd={onTransitionEnd}
|
>
|
||||||
>
|
<RecordPlayer loading={loading} empty={recordList.length <= 0}
|
||||||
<RecordPlayer loading={loading}
|
playerBarWidth={playerBarWidth} currentTime={currentTime} hasLyric={hasLyric} currentLyric={currentLyric} />
|
||||||
playerBarWidth={playerBarWidth} currentTime={currentTime} hasLyric={hasLyric} currentLyric={currentLyric} />
|
</Main>
|
||||||
</Main>
|
|
||||||
</ThemeProvider>
|
|
||||||
</Box >
|
</Box >
|
||||||
}
|
}
|
@ -1,34 +0,0 @@
|
|||||||
import Stack from '@mui/material/Stack';
|
|
||||||
import Box from '@mui/material/Box';
|
|
||||||
import Skeleton from '@mui/material/Skeleton';
|
|
||||||
import { Container } from '@mui/material';
|
|
||||||
|
|
||||||
export default function () {
|
|
||||||
return (
|
|
||||||
<Box spacing={1} sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
// justifyContent: "space-between",
|
|
||||||
height: `calc(100vh - 48px)`,
|
|
||||||
}}>
|
|
||||||
<Skeleton variant="text" sx={{ fontSize: '1rem' }} />
|
|
||||||
<Container disableGutters maxWidth={false} sx={{
|
|
||||||
height: 60,
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
marginTop: 2,
|
|
||||||
marginBottom: 0.5,
|
|
||||||
}}>
|
|
||||||
<Skeleton variant="rounded" width={"30%"} height={60} />
|
|
||||||
<Skeleton variant="circular" width={30} height={30} />
|
|
||||||
</Container>
|
|
||||||
{/* For variant="text", adjust the height via font-size */}
|
|
||||||
|
|
||||||
{/* For other variants, adjust the size with `width` and `height` */}
|
|
||||||
<Skeleton variant="rounded" animation="wave" width="100%" height={70} sx={{ marginBottom: 0.5 }} />
|
|
||||||
<Skeleton variant="rounded" animation="wave" width="100%" height={32} sx={{ marginBottom: 2 }} />
|
|
||||||
<Skeleton variant="rounded" width="100%" sx={{ marginBottom: 2, flexGrow: 1 }} />
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
@ -5,7 +5,7 @@ import pauseIcon from "./assets/play.png";
|
|||||||
import playIcon from "./assets/pause.png";
|
import playIcon from "./assets/pause.png";
|
||||||
import downloadIcon from "./assets/download.png";
|
import downloadIcon from "./assets/download.png";
|
||||||
import { setCurrentTime, setPauseState, togglePauseState, setCurrentWaveData } from "./business/recorderSlice.js"
|
import { setCurrentTime, setPauseState, togglePauseState, setCurrentWaveData } from "./business/recorderSlice.js"
|
||||||
import { audioWaveData, sampleInterval } from "./business/utilities"
|
import { audioWaveData, sampleInterval, exportRecordLyric } from "./business/utilities"
|
||||||
import ProgressBar from "./components/ProgressBar";
|
import ProgressBar from "./components/ProgressBar";
|
||||||
|
|
||||||
const durationFormat = (time) => {
|
const durationFormat = (time) => {
|
||||||
@ -17,7 +17,7 @@ const durationFormat = (time) => {
|
|||||||
return hour.toString().padStart(2, '0') + ":" + minute.toString().padStart(2, '0') + ":" + second.toString().padStart(2, '0');
|
return hour.toString().padStart(2, '0') + ":" + minute.toString().padStart(2, '0') + ":" + second.toString().padStart(2, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ({ width, currentTime }) {
|
export default function ({ width, lyric, currentTime }) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [duration, setDuration] = useState(0); // 秒,有小数点
|
const [duration, setDuration] = useState(0); // 秒,有小数点
|
||||||
const [playbackRate, setPlaybackRate] = useState(1.0);
|
const [playbackRate, setPlaybackRate] = useState(1.0);
|
||||||
@ -54,6 +54,7 @@ export default function ({ width, currentTime }) {
|
|||||||
link.href = currentBlob;
|
link.href = currentBlob;
|
||||||
link.download = recordList.at(currentIndex).name;
|
link.download = recordList.at(currentIndex).name;
|
||||||
link.click();
|
link.click();
|
||||||
|
exportRecordLyric(recordList.at(currentIndex).type, lyric, recordList.at(currentIndex).editName + ".txt");
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDurationChange = (event) => {
|
const onDurationChange = (event) => {
|
||||||
@ -86,7 +87,7 @@ export default function ({ width, currentTime }) {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}>
|
}}>
|
||||||
<Typography variant="h6" sx={{ flexGrow: 1 }} >{recordList.length > 0 ? recordList.at(currentIndex).editName : ""}</Typography>
|
<Typography variant="h6" sx={{ flexGrow: 1 }} >{recordList.length > 0 ? recordList.at(currentIndex).editName : "暂无内容"}</Typography>
|
||||||
<IconButton onClick={onDownload}>
|
<IconButton onClick={onDownload}>
|
||||||
<img src={downloadIcon} />
|
<img src={downloadIcon} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -13,10 +13,12 @@ function isHighlight(currentTime, { start, end }) {
|
|||||||
// type: 3 --> 双语对话
|
// type: 3 --> 双语对话
|
||||||
|
|
||||||
const PlainText = ({ lyrics }) => {
|
const PlainText = ({ lyrics }) => {
|
||||||
|
if (typeof lyrics !== "string") return <React.Fragment />;
|
||||||
return <div style={{ whiteSpace: "pre-wrap" }}>{lyrics}</div>
|
return <div style={{ whiteSpace: "pre-wrap" }}>{lyrics}</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
const ImportAudio = ({ lyrics, currentTime }) => { // 导入音频
|
const ImportAudio = ({ lyrics, currentTime }) => { // 导入音频
|
||||||
|
if (typeof lyrics !== "object") return <React.Fragment />;
|
||||||
const onClick = (index) => {
|
const onClick = (index) => {
|
||||||
console.log("onClick", index);
|
console.log("onClick", index);
|
||||||
}
|
}
|
||||||
@ -29,6 +31,7 @@ const ImportAudio = ({ lyrics, currentTime }) => { // 导入音频
|
|||||||
};
|
};
|
||||||
|
|
||||||
const BilingualDialogue = ({ lyrics }) => { // 双语对话
|
const BilingualDialogue = ({ lyrics }) => { // 双语对话
|
||||||
|
if (typeof lyrics !== "object") return <React.Fragment />;
|
||||||
return <div> {lyrics.map((lyric, index) => {
|
return <div> {lyrics.map((lyric, index) => {
|
||||||
return <div index={index} style={{ paddingBottom: 40 }}>
|
return <div index={index} style={{ paddingBottom: 40 }}>
|
||||||
<Typography align="left" >{lyric.asr}</Typography>
|
<Typography align="left" >{lyric.asr}</Typography>
|
||||||
|
BIN
src/assets/appbar_logo.png
Normal file
After Width: | Height: | Size: 821 B |
BIN
src/assets/empty_hint.png
Normal file
After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 12 KiB |
@ -1,4 +1,5 @@
|
|||||||
import { createSlice } from '@reduxjs/toolkit'
|
import { createSlice } from '@reduxjs/toolkit'
|
||||||
|
import yzs from "./request.js";
|
||||||
|
|
||||||
// type: 0 --> 声文速记 纯文本,已适配
|
// type: 0 --> 声文速记 纯文本,已适配
|
||||||
// type: 1 --> 导入音频
|
// type: 1 --> 导入音频
|
||||||
@ -62,15 +63,35 @@ export const {
|
|||||||
|
|
||||||
export default recorderSlice.reducer
|
export default recorderSlice.reducer
|
||||||
|
|
||||||
const fecthRecord = (index) => {
|
const fetchRecord = (accessToken, index, record) => {
|
||||||
console.log("begin fetch record item", index);
|
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
dispatch(setLoading());
|
dispatch(setLoading());
|
||||||
// testPromiseLoading(2000, true).then(e => {
|
let promises = [];
|
||||||
// console.log("end fetch record item", index);
|
if (record.transResultUrl) {
|
||||||
// dispatch(setLoadFinished());
|
let promise1 = yzs.download(accessToken, record.transResultUrl).then(
|
||||||
// });
|
blob => blob.text()
|
||||||
|
).then(text => {
|
||||||
|
// console.log("type", record.type, text);
|
||||||
|
let payload = null;
|
||||||
|
if (record.type === 1 || record.type === 3) {
|
||||||
|
payload = JSON.parse(text)
|
||||||
|
} else {
|
||||||
|
payload = text;
|
||||||
|
}
|
||||||
|
dispatch(setCurrentLyric(payload));
|
||||||
|
});
|
||||||
|
promises.push(promise1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let promise2 = yzs.download(accessToken, record.audioUrl).then(blob => {
|
||||||
|
dispatch(setCurrentBlob(URL.createObjectURL(blob)));
|
||||||
|
});
|
||||||
|
|
||||||
|
promises.push(promise2);
|
||||||
|
Promise.all(promises).then(() => {
|
||||||
|
dispatch(setLoadFinished());
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export { fecthRecord };
|
export { fetchRecord };
|
@ -15,6 +15,7 @@ const appSecret = "c5eccccfec16d46fe9ac678d69198415";
|
|||||||
function constructParameter(body) {
|
function constructParameter(body) {
|
||||||
let params = [];
|
let params = [];
|
||||||
for (let key in body) {
|
for (let key in body) {
|
||||||
|
if (key === "smsTemplateId") continue;
|
||||||
params.push(body[key].toString());
|
params.push(body[key].toString());
|
||||||
}
|
}
|
||||||
params.sort();
|
params.sort();
|
||||||
@ -222,6 +223,7 @@ const yzs = {
|
|||||||
body.clientId = udid;
|
body.clientId = udid;
|
||||||
body.timestamp = Math.round(new Date().getTime() / 1000);
|
body.timestamp = Math.round(new Date().getTime() / 1000);
|
||||||
body.userCell = userCell;
|
body.userCell = userCell;
|
||||||
|
body.smsTemplateId = 316;
|
||||||
return fetch("/rest/v2/phone/send_phone_code", {
|
return fetch("/rest/v2/phone/send_phone_code", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: constructParameter(body),
|
body: constructParameter(body),
|
||||||
|
@ -33,6 +33,35 @@ function audioWaveData(url, interval) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// type: 0 --> 声文速记 纯文本,已适配
|
||||||
|
// type: 1 --> 导入音频
|
||||||
|
// type: 2 --> 同传翻译 纯文本,已适配
|
||||||
|
// type: 3 --> 双语对话
|
||||||
|
function exportRecordLyric(type, lyric, filename) {
|
||||||
|
let element = document.createElement('a');
|
||||||
|
|
||||||
|
let text = "";
|
||||||
|
if (type === 0 || type === 2) {
|
||||||
|
text = lyric;
|
||||||
|
} else if (type === 1) {
|
||||||
|
text = lyric.reduce((accumulator, currentValue) => accumulator + currentValue.text, text);
|
||||||
|
} else if (type === 3) {
|
||||||
|
text = lyric.reduce((accumulator, currentValue) => {
|
||||||
|
if (currentValue.head) return accumulator;
|
||||||
|
return accumulator + currentValue.asr + "\n" + currentValue.translate + "\n\n";
|
||||||
|
}, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
|
||||||
|
element.setAttribute('download', filename);
|
||||||
|
|
||||||
|
element.style.display = 'none';
|
||||||
|
document.body.appendChild(element);
|
||||||
|
|
||||||
|
element.click();
|
||||||
|
document.body.removeChild(element);
|
||||||
|
}
|
||||||
|
|
||||||
function validatePhoneNumber(phoneNumber) {
|
function validatePhoneNumber(phoneNumber) {
|
||||||
if (phoneNumber.length !== 11) {
|
if (phoneNumber.length !== 11) {
|
||||||
return false;
|
return false;
|
||||||
@ -47,4 +76,4 @@ function textHintOfValidatePhoneNumber(phoneNumber) {
|
|||||||
return "请输入正确的手机号码";
|
return "请输入正确的手机号码";
|
||||||
}
|
}
|
||||||
|
|
||||||
export { sampleInterval, audioWaveData, validatePhoneNumber, textHintOfValidatePhoneNumber };
|
export { sampleInterval, audioWaveData, validatePhoneNumber, textHintOfValidatePhoneNumber, exportRecordLyric };
|
@ -8,19 +8,18 @@ import ListItemButton from '@mui/material/ListItemButton';
|
|||||||
import ListItemText from '@mui/material/ListItemText';
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import Toolbar from '@mui/material/Toolbar';
|
import Toolbar from '@mui/material/Toolbar';
|
||||||
import { setCurrentIndex, fecthRecord } from "../business/recorderSlice.js"
|
import { setCurrentIndex, fetchRecord } from "../business/recorderSlice.js"
|
||||||
import AccessTimeFilledIcon from '@mui/icons-material/AccessTimeFilled';
|
import AccessTimeFilledIcon from '@mui/icons-material/AccessTimeFilled';
|
||||||
|
|
||||||
const drawerWidth = 240;
|
const drawerWidth = 240;
|
||||||
|
|
||||||
export default function ({ open, recordList, currentIndex, fetchRecord }) {
|
export default function ({ open, recordList, currentIndex }) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const accessToken = useSelector(state => state.user.accessToken);
|
const accessToken = useSelector(state => state.user.accessToken);
|
||||||
const onSelected = (event, index) => {
|
const onSelected = (event, index) => {
|
||||||
console.log("onSelected", index, recordList.at(index).transResultUrl)
|
console.log("onSelected", index, recordList.at(index).transResultUrl)
|
||||||
dispatch(setCurrentIndex(index));
|
dispatch(setCurrentIndex(index));
|
||||||
fetchRecord(accessToken, recordList.at(index));
|
dispatch(fetchRecord(accessToken, index, recordList.at(index)));
|
||||||
dispatch(fecthRecord(index));
|
|
||||||
}
|
}
|
||||||
return <Drawer
|
return <Drawer
|
||||||
variant="persistent"
|
variant="persistent"
|
||||||
|