feat: prepare demo stage 1 (#1412)
Co-authored-by: sealday <sealday@gmail.com> Reviewed-on: daoyoucloud/tachybase#1412
This commit is contained in:
parent
aa49bd3133
commit
1c9b3b40eb
@ -24,6 +24,7 @@ export class AppSupervisor extends EventEmitter implements AsyncEmitter {
|
|||||||
public apps: {
|
public apps: {
|
||||||
[appName: string]: Application;
|
[appName: string]: Application;
|
||||||
} = {};
|
} = {};
|
||||||
|
public lastSeenAt: Map<string, number> = new Map();
|
||||||
|
|
||||||
public appErrors: {
|
public appErrors: {
|
||||||
[appName: string]: Error;
|
[appName: string]: Error;
|
||||||
@ -107,6 +108,14 @@ export class AppSupervisor extends EventEmitter implements AsyncEmitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
touchApp(appName: string) {
|
||||||
|
if (!this.hasApp(appName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastSeenAt.set(appName, Math.floor(Date.now() / 1000));
|
||||||
|
}
|
||||||
|
|
||||||
getMutexOfApp(appName: string) {
|
getMutexOfApp(appName: string) {
|
||||||
if (!this.appMutexes[appName]) {
|
if (!this.appMutexes[appName]) {
|
||||||
this.appMutexes[appName] = new Mutex();
|
this.appMutexes[appName] = new Mutex();
|
||||||
@ -238,6 +247,7 @@ export class AppSupervisor extends EventEmitter implements AsyncEmitter {
|
|||||||
delete this.appErrors[app.name];
|
delete this.appErrors[app.name];
|
||||||
delete this.lastMaintainingMessage[app.name];
|
delete this.lastMaintainingMessage[app.name];
|
||||||
delete this.statusBeforeCommanding[app.name];
|
delete this.statusBeforeCommanding[app.name];
|
||||||
|
this.lastSeenAt.delete(app.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('maintainingMessageChanged', ({ message, maintainingStatus }) => {
|
app.on('maintainingMessageChanged', ({ message, maintainingStatus }) => {
|
||||||
|
@ -258,6 +258,10 @@ export class Gateway extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (handleApp !== 'main') {
|
||||||
|
AppSupervisor.getInstance().touchApp(handleApp);
|
||||||
|
}
|
||||||
|
|
||||||
app.callback()(req, res);
|
app.callback()(req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
],
|
],
|
||||||
"main": "dist/server/index.js",
|
"main": "dist/server/index.js",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@alicloud/captcha20230305": "^1.1.3",
|
||||||
|
"@alicloud/openapi-client": "^0.4.10",
|
||||||
"@ant-design/icons": "~5.3.7",
|
"@ant-design/icons": "~5.3.7",
|
||||||
"@react-pdf/renderer": "^3.3.2",
|
"@react-pdf/renderer": "^3.3.2",
|
||||||
"@tachybase/components": "workspace:*",
|
"@tachybase/components": "workspace:*",
|
||||||
@ -24,6 +26,7 @@
|
|||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
|
"nanoid": "^5.0.7",
|
||||||
"path-to-regexp": "^6.1.0",
|
"path-to-regexp": "^6.1.0",
|
||||||
"qrcode": "^1.5.1",
|
"qrcode": "^1.5.1",
|
||||||
"qrcode.react": "^3.1.0",
|
"qrcode.react": "^3.1.0",
|
||||||
@ -35,6 +38,7 @@
|
|||||||
"react-transition-group": "^4.4.5",
|
"react-transition-group": "^4.4.5",
|
||||||
"react-zoom-pan-pinch": "^3.4.4",
|
"react-zoom-pan-pinch": "^3.4.4",
|
||||||
"redis": "^4.6.11",
|
"redis": "^4.6.11",
|
||||||
|
"sequelize": "^6.37.3",
|
||||||
"signature_pad": "4.1.7",
|
"signature_pad": "4.1.7",
|
||||||
"throttle-debounce": "^5.0.0",
|
"throttle-debounce": "^5.0.0",
|
||||||
"vitest": "^1.4.0",
|
"vitest": "^1.4.0",
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
import { DrawBox } from './DrawBox';
|
import { DrawBox } from './DrawBox';
|
||||||
import { npxLine } from './utils';
|
import { dpr, drawFontLine, npx, npxLine, thinLineWidth } from './utils';
|
||||||
import { drawFontLine } from './utils';
|
|
||||||
import { npx } from './utils';
|
|
||||||
import { thinLineWidth } from './utils';
|
|
||||||
import { dpr } from './utils';
|
|
||||||
|
|
||||||
interface Attr {
|
interface Attr {
|
||||||
align?: 'left' | 'center' | 'right';
|
align?: 'left' | 'center' | 'right';
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { h } from './element';
|
import { cssPrefix } from '../config';
|
||||||
import Icon from './icon';
|
|
||||||
import DropdownColor from './dropdown_color';
|
import DropdownColor from './dropdown_color';
|
||||||
import DropdownLineType from './dropdown_linetype';
|
import DropdownLineType from './dropdown_linetype';
|
||||||
import { cssPrefix } from '../config';
|
import { h } from './element';
|
||||||
|
import Icon from './icon';
|
||||||
|
|
||||||
function buildTable(...trs) {
|
function buildTable(...trs) {
|
||||||
return h('table', '').child(h('tbody', '').children(...trs));
|
return h('table', '').child(h('tbody', '').children(...trs));
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { h } from './element';
|
|
||||||
import { bindClickoutside, unbindClickoutside } from './event';
|
|
||||||
import { cssPrefix } from '../config';
|
import { cssPrefix } from '../config';
|
||||||
import Icon from './icon';
|
|
||||||
import FormInput from './form_input';
|
|
||||||
import Dropdown from './dropdown';
|
|
||||||
// Record: temp not used
|
// Record: temp not used
|
||||||
// import { xtoast } from './message';
|
// import { xtoast } from './message';
|
||||||
import { tf } from '../locale/locale';
|
import { tf } from '../locale/locale';
|
||||||
|
import Dropdown from './dropdown';
|
||||||
|
import { h } from './element';
|
||||||
|
import { bindClickoutside, unbindClickoutside } from './event';
|
||||||
|
import FormInput from './form_input';
|
||||||
|
import Icon from './icon';
|
||||||
|
|
||||||
class DropdownMore extends Dropdown {
|
class DropdownMore extends Dropdown {
|
||||||
constructor(click) {
|
constructor(click) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { HComponent } from './element';
|
|
||||||
import { cssPrefix } from '../config';
|
import { cssPrefix } from '../config';
|
||||||
import { t } from '../locale/locale';
|
import { t } from '../locale/locale';
|
||||||
|
import { HComponent } from './element';
|
||||||
|
|
||||||
export default class Button extends HComponent {
|
export default class Button extends HComponent {
|
||||||
// type: primary
|
// type: primary
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
import { t } from '../locale/locale';
|
||||||
import { h } from './element';
|
import { h } from './element';
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
import { t } from '../locale/locale';
|
|
||||||
|
|
||||||
function addMonth(date, step) {
|
function addMonth(date, step) {
|
||||||
date.setMonth(date.getMonth() + step);
|
date.setMonth(date.getMonth() + step);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { h } from './element';
|
|
||||||
import { cssPrefix } from '../config';
|
import { cssPrefix } from '../config';
|
||||||
|
import { h } from './element';
|
||||||
|
|
||||||
const themeColorPlaceHolders = [
|
const themeColorPlaceHolders = [
|
||||||
'#ffffff',
|
'#ffffff',
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { h } from './element';
|
|
||||||
import { bindClickoutside, unbindClickoutside } from './event';
|
|
||||||
import { cssPrefix } from '../config';
|
import { cssPrefix } from '../config';
|
||||||
import { tf } from '../locale/locale';
|
import { tf } from '../locale/locale';
|
||||||
|
import { h } from './element';
|
||||||
|
import { bindClickoutside, unbindClickoutside } from './event';
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{ key: 'copy', title: tf('contextmenu.copy'), label: 'Ctrl+C' },
|
{ key: 'copy', title: tf('contextmenu.copy'), label: 'Ctrl+C' },
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
import { cssPrefix } from '../config';
|
||||||
import Calendar from './calendar';
|
import Calendar from './calendar';
|
||||||
import { h } from './element';
|
import { h } from './element';
|
||||||
import { cssPrefix } from '../config';
|
|
||||||
|
|
||||||
export default class Datepicker {
|
export default class Datepicker {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { HComponent, h } from './element';
|
|
||||||
import { bindClickoutside, unbindClickoutside } from './event';
|
|
||||||
import { cssPrefix } from '../config';
|
import { cssPrefix } from '../config';
|
||||||
|
import { h, HComponent } from './element';
|
||||||
|
import { bindClickoutside, unbindClickoutside } from './event';
|
||||||
|
|
||||||
export default class Dropdown extends HComponent {
|
export default class Dropdown extends HComponent {
|
||||||
constructor(title, width, showArrow, placement, ...children) {
|
constructor(title, width, showArrow, placement, ...children) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import { cssPrefix } from '../config';
|
||||||
import Dropdown from './dropdown';
|
import Dropdown from './dropdown';
|
||||||
import { h } from './element';
|
import { h } from './element';
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
import { cssPrefix } from '../config';
|
|
||||||
|
|
||||||
function buildItemWithIcon(iconName) {
|
function buildItemWithIcon(iconName) {
|
||||||
return h('div', `${cssPrefix}-item`).child(new Icon(iconName));
|
return h('div', `${cssPrefix}-item`).child(new Icon(iconName));
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
import BorderPalette from './border_palette';
|
||||||
import Dropdown from './dropdown';
|
import Dropdown from './dropdown';
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
import BorderPalette from './border_palette';
|
|
||||||
|
|
||||||
export default class DropdownBorder extends Dropdown {
|
export default class DropdownBorder extends Dropdown {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
import ColorPalette from './color_palette';
|
||||||
import Dropdown from './dropdown';
|
import Dropdown from './dropdown';
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
import ColorPalette from './color_palette';
|
|
||||||
|
|
||||||
export default class DropdownColor extends Dropdown {
|
export default class DropdownColor extends Dropdown {
|
||||||
constructor(iconName, color) {
|
constructor(iconName, color) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import { cssPrefix } from '../config';
|
||||||
|
import { baseFonts } from '../core/font';
|
||||||
import Dropdown from './dropdown';
|
import Dropdown from './dropdown';
|
||||||
import { h } from './element';
|
import { h } from './element';
|
||||||
import { baseFonts } from '../core/font';
|
|
||||||
import { cssPrefix } from '../config';
|
|
||||||
|
|
||||||
export default class DropdownFont extends Dropdown {
|
export default class DropdownFont extends Dropdown {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import { cssPrefix } from '../config';
|
||||||
|
import { fontSizes } from '../core/font';
|
||||||
import Dropdown from './dropdown';
|
import Dropdown from './dropdown';
|
||||||
import { h } from './element';
|
import { h } from './element';
|
||||||
import { fontSizes } from '../core/font';
|
|
||||||
import { cssPrefix } from '../config';
|
|
||||||
|
|
||||||
export default class DropdownFontSize extends Dropdown {
|
export default class DropdownFontSize extends Dropdown {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import { cssPrefix } from '../config';
|
||||||
|
import { baseFormats } from '../core/format';
|
||||||
import Dropdown from './dropdown';
|
import Dropdown from './dropdown';
|
||||||
import { h } from './element';
|
import { h } from './element';
|
||||||
import { baseFormats } from '../core/format';
|
|
||||||
import { cssPrefix } from '../config';
|
|
||||||
|
|
||||||
export default class DropdownFormat extends Dropdown {
|
export default class DropdownFormat extends Dropdown {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import Dropdown from './dropdown';
|
|
||||||
import Icon from './icon';
|
|
||||||
import { h } from './element';
|
|
||||||
import { baseFormulas } from '../core/formula';
|
|
||||||
import { cssPrefix } from '../config';
|
import { cssPrefix } from '../config';
|
||||||
|
import { baseFormulas } from '../core/formula';
|
||||||
|
import Dropdown from './dropdown';
|
||||||
|
import { h } from './element';
|
||||||
|
import Icon from './icon';
|
||||||
|
|
||||||
export default class DropdownFormula extends Dropdown {
|
export default class DropdownFormula extends Dropdown {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import { cssPrefix } from '../config';
|
||||||
import Dropdown from './dropdown';
|
import Dropdown from './dropdown';
|
||||||
import { h } from './element';
|
import { h } from './element';
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
import { cssPrefix } from '../config';
|
|
||||||
|
|
||||||
const lineTypes = [
|
const lineTypes = [
|
||||||
[
|
[
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import { cssPrefix } from '../config';
|
||||||
|
import Datepicker from './datepicker';
|
||||||
import { h } from './element';
|
import { h } from './element';
|
||||||
import Suggest from './suggest';
|
import Suggest from './suggest';
|
||||||
import Datepicker from './datepicker';
|
|
||||||
import { cssPrefix } from '../config';
|
|
||||||
|
|
||||||
function resetTextareaSize() {
|
function resetTextareaSize() {
|
||||||
const { inputText } = this;
|
const { inputText } = this;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
class Element {
|
class Element {
|
||||||
el: HTMLElement;
|
el: HTMLElement;
|
||||||
private _data: Record<string, any>;
|
private _data: Record<string, any>;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { h } from './element';
|
|
||||||
import { cssPrefix } from '../config';
|
import { cssPrefix } from '../config';
|
||||||
import { t } from '../locale/locale';
|
import { t } from '../locale/locale';
|
||||||
|
import { h } from './element';
|
||||||
|
|
||||||
const patterns = {
|
const patterns = {
|
||||||
number: /(^\d+$)|(^\d+(\.\d{0,4})?$)/,
|
number: /(^\d+$)|(^\d+(\.\d{0,4})?$)/,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { h } from './element';
|
|
||||||
import { cssPrefix } from '../config';
|
import { cssPrefix } from '../config';
|
||||||
|
import { h } from './element';
|
||||||
|
|
||||||
export default class FormInput {
|
export default class FormInput {
|
||||||
constructor(width, hint) {
|
constructor(width, hint) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
import { cssPrefix } from '../config';
|
||||||
import { h } from './element';
|
import { h } from './element';
|
||||||
import Suggest from './suggest';
|
import Suggest from './suggest';
|
||||||
import { cssPrefix } from '../config';
|
|
||||||
|
|
||||||
export default class FormSelect {
|
export default class FormSelect {
|
||||||
constructor(key, items, width, getTitle = (it) => it, change = () => {}) {
|
constructor(key, items, width, getTitle = (it) => it, change = () => {}) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { HComponent, h } from './element';
|
|
||||||
import { cssPrefix } from '../config';
|
import { cssPrefix } from '../config';
|
||||||
|
import { h, HComponent } from './element';
|
||||||
|
|
||||||
export default class Icon extends HComponent {
|
export default class Icon extends HComponent {
|
||||||
constructor(name) {
|
constructor(name) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
import { cssPrefix } from '../config';
|
||||||
import { h } from './element';
|
import { h } from './element';
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
import { cssPrefix } from '../config';
|
|
||||||
|
|
||||||
export function xtoast(title, content) {
|
export function xtoast(title, content) {
|
||||||
const el = h('div', `${cssPrefix}-toast`);
|
const el = h('div', `${cssPrefix}-toast`);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { h, HComponent } from './element';
|
|
||||||
import Icon from './icon';
|
|
||||||
import { cssPrefix } from '../config';
|
import { cssPrefix } from '../config';
|
||||||
|
import { h, HComponent } from './element';
|
||||||
import { bind, unbind } from './event';
|
import { bind, unbind } from './event';
|
||||||
|
import Icon from './icon';
|
||||||
|
|
||||||
export default class Modal {
|
export default class Modal {
|
||||||
el: HComponent;
|
el: HComponent;
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import Modal from './modal';
|
import { cssPrefix } from '../config';
|
||||||
|
import { t } from '../locale/locale';
|
||||||
|
import Button from './button';
|
||||||
|
import { h } from './element';
|
||||||
|
import FormField from './form_field';
|
||||||
import FormInput from './form_input';
|
import FormInput from './form_input';
|
||||||
import FormSelect from './form_select';
|
import FormSelect from './form_select';
|
||||||
import FormField from './form_field';
|
import Modal from './modal';
|
||||||
import Button from './button';
|
|
||||||
import { t } from '../locale/locale';
|
|
||||||
import { h } from './element';
|
|
||||||
import { cssPrefix } from '../config';
|
|
||||||
|
|
||||||
const fieldLabelWidth = 100;
|
const fieldLabelWidth = 100;
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { h } from './element';
|
|
||||||
import { cssPrefix } from '../config';
|
|
||||||
import Button from './button';
|
|
||||||
import { Draw } from '../canvas';
|
import { Draw } from '../canvas';
|
||||||
import { renderCell } from './table';
|
import { cssPrefix } from '../config';
|
||||||
import { t } from '../locale/locale';
|
import { t } from '../locale/locale';
|
||||||
|
import Button from './button';
|
||||||
|
import { h } from './element';
|
||||||
|
import { renderCell } from './table';
|
||||||
|
|
||||||
// resolution: 72 => 595 x 842
|
// resolution: 72 => 595 x 842
|
||||||
// 150 => 1240 x 1754
|
// 150 => 1240 x 1754
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
import { cssPrefix } from '../config';
|
||||||
import { h } from './element';
|
import { h } from './element';
|
||||||
import { mouseMoveUp } from './event';
|
import { mouseMoveUp } from './event';
|
||||||
import { cssPrefix } from '../config';
|
|
||||||
|
|
||||||
export default class Resizer {
|
export default class Resizer {
|
||||||
constructor(vertical = false, minDistance) {
|
constructor(vertical = false, minDistance) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { h, HComponent } from './element';
|
|
||||||
import { cssPrefix } from '../config';
|
import { cssPrefix } from '../config';
|
||||||
|
import { h, HComponent } from './element';
|
||||||
|
|
||||||
export default class Scrollbar {
|
export default class Scrollbar {
|
||||||
private el: HComponent;
|
private el: HComponent;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { h } from './element';
|
|
||||||
import { cssPrefix } from '../config';
|
import { cssPrefix } from '../config';
|
||||||
import { CellRange } from '../core/cell_range';
|
import { CellRange } from '../core/cell_range';
|
||||||
|
import { h } from './element';
|
||||||
|
|
||||||
const selectorHeightBorderWidth = 2 * 2 - 1;
|
const selectorHeightBorderWidth = 2 * 2 - 1;
|
||||||
let startZIndex = 10;
|
let startZIndex = 10;
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import { h } from './element';
|
|
||||||
import { bind, mouseMoveUp, bindTouch, createEventEmitter } from './event';
|
|
||||||
import Resizer from './resizer';
|
|
||||||
import Scrollbar from './scrollbar';
|
|
||||||
import Selector from './selector';
|
|
||||||
import Editor from './editor';
|
|
||||||
import Print from './print';
|
|
||||||
import ContextMenu from './contextmenu';
|
|
||||||
import Table from './table';
|
|
||||||
import Toolbar from './toolbar/index';
|
|
||||||
import ModalValidation from './modal_validation';
|
|
||||||
import SortFilter from './sort_filter';
|
|
||||||
import { xtoast } from './message';
|
|
||||||
import { cssPrefix } from '../config';
|
import { cssPrefix } from '../config';
|
||||||
import { formulas } from '../core/formula';
|
import { formulas } from '../core/formula';
|
||||||
import { throttle } from '../core/helper';
|
import { throttle } from '../core/helper';
|
||||||
|
import ContextMenu from './contextmenu';
|
||||||
|
import Editor from './editor';
|
||||||
|
import { h } from './element';
|
||||||
|
import { bind, bindTouch, createEventEmitter, mouseMoveUp } from './event';
|
||||||
|
import { xtoast } from './message';
|
||||||
|
import ModalValidation from './modal_validation';
|
||||||
|
import Print from './print';
|
||||||
|
import Resizer from './resizer';
|
||||||
|
import Scrollbar from './scrollbar';
|
||||||
|
import Selector from './selector';
|
||||||
|
import SortFilter from './sort_filter';
|
||||||
|
import Table from './table';
|
||||||
|
import Toolbar from './toolbar/index';
|
||||||
|
|
||||||
function scrollbarMove() {
|
function scrollbarMove() {
|
||||||
const { data, verticalScrollbar, horizontalScrollbar } = this;
|
const { data, verticalScrollbar, horizontalScrollbar } = this;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { h } from './element';
|
|
||||||
import Button from './button';
|
|
||||||
import { bindClickoutside, unbindClickoutside } from './event';
|
|
||||||
import { cssPrefix } from '../config';
|
import { cssPrefix } from '../config';
|
||||||
import { t } from '../locale/locale';
|
import { t } from '../locale/locale';
|
||||||
|
import Button from './button';
|
||||||
|
import { h } from './element';
|
||||||
|
import { bindClickoutside, unbindClickoutside } from './event';
|
||||||
|
|
||||||
function buildMenu(clsName) {
|
function buildMenu(clsName) {
|
||||||
return h('div', `${cssPrefix}-item ${clsName}`);
|
return h('div', `${cssPrefix}-item ${clsName}`);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
import { cssPrefix } from '../config';
|
||||||
import { h } from './element';
|
import { h } from './element';
|
||||||
import { bindClickoutside, unbindClickoutside } from './event';
|
import { bindClickoutside, unbindClickoutside } from './event';
|
||||||
import { cssPrefix } from '../config';
|
|
||||||
|
|
||||||
function inputMovePrev(evt) {
|
function inputMovePrev(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
|
import { Draw, DrawBox, npx, thinLineWidth } from '../canvas';
|
||||||
import { stringAt } from '../core/alphabet';
|
import { stringAt } from '../core/alphabet';
|
||||||
import { getFontSizePxByPt } from '../core/font';
|
|
||||||
import _cell from '../core/cell';
|
import _cell from '../core/cell';
|
||||||
import { formulam } from '../core/formula';
|
|
||||||
import { formatm } from '../core/format';
|
|
||||||
|
|
||||||
import { Draw, DrawBox, thinLineWidth, npx } from '../canvas';
|
|
||||||
import DataProxy from '../core/data_proxy';
|
import DataProxy from '../core/data_proxy';
|
||||||
|
import { getFontSizePxByPt } from '../core/font';
|
||||||
|
import { formatm } from '../core/format';
|
||||||
|
import { formulam } from '../core/formula';
|
||||||
|
|
||||||
const cellPaddingWidth = 5;
|
const cellPaddingWidth = 5;
|
||||||
const tableFixedHeaderCleanStyle = { fillStyle: '#f4f5f8' };
|
const tableFixedHeaderCleanStyle = { fillStyle: '#f4f5f8' };
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { h, HComponent } from './element';
|
import { cssPrefix } from '../config';
|
||||||
import { bind } from './event';
|
import { t } from '../locale/locale';
|
||||||
import tooltip from './tooltip';
|
import Dropdown from './dropdown';
|
||||||
|
import DropdownAlign from './dropdown_align';
|
||||||
|
import DropdownBorder from './dropdown_border';
|
||||||
|
import DropdownColor from './dropdown_color';
|
||||||
import DropdownFont from './dropdown_font';
|
import DropdownFont from './dropdown_font';
|
||||||
import DropdownFontSize from './dropdown_fontsize';
|
import DropdownFontSize from './dropdown_fontsize';
|
||||||
import DropdownFormat from './dropdown_format';
|
import DropdownFormat from './dropdown_format';
|
||||||
import DropdownFormula from './dropdown_formula';
|
import DropdownFormula from './dropdown_formula';
|
||||||
import DropdownColor from './dropdown_color';
|
import { h, HComponent } from './element';
|
||||||
import DropdownAlign from './dropdown_align';
|
import { bind } from './event';
|
||||||
import DropdownBorder from './dropdown_border';
|
|
||||||
import Dropdown from './dropdown';
|
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
import { cssPrefix } from '../config';
|
import tooltip from './tooltip';
|
||||||
import { t } from '../locale/locale';
|
|
||||||
|
|
||||||
function buildIcon(name) {
|
function buildIcon(name) {
|
||||||
return new Icon(name);
|
return new Icon(name);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import DropdownItem from './dropdown_item';
|
|
||||||
import DropdownAlign from '../dropdown_align';
|
import DropdownAlign from '../dropdown_align';
|
||||||
|
import DropdownItem from './dropdown_item';
|
||||||
|
|
||||||
export default class Align extends DropdownItem {
|
export default class Align extends DropdownItem {
|
||||||
constructor(value) {
|
constructor(value) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import DropdownItem from './dropdown_item';
|
|
||||||
import DropdownBorder from '../dropdown_border';
|
import DropdownBorder from '../dropdown_border';
|
||||||
|
import DropdownItem from './dropdown_item';
|
||||||
|
|
||||||
export default class Border extends DropdownItem {
|
export default class Border extends DropdownItem {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import DropdownItem from './dropdown_item';
|
|
||||||
import DropdownColor from '../dropdown_color';
|
import DropdownColor from '../dropdown_color';
|
||||||
|
import DropdownItem from './dropdown_item';
|
||||||
|
|
||||||
export default class FillColor extends DropdownItem {
|
export default class FillColor extends DropdownItem {
|
||||||
constructor(color) {
|
constructor(color) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import DropdownItem from './dropdown_item';
|
|
||||||
import DropdownFont from '../dropdown_font';
|
import DropdownFont from '../dropdown_font';
|
||||||
|
import DropdownItem from './dropdown_item';
|
||||||
|
|
||||||
export default class Font extends DropdownItem {
|
export default class Font extends DropdownItem {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import DropdownItem from './dropdown_item';
|
|
||||||
import DropdownFontsize from '../dropdown_fontsize';
|
import DropdownFontsize from '../dropdown_fontsize';
|
||||||
|
import DropdownItem from './dropdown_item';
|
||||||
|
|
||||||
export default class Format extends DropdownItem {
|
export default class Format extends DropdownItem {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import DropdownItem from './dropdown_item';
|
|
||||||
import DropdownFormat from '../dropdown_format';
|
import DropdownFormat from '../dropdown_format';
|
||||||
|
import DropdownItem from './dropdown_item';
|
||||||
|
|
||||||
export default class Format extends DropdownItem {
|
export default class Format extends DropdownItem {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import DropdownItem from './dropdown_item';
|
|
||||||
import DropdownFormula from '../dropdown_formula';
|
import DropdownFormula from '../dropdown_formula';
|
||||||
|
import DropdownItem from './dropdown_item';
|
||||||
|
|
||||||
export default class Format extends DropdownItem {
|
export default class Format extends DropdownItem {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Item from './item';
|
|
||||||
import Icon from '../icon';
|
import Icon from '../icon';
|
||||||
|
import Item from './item';
|
||||||
|
|
||||||
export default class IconItem extends Item {
|
export default class IconItem extends Item {
|
||||||
element() {
|
element() {
|
||||||
|
@ -1,33 +1,32 @@
|
|||||||
/* global window */
|
/* global window */
|
||||||
|
|
||||||
|
import { cssPrefix } from '../../config';
|
||||||
|
import { h } from '../element';
|
||||||
|
import { bind } from '../event';
|
||||||
import Align from './align';
|
import Align from './align';
|
||||||
import Valign from './valign';
|
|
||||||
import Autofilter from './autofilter';
|
import Autofilter from './autofilter';
|
||||||
import Bold from './bold';
|
import Bold from './bold';
|
||||||
import Italic from './italic';
|
|
||||||
import Strike from './strike';
|
|
||||||
import Underline from './underline';
|
|
||||||
import Border from './border';
|
import Border from './border';
|
||||||
import Clearformat from './clearformat';
|
import Clearformat from './clearformat';
|
||||||
import Paintformat from './paintformat';
|
|
||||||
import TextColor from './text_color';
|
|
||||||
import FillColor from './fill_color';
|
import FillColor from './fill_color';
|
||||||
import FontSize from './font_size';
|
|
||||||
import Font from './font';
|
import Font from './font';
|
||||||
|
import FontSize from './font_size';
|
||||||
import Format from './format';
|
import Format from './format';
|
||||||
import Formula from './formula';
|
import Formula from './formula';
|
||||||
import Freeze from './freeze';
|
import Freeze from './freeze';
|
||||||
import Merge from './merge';
|
import Italic from './italic';
|
||||||
import Redo from './redo';
|
|
||||||
import Undo from './undo';
|
|
||||||
import Print from './print';
|
|
||||||
import Textwrap from './textwrap';
|
|
||||||
import More from './more';
|
|
||||||
import Item from './item';
|
import Item from './item';
|
||||||
|
import Merge from './merge';
|
||||||
import { h } from '../element';
|
import More from './more';
|
||||||
import { cssPrefix } from '../../config';
|
import Paintformat from './paintformat';
|
||||||
import { bind } from '../event';
|
import Print from './print';
|
||||||
|
import Redo from './redo';
|
||||||
|
import Strike from './strike';
|
||||||
|
import TextColor from './text_color';
|
||||||
|
import Textwrap from './textwrap';
|
||||||
|
import Underline from './underline';
|
||||||
|
import Undo from './undo';
|
||||||
|
import Valign from './valign';
|
||||||
|
|
||||||
function buildDivider() {
|
function buildDivider() {
|
||||||
return h('div', `${cssPrefix}-toolbar-divider`);
|
return h('div', `${cssPrefix}-toolbar-divider`);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { cssPrefix } from '../../config';
|
import { cssPrefix } from '../../config';
|
||||||
import tooltip from '../tooltip';
|
|
||||||
import { h } from '../element';
|
|
||||||
import { t } from '../../locale/locale';
|
import { t } from '../../locale/locale';
|
||||||
|
import { h } from '../element';
|
||||||
|
import tooltip from '../tooltip';
|
||||||
|
|
||||||
export default class Item {
|
export default class Item {
|
||||||
// tooltip
|
// tooltip
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import Dropdown from '../dropdown';
|
|
||||||
import DropdownItem from './dropdown_item';
|
|
||||||
|
|
||||||
import { cssPrefix } from '../../config';
|
import { cssPrefix } from '../../config';
|
||||||
|
import Dropdown from '../dropdown';
|
||||||
import { h } from '../element';
|
import { h } from '../element';
|
||||||
import Icon from '../icon';
|
import Icon from '../icon';
|
||||||
|
import DropdownItem from './dropdown_item';
|
||||||
|
|
||||||
class DropdownMore extends Dropdown {
|
class DropdownMore extends Dropdown {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import DropdownItem from './dropdown_item';
|
|
||||||
import DropdownColor from '../dropdown_color';
|
import DropdownColor from '../dropdown_color';
|
||||||
|
import DropdownItem from './dropdown_item';
|
||||||
|
|
||||||
export default class TextColor extends DropdownItem {
|
export default class TextColor extends DropdownItem {
|
||||||
constructor(color) {
|
constructor(color) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Item from './item';
|
|
||||||
import Icon from '../icon';
|
import Icon from '../icon';
|
||||||
|
import Item from './item';
|
||||||
|
|
||||||
export default class ToggleItem extends Item {
|
export default class ToggleItem extends Item {
|
||||||
element() {
|
element() {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import DropdownItem from './dropdown_item';
|
|
||||||
import DropdownAlign from '../dropdown_align';
|
import DropdownAlign from '../dropdown_align';
|
||||||
|
import DropdownItem from './dropdown_item';
|
||||||
|
|
||||||
export default class Valign extends DropdownItem {
|
export default class Valign extends DropdownItem {
|
||||||
constructor(value) {
|
constructor(value) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
import { cssPrefix } from '../config';
|
||||||
import { h } from './element';
|
import { h } from './element';
|
||||||
import { bind } from './event';
|
import { bind } from './event';
|
||||||
import { cssPrefix } from '../config';
|
|
||||||
|
|
||||||
export default function tooltip(html: string, target) {
|
export default function tooltip(html: string, target) {
|
||||||
if (target.classList.contains('active')) {
|
if (target.classList.contains('active')) {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import { cssPrefix } from '../config';
|
import { cssPrefix } from '../config';
|
||||||
import { tf } from '../locale/locale';
|
import { tf } from '../locale/locale';
|
||||||
import { Icon } from './Icon';
|
import { Icon } from './Icon';
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||||
import { cssPrefix } from '../config';
|
|
||||||
import { Bottombar } from './Bottombar';
|
|
||||||
import { Toolbar } from './Toolbar';
|
|
||||||
import { Table } from './Table';
|
|
||||||
import { Print } from './Print';
|
|
||||||
import { Options } from '..';
|
import { Options } from '..';
|
||||||
|
import { cssPrefix } from '../config';
|
||||||
import DataProxy from '../core/data_proxy';
|
import DataProxy from '../core/data_proxy';
|
||||||
|
import { Bottombar } from './Bottombar';
|
||||||
|
import { Print } from './Print';
|
||||||
|
import { Table } from './Table';
|
||||||
|
import { Toolbar } from './Toolbar';
|
||||||
|
|
||||||
const SheetContext = createContext<DataProxy>(null);
|
const SheetContext = createContext<DataProxy>(null);
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import { cssPrefix } from '../config';
|
|
||||||
import { useSheetData } from './SheetRoot';
|
|
||||||
import { Draw } from '../canvas';
|
import { Draw } from '../canvas';
|
||||||
import CanvasTable from '../component/table';
|
import CanvasTable from '../component/table';
|
||||||
|
import { cssPrefix } from '../config';
|
||||||
|
import { useSheetData } from './SheetRoot';
|
||||||
|
|
||||||
export const Table = () => {
|
export const Table = () => {
|
||||||
const data = useSheetData();
|
const data = useSheetData();
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { cssPrefix } from '../config';
|
import { cssPrefix } from '../config';
|
||||||
import { Icon } from './Icon';
|
import { Icon } from './Icon';
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { CellRange } from './cell_range';
|
import { CellRange } from './cell_range';
|
||||||
|
|
||||||
// operator: all|eq|neq|gt|gte|lt|lte|in|be
|
// operator: all|eq|neq|gt|gte|lt|lte|in|be
|
||||||
// value:
|
// value:
|
||||||
// in => []
|
// in => []
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { xy2expr, expr2xy } from './alphabet';
|
import { expr2xy, xy2expr } from './alphabet';
|
||||||
|
|
||||||
class CellRange {
|
class CellRange {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import Selector from './selector';
|
|
||||||
import Scroll from './scroll';
|
|
||||||
import History from './history';
|
|
||||||
import Clipboard from './clipboard';
|
|
||||||
import AutoFilter from './auto_filter';
|
|
||||||
import { Merges } from './merge';
|
|
||||||
import helper from './helper';
|
|
||||||
import { Rows } from './row';
|
|
||||||
import { Cols } from './col';
|
|
||||||
import { Validations } from './validation';
|
|
||||||
import { CellRange } from './cell_range';
|
|
||||||
import { expr2xy, xy2expr } from './alphabet';
|
|
||||||
import { t } from '../locale/locale';
|
import { t } from '../locale/locale';
|
||||||
|
import { expr2xy, xy2expr } from './alphabet';
|
||||||
|
import AutoFilter from './auto_filter';
|
||||||
|
import { CellRange } from './cell_range';
|
||||||
|
import Clipboard from './clipboard';
|
||||||
|
import { Cols } from './col';
|
||||||
|
import helper from './helper';
|
||||||
|
import History from './history';
|
||||||
|
import { Merges } from './merge';
|
||||||
|
import { Rows } from './row';
|
||||||
|
import Scroll from './scroll';
|
||||||
|
import Selector from './selector';
|
||||||
|
import { Validations } from './validation';
|
||||||
|
|
||||||
// private methods
|
// private methods
|
||||||
/*
|
/*
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import helper from './helper';
|
|
||||||
import { expr2expr } from './alphabet';
|
import { expr2expr } from './alphabet';
|
||||||
|
import helper from './helper';
|
||||||
|
|
||||||
class Rows {
|
class Rows {
|
||||||
len: number;
|
len: number;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Validator from './validator';
|
|
||||||
import { CellRange } from './cell_range';
|
import { CellRange } from './cell_range';
|
||||||
|
import Validator from './validator';
|
||||||
|
|
||||||
class Validation {
|
class Validation {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -1,32 +1,32 @@
|
|||||||
@css-prefix: x-spreadsheet;
|
@css-prefix: x-spreadsheet;
|
||||||
// color
|
// color
|
||||||
@red-color: #DB2828;
|
@red-color: #db2828;
|
||||||
@red-hover-color: #d01919;
|
@red-hover-color: #d01919;
|
||||||
@orange-color: #F2711C;
|
@orange-color: #f2711c;
|
||||||
@orange-hover-color: #f26202;
|
@orange-hover-color: #f26202;
|
||||||
@yellow-color: #FBBD08;
|
@yellow-color: #fbbd08;
|
||||||
@yellow-hover-color: #eaae00;
|
@yellow-hover-color: #eaae00;
|
||||||
@olive-color: #B5CC18;
|
@olive-color: #b5cc18;
|
||||||
@olive-hover-color: #a7bd0d;
|
@olive-hover-color: #a7bd0d;
|
||||||
@green-color: #21BA45;
|
@green-color: #21ba45;
|
||||||
@green-hover-color: #16ab39;
|
@green-hover-color: #16ab39;
|
||||||
@teal-color: #00B5AD;
|
@teal-color: #00b5ad;
|
||||||
@teal-hover-color: #009c95;
|
@teal-hover-color: #009c95;
|
||||||
@blue-color: #2185D0;
|
@blue-color: #2185d0;
|
||||||
@blue-hover-color: #1678c2;
|
@blue-hover-color: #1678c2;
|
||||||
@violet-color: #6435C9;
|
@violet-color: #6435c9;
|
||||||
@violet-hover-color: #5829bb;
|
@violet-hover-color: #5829bb;
|
||||||
@purple-color: #A333C8;
|
@purple-color: #a333c8;
|
||||||
@purple-hover-color: #9627ba;
|
@purple-hover-color: #9627ba;
|
||||||
@pink-color: #E03997;
|
@pink-color: #e03997;
|
||||||
@pink-hover-color: #e61a8d;
|
@pink-hover-color: #e61a8d;
|
||||||
@brown-color: #A5673F;
|
@brown-color: #a5673f;
|
||||||
@brown-hover-color: #975b33;
|
@brown-hover-color: #975b33;
|
||||||
@grey-color: #767676;
|
@grey-color: #767676;
|
||||||
@grey-hover-color: #838383;
|
@grey-hover-color: #838383;
|
||||||
@dark-color: #343a40;
|
@dark-color: #343a40;
|
||||||
@dark-hover-color: darken(@dark-color, 10%);
|
@dark-hover-color: darken(@dark-color, 10%);
|
||||||
@black-color: #1B1C1D;
|
@black-color: #1b1c1d;
|
||||||
@black-hover-color: #27292a;
|
@black-hover-color: #27292a;
|
||||||
|
|
||||||
// base
|
// base
|
||||||
@ -37,7 +37,7 @@
|
|||||||
@border: 1px solid @border-color;
|
@border: 1px solid @border-color;
|
||||||
@input-border: @border;
|
@input-border: @border;
|
||||||
@input-padding: 0.5em 0.75em;
|
@input-padding: 0.5em 0.75em;
|
||||||
@input-box-shadow: inset 0 1px 2px hsla(0,0%,4%,.06);
|
@input-box-shadow: inset 0 1px 2px hsla(0, 0%, 4%, 0.06);
|
||||||
@border-radius: 2px;
|
@border-radius: 2px;
|
||||||
@form-field-height: 30px;
|
@form-field-height: 30px;
|
||||||
@primary-color: @blue-color;
|
@primary-color: @blue-color;
|
||||||
@ -47,7 +47,8 @@
|
|||||||
.type-primary() {
|
.type-primary() {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: @primary-color;
|
background-color: @primary-color;
|
||||||
&:hover, &.active {
|
&:hover,
|
||||||
|
&.active {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: @primary-hover-color;
|
background-color: @primary-hover-color;
|
||||||
}
|
}
|
||||||
@ -68,7 +69,13 @@ body {
|
|||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
font: 400 13px Arial, 'Lato', 'Source Sans Pro', Roboto, Helvetica, sans-serif;
|
font:
|
||||||
|
400 13px Arial,
|
||||||
|
'Lato',
|
||||||
|
'Source Sans Pro',
|
||||||
|
Roboto,
|
||||||
|
Helvetica,
|
||||||
|
sans-serif;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,14 +105,14 @@ body {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
left: calc(50% - 4px);
|
left: calc(50% - 4px);
|
||||||
top: -4px;
|
top: -4px;
|
||||||
content: "";
|
content: '';
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
background: inherit;
|
background: inherit;
|
||||||
-webkit-transform: rotate(45deg);
|
-webkit-transform: rotate(45deg);
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
box-shadow: 1px 1px 3px -1px rgba(0, 0, 0, .3);
|
box-shadow: 1px 1px 3px -1px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,7 +170,7 @@ body {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
.@{css-prefix}-icon-img {
|
.@{css-prefix}-icon-img {
|
||||||
opacity: .8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@ -185,9 +192,7 @@ body {
|
|||||||
left: 0;
|
left: 0;
|
||||||
top: -3px;
|
top: -3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.@{css-prefix}-dropdown {
|
.@{css-prefix}-dropdown {
|
||||||
@ -197,7 +202,7 @@ body {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 200;
|
z-index: 200;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 1px 2px 5px 2px rgba(51,51,51,.15);
|
box-shadow: 1px 2px 5px 2px rgba(51, 51, 51, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.bottom-left {
|
&.bottom-left {
|
||||||
@ -228,7 +233,6 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.@{css-prefix}-dropdown-title {
|
.@{css-prefix}-dropdown-title {
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@ -253,7 +257,7 @@ body {
|
|||||||
z-index: 11;
|
z-index: 11;
|
||||||
|
|
||||||
.@{css-prefix}-resizer-hover {
|
.@{css-prefix}-resizer-hover {
|
||||||
background-color: rgba(75, 137, 255, .25);
|
background-color: rgba(75, 137, 255, 0.25);
|
||||||
}
|
}
|
||||||
.@{css-prefix}-resizer-line {
|
.@{css-prefix}-resizer-line {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -322,7 +326,8 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.@{css-prefix}-editor, .@{css-prefix}-selector {
|
.@{css-prefix}-editor,
|
||||||
|
.@{css-prefix}-selector {
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -347,10 +352,11 @@ body {
|
|||||||
.@{css-prefix}-selector-area {
|
.@{css-prefix}-selector-area {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border: 2px solid rgb(75, 137, 255);
|
border: 2px solid rgb(75, 137, 255);
|
||||||
background: rgba(75, 137, 255, .1);
|
background: rgba(75, 137, 255, 0.1);
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
}
|
}
|
||||||
.@{css-prefix}-selector-clipboard, .@{css-prefix}-selector-autofill {
|
.@{css-prefix}-selector-clipboard,
|
||||||
|
.@{css-prefix}-selector-autofill {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
@ -359,7 +365,7 @@ body {
|
|||||||
border: 2px dashed rgb(75, 137, 255);
|
border: 2px dashed rgb(75, 137, 255);
|
||||||
}
|
}
|
||||||
.@{css-prefix}-selector-autofill {
|
.@{css-prefix}-selector-autofill {
|
||||||
border: 1px dashed rgba(0, 0, 0, .45); // #606060; // rgba(0, 0, 0, .2);
|
border: 1px dashed rgba(0, 0, 0, 0.45); // #606060; // rgba(0, 0, 0, .2);
|
||||||
}
|
}
|
||||||
.@{css-prefix}-selector-corner {
|
.@{css-prefix}-selector-corner {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
@ -392,7 +398,13 @@ body {
|
|||||||
resize: none;
|
resize: none;
|
||||||
text-align: start;
|
text-align: start;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
font: 400 13px Arial, 'Lato', 'Source Sans Pro', Roboto, Helvetica, sans-serif;
|
font:
|
||||||
|
400 13px Arial,
|
||||||
|
'Lato',
|
||||||
|
'Source Sans Pro',
|
||||||
|
Roboto,
|
||||||
|
Helvetica,
|
||||||
|
sans-serif;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
@ -416,7 +428,7 @@ body {
|
|||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
outline: none;
|
outline: none;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
color: rgba(0, 0, 0, .9);
|
color: rgba(0, 0, 0, 0.9);
|
||||||
line-height: 26px;
|
line-height: 26px;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 2px 10px;
|
padding: 2px 10px;
|
||||||
@ -429,8 +441,9 @@ body {
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover, &.active {
|
&:hover,
|
||||||
background: rgba(0, 0, 0, .05);
|
&.active {
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
// &.active {
|
// &.active {
|
||||||
@ -442,12 +455,12 @@ body {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
border: none;
|
border: none;
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, .1);
|
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
float: right;
|
float: right;
|
||||||
opacity: .65;
|
opacity: 0.65;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -489,7 +502,6 @@ body {
|
|||||||
// transform: rotate(45deg);
|
// transform: rotate(45deg);
|
||||||
// -webkit-transform: rotate(45deg);
|
// -webkit-transform: rotate(45deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.@{css-prefix}-checkbox {
|
.@{css-prefix}-checkbox {
|
||||||
@ -533,7 +545,7 @@ body {
|
|||||||
margin: 10px;
|
margin: 10px;
|
||||||
|
|
||||||
.@{css-prefix}-header {
|
.@{css-prefix}-header {
|
||||||
padding: .5em .75em;
|
padding: 0.5em 0.75em;
|
||||||
background: #f8f8f9;
|
background: #f8f8f9;
|
||||||
border-bottom: 1px solid #e9e9e9;
|
border-bottom: 1px solid #e9e9e9;
|
||||||
border-left: 1px solid transparent;
|
border-left: 1px solid transparent;
|
||||||
@ -555,7 +567,8 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.@{css-prefix}-toolbar, .@{css-prefix}-bottombar {
|
.@{css-prefix}-toolbar,
|
||||||
|
.@{css-prefix}-bottombar {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
padding: 0 30px;
|
padding: 0 30px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
@ -586,7 +599,7 @@ body {
|
|||||||
.@{css-prefix}-menu > li {
|
.@{css-prefix}-menu > li {
|
||||||
float: left;
|
float: left;
|
||||||
line-height: 1.25em;
|
line-height: 1.25em;
|
||||||
padding: .785em 1em;
|
padding: 0.785em 1em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
@ -594,12 +607,12 @@ body {
|
|||||||
color: #80868b;
|
color: #80868b;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all .3s;
|
transition: all 0.3s;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
color: rgba(0, 0, 0, .65);
|
color: rgba(0, 0, 0, 0.65);
|
||||||
}
|
}
|
||||||
|
|
||||||
.@{css-prefix}-icon {
|
.@{css-prefix}-icon {
|
||||||
@ -607,7 +620,7 @@ body {
|
|||||||
|
|
||||||
.@{css-prefix}-icon-img {
|
.@{css-prefix}-icon-img {
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: .85;
|
opacity: 0.85;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -649,8 +662,9 @@ body {
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover, &.active {
|
&:hover,
|
||||||
background: rgba(0, 0, 0, .08);
|
&.active {
|
||||||
|
background: rgba(0, 0, 0, 0.08);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -714,7 +728,6 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.@{css-prefix}-canvas-card-wraper {
|
.@{css-prefix}-canvas-card-wraper {
|
||||||
margin: 40px 20px;
|
margin: 40px 20px;
|
||||||
}
|
}
|
||||||
@ -723,11 +736,14 @@ body {
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
page-break-before: auto;
|
page-break-before: auto;
|
||||||
page-break-after: always;
|
page-break-after: always;
|
||||||
box-shadow: 0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 3px rgba(0,0,0,0.12), 0 4px 5px 0 rgba(0,0,0,0.20);
|
box-shadow:
|
||||||
|
0 8px 10px 1px rgba(0, 0, 0, 0.14),
|
||||||
|
0 3px 14px 3px rgba(0, 0, 0, 0.12),
|
||||||
|
0 4px 5px 0 rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.@{css-prefix}-calendar {
|
.@{css-prefix}-calendar {
|
||||||
color: rgba(0,0,0,.65);
|
color: rgba(0, 0, 0, 0.65);
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
@ -753,7 +769,7 @@ body {
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
a:hover {
|
a:hover {
|
||||||
background: rgba(0,0,0,.08);
|
background: rgba(0, 0, 0, 0.08);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -762,7 +778,8 @@ body {
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
|
|
||||||
th, td {
|
th,
|
||||||
|
td {
|
||||||
width: 100%/7;
|
width: 100%/7;
|
||||||
min-width: 32px;
|
min-width: 32px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -778,9 +795,10 @@ body {
|
|||||||
background: #ecf6fd;
|
background: #ecf6fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active, &.active:hover {
|
&.active,
|
||||||
|
&.active:hover {
|
||||||
background: #ecf6fd;
|
background: #ecf6fd;
|
||||||
color: #2185D0;
|
color: #2185d0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
@ -792,7 +810,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.@{css-prefix}-datepicker {
|
.@{css-prefix}-datepicker {
|
||||||
box-shadow: 2px 2px 5px rgba(0,0,0,.2);
|
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: calc(100% + 5px);
|
top: calc(100% + 5px);
|
||||||
@ -818,11 +836,11 @@ body {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
padding: .75em 1em;
|
padding: 0.75em 1em;
|
||||||
color: rgba(0,0,0,.6);
|
color: rgba(0, 0, 0, 0.6);
|
||||||
background: #E0E1E2;
|
background: #e0e1e2;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-family: "Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;
|
font-family: 'Lato', 'proxima-nova', 'Helvetica Neue', Arial, sans-serif;
|
||||||
//box-shadow: 0px 1px 2px -1px rgba(255,255,255,0.5) inset, 0px -2px 0px 0px rgba(0,0,0,0.1) inset;
|
//box-shadow: 0px 1px 2px -1px rgba(255,255,255,0.5) inset, 0px -2px 0px 0px rgba(0,0,0,0.1) inset;
|
||||||
//box-shadow: 0 0 0 0 rgba(0,0,0,.5) inset;
|
//box-shadow: 0 0 0 0 rgba(0,0,0,.5) inset;
|
||||||
outline: none;
|
outline: none;
|
||||||
@ -831,9 +849,10 @@ body {
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
transition: all 0.1s linear;
|
transition: all 0.1s linear;
|
||||||
|
|
||||||
&.active, &:hover {
|
&.active,
|
||||||
background-color: #C0C1C2;
|
&:hover {
|
||||||
color: rgba(0,0,0,.8);
|
background-color: #c0c1c2;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.primary {
|
&.primary {
|
||||||
@ -847,7 +866,7 @@ body {
|
|||||||
position: relative;
|
position: relative;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
color: rgba(0,0,0,.87);
|
color: rgba(0, 0, 0, 0.87);
|
||||||
|
|
||||||
input {
|
input {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
@ -863,12 +882,14 @@ body {
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #e9e9e9;
|
border: 1px solid #e9e9e9;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
transition: box-shadow .1s ease,border-color .1s ease;
|
transition:
|
||||||
box-shadow: inset 0 1px 2px hsla(0,0%,4%,.06);
|
box-shadow 0.1s ease,
|
||||||
|
border-color 0.1s ease;
|
||||||
|
box-shadow: inset 0 1px 2px hsla(0, 0%, 4%, 0.06);
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
border-color: rgb(75, 137, 255);
|
border-color: rgb(75, 137, 255);
|
||||||
box-shadow: inset 0 1px 2px rgba(75, 137, 255, .2);
|
box-shadow: inset 0 1px 2px rgba(75, 137, 255, 0.2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -880,9 +901,9 @@ body {
|
|||||||
border: @input-border;
|
border: @input-border;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: rgba(0,0,0,.87);
|
color: rgba(0, 0, 0, 0.87);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
box-shadow: inset 0 1px 2px hsla(0,0%,4%,.06);
|
box-shadow: inset 0 1px 2px hsla(0, 0%, 4%, 0.06);
|
||||||
|
|
||||||
.input-text {
|
.input-text {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@ -918,14 +939,15 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.error {
|
&.error {
|
||||||
.@{css-prefix}-form-select, input {
|
.@{css-prefix}-form-select,
|
||||||
|
input {
|
||||||
border-color: #f04134;
|
border-color: #f04134;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tip {
|
.tip {
|
||||||
color: #f04134;
|
color: #f04134;
|
||||||
font-size: .9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// form end
|
// form end
|
||||||
@ -959,7 +981,7 @@ form fieldset {
|
|||||||
|
|
||||||
label {
|
label {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: .5em;
|
margin-bottom: 0.5em;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
@ -970,42 +992,44 @@ form fieldset {
|
|||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border: none;
|
border: none;
|
||||||
border-bottom: 2px solid #ddd;
|
border-bottom: 2px solid #ddd;
|
||||||
padding: .5em .85em;
|
padding: 0.5em 0.85em;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.@{css-prefix}-modal, .@{css-prefix}-toast {
|
.@{css-prefix}-modal,
|
||||||
|
.@{css-prefix}-toast {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 1001;
|
z-index: 1001;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
line-height: @line-height;
|
line-height: @line-height;
|
||||||
min-width: 360px;
|
min-width: 360px;
|
||||||
color: rgba(0,0,0,.87);
|
color: rgba(0, 0, 0, 0.87);
|
||||||
font-family: 'Lato', 'Source Sans Pro', Roboto, Helvetica, Arial, sans-serif;
|
font-family: 'Lato', 'Source Sans Pro', Roboto, Helvetica, Arial, sans-serif;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid rgba(0,0,0,.1);
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
background-clip: padding-box;
|
background-clip: padding-box;
|
||||||
box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 8px;
|
box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.@{css-prefix}-toast {
|
.@{css-prefix}-toast {
|
||||||
background-color: rgba(255,255,255,.85);
|
background-color: rgba(255, 255, 255, 0.85);
|
||||||
}
|
}
|
||||||
|
|
||||||
.@{css-prefix}-modal-header, .@{css-prefix}-toast-header {
|
.@{css-prefix}-modal-header,
|
||||||
|
.@{css-prefix}-toast-header {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
background-clip: padding-box;
|
background-clip: padding-box;
|
||||||
background-color: rgba(255,255,255,.85);
|
background-color: rgba(255, 255, 255, 0.85);
|
||||||
border-bottom: 1px solid rgba(0,0,0,.05);
|
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
border-radius: 4px 4px 0 0;
|
border-radius: 4px 4px 0 0;
|
||||||
|
|
||||||
.@{css-prefix}-icon {
|
.@{css-prefix}-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: .8em;
|
right: 0.8em;
|
||||||
top: .65em;
|
top: 0.65em;
|
||||||
border-radius: 18px;
|
border-radius: 18px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@ -1021,7 +1045,7 @@ form fieldset {
|
|||||||
|
|
||||||
.@{css-prefix}-modal-header {
|
.@{css-prefix}-modal-header {
|
||||||
border-bottom: @border-style;
|
border-bottom: @border-style;
|
||||||
background: rgba(0, 0, 0, .08);
|
background: rgba(0, 0, 0, 0.08);
|
||||||
font-size: 1.0785em;
|
font-size: 1.0785em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1029,7 +1053,7 @@ form fieldset {
|
|||||||
.@{css-prefix}-modal-content,
|
.@{css-prefix}-modal-content,
|
||||||
.@{css-prefix}-toast-header,
|
.@{css-prefix}-toast-header,
|
||||||
.@{css-prefix}-toast-content {
|
.@{css-prefix}-toast-content {
|
||||||
padding: .75em 1em;
|
padding: 0.75em 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 320px) and (max-width: 480px) {
|
@media screen and (min-width: 320px) and (max-width: 480px) {
|
||||||
|
@ -0,0 +1,239 @@
|
|||||||
|
import React, { useContext, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
CollectionProvider,
|
||||||
|
css,
|
||||||
|
Grid,
|
||||||
|
i18n,
|
||||||
|
Plugin,
|
||||||
|
SchemaComponent,
|
||||||
|
SchemaComponentContext,
|
||||||
|
useAPIClient,
|
||||||
|
useRequest,
|
||||||
|
useTranslation,
|
||||||
|
useViewport,
|
||||||
|
} from '@tachybase/client';
|
||||||
|
import { FormItem, FormLayout } from '@tachybase/components';
|
||||||
|
import { formSchema } from '@tachybase/plugin-multi-app-manager/client';
|
||||||
|
import { createForm, useField, useForm } from '@tachybase/schema';
|
||||||
|
|
||||||
|
import { Alert } from 'antd';
|
||||||
|
|
||||||
|
const ft = 'https://o.alicdn.com/captcha-frontend/aliyunCaptcha/AliyunCaptcha.js';
|
||||||
|
const sceneId = 'jruas0rs';
|
||||||
|
const prefix = '1bze12';
|
||||||
|
const changeLang = (locale = 'zh-CN') => {
|
||||||
|
const e = { language: 'cn', region: 'cn' };
|
||||||
|
return { 'zh-CN': e, 'en-US': { language: 'en', region: 'sgp' } }[locale] || e;
|
||||||
|
};
|
||||||
|
const Captcha = (t) => {
|
||||||
|
const { request, onSuccess, onError, language = 'zh-CN' } = t;
|
||||||
|
const getInstance = () => {};
|
||||||
|
const captchaVerifyCallback = async (param) => {
|
||||||
|
try {
|
||||||
|
return await request({ captchaVerifyParam: param, sceneId }), { captchaResult: true };
|
||||||
|
} catch (s) {
|
||||||
|
return onError == null || onError(s), { captchaResult: false };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onBizResultCallback = () => {
|
||||||
|
onSuccess == null || onSuccess();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const dynamicScript = document.createElement('script');
|
||||||
|
dynamicScript.type = 'text/javascript';
|
||||||
|
dynamicScript.src = ft;
|
||||||
|
dynamicScript.onload = () => {
|
||||||
|
// @ts-ignore
|
||||||
|
window.initAliyunCaptcha({
|
||||||
|
SceneId: sceneId,
|
||||||
|
prefix: prefix,
|
||||||
|
mode: 'embed',
|
||||||
|
element: '#captcha-element',
|
||||||
|
button: '#deploy',
|
||||||
|
captchaVerifyCallback: captchaVerifyCallback,
|
||||||
|
onBizResultCallback: onBizResultCallback,
|
||||||
|
getInstance: getInstance,
|
||||||
|
slideStyle: { width: 360, height: 40 },
|
||||||
|
...changeLang(language),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
document.head.appendChild(dynamicScript);
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id="captcha-element"
|
||||||
|
className={css`
|
||||||
|
margin-top: 24px;
|
||||||
|
#aliyunCaptcha-window-embed {
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
#aliyunCaptcha-sliding-body {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
i18n.addResources('zh-CN', 'demo', {
|
||||||
|
'Deploy a demo site': '开通 tachybase 体验站点',
|
||||||
|
Email: '电子邮箱',
|
||||||
|
Yes: '是',
|
||||||
|
No: '否',
|
||||||
|
'Application template': '应用模板',
|
||||||
|
'Are you a developer': '你是一名开发者吗',
|
||||||
|
'Deploy Now': '立即开通',
|
||||||
|
Industry: '所在行业',
|
||||||
|
'Please fill in the necessary information and the demo site will be automatically created within one minute.':
|
||||||
|
'请填写必要的信息,体验站将在一分钟内自动创建成功。',
|
||||||
|
'Demo site deployed successfully!': '体验站点开通成功!',
|
||||||
|
'What you want to do with tachybase': '你希望用 tachybase 来做什么',
|
||||||
|
'Used to receive demo site information, please be sure to fill in correctly': '用于接收体验站点信息,请务必正确填写',
|
||||||
|
'Please briefly describe the purpose': '请简要描述用途,方便我们后续针对性的开发',
|
||||||
|
'Please briefly describe your industry': '请简要描述你所在的行业,方便我们后续针对性的开发',
|
||||||
|
});
|
||||||
|
const DemoProvider = React.createContext(null);
|
||||||
|
function useApplicationAction() {
|
||||||
|
const api = useAPIClient();
|
||||||
|
const form = useForm();
|
||||||
|
const field = useField();
|
||||||
|
const [, setSuccess] = useContext(DemoProvider);
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
await form.submit();
|
||||||
|
field.data = field.data || {};
|
||||||
|
field.data.loading = true;
|
||||||
|
try {
|
||||||
|
await api.request({ url: 'applicationForms:create', method: 'post', data: form.values });
|
||||||
|
field.data.loading = false;
|
||||||
|
setSuccess(true);
|
||||||
|
} catch (a) {
|
||||||
|
field.data.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function DemoApplyForm() {
|
||||||
|
useViewport();
|
||||||
|
const schemaComponentContext = React.useContext(SchemaComponentContext);
|
||||||
|
const [isSuccess, setSuccess] = React.useState(false);
|
||||||
|
const [loading, setLoading] = React.useState(false);
|
||||||
|
const api = useAPIClient();
|
||||||
|
// @ts-ignore
|
||||||
|
const { t, i18n } = useTranslation('demo');
|
||||||
|
const form = createForm();
|
||||||
|
const { data, loading: isLoading } = useRequest({ url: 'applicationForms:getTemplateNameUiSchema' });
|
||||||
|
const onRequest = async (requestData) => {
|
||||||
|
await form.validate();
|
||||||
|
await api.request({ url: 'applicationForms:create', method: 'post', data: { ...form.values, ...requestData } });
|
||||||
|
};
|
||||||
|
return isLoading ? null : (
|
||||||
|
<div style={{ margin: '0 auto', paddingTop: '15vh', paddingBottom: 50, maxWidth: 350 }}>
|
||||||
|
<title>{[t('Deploy a demo site'), ' - tachybase']}</title>
|
||||||
|
{isSuccess ? (
|
||||||
|
<Alert
|
||||||
|
message={t('Demo site deployed successfully!')}
|
||||||
|
description={
|
||||||
|
i18n.language === 'zh-CN' ? (
|
||||||
|
<div>
|
||||||
|
你将在几秒钟内收到邮件,其中包含体验站点的地址、账号和 密码。
|
||||||
|
<br />
|
||||||
|
该站点仅用于预览,你在体验站点所作的操作、所存储的数据 可能会丢失。
|
||||||
|
<br />
|
||||||
|
该站点将会在 48 小时后失效,之后你可以重新申请体验站点。
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
You will receive an email in a few seconds with the URL, username and password of the demo site.
|
||||||
|
<br />
|
||||||
|
The site is for preview purposes only, your actions and data stored on the site may be lost.
|
||||||
|
<br />
|
||||||
|
The site will expire after 48 hours, after which you can reapply for the site.
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
type="success"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div style={{ marginTop: 16 }}>
|
||||||
|
<DemoProvider.Provider value={[isSuccess, setSuccess]}>
|
||||||
|
<CollectionProvider name={(data as any)?.data?.collection}>
|
||||||
|
<SchemaComponentContext.Provider value={{ ...schemaComponentContext, designable: false }}>
|
||||||
|
<SchemaComponent
|
||||||
|
components={{
|
||||||
|
FormLayout,
|
||||||
|
FormItem,
|
||||||
|
Grid,
|
||||||
|
Captcha,
|
||||||
|
}}
|
||||||
|
scope={{ useApplicationAction }}
|
||||||
|
schema={{
|
||||||
|
name: 'appform',
|
||||||
|
type: 'object',
|
||||||
|
'x-component': 'FormV2',
|
||||||
|
'x-use-component-props': () => ({ form: form }),
|
||||||
|
properties: {
|
||||||
|
layout: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'FormLayout',
|
||||||
|
'x-component-props': { layout: 'vertical' },
|
||||||
|
properties: { div: (data as any)?.data?.schema },
|
||||||
|
},
|
||||||
|
captcha: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Captcha',
|
||||||
|
'x-component-props': {
|
||||||
|
request: onRequest,
|
||||||
|
language: i18n.language,
|
||||||
|
onSuccess: () => {
|
||||||
|
setSuccess(true), setLoading(false);
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
setLoading(false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'div',
|
||||||
|
'x-component-props': { id: 'deploy' },
|
||||||
|
properties: {
|
||||||
|
submit: {
|
||||||
|
type: 'void',
|
||||||
|
title: t('Deploy Now'),
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
block: true,
|
||||||
|
loading: loading,
|
||||||
|
type: 'primary',
|
||||||
|
style: { width: '100%' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SchemaComponentContext.Provider>
|
||||||
|
</CollectionProvider>
|
||||||
|
</DemoProvider.Provider>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export class PluginDemo extends Plugin {
|
||||||
|
async load() {
|
||||||
|
formSchema.properties['options.isTemplate'] = {
|
||||||
|
'x-component': 'Checkbox',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-content': 'As an application template',
|
||||||
|
};
|
||||||
|
formSchema.properties['options.persistent'] = {
|
||||||
|
'x-component': 'Checkbox',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-content': 'Persistent',
|
||||||
|
};
|
||||||
|
this.router.add('new', { path: '/new', Component: DemoApplyForm });
|
||||||
|
}
|
||||||
|
}
|
@ -3,9 +3,9 @@ import { Plugin, SchemaComponentContext, useSchemaComponentContext } from '@tach
|
|||||||
import { PluginACLClient } from '@tachybase/plugin-acl/client';
|
import { PluginACLClient } from '@tachybase/plugin-acl/client';
|
||||||
|
|
||||||
import { tval } from '../../locale';
|
import { tval } from '../../locale';
|
||||||
|
import { DepartmentManagementComponent } from './components/DepartmentManagementComponent';
|
||||||
import { Departments } from './components/Departments';
|
import { Departments } from './components/Departments';
|
||||||
import { DepartmentsProvider } from './components/DepartmentsProvider';
|
import { DepartmentsProvider } from './components/DepartmentsProvider';
|
||||||
import { DepartmentManagementComponent } from './components/DepartmentManagementComponent';
|
|
||||||
import { UserDepartmentsFieldNotSupport } from './components/UserDepartmentsFieldNotSupport';
|
import { UserDepartmentsFieldNotSupport } from './components/UserDepartmentsFieldNotSupport';
|
||||||
import { DepartmentOwnersFieldSetting } from './settings/DepartmentOwnersFieldSetting';
|
import { DepartmentOwnersFieldSetting } from './settings/DepartmentOwnersFieldSetting';
|
||||||
import { UserDepartmentsFieldSetting } from './settings/UserDepartmentsFieldSetting';
|
import { UserDepartmentsFieldSetting } from './settings/UserDepartmentsFieldSetting';
|
||||||
|
@ -18,6 +18,7 @@ import { PluginAssistant } from './features/assistant';
|
|||||||
import { PluginGroupBlock } from './features/block-group';
|
import { PluginGroupBlock } from './features/block-group';
|
||||||
import { PluginContextMenu } from './features/context-menu';
|
import { PluginContextMenu } from './features/context-menu';
|
||||||
import { PluginCustomComponents } from './features/custom-components';
|
import { PluginCustomComponents } from './features/custom-components';
|
||||||
|
import { PluginDemo } from './features/demo';
|
||||||
import { DepartmentsPlugin } from './features/departments';
|
import { DepartmentsPlugin } from './features/departments';
|
||||||
import { EmbedPlugin } from './features/embed';
|
import { EmbedPlugin } from './features/embed';
|
||||||
import { PluginExtendedFilterForm } from './features/extended-filter-form';
|
import { PluginExtendedFilterForm } from './features/extended-filter-form';
|
||||||
@ -82,6 +83,7 @@ export class PluginCoreClient extends Plugin {
|
|||||||
await this.app.pm.add(PluginFieldAppends);
|
await this.app.pm.add(PluginFieldAppends);
|
||||||
await this.app.pm.add(PluginCustomComponents);
|
await this.app.pm.add(PluginCustomComponents);
|
||||||
await this.app.pm.add(PluginSheet);
|
await this.app.pm.add(PluginSheet);
|
||||||
|
await this.app.pm.add(PluginDemo);
|
||||||
}
|
}
|
||||||
|
|
||||||
async registerSettings() {
|
async registerSettings() {
|
||||||
|
@ -0,0 +1,85 @@
|
|||||||
|
import { defineCollection } from '@tachybase/database';
|
||||||
|
|
||||||
|
export default defineCollection({
|
||||||
|
name: 'applicationForms',
|
||||||
|
title: '申请表单',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'application',
|
||||||
|
type: 'belongsTo',
|
||||||
|
target: 'applications',
|
||||||
|
foreignKey: 'applicationName',
|
||||||
|
targetKey: 'name',
|
||||||
|
onDelete: 'SET NULL',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'templateName',
|
||||||
|
type: 'string',
|
||||||
|
interface: 'select',
|
||||||
|
uiSchema: {
|
||||||
|
title: '模板',
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Select',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'url',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
type: 'string',
|
||||||
|
interface: 'input',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
title: '邮箱',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'note',
|
||||||
|
type: 'string',
|
||||||
|
interface: 'textarea',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
title: '你希望用来做什么',
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'industry',
|
||||||
|
interface: 'input',
|
||||||
|
type: 'string',
|
||||||
|
uiSchema: {
|
||||||
|
title: '所在行业',
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'isDeveloper',
|
||||||
|
interface: 'radioGroup',
|
||||||
|
type: 'string',
|
||||||
|
uiSchema: {
|
||||||
|
title: '你是一名开发者吗?',
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Radio.Group',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
enum: [
|
||||||
|
{
|
||||||
|
value: 'yes',
|
||||||
|
label: '是',
|
||||||
|
color: 'geekblue',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'no',
|
||||||
|
label: '不是',
|
||||||
|
color: 'cyan',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
@ -0,0 +1,50 @@
|
|||||||
|
function notificationTemplate(lang) {
|
||||||
|
return {
|
||||||
|
'zh-CN': {
|
||||||
|
name: 'demo',
|
||||||
|
subject: 'TachyBase 体验站点开通成功!',
|
||||||
|
body: `
|
||||||
|
<p>地址:https://<%= appId %>.v7.demo-cn.tachybase.com/</p>
|
||||||
|
<p>账号:admin@tachybase.com</p>
|
||||||
|
<p>密码:admin123</p>
|
||||||
|
<p>该站点仅用于体验,你在体验站点所做的操作、所存储的数据可能会丢失。</p>
|
||||||
|
<p>该站点将会在 48 小时后失效,之后你可以重新申请体验站点。</p>
|
||||||
|
`,
|
||||||
|
receiver_options: {},
|
||||||
|
},
|
||||||
|
'en-US': {
|
||||||
|
name: 'demo',
|
||||||
|
subject: 'TachyBase demo site deployed successfully!',
|
||||||
|
body: `
|
||||||
|
<p>URL: https://<%= appId %>.v7.demo.tachybase.com/</p>
|
||||||
|
<p>Account: admin@tachybase.com</p>
|
||||||
|
<p>Password: admin123</p>
|
||||||
|
<p>The site is for preview purposes only, your actions and data stored on the site may be lost.</p>
|
||||||
|
<p>The site will expire after 48 hours, after which you can reapply for the site.</p>
|
||||||
|
`,
|
||||||
|
receiver_options: {},
|
||||||
|
},
|
||||||
|
}[lang || 'en-US'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createNotificationRecord(app) {
|
||||||
|
await app.db.getRepository('notifications').create({
|
||||||
|
values: {
|
||||||
|
...notificationTemplate(app.i18n.language || 'en-US'),
|
||||||
|
service: {
|
||||||
|
type: 'email',
|
||||||
|
title: '阿里云邮件推送',
|
||||||
|
options: {
|
||||||
|
auth: {
|
||||||
|
pass: process.env.ALIYUN_MAIL_AUTH_PASS,
|
||||||
|
user: 'noreply@demo.tachybase.com',
|
||||||
|
},
|
||||||
|
from: 'TachyBase<noreply@demo.tachybase.com>',
|
||||||
|
host: 'smtpdm.aliyun.com',
|
||||||
|
port: 465,
|
||||||
|
secure: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,306 @@
|
|||||||
|
import { resolve } from 'path';
|
||||||
|
import { CollectionRepository } from '@tachybase/plugin-collection-manager';
|
||||||
|
import PluginMultiAppManager from '@tachybase/plugin-multi-app-manager';
|
||||||
|
import { UiSchemaRepository } from '@tachybase/plugin-ui-schema-storage';
|
||||||
|
import { AppSupervisor, Gateway, Plugin } from '@tachybase/server';
|
||||||
|
import { uid } from '@tachybase/utils';
|
||||||
|
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
import { Sequelize } from 'sequelize';
|
||||||
|
|
||||||
|
import { createNotificationRecord } from './create-notification';
|
||||||
|
import { createAliCaptchaClient, createAliCaptchaRequest } from './services';
|
||||||
|
|
||||||
|
const userApplicationPrefix = 'a_';
|
||||||
|
|
||||||
|
export class PluginDemo extends Plugin {
|
||||||
|
async afterAdd() {
|
||||||
|
this.app.resourcer.use(async (ctx, next) => {
|
||||||
|
if (this.app.name !== 'main' && ctx.action.actionName === 'disable' && ctx.action.resourceName === 'pm') {
|
||||||
|
const { filterByTk } = ctx.action.params;
|
||||||
|
if (['disable-pm-add', '@tachybase/plugin-disable-pm-add'].includes(filterByTk)) {
|
||||||
|
ctx.throw(403, 'No permissions');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await next();
|
||||||
|
if (this.app.name === 'main') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ctx.action.actionName === 'list' && ctx.action.resourceName === 'pm') {
|
||||||
|
ctx.body = ctx.body?.filter((item) => {
|
||||||
|
return ![
|
||||||
|
'@tachybase/plugin-verdaccio',
|
||||||
|
'@tachybase/plugin-demo-platform',
|
||||||
|
'@tachybase/plugin-disable-pm-add',
|
||||||
|
'@tachybase/plugin-notifications',
|
||||||
|
'@tachybase/plugin-multi-app-manager',
|
||||||
|
'@tachybase/plugin-multi-app-share-collection',
|
||||||
|
].includes(item.packageName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async beforeLoad() {
|
||||||
|
this.app.cronJobManager.addJob({
|
||||||
|
cronTime: '0 0 * * *',
|
||||||
|
onTick: async () => {
|
||||||
|
await this.cleanExpiredApplications();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.app.cronJobManager.addJob({
|
||||||
|
cronTime: '0 */30 * * * *',
|
||||||
|
onTick: async () => {
|
||||||
|
const expiredAt = new Date(new Date().getTime() - 30 * 60 * 1e3);
|
||||||
|
await this.removeAppInMemoryThatNotActiveAfterTime(expiredAt);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.db.on('applications.beforeCreate', async (model) => {
|
||||||
|
if (!model.name) {
|
||||||
|
model.set('name', `${userApplicationPrefix}${uid()}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.db.on('applications.afterDestroy', async (model, options) => {
|
||||||
|
const transaction = options.transaction;
|
||||||
|
const deleteDatabase = async () => {
|
||||||
|
const sql = `DROP DATABASE IF EXISTS "${model.get('name')}"`;
|
||||||
|
await this.db.sequelize.query(sql);
|
||||||
|
};
|
||||||
|
if (!transaction) {
|
||||||
|
await deleteDatabase();
|
||||||
|
} else {
|
||||||
|
transaction.afterCommit(async () => {
|
||||||
|
await deleteDatabase();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.db.on('applicationForms.beforeCreate', async (model, options) => {
|
||||||
|
if (!model.get('templateName')) {
|
||||||
|
throw new Error('templateName is required');
|
||||||
|
}
|
||||||
|
if (!model.get('email')) {
|
||||||
|
throw new Error('email is required');
|
||||||
|
}
|
||||||
|
model.set('applicationName', `${userApplicationPrefix}${uid()}`);
|
||||||
|
model.set('url', `https://${model.get('applicationName')}.${process.env.DEMO_HOST}`);
|
||||||
|
});
|
||||||
|
this.db.on('applicationForms.afterCreate', async (model, options) => {
|
||||||
|
const { transaction } = options;
|
||||||
|
transaction.afterCommit(() => {
|
||||||
|
this.db.sequelize.transaction(async (newTransaction) => {
|
||||||
|
try {
|
||||||
|
const application = await this.db.getRepository('applications').create({
|
||||||
|
transaction: newTransaction,
|
||||||
|
values: {
|
||||||
|
name: model.get('applicationName'),
|
||||||
|
options: {
|
||||||
|
templateName: model.get('templateName'),
|
||||||
|
authManager: {
|
||||||
|
jwt: {
|
||||||
|
secret: nanoid(48),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
context: {
|
||||||
|
templateName: model.get('templateName'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await model.setApplication(application, { transaction: newTransaction });
|
||||||
|
const notification = await this.db.getRepository('notifications').findOne({ transaction: newTransaction });
|
||||||
|
notification
|
||||||
|
.send({
|
||||||
|
to: model.get('email'),
|
||||||
|
appId: application.get('name'),
|
||||||
|
})
|
||||||
|
.catch((e) => console.log(e));
|
||||||
|
} catch (e) {
|
||||||
|
this.app.log.error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.app.resourcer.registerActionHandler('updateTpl', async (ctx, next) => {
|
||||||
|
const { resourceName } = ctx.action;
|
||||||
|
if (resourceName !== 'applications') {
|
||||||
|
return await next();
|
||||||
|
}
|
||||||
|
const { filterByTk } = ctx.action.params;
|
||||||
|
const applicationName = filterByTk;
|
||||||
|
if (!applicationName.startsWith('demo')) {
|
||||||
|
throw new Error('Only template application can be update to tpl');
|
||||||
|
}
|
||||||
|
const templateName = `${applicationName}_tpl`;
|
||||||
|
await ctx.db.sequelize.query(`DROP DATABASE IF EXISTS "${templateName}"`);
|
||||||
|
await ctx.db.sequelize.query(`CREATE DATABASE "${templateName}" TEMPLATE "${applicationName}"`);
|
||||||
|
ctx.body = 'ok';
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async cleanExpiredApplications() {
|
||||||
|
const now = new Date();
|
||||||
|
const expiredTime = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1e3);
|
||||||
|
const applications = await this.db.getRepository('applications').find({
|
||||||
|
filter: {
|
||||||
|
'createdAt.$lte': expiredTime,
|
||||||
|
// 创建时间超过两天
|
||||||
|
'options.templateName.$ne': null,
|
||||||
|
// 非模板应用
|
||||||
|
'options.persistent.$not': true,
|
||||||
|
// 非持久化应用
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await this.db.getRepository('applications').destroy({
|
||||||
|
filterByTk: applications.map((application) => application.get('name')),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async removeAppInMemoryThatNotActiveAfterTime(expiredAt) {
|
||||||
|
const expiredAtInUnix = Math.floor(expiredAt.getTime() / 1e3);
|
||||||
|
const applications = await this.db.getRepository('applications').find({
|
||||||
|
filter: {
|
||||||
|
'options.templateName.$ne': null,
|
||||||
|
'options.persistent.$not': true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
for (const application of applications) {
|
||||||
|
const lastSeenAt = AppSupervisor.getInstance().lastSeenAt.get(application.get('name'));
|
||||||
|
if (lastSeenAt && lastSeenAt < expiredAtInUnix) {
|
||||||
|
await AppSupervisor.getInstance().removeApp(application.get('name'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async load() {
|
||||||
|
await this.db.import({ directory: resolve(__dirname, 'collections') });
|
||||||
|
Gateway.getInstance().addAppSelectorMiddleware(async (ctx, next) => {
|
||||||
|
if (!ctx.resolvedAppName) {
|
||||||
|
const req = ctx.req;
|
||||||
|
const headers = req.headers;
|
||||||
|
const hostname = headers['x-hostname'] || headers['host'];
|
||||||
|
const firstPart = hostname.split('.')[0];
|
||||||
|
if (firstPart.startsWith(userApplicationPrefix)) {
|
||||||
|
ctx.resolvedAppName = firstPart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
this.app.resourcer.registerActionHandler('applicationForms:getTemplateNameUiSchema', async (ctx, next) => {
|
||||||
|
const uiSchemaTemplates = this.db.getRepository('uiSchemaTemplates');
|
||||||
|
const uiSchemas = this.db.getRepository<UiSchemaRepository>('uiSchemas');
|
||||||
|
const collection = this.db.getRepository('collections');
|
||||||
|
const instance = await uiSchemaTemplates.findOne({
|
||||||
|
filter: {
|
||||||
|
name: 'demo',
|
||||||
|
collectionName: 'applicationForms',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
ctx.body = {
|
||||||
|
collection: await collection.findOne({
|
||||||
|
filter: {
|
||||||
|
name: 'applicationForms',
|
||||||
|
},
|
||||||
|
appends: ['fields'],
|
||||||
|
}),
|
||||||
|
schema: await uiSchemas.getJsonSchema(instance['uid']),
|
||||||
|
};
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
this.app.acl.allow('applicationForms', 'getTemplateNameUiSchema', 'public');
|
||||||
|
this.app.acl.allow('applicationForms', 'create', 'public');
|
||||||
|
const aliCaptchaClient = createAliCaptchaClient();
|
||||||
|
this.app.resourcer.use(async (ctx, next) => {
|
||||||
|
if (ctx.action.actionName === 'create' && ctx.action.resourceName === 'applicationForms') {
|
||||||
|
const { senceId } = ctx.action.params;
|
||||||
|
const { captchaVerifyParam } = ctx.action.params.values;
|
||||||
|
delete ctx.action.params.values.captchaVerifyParam;
|
||||||
|
const request = createAliCaptchaRequest({
|
||||||
|
senceId,
|
||||||
|
captchaVerifyParam,
|
||||||
|
});
|
||||||
|
const resp = await aliCaptchaClient.verifyIntelligentCaptcha(request);
|
||||||
|
const captchaVerifyResult = resp.body.result.verifyResult;
|
||||||
|
if (!captchaVerifyResult) {
|
||||||
|
ctx.throw(400, 'captcha verify failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
const multiAppManager = this.pm.get('multi-app-manager') as PluginMultiAppManager;
|
||||||
|
multiAppManager.setAppDbCreator(async (app, options) => {
|
||||||
|
const { context = {} } = options;
|
||||||
|
const databaseOptions = app.options.database;
|
||||||
|
// @ts-ignore
|
||||||
|
const { host, port, username, password, database } = databaseOptions;
|
||||||
|
const { templateName } = context;
|
||||||
|
const client = new Sequelize({
|
||||||
|
dialect: 'postgres',
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
database: 'postgres',
|
||||||
|
});
|
||||||
|
if (templateName) {
|
||||||
|
let retryCount = 0;
|
||||||
|
let success = false;
|
||||||
|
const maxRetries = 5;
|
||||||
|
const retryDelay = 1e3;
|
||||||
|
while (retryCount < maxRetries && !success) {
|
||||||
|
try {
|
||||||
|
const res = await client.query(`CREATE DATABASE "${database}" TEMPLATE "${templateName}_tpl"`);
|
||||||
|
success = true;
|
||||||
|
console.log('Database created successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to create database:', error);
|
||||||
|
retryCount++;
|
||||||
|
console.log(`Retrying (${retryCount})...`);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!success) {
|
||||||
|
throw new Error('Failed to create database after multiple retries');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await client.query(`DROP DATABASE IF EXISTS "${database}"`);
|
||||||
|
await client.query(`CREATE DATABASE "${database}"`);
|
||||||
|
}
|
||||||
|
await client.close();
|
||||||
|
});
|
||||||
|
multiAppManager.setSubAppUpgradeHandler(async (mainApp) => {
|
||||||
|
const templateApps = await mainApp.db.getRepository('applications').find({
|
||||||
|
filter: {
|
||||||
|
'options.isTemplate': true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const appSupervisor = AppSupervisor.getInstance();
|
||||||
|
for (const instance of templateApps) {
|
||||||
|
const subApp = await appSupervisor.getApp(instance.name, {
|
||||||
|
upgrading: true,
|
||||||
|
});
|
||||||
|
await subApp.runAsCLI(['upgrade'], { from: 'user' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async install(options) {
|
||||||
|
if (!this.db.inDialect('postgres')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.pm.get('multi-app-manager').enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await createNotificationRecord(this.app);
|
||||||
|
const repo = this.db.getRepository<CollectionRepository>('collections');
|
||||||
|
if (repo) {
|
||||||
|
await repo.db2cm('applicationForms');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async beforeEnable() {
|
||||||
|
if (!this.db.inDialect('postgres')) {
|
||||||
|
throw new Error('demo-platform plugin only support postgres');
|
||||||
|
}
|
||||||
|
if (!this.pm.get('multi-app-manager')?.enabled) {
|
||||||
|
throw new Error(`${this.name} plugin need multi-app-manager plugin enabled`);
|
||||||
|
}
|
||||||
|
if (!this.pm.get('notifications')?.enabled && !this.pm.get('@tachybase/plugin-notifications')?.enabled) {
|
||||||
|
throw new Error(`${this.name} plugin need notifications plugin enabled`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
import Captcha, { VerifyIntelligentCaptchaRequest } from '@alicloud/captcha20230305';
|
||||||
|
import { Config } from '@alicloud/openapi-client';
|
||||||
|
|
||||||
|
export function createAliCaptchaClient() {
|
||||||
|
const config = new Config({});
|
||||||
|
config.accessKeyId = process.env['ALI_CAPTCHA_ACCESS_KEY_ID'];
|
||||||
|
config.accessKeySecret = process.env['ALI_CAPTCHA_ACCESS_KEY_SECRET'];
|
||||||
|
config.endpoint = process.env['ALI_CAPTCH_ENDPOINT']
|
||||||
|
? process.env['ALI_CAPTCH_ENDPOINT']
|
||||||
|
: 'captcha.cn-shanghai.aliyuncs.com';
|
||||||
|
config.connectTimeout = 5e3;
|
||||||
|
config.readTimeout = 5e3;
|
||||||
|
return new Captcha(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createAliCaptchaRequest({ senceId, captchaVerifyParam }) {
|
||||||
|
const request = new VerifyIntelligentCaptchaRequest({});
|
||||||
|
request.sceneId = senceId;
|
||||||
|
request.captchaVerifyParam = captchaVerifyParam;
|
||||||
|
return request;
|
||||||
|
}
|
@ -4,6 +4,7 @@ import path from 'path';
|
|||||||
import { Plugin } from '@tachybase/server';
|
import { Plugin } from '@tachybase/server';
|
||||||
import { Container } from '@tachybase/utils';
|
import { Container } from '@tachybase/utils';
|
||||||
|
|
||||||
|
import { PluginDemo } from './features/demo/plugin';
|
||||||
import { DepartmentsPlugin } from './features/departments';
|
import { DepartmentsPlugin } from './features/departments';
|
||||||
import CalcField from './fields/calc';
|
import CalcField from './fields/calc';
|
||||||
import TstzrangeField from './fields/tstzrange';
|
import TstzrangeField from './fields/tstzrange';
|
||||||
@ -19,6 +20,7 @@ export class PluginCoreServer extends Plugin {
|
|||||||
tstzrange: TstzrangeField,
|
tstzrange: TstzrangeField,
|
||||||
});
|
});
|
||||||
this.addFeature(DepartmentsPlugin);
|
this.addFeature(DepartmentsPlugin);
|
||||||
|
this.addFeature(PluginDemo);
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
|
10276
pnpm-lock.yaml
10276
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user