Compare commits

..

10 Commits

Author SHA1 Message Date
Aaron Yu f0d66aff65 support hide logo 2022-11-27 17:14:56 +08:00
Aaron Yu a4f813c440 fix tenant case sensitive 2022-11-12 16:17:37 +08:00
Aaron Yu 711c50c609 add keydown 2022-09-04 18:25:37 +08:00
Aaron Yu 9a52b3dd47 add tenant permission 2022-09-04 17:06:49 +08:00
Aaron Yu 7e44a00d30 add login 2022-09-03 11:18:59 +08:00
Aaron Yu b18e96d1b0 support multiple tenants 2022-07-10 14:55:23 +08:00
Aaron Yu 44a67f8200 support setting 2022-07-10 14:34:52 +08:00
Aaron Yu cc986deb67 Use oss as service 2022-03-09 00:11:28 +08:00
Aaron Yu f6cc944fd4 bug fix 2022-03-08 00:41:18 +08:00
Aaron Yu 41d7466caf Add ls support 2022-03-08 00:11:23 +08:00
12 changed files with 17497 additions and 430 deletions

17264
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,19 +2,23 @@
"name": "playable-preview", "name": "playable-preview",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"homepage": "/topwar", "homepage": "/preview",
"dependencies": { "dependencies": {
"@emotion/react": "^11.7.1", "@emotion/react": "^11.10.4",
"@emotion/styled": "^11.6.0", "@emotion/styled": "^11.10.4",
"@mui/material": "^5.2.5", "@mui/material": "^5.10.3",
"@testing-library/jest-dom": "^5.16.1", "@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2", "@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"ali-oss": "^6.17.1",
"axios": "^0.24.0", "axios": "^0.24.0",
"localstorage-slim": "^2.2.0",
"qrcode": "^1.5.0", "qrcode": "^1.5.0",
"query-string": "^7.1.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-copy-to-clipboard": "^5.0.4", "react-copy-to-clipboard": "^5.0.4",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-modal-login": "^2.0.7",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",
"web-vitals": "^2.1.2" "web-vitals": "^2.1.2"
}, },

View File

@ -38,3 +38,11 @@
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
.MuiFormControlLabel-label {
font-size: 13px !important;
}
.MuiDialog-root {
background-color: white;
}

View File

