first commit

This commit is contained in:
lingxiao865
2026-02-10 08:05:03 +08:00
commit c5af079d8c
1094 changed files with 97530 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
const defaultOption = {
valueKey: 'value',
defaultValueKey: 'defaultValue',
changeEventName: 'change',
strict: true,
};
/**
* 受控函数
* 用法示例:
* {
* attached() {
* this.control = useControl.call(this);
* }
* }
* 注意事项:
* 1命名规范约束value等命名一般不需要改。内部属性统一命名以_开头。
* 2value默认值小程序number类型未传值undefined会初始化为0导致无法判断。建议默认值设置为null
* 3prop变化需要开发者自己监听observers = { value(val):{ this.control.set(val) } }
* @param this 页面实例
* @param option 配置项 参见ControlOption
* @returns
*/
function useControl(option) {
const { valueKey, defaultValueKey, changeEventName, strict } = {
...defaultOption,
...option,
};
const props = this.properties || {};
const value = props[valueKey];
// 半受控时不需要defaultValueKey默认值与valueKey相同
const defaultValue = props[strict ? defaultValueKey : valueKey];
let controlled = false;
// 完全受控模式:检查受控属性,判断是否受控
if (strict && typeof value !== 'undefined' && value !== null) {
controlled = true;
}
const set = (newVal, extObj, fn) => {
this.setData(
{
[`_${valueKey}`]: newVal,
...extObj,
},
fn,
);
};
return {
controlled,
initValue: controlled ? value : defaultValue,
set,
get: () => this.data[`_${valueKey}`],
change: (newVal, customChangeData, customUpdateFn) => {
this.$emit(changeEventName, typeof customChangeData !== 'undefined' ? customChangeData : newVal);
// 完全受控模式使用了受控属性必须配合change事件来更新
if (controlled) {
return;
}
if (typeof customUpdateFn === 'function') {
customUpdateFn();
} else {
set(newVal);
}
},
};
}
export { useControl };

View File

@@ -0,0 +1,99 @@
import { isObject } from '../validator';
/** ****************************************************************
MIT License https://github.com/qiu8310/minapp/blob/v1.0.0-alpha.1/packages/minapp-core/src/system/util/object.ts
Author Mora <qiuzhongleiabc@126.com> (https://github.com/qiu8310)
****************************************************************** */
export const getPrototypeOf = function (obj) {
return Object.getPrototypeOf ? Object.getPrototypeOf(obj) : obj.__proto__;
};
/**
* 遍历继承关系类的 prototype
*
* @export
* @param {Function} callback - 回调函数,函数参数是遍历的每个实例的 prototype函数如果返回 false会终止遍历
* @param {any} fromCtor - 要遍历的起始 class 或 prototype
* @param {any} toCtor - 要遍历的结束 class 或 prototype
* @param {boolean} [includeToCtor=true] - 是否要包含结束 toCtor 本身
*
* @example
* A -> B -> C
*
* 在 C 中调用: iterateInheritedPrototype(fn, A, C, true)
* 则fn 会被调用三次,分别是 fn(A.prototype) fn(B.prototype) fn(C.prototype)
*/
export const iterateInheritedPrototype = function iterateInheritedPrototype(
callback,
fromCtor,
toCtor,
includeToCtor = true,
) {
let proto = fromCtor.prototype || fromCtor;
const toProto = toCtor.prototype || toCtor;
while (proto) {
if (!includeToCtor && proto === toProto) break;
if (callback(proto) === false) break;
if (proto === toProto) break;
proto = getPrototypeOf(proto);
}
};
/**
*
* 将一个可能包含原型链的对象扁平化成单个对象
*
* 如,现有这样的类的继承关系 A -> B -> C当创建一个实例 a = new A() 时
*
* a 实例会存有 B、C 的原型链,使用此函数 newa = toObject(a) 之后,
* newa 就会变成一个 PlainObject但它有 A、B、C 上的所有属性和方法,
* 当然不包括静态属性或方法
*
* 注意1用此方法的话尽量避免在类中使用胖函数胖函数的 this 死死的绑定
* 在原对象中,无法重新绑定
*
* 注意2类继承的时候不要在函数中调用 supertoObject 之后是扁平的,没有 super 之说
*/
export const toObject = function toObject(
something,
options = {},
) {
const obj = {};
if (!isObject(something)) return obj;
const excludes = options.excludes || ['constructor'];
const { enumerable = true, configurable = 0, writable = 0 } = options;
const defaultDesc = {};
if (enumerable !== 0) defaultDesc.enumerable = enumerable;
if (configurable !== 0) defaultDesc.configurable = configurable;
if (writable !== 0) defaultDesc.writable = writable;
iterateInheritedPrototype(
(proto) => {
Object.getOwnPropertyNames(proto).forEach((key) => {
if (excludes.indexOf(key) >= 0) return;
if (Object.prototype.hasOwnProperty.call(obj, key)) return;
const desc = Object.getOwnPropertyDescriptor(proto, key);
const fnKeys = ['get', 'set', 'value'];
fnKeys.forEach((k) => {
if (typeof desc[k] === 'function') {
const oldFn = desc[k];
desc[k] = function (...args) {
return oldFn.apply(Object.prototype.hasOwnProperty.call(options, 'bindTo') ? options.bindTo : this, args);
};
}
});
Object.defineProperty(obj, key, { ...desc, ...defaultDesc });
});
},
something,
options.till || Object,
false,
);
return obj;
};

