소스 검색

invokeServer

luoyifan 4 년 전
부모
커밋
72679c0572
8개의 변경된 파일576개의 추가작업 그리고 7개의 파일을 삭제
  1. 3 6
      src/controls/combo.js
  2. 2 0
      src/index.ts
  3. 2 0
      src/init.ts
  4. 295 0
      src/lib/ajax.ts
  5. 148 0
      src/lib/config.ts
  6. 23 0
      src/lib/lib.ts
  7. 102 0
      src/types.ts
  8. 1 1
      src/utils.ts

+ 3 - 6
src/controls/combo.js

@@ -1,16 +1,13 @@
 export default function () {
-    /**
-     * 改变必填项之前加星号
-     */
-    const t1 = Ext.form.field.Text.prototype.initComponent
-    Ext.form.field.Text.override({
+    const initComponentOrgin = Ext.form.field.ComboBox.prototype.initComponent
+    Ext.form.field.ComboBox.override({
         initComponent: function () {
             if (this.allowBlank === false || this.validateBlank === true) {
                 this.beforeLabelTextTpl = [
                     '<span style="color:red;font-weight:bold" data-qtip="必填选项">*</span>'
                 ];
             }
-            t1.call(this)
+            initComponentOrgin.call(this)
         }
     });
 }

+ 2 - 0
src/index.ts

@@ -17,5 +17,7 @@ export {
     Defaults
 }
 
+export * from './lib/ajax'
 export * from './Scope'
 export * from './init'
+export * from './lib/config'

+ 2 - 0
src/init.ts

@@ -1,6 +1,7 @@
 import _ from 'lodash'
 import initMainTab from './controls/MainTab'
 import initTextfield from './controls/textfield'
+import initCombo from './controls/combo'
 
 export function init() {
     // 引入 filters 过滤插件
@@ -52,4 +53,5 @@ export function init() {
 
     initMainTab()
     initTextfield()
+    initCombo()
 }

+ 295 - 0
src/lib/ajax.ts

@@ -0,0 +1,295 @@
+import axios, {AxiosRequestConfig, AxiosResponse} from 'axios'
+import {PlainObject} from "../types";
+import _ from 'lodash'
+import Qs from 'qs'
+import {serverInvokeUrlTransform, sqlUrlTransform, ajax} from "./config";
+
+export type ApiMethod =
+    'get'
+    | 'post'
+    | 'put'
+    | 'patch'
+    | 'delete'
+    | 'download'
+    | 'post-json'
+    | 'post-file'
+    | 'invoke'
+    | 'sql'
+
+export type ApiDataType = 'json' | 'form-data' | 'form'
+
+/**
+ * 调用服务器 Ajax
+ */
+export function invokeServer(url: string, ...args: any[]) {
+    // @ts-ignore
+    return ajax.func({
+        url: url,
+        method: 'invoke',
+        args: args
+    })
+}
+
+export interface ApiObject {
+    url: string
+    param: PlainObject
+    method: ApiMethod
+    data?: object
+    args: any[],
+    db?: string
+    headers?: PlainObject
+    filterModel?: { [key: string]: any; };
+    sortModel?: { colId: string | undefined; sort: string | null | undefined; }[];
+    config?: {
+        withCredentials?: boolean
+        cancelExecutor?: (cancel: Function) => void
+    }
+    dataType?: ApiDataType
+
+    /**
+     * 下载文件名
+     */
+    fileName?: string,
+
+    /**
+     * 上传文件(如果需要的话)
+     */
+    file?: any,
+}
+
+
+export type ApiFunction = (api: ApiObject) => Promise<ApiResult>;
+
+/**
+ * 组件中 Api 的声明
+ */
+export type Api = ApiFunction | ApiObject;
+
+export interface ApiResult {
+    rawData: object
+    status: number
+    headers: object
+    data?: any
+    pagination?: any
+    success: boolean
+    msg: string
+    errors?: {
+        [propName: string]: string
+    }
+}
+
+export interface CreateAjaxOption {
+    baseUrl: string
+    timeout: number
+}
+
+
+/**
+ * 创建一个 Ajax 客户端
+ */
+export function createAjax(createOption: CreateAjaxOption): ApiFunction {
+
+    if (createOption.baseUrl) {
+        axios.defaults.baseURL = createOption.baseUrl
+        axios.defaults.timeout = createOption.timeout
+        axios.defaults.timeoutErrorMessage = '网络超时'
+    }
+
+    return function (option: ApiObject) {
+
+        //@ts-ignore
+        option.method = (option.method || 'get').toLocaleLowerCase();
+
+        //@ts-ignore
+        const ax: AxiosRequestConfig = {
+            ...option
+        };
+
+        switch (option.method) {
+            case 'get':
+                ax.method = 'GET';
+                ax.params = option.param;
+                ax.headers = {
+                    ...option.headers
+                };
+                break
+
+            case 'post':
+                ax.method = 'POST';
+                ax.headers = {
+                    'Content-Type': 'application/x-www-form-urlencoded',
+                    ...option.headers
+                };
+                ax.params = option.param;
+                ax.data = Qs.stringify(option.data);
+                break
+
+            case 'put' :
+            case 'patch':
+            case 'delete':
+                ax.method = option.method;
+                ax.headers = option.headers;
+                ax.params = option.param;
+                ax.data = Qs.stringify(option.data);
+                break
+
+            case 'download':
+                downLoad(createOption.baseUrl + option.url, option.fileName || 'file',
+                    option.data, option.headers);
+                return new Promise<ApiResult>((resolver, reject) => {
+                });
+                break
+
+            case "invoke":
+                ax.url = serverInvokeUrlTransform(option.url)
+                ax.method = 'POST';
+                ax.headers = {
+                    'Content-Type': 'application/json',
+                    ...option.headers
+                };
+                if (typeof option.args === 'object') {
+                    ax.data = JSON.stringify({
+                        args: option.args,
+                    })
+                } else {
+                    ax.data = JSON.stringify({
+                        args: [
+                            {
+                                ...option.data,
+                                ...option.param
+                            }
+                        ],
+                        filterModel: option.filterModel,
+                        sortModel: option.sortModel
+                    })
+                }
+                break
+
+            case 'post-json':
+                ax.method = 'POST';
+                ax.headers = {
+                    'Content-Type': 'application/json',
+                    ...option.headers
+                };
+                ax.data = JSON.stringify({...option.data, filterModel: option.filterModel, sortModel: option.sortModel})
+                break
+
+            case 'post-file':
+                //TODO 刘壮. 上传文件
+                var forms = new FormData();
+                ax.headers = {
+                    'Content-Type': 'multipart/form-data',
+                    ...option.headers
+                };
+                _.forOwn(option.data, (value, key) => {
+                    if (key === 'files') {
+                        let i = 0;
+                        _.each(value, f => {
+                            // @ts-ignore
+                            forms.append('file' + (++i), f)
+                        })
+                    } else {
+                        forms.append(key, value)
+                    }
+                });
+                ax.data = forms;
+                ax.method = 'POST'
+                break
+
+            case "sql":
+                ax.url = sqlUrlTransform(option.url)
+                ax.method = 'POST';
+                ax.headers = {
+                    'Content-Type': 'application/json',
+                    ...option.headers
+                };
+                ax.data = JSON.stringify({
+                    args: [option.data],
+                    db: option.db,
+                    filterModel: option.filterModel,
+                    sortModel: option.sortModel
+                })
+                break
+
+            default:
+                throw new Error('not implements')
+        }
+
+        return new Promise<ApiResult>((resolver, reject) => {
+            axios(ax).then((resolverRaw: AxiosResponse<any>) => {
+                const apiResult: ApiResult = {
+                    rawData: resolverRaw.data,
+                    status: resolverRaw.status,
+                    success: (resolverRaw.data && resolverRaw.data.success),
+                    data: resolverRaw.data.data,
+                    pagination: resolverRaw.data.pagination,
+                    msg: (resolverRaw.data.msg),
+                    errors: resolverRaw.data.errors,
+                    headers: resolverRaw.headers
+                }
+                resolver(apiResult)
+
+            }).catch((reason: any) => {
+                reject(reason)
+            })
+        })
+    }
+}
+
+
+export function downLoad(downLoadUrl: string, filename: string, data: any, header: any, isJson: boolean = false) {
+    const YvanUI: any = _.get(window, 'YvanUI');
+    YvanUI.loading();
+    const createObjectURL = (object: any) => {
+        return (window.URL) ? window.URL.createObjectURL(object) : _.get(window, 'webkitURL').createObjectURL(object)
+    };
+
+    // const formData = new FormData();
+    // _.forOwn(data, (v, k) => {
+    //     formData.append(k, v);
+    // });
+    let formData = '';
+
+    const xhr = new XMLHttpRequest();
+    xhr.open('POST', downLoadUrl);
+    xhr.responseType = 'blob';
+    // xhr.setRequestHeader('Authorization', $.cookie('auth'))
+    if (isJson) {
+        formData = data ? JSON.stringify(data) : '';
+        xhr.setRequestHeader('Content-Type', 'application/json');
+    } else {
+        formData = data ? Qs.stringify(data) : '';
+        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+    }
+    //
+    if (header) {
+        _.forOwn(header, (v, k) => {
+            xhr.setRequestHeader(k, v);
+        })
+    }
+    xhr.onload = function (e: any) {
+        if (this.status === 200) {
+            const blob = this.response;
+            if (_.hasIn(window, 'navigator.msSaveOrOpenBlob')) {
+                navigator.msSaveBlob(blob, filename)
+                YvanUI.clearLoading();
+
+            } else {
+                const a = document.createElement('a')
+                const url = createObjectURL(blob)
+                a.href = url
+                a.download = filename
+                document.append(a)
+                a.click()
+                a.remove()
+                window.URL.revokeObjectURL(url)
+                YvanUI.clearLoading()
+            }
+        }
+    };
+    xhr.send(formData);
+}
+
+
+
+

+ 148 - 0
src/lib/config.ts

@@ -0,0 +1,148 @@
+import {VJson, VJsonProcess} from "../types";
+import {ApiFunction} from "./ajax";
+import _ from 'lodash'
+
+export type ServerInvokeUrlTransformFunction = (jsUrl: string) => string | undefined;
+
+export type SqlUrlTransformFunction = (jsUrl: string) => string | undefined;
+
+/**
+ * YvanUI 全局扩展配置
+ */
+export interface ConfigOption {
+
+    /**
+     * 是否在设计器模式
+     */
+    designMode: boolean
+
+    /**
+     * 扩展自定义的 vjson 运行时 修改
+     */
+    beforeResolverVJson: (module: any, vJson: VJson) => VJson;
+
+    /**
+     * 扩展自定义的 ajax 方法
+     */
+    ajax: ApiFunction;
+
+    /**
+     * serverJS Url转换为Ajax请求
+     */
+    serverInvokeUrlTransform: ServerInvokeUrlTransformFunction;
+
+    /**
+     * Sql Url转换为Ajax请求
+     */
+    sqlUrlTransform: SqlUrlTransformFunction;
+
+    /**
+     * 组件渲染过滤器, 如果方法返回 false 代表该组件不需要被渲染.
+     * 可以修改 vjson 的内容,但不能删除他
+     */
+    componentRenderFilter: (vjson: any) => undefined | boolean
+
+    /**
+     * 获取拼音首字母的函数
+     */
+    pinyinFunction: (py: string) => string
+
+    /**
+     * 需要添加的 VJsonProcess 处理过程
+     */
+    vjsonProcess: VJsonProcess
+}
+
+/**
+ * 全局 vjson 运行时
+ */
+export let beforeResolverVJson: (undefined | ((module: any, vJson: VJson) => VJson)) = undefined;
+
+/**
+ * 组件渲染过滤器, 如果方法返回 false 代表该组件不需要被渲染.
+ * 可以修改 vjson 的内容,但不能删除他
+ */
+export let componentRenderFilter: (undefined | ((vjson: any) => undefined | boolean)) = undefined;
+
+/**
+ * 全局 ajax 方法
+ */
+export const ajax: {
+    func?: ApiFunction
+} = {}
+
+/**
+ * 计算拼音的函数
+ */
+let pinyinFunc: Function
+
+export const vjsonProcessChain: VJsonProcess[] = []
+
+/**
+ * 添加一个 VJson 的处理过程
+ * @param func VJson 处理函数
+ */
+export function pushVJsonProcess(func: VJsonProcess) {
+    vjsonProcessChain.push(func)
+}
+
+let designMode: boolean = false
+
+export function isDesignMode(): boolean {
+    return designMode
+}
+
+export function setDesignMode(v: boolean) {
+    designMode = v
+}
+
+/**
+ * 将业务定义的 url 转换为调用服务端 groovy 的 url
+ */
+export function serverInvokeUrlTransform(url: string): string {
+    return _.get(window, '_YvanUI_serverInvokePrefix')(url);
+}
+
+/**
+ * 将业务定义的 url 转换为调用服务端 sql 的 Url
+ */
+export function sqlUrlTransform(url: string): string {
+    return _.get(window, '_YvanUI_sqlPrefix')(url);
+}
+
+/**
+ * YvanUI 全局扩展配置
+ */
+export function extend(option: Partial<ConfigOption>) {
+    if (option.beforeResolverVJson) {
+        beforeResolverVJson = option.beforeResolverVJson
+    }
+
+    if (option.ajax) {
+        ajax.func = option.ajax
+    }
+
+    if (option.serverInvokeUrlTransform) {
+        _.extend(window, {_YvanUI_serverInvokePrefix: option.serverInvokeUrlTransform});
+    }
+
+    if (option.sqlUrlTransform) {
+        _.extend(window, {_YvanUI_sqlPrefix: option.sqlUrlTransform});
+    }
+
+    if (option.componentRenderFilter) {
+        componentRenderFilter = option.componentRenderFilter
+    }
+
+    if (option.pinyinFunction) {
+        pinyinFunc = option.pinyinFunction
+    }
+
+    if (typeof option.designMode !== 'undefined') {
+        setDesignMode(option.designMode)
+    }
+
+    if (option.vjsonProcess) {
+        pushVJsonProcess(option.vjsonProcess)
+    }
+}

+ 23 - 0
src/lib/lib.ts

@@ -0,0 +1,23 @@
+import _ from 'lodash'
+import {FunctionRegiste} from "../types";
+
+export const LibList: FunctionRegiste[] = []
+
+export function Lib(registe: FunctionRegiste) {
+    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
+        LibList.push({
+            ...registe,
+            name: propertyKey,
+            target: target[propertyKey]
+        })
+
+        if (registe.type === 'system') {
+            _.set(window, 'system.' + propertyKey, target[propertyKey])
+
+        } else if (registe.type === 'format') {
+            _.set(window, 'format.' + propertyKey, target[propertyKey])
+        }
+
+        return target
+    }
+}

+ 102 - 0
src/types.ts

@@ -0,0 +1,102 @@
+export interface PlainObject {
+    [propsName: string]: any;
+}
+
+export interface VJson {
+    [propName: string]: any;
+}
+
+/**
+ * 处理 VJson 的
+ * @param this 是指 scope 对象
+ * @param vjson 是待处理的 VJson 对象
+ * @result 返回处理 VJson 的 Promise 异步结果
+ */
+export type VJsonProcess = (this: any, vjson: VJson) => Promise<VJson>
+
+/**
+ * 注册函数中的参数
+ */
+export interface FunctionArgument {
+    /**
+     * 列举类型
+     */
+    type: 'module' | 'control'
+
+    /**
+     * 参数中文说明
+     */
+    title: string
+
+    /**
+     * 参数名称
+     */
+    name: string
+
+    /**
+     * 过滤器
+     * @param list
+     */
+    filter?: (items: any[]) => any[]
+}
+
+/**
+ * 注册函数, 用在 @Lib 装饰器
+ */
+export interface FunctionRegiste {
+    /**
+     * 函数名
+     */
+    name?: string
+
+    /**
+     * 中文说明
+     */
+    title?: string
+
+    /**
+     * 作者
+     */
+    author?: string
+
+    /**
+     * 创建时间
+     */
+    createAt?: string
+
+    /**
+     * 更新时间
+     */
+    updateAt?: string
+
+    /**
+     * 备注
+     */
+    remark?: string
+
+    /**
+     * 类型
+     * system: 系统方法
+     * format: 格式化作用
+     * ajaxMethod: AjaxMethod
+     * meta: 元数据
+     */
+    type: 'system' | 'format' | 'ajaxMethod' | 'meta'
+
+    /**
+     * 分类名称
+     */
+    category?: string
+
+    args?: FunctionArgument[]
+
+    /**
+     * 文档链接
+     */
+    link?: string
+
+    /**
+     * 方法体
+     */
+    target?: Function
+}

+ 1 - 1
src/utils.ts

@@ -5,4 +5,4 @@ export function invokeMethod(fn: any, sender: any, args: any) {
     if (typeof fn === 'function') {
         fn.apply(sender, ...args)
     }
-}
+}