@ -1,150 +1,289 @@
import './App.css'; import "./App.css";
import Pane from './Components/pane'; import * as ProjectApi from "./apis/projectApi";
import DeviceFrame from './Components/deviceFrame'; import * as LoginApi from "./apis/loginApi";
import waterMarkPng from './images/water-mark.png' import * as utils from "./utils";
import React from 'react'; import Pane from "./Components/pane";
import SettingFrame from './Components/settingsFrame'; import DeviceFrame from "./Components/deviceFrame";
import QrCode from 'qrcode' import waterMarkPng from "./images/water-mark.png";
import { BaseUrl } from './constants'; import React from "react";
import SettingFrame from "./Components/settingsFrame";
import QrCode from "qrcode";
import {
getLoginInfo,
getProjectSettingValue,
setLoginInfo,
setProjectSetting,
} from "./storage";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogTitle from "@mui/material/DialogTitle";
import Checkbox from "@mui/material/Checkbox";
import FormControlLabel from "@mui/material/FormControlLabel";
import queryString from "query-string";
var project = "topwar";
class App extends React.Component { class App extends React.Component {
constructor() { constructor() {
super(); super();
this.state = { this.state = {
mode: 'normal', mode: "normal",
projects: [], projects: [],
selectedProject: {}, selectedProject: {},
device: 'android-h', selectedProjectName: "",
htmlUrl: '', device: "android-h",
dataUrl: '', htmlUrl: "",
loading: false dataUrl: "",
} loading: false,
loginOpen: false,
loginErrorMessage: "",
hideLogo: false,
loginForm: {
account: "",
password: "",
keepLogin: false,
},
};
} }
componentDidMount() { componentDidMount() {
this.fetchData(); const loginInfo = getLoginInfo();
this.refreshQrCode() var tenant = utils.getTenant();
const hideLogo = queryString.parseUrl(window.location.href)?.query
?.hideLogo;
this.setState({ hideLogo: !!hideLogo });
if (loginInfo && loginInfo.permissionPages.includes(tenant)) {
this.fetchData();
this.refreshQrCode();
} else {
this.setState({ loginOpen: true });
loginInfo &&
this.setState({ loginErrorMessage: "无权限访问,请切换账号" });
}
} }
login = async () => {
var { account, password, keepLogin } = this.state.loginForm;
var user = await LoginApi.UserLogin({ account, password });
var tenant = utils.getTenant();
if (user && user.permissionPages.includes(tenant)) {
this.setState({
loginOpen: false,
});
if (keepLogin) {
setLoginInfo(user);
}
this.fetchData();
this.refreshQrCode();
} else {
this.setState({
loginErrorMessage: user
? `无权限访问${tenant},请切换账号`
: "账号或密码错误",
});
}
};
refreshQrCode() { refreshQrCode() {
var {selectedProject} = this.state; var { selectedProject } = this.state;
var rawUrl = encodeURI(!selectedProject?.HtmlUrl?.startsWith("http") ? BaseUrl + selectedProject.HtmlUrl : selectedProject.HtmlUrl) selectedProject.HtmlUrl &&
selectedProject.HtmlUrl && QrCode.toDataURL([{ data: rawUrl, mode: 'byte'}], { width: 120 }) QrCode.toDataURL([{ data: this.selectedProjectUrl, mode: "byte" }], {
.then((dataUrl) => { width: 120,
this.setState({ }).then((dataUrl) => {
dataUrl: dataUrl this.setState({
}) dataUrl: dataUrl,
}) });
});
} }
setSelectedProject(selected) { get selectedProjectUrl() {
var { selectedProject } = this.state;
var settingValue = getProjectSettingValue(selectedProject?.Name) || [
1, 1, 1,
];
var rawUrl = selectedProject?.HtmlUrl
? encodeURI(selectedProject?.HtmlUrl)
: "";
if (rawUrl) {
rawUrl += `?datanumber=${settingValue[0]}&datanumber1=${settingValue[1]}&datanumber2=${settingValue[2]}&lunaOrHtml=false`;
}
return rawUrl;
}
async setSelectedProject(selectedProjectName) {
this.setState({ this.setState({
selectedProject: selected selectedProjectName: selectedProjectName,
});
console.log("selected:", selectedProjectName);
await this.updateSelectedProject(selectedProjectName);
}
async updateSelectedProject(projectName) {
var projectSetting = await ProjectApi.getProjectSetting(projectName);
this.setState({
selectedProject: projectSetting,
}); });
} }
setDevice(selected) { setDevice(selected) {
this.setState({ this.setState({
device: selected device: selected,
}); });
} }
setMode(mode) { setMode(mode) {
this.setState({ this.setState({
mode: mode mode: mode,
});
}
setLoginForm(form) {
this.setState({
loginForm: {
...this.state.loginForm,
...form,
},
}); });
} }
async fetchData() { async fetchData() {
this.setState({ this.setState({
loading: true loading: true,
})
var projects = await this.fetchProjects();
this.setState({
projects,
selectedProject: projects[0],
loading: false
}); });
this.refreshQrCode()
var projectNames = (await this.fetchProjects()) || ["无项目"];
this.setState({
projects: projectNames,
selectedProjectName: projectNames[0],
loading: false,
});
await this.updateSelectedProject(projectNames[0]);
this.refreshQrCode();
} }
async fetchProjects() { async fetchProjects() {
var url = project return await ProjectApi.getProjects();
? `${BaseUrl}home/GetServerProjectJsonData?project=${project}`
: `${BaseUrl}home/GetServerJsonData`;
var response = await fetch(url, {
headers: {
'Content-Type': 'application/json'
},
});
return await response.json();
} }
async refreshProject() { get projectSettingValue() {
var { selectedProject } = this.state; return getProjectSettingValue(this.state.selectedProjectName);
var projects = await this.fetchProjects();
if (selectedProject) {
var updatedSelectedProject = projects.find(p => p.HtmlUrl === selectedProject.HtmlUrl)
console.log(updatedSelectedProject);
if (updatedSelectedProject) {
this.setState({
selectedProject: updatedSelectedProject
})
}
}
this.setState({
projects,
})
} }
UpdateSetting(obj) { UpdateSetting(obj) {
var body = { setProjectSetting(this.state.selectedProject.Name, [
...obj, +obj.topType,
fileName: this.state.selectedProject.Name, +obj.centreType,
}; +obj.middleType,
var query = Object.keys(body).map(k => `${k}=${body[k]}`).join('&') ]);
fetch(`${BaseUrl}home/UpdateFileContentNew?${query}`)
.then(() => this.refreshProject())
.catch(e => console.log('update fails', e));
} }
render() { render() {
var { mode, device, projects, selectedProject, dataUrl, loading } = this.state; var {
mode,
device,
projects,
selectedProject,
dataUrl,
loading,
loginOpen,
loginForm,
hideLogo,
} = this.state;
return ( return (
<div className="App"> <div className="App">
<Dialog open={loginOpen}>
<DialogTitle>登录</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
id="name"
label="账号"
fullWidth
value={loginForm.account}
variant="standard"
onChange={(e) => this.setLoginForm({ account: e.target.value })}
onKeyDown={(e) => e.keyCode === 13 && this.login()}
/>
<TextField
autoFocus
margin="dense"
id="name"
label="密码"
type={"password"}
fullWidth
value={loginForm.password}
variant="standard"
onChange={(e) => this.setLoginForm({ password: e.target.value })}
onKeyDown={(e) => e.keyCode === 13 && this.login()}
/>
</DialogContent>
<DialogActions>
{this.state.loginErrorMessage && (
<span
style={{
color: "red",
fontSize: "12px",
margin: "0 auto 0 10px",
}}
>
{this.state.loginErrorMessage}
</span>
)}
<FormControlLabel
control={
<Checkbox
checked={loginForm.keepLogin}
onChange={(_, checked) =>
this.setLoginForm({ keepLogin: checked })
}
/>
}
label="保持登录状态7天"
/>
<Button onClick={() => this.login()}>提交</Button>
</DialogActions>
</Dialog>
<Pane <Pane
mode={mode} mode={mode}
onModeChange={(_mode) => this.setMode(_mode) } onModeChange={(_mode) => this.setMode(_mode)}
projects={projects} projects={projects}
device={device} device={device}
loading={loading} loading={loading}
hideLogo={hideLogo}
onProjectSelect={(project) => { onProjectSelect={(project) => {
this.setSelectedProject(project) this.setSelectedProject(project);
}} }}
onDeviceChange={(_device) => this.setDevice(_device)}/> onDeviceChange={(_device) => this.setDevice(_device)}
<div style={{ />
display: 'flex', <div
flex: 1, style={{
alignItems: 'center', display: "flex",
backgroundImage: `url('${waterMarkPng}')`, flex: 1,
backgroundPositionX: 'right', alignItems: "center",
backgroundPositionY: 'bottom', backgroundImage: hideLogo ? "" : `url(${waterMarkPng})`,
backgroundRepeat: 'no-repeat' backgroundPositionX: "right",
}}> backgroundPositionY: "bottom",
{ backgroundRepeat: "no-repeat",
mode === "normal" }}
? <DeviceFrame >
htmlUrl={selectedProject.HtmlUrl} {mode === "normal" ? (
<DeviceFrame
htmlUrl={this.selectedProjectUrl}
device={device} device={device}
qrDataUrl={dataUrl} qrDataUrl={dataUrl}
refreshQrCode={() => this.refreshQrCode()}/> refreshQrCode={() => this.refreshQrCode()}
: <SettingFrame />
setting={selectedProject.TextObjStr} ) : (
settingValue={selectedProject.OldSlectType ? selectedProject.OldSlectType.split('|').map(v => +v) : [1, 1, 1]} <SettingFrame
generate={(value) => this.UpdateSetting(value)}/> setting={selectedProject?.TextObjStr}
} settingValue={this.projectSettingValue}
generate={(value) => this.UpdateSetting(value)}
/>
)}
</div> </div>
</div> </div>
); );

View File

@ -102,8 +102,9 @@ function DeviceFrame(props) {
transform: 'translate(-50%, -50%)', transform: 'translate(-50%, -50%)',
paddingBottom: config.offsetv paddingBottom: config.offsetv
}} }}
allow="clipboard-read; clipboard-write *"
title='preview' title='preview'
src={absoluteUrl + '?cacheKey=' + cacheKey} src={absoluteUrl + '&cacheKey=' + cacheKey}
width={config.width} width={config.width}
height={config.height} height={config.height}
frameBorder={'0'}/> frameBorder={'0'}/>

View File

@ -1,65 +1,64 @@
import './index.css'; import "./index.css";
import Select from '@mui/material/Select'; import Select from "@mui/material/Select";
import MenuItem from '@mui/material/MenuItem' import MenuItem from "@mui/material/MenuItem";
import { useEffect, useState } from 'react'; import { useEffect, useState } from "react";
import { CircularProgress, List, ListItem } from '@mui/material'; import { CircularProgress, List, ListItem } from "@mui/material";
import ipadPng from '../../images/ipad.png' import ipadPng from "../../images/ipad.png";
import iphonePng from '../../images/iphone.png' import iphonePng from "../../images/iphone.png";
import androidPng from '../../images/android.png' import androidPng from "../../images/android.png";
import logoPanePng from '../../images/logo-pane.png' import logoPanePng from "../../images/logo-pane.png";
import horizentalChecked from '../../images/horizental-button-checked.png' import horizentalChecked from "../../images/horizental-button-checked.png";
import horizentalButton from '../../images/horizental-button.png' import horizentalButton from "../../images/horizental-button.png";
import verticalChecked from '../../images/vertical-button-checked.png' import verticalChecked from "../../images/vertical-button-checked.png";
import verticalButton from '../../images/vertical-button.png' import verticalButton from "../../images/vertical-button.png";
import modeSelectPng from '../../images/mode-select.png' import modeSelectPng from "../../images/mode-select.png";
var deviceConfigs = [ var deviceConfigs = [
{ {
name: 'iPhone', name: "iPhone",
icon: iphonePng, icon: iphonePng,
vertical: 'iphone-v', vertical: "iphone-v",
horizental: 'iphone-h' horizental: "iphone-h",
}, },
{ {
name: 'Android Phone', name: "Android Phone",
icon: androidPng, icon: androidPng,
vertical: 'android-v', vertical: "android-v",
horizental: 'android-h' horizental: "android-h",
}, },
{ {
name: 'iPad', name: "iPad",
icon: ipadPng, icon: ipadPng,
vertical: 'ipad-v', vertical: "ipad-v",
horizental: 'ipad-h' horizental: "ipad-h",
}, },
]; ];
function Pane(props) { function Pane(props) {
var [mode, setMode] = useState(props.mode); var [mode, setMode] = useState(props.mode);
var [device, setDevice] = useState(props.device) var [device, setDevice] = useState(props.device);
var [project, setProject] = useState(props.project) var [project, setProject] = useState(props.project);
var modeChange = (e) => { var modeChange = (e) => {
var _mode = e.target.value; var _mode = e.target.value;
setMode(_mode); setMode(_mode);
props.onModeChange(e.target.value) props.onModeChange(e.target.value);
}; };
var deviceChange = (_device) => { var deviceChange = (_device) => {
setDevice(_device); setDevice(_device);
props.onDeviceChange(_device) props.onDeviceChange(_device);
}; };
var projectSelect = (e) => { var projectSelect = (e) => {
var projectName = e.target.value var projectName = e.target.value;
setProject(projectName) setProject(projectName);
var projectObj = props.projects.find((p) => p.Name === projectName) props.onProjectSelect(projectName);
props.onProjectSelect(projectObj) };
}
useEffect(() => { useEffect(() => {
props.projects.length && !project && setProject(props.projects[0].Name); props.projects.length && !project && setProject(props.projects[0]);
}, [props.projects]) }, [props.projects]);
return ( return (
<div className="Pane"> <div className="Pane">
@ -67,76 +66,105 @@ function Pane(props) {
id="mode-select" id="mode-select"
value={mode} value={mode}
onChange={(e) => modeChange(e)} onChange={(e) => modeChange(e)}
startAdornment={<img src={modeSelectPng} height={'30px'} alt='selected'/>} startAdornment={
<img src={modeSelectPng} height={"30px"} alt="selected" />
}
sx={{ sx={{
width: '100%', width: "100%",
border: 'none', border: "none",
background: 'rgb(69, 115, 191)', background: "rgb(69, 115, 191)",
color: 'white', color: "white",
fontWeight: 'bold' fontWeight: "bold",
}} }}
MenuProps={{ MenuProps={{
classes: { classes: {
paper: 'selectPager' paper: "selectPager",
} },
}} }}
> >
<MenuItem value={'normal'}>Normal Preview</MenuItem> <MenuItem value={"normal"}>Normal Preview</MenuItem>
<MenuItem value={'dynamic'}>Dynamic Preview</MenuItem> <MenuItem value={"dynamic"}>Dynamic Preview</MenuItem>
</Select> </Select>
<List> <List>
{ {deviceConfigs.map((_device) => (
deviceConfigs.map(_device => <ListItem
<ListItem key={_device.name}
key={_device.name} sx={{
sx={{ display: "flex",
display: 'flex', textAlign: "center",
textAlign: 'center' }}
}}> >
<div style={{ width: '120px', alignItems: 'center' }}> <div style={{ width: "120px", alignItems: "center" }}>
<img className='deviceIcon' src={_device.icon} alt={_device.name}/> <img
className="deviceIcon"
src={_device.icon}
alt={_device.name}
/>
</div>
<div className="deviceContent">
<p style={{ margin: "14px 0" }}>{_device.name}</p>
<div className="deviceButtons">
<img
src={
device === _device.horizental
? horizentalChecked
: horizentalButton
}
alt="horizental"
onClick={() => deviceChange(_device.horizental)}
/>
<img
src={
device === _device.vertical
? verticalChecked
: verticalButton
}
alt="vertical"
onClick={() => deviceChange(_device.vertical)}
/>
</div> </div>
<div className='deviceContent'> </div>
<p style={{ margin: "14px 0"}}>{_device.name}</p> </ListItem>
<div className='deviceButtons'> ))}
<img src={device === _device.horizental ? horizentalChecked : horizentalButton} </List>
alt='horizental' <div
onClick={() => deviceChange(_device.horizental)}/> style={{
<img src={device === _device.vertical ? verticalChecked : verticalButton} marginTop: "50px",
alt='vertical' color: "white",
onClick={() => deviceChange(_device.vertical)}/> }}
</div> >
</div> {props.loading ? (
</ListItem>) <CircularProgress color="inherit" />
} ) : (
</List> project && (
<div style={{ <Select
marginTop: '50px',
color: 'white'
}}>
{
props.loading
?
<CircularProgress color='inherit'/>
:
project && <Select
id="project-select" id="project-select"
value={project} value={project}
onChange={(e) => projectSelect(e)} onChange={(e) => projectSelect(e)}
placeholder='选择项目' placeholder="选择项目"
sx={{ sx={{
background: 'rgb(115, 158, 211)', background: "rgb(115, 158, 211)",
width: '80%', width: "80%",
color: 'white' color: "white",
}} }}
> >
{ {props.projects.map((pName, i) => (
props.projects.map((p, i) => <MenuItem key={i} value={p.Name}>{p.Name}</MenuItem>) <MenuItem key={i} value={pName}>
} {pName}
</MenuItem>
))}
</Select> </Select>
} )
</div> )}
<img src={logoPanePng} alt='logo' width={'100%'} className='bottomLogo'/> </div>
{!props.hideLogo && (
<img
src={logoPanePng}
alt="logo"
width={"100%"}
className="bottomLogo"
/>
)}
</div> </div>
); );
} }

View File

@ -9,6 +9,7 @@ var rowTitles = ['开头', '中间', '结尾']
function SettingFrame(props) { function SettingFrame(props) {
var [settingArr, setSettingArr] = useState([1, 2, 3]) var [settingArr, setSettingArr] = useState([1, 2, 3])
var [openPopup, setOpenPopup] = useState(false); var [openPopup, setOpenPopup] = useState(false);
console.log(props.settingValue)
useEffect(() => { useEffect(() => {
setSettingArr(props.settingValue) setSettingArr(props.settingValue)
}, [props.settingValue]) }, [props.settingValue])

View File

@ -0,0 +1,23 @@
var BaseUrl = "http://api.soyootech.com/";
export async function UserLogin(loginForm) {
try {
var response = await fetch(BaseUrl + "SoyooUser/previewlogin", {
body: JSON.stringify({
account: loginForm.account,
password: loginForm.password,
}),
headers: {
"Content-Type": "application/json",
},
method: "POST",
});
var result = await response.json();
result.permissionPages = result.permissionPages.map((permission) =>
permission.toLowerCase()
);
return result;
} catch (error) {
console.log(error);
return null;
}
}

View File

@ -0,0 +1,46 @@
import aliOss from "ali-oss"
import * as utils from "../utils"
var client = new aliOss({
region: 'oss-cn-shanghai',
// 阿里云账号AccessKey拥有所有API的访问权限风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维请登录RAM控制台创建RAM用户。
accessKeyId: 'LTAI5t7sVPM6mQwEubZ1Hw36',
accessKeySecret: 'g0rZC9rTUvQw2jyNe8ibrFtIioOwST',
// 填写Bucket名称。
bucket: 'web-preview',
});
const baseBucketUrl = `http://web-preview.soyootech.com/`
function getTenantPrefix () {
var tenant = utils.getTenant()
return tenant === "funplus" ? "" : `_${tenant}/`
}
// see more in https://www.npmjs.com/package/ali-oss#listquery-options
export async function getProjects() {
var prefix = getTenantPrefix()
if (!prefix) {
return client.list({ delimiter: "/"})
.then(result =>
result.prefixes.filter(item => !item.startsWith("_"))
.map(item => item.slice(0, item.length - 1))
)
} else {
return client.list({ prefix: prefix, delimiter: '/' })
.then(result =>
result.prefixes?.map(item => item.slice(prefix.length, item.length - 1)))
.catch(() => []);
}
}
export async function getProjectSetting(projectName) {
return fetch(`${baseBucketUrl}${getTenantPrefix()}${projectName}/project.json?time=${new Date().getTime()}`).then(response => {
return response.body;
}).then(stream => {
return new Response(stream, { headers: { "Content-Type": "text/plain" } }).text();
})
.then(result => {
return JSON.parse(result);
});;
}

View File

@ -1 +1,2 @@
export var BaseUrl = "http://123.56.161.61:1157/"; export var BaseUrl = "http://123.56.161.61:1157/";
// export var BaseUrl = "https://localhost:44380/";

24
src/storage.js 100644
View File

@ -0,0 +1,24 @@
import ls from "localstorage-slim";
import * as Utils from "./utils";
const settingPrefix = "ps_";
export const setProjectSetting = (projectName, settingValueArr) => {
var tenant = Utils.getTenant();
ls.set(settingPrefix + tenant + "_" + projectName, settingValueArr, {
ttl: 3600 * 24 * 90,
});
};
export const getProjectSettingValue = (projectName) => {
var tenant = Utils.getTenant();
return ls.get(settingPrefix + tenant + "_" + projectName) || [1, 1, 1];
};
export const setLoginInfo = (loginInfo) => {
ls.set("loginInfo", loginInfo, { ttl: 3600 * 24 * 7 });
};
export const getLoginInfo = () => {
return ls.get("loginInfo");
};

6
src/utils.js 100644
View File

@ -0,0 +1,6 @@
import queryString from "query-string";
export function getTenant() {
const tenant = queryString.parseUrl(window.location.href)?.query?.tenant;
return tenant?.toLowerCase() || "funplus";
}