View File

@@ -0,0 +1,4 @@
export * from './superComponent';
export * from './flatTool';
export * from './instantiationDecorator';
export * from './control';

View File

@@ -0,0 +1,251 @@
/* eslint-disable no-param-reassign */
import { isPlainObject } from '../validator';
import { canUseVirtualHost } from '../version';
import { toCamel, toPascal, hyphenate } from '../utils';
const getInnerControlledValue = key => `data${key.replace(/^(\w)/, (e, t) => t.toUpperCase())}`;
const getDefaultKey = key => `default${key.replace(/^(\w)/, (e, t) => t.toUpperCase())}`;
const ARIAL_PROPS = [
{ key: 'ariaHidden', type: Boolean },
{ key: 'ariaRole', type: String },
{ key: 'ariaLabel', type: String },
{ key: 'ariaLabelledby', type: String },
{ key: 'ariaDescribedby', type: String },
{ key: 'ariaBusy', type: Boolean },
];
const getPropsDefault = (type, disableBoolean = false) => {
if (type === Boolean && !disableBoolean) {
return false;
}
if (type === String) {
return '';
}
return undefined;
};
const COMMON_PROPS = {
...ARIAL_PROPS.reduce((acc, item) => ({
...acc,
[item.key]: {
type: item.type,
default: getPropsDefault(item.type),
},
}), {}),
customStyle: { type: [String, Object], default: '' },
};
export const toComponent = function (options) {
if (!options.properties && options.props) {
options.properties = options.props;
}
if (options.properties) {
Object.keys(options.properties).forEach((k) => {
let opt = options.properties[k];
// 如果不是 Object 类型,则默认指定 type = options.properties[k]
if (!isPlainObject(opt)) {
opt = { type: opt };
}
options.properties[k] = opt;
});
}
if (!options.methods) options.methods = {};
if (!options.lifetimes) options.lifetimes = {};
const oldCreated = options.created;
const { controlledProps = [] } = options;
options.created = function (...args) {
if (oldCreated) {
oldCreated.apply(this, args);
}
controlledProps.forEach(({ key }) => {
const defaultKey = getDefaultKey(key);
const tDataKey = getInnerControlledValue(key);
this[tDataKey] = this[key];
if (this[key] == null) {
this._selfControlled = true;
}
if (this[key] == null && this[defaultKey] != null) {
this[tDataKey] = this[defaultKey];
}
});
};
options.methods._trigger = function (evtName, detail, opts) {
const target = controlledProps.find(item => item.event === evtName);
if (target) {
const { key } = target;
if (this._selfControlled) {
const tDataKey = getInnerControlledValue(key);
this[tDataKey] = detail[key];
}
this.$emit(
`update:${key}`,
detail[key],
opts,
);
}
this.$emit(
evtName,
detail,
opts,
);
};
return options;
};
/**
* 将一个继承了 BaseComponent 的类转化成 小程序 Component 的调用
* 根据最新的微信 d.ts 描述文件Component 在实例化的时候,会忽略不支持的自定义属性
*/
export const wxComponent = function wxComponent() {
return function (baseConstructor) {
class WxComponent extends baseConstructor {
// 暂时移除了冗余的代码,后续补充
}
const current = new WxComponent();
current.options = current.options || {};
// 所有组件默认都开启css作用域
// 写到这里是为了防止组件设置 options 时无意覆盖掉了 addGlobalClass
// 使用 "styleIsolation": "apply-shared" 代替 addGlobalClass: truesee https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/glass-easel/migration.html#JSON-%E9%85%8D%E7%BD%AE
// if (current.options.addGlobalClass === undefined) {
// current.options.addGlobalClass = true;
// }
if (canUseVirtualHost() && current.options.virtualHost == null) {
current.options.virtualHost = true;
}
const obj = toComponent(current);
return obj;
};
};
function sortPropsType(type) {
if (!Array.isArray(type)) {
return type;
}
type.sort((a, b) => {
if (a === Boolean) {
return -1;
}
if (b === Boolean) {
return 1;
}
return 0;
});
return type;
}
function filterProps(props, controlledProps) {
const newProps = {};
const emits = [];
const reg = /^on[A-Z][a-z]/;
const controlledKeys = Object.values(controlledProps).map(item => item.key);
const unControlledKeys = controlledKeys.map(key => getDefaultKey(key));
Object.keys(props).forEach((key) => {
const curType = props[key].type || props[key];
if (reg.test(key) && props[key].type === Function) {
const str = key.replace(/^on/, '');
const eventName = str.charAt(0).toLowerCase() + str.slice(1);
emits.push(...[hyphenate(eventName), eventName]);
} else if (controlledKeys.indexOf(key) > -1
|| unControlledKeys.indexOf(key) > -1
) {
const newType = Array.isArray(curType) ? curType : [curType];
newProps[key] = {
type: [null, ...newType],
default: null,
};
} else if (
[Boolean, String].indexOf(props[key].type) > -1
&& props[key].default === undefined
) {
newProps[key] = {
...props[key],
default: getPropsDefault(props[key].type, true),
};
} else {
newProps[key] = {
...(typeof props[key] === 'object' ? props[key] : {}),
type: sortPropsType(curType),
};
}
});
return {
newProps,
emits,
};
}
const getEmitsByControlledProps = controlledProps => Object.values(controlledProps).map(item => `update:${item.key}`);
export const uniComponent = function (info) {
const { newProps, emits } = filterProps(info.props || {}, info.controlledProps || {});
info.props = {
...getExternalClasses(info),
...newProps,
...COMMON_PROPS,
};
info.emits = Array.from(new Set([
...(info.emits || []),
...(getEmitsByControlledProps(info.controlledProps || {})),
...emits,
]));
info.options = {
...(info.options || {}),
multipleSlots: true,
};
if (canUseVirtualHost() && info.options.virtualHost == null) {
info.options.virtualHost = true;
}
if (!info.options.styleIsolation) {
info.options.styleIsolation = 'shared';
}
if (info.name) {
info.name = toPascal(info.name);
}
const obj = toComponent(info);
return obj;
};
export function getExternalClasses(info) {
if (!info.externalClasses) {
return {};
}
const { externalClasses } = info;
const list = Array.isArray(externalClasses) ? externalClasses : [externalClasses];
return list.reduce((acc, item) => ({
...acc,
[toCamel(item)]: {
type: String,
default: '',
},
}), {});
}

View File

@@ -0,0 +1,5 @@
export class SuperComponent {
constructor() {
this.app = getApp();
}
}