默认移除 iot、cloud、design 模块,将以其他方式按需引入

This commit is contained in:
神仙都没用 2024-03-06 15:49:32 +08:00
parent d783f473f9
commit 25ec0b9cad
45 changed files with 28 additions and 4749 deletions

View File

@ -26,7 +26,6 @@
"mitt": "^3.0.1",
"mockjs": "^1.1.0",
"monaco-editor": "0.36.0",
"mqtt": "^4.3.7",
"nprogress": "^0.2.0",
"pinia": "^2.1.7",
"socket.io-client": "^4.7.2",

View File

@ -11,7 +11,10 @@ export default defineComponent({
type: null,
default: UserFilled
},
size: [String, Number] as PropType<"large" | "default" | "small" | number>,
size: {
type: [String, Number] as PropType<"large" | "default" | "small" | number>,
default: 40
},
shape: {
type: String as PropType<"circle" | "square">,
default: "square"
@ -24,12 +27,18 @@ export default defineComponent({
setup(props) {
return () => {
const height = props.size + "px";
return (
<div
class="cl-avatar"
style={{
height
}}
>
<el-avatar
style={{
display: "block",
margin: "auto",
height: props.size + "px",
height,
width: props.size + "px"
}}
{...{
@ -37,6 +46,7 @@ export default defineComponent({
src: props.modelValue || props.src
}}
/>
</div>
);
};
}

View File

@ -2,7 +2,6 @@
<div
class="cl-image"
:style="{
justifyContent: justify,
height: style.h
}"
>
@ -50,10 +49,6 @@ export default defineComponent({
fit: {
type: String as PropType<"" | "contain" | "cover" | "none" | "fill" | "scale-down">,
default: "cover"
},
justify: {
type: String,
default: "center"
}
},

View File

@ -1,123 +0,0 @@
<template>
<cl-dialog v-model="visible" :title="title" width="1200px">
<cl-crud ref="Crud" padding="0">
<cl-row>
<!-- 刷新按钮 -->
<cl-refresh-btn />
</cl-row>
<cl-row>
<!-- 数据表格 -->
<cl-table ref="Table" />
</cl-row>
<cl-row>
<cl-flex1 />
<!-- 分页控件 -->
<cl-pagination />
</cl-row>
</cl-crud>
</cl-dialog>
</template>
<script lang="ts" setup>
import { useCrud, useTable } from "@cool-vue/crud";
import { nextTick, ref } from "vue";
import { LogType } from "../dict";
import { useCool } from "/@/cool";
const { service } = useCool();
//
const visible = ref(false);
//
const title = ref("");
// cl-table
const Table = useTable({
autoHeight: false,
columns: [
{
label: "#",
type: "index"
},
{
label: "请求",
prop: "request",
minWidth: 150,
component: {
name: "cl-code-json",
props: {
popover: true
}
}
},
{
label: "耗时",
prop: "time",
minWidth: 100,
formatter(row) {
return row.time + "ms";
}
},
{
label: "结果",
prop: "result",
minWidth: 150,
component: {
name: "cl-code-json",
props: {
popover: true
}
}
},
{
label: "类型",
prop: "type",
minWidth: 100,
dict: LogType
},
{
label: "异常信息",
prop: "error",
minWidth: 150,
showOverflowTooltip: true
},
{
label: "执行时间",
prop: "createTime",
minWidth: 160,
sortable: "desc"
}
]
});
// cl-crud
const Crud = useCrud({
service: service.cloud.func.log
});
//
function open(data: Eps.CloudFuncInfoEntity) {
visible.value = true;
title.value = `日志列表(${data.name}`;
nextTick(() => {
Crud.value?.refresh({
page: 1,
infoId: data.id
});
});
}
//
function close() {
visible.value = false;
}
defineExpose({
open,
close
});
</script>

View File

@ -1,21 +0,0 @@
import type { ModuleConfig } from "/@/cool";
import { CodeDeclare } from "./dict";
import { addDeclare } from "/@/plugins/editor-monaco";
export default (): ModuleConfig => {
return {
views: [
{
path: "/cloud/func/dev",
meta: {
label: "云函数开发"
},
component: () => import("./views/func/dev.vue")
}
],
onLoad() {
addDeclare(CodeDeclare);
}
};
};

View File

@ -1,807 +0,0 @@
export const CodeSnippets = {
db: `import { BaseEntity } from '@cool-midway/core';
import { Column, Entity } from 'typeorm';
/**
*
*/
@Entity('xxx_xxx')
export class xxxEntity extends BaseEntity {
@Column({ comment: 'xxx' })
xxx: string;
}`,
func: `import { CloudCrud } from '@cool-midway/cloud';
/**
*
*/
export class Xxx extends CloudCrud {
async main() {
this.setCurdOption({
entity: 'xxx',
api: ['add', 'delete', 'update', 'info', 'list', 'page']
});
}
}`,
req: `{
"method": "xxx",
"params": {
}
}`
};
export const CodeDeclare = `
declare module 'typeorm' {
export declare function Entity(options?: EntityOptions): ClassDecorator;
export declare function Entity(name?: string, options?: EntityOptions): ClassDecorator;
export interface EntityOptions {
/**
* Table name.
* If not specified then naming strategy will generate table name from entity name.
*/
name?: string;
/**
* Specifies a default order by used for queries from this table when no explicit order by is specified.
*/
orderBy?: OrderByCondition | ((object: any) => OrderByCondition | any);
/**
* Table's database engine type (like "InnoDB", "MyISAM", etc).
* It is used only during table creation.
* If you update this value and table is already created, it will not change table's engine type.
* Note that not all databases support this option.
*/
engine?: string;
/**
* Database name. Used in Mysql and Sql Server.
*/
database?: string;
/**
* Schema name. Used in Postgres and Sql Server.
*/
schema?: string;
/**
* Indicates if schema synchronization is enabled or disabled for this entity.
* If it will be set to false then schema sync will and migrations ignore this entity.
* By default schema synchronization is enabled for all entities.
*/
synchronize?: boolean;
/**
* If set to 'true' this option disables Sqlite's default behaviour of secretly creating
* an integer primary key column named 'rowid' on table creation.
* @see https://www.sqlite.org/withoutrowid.html.
*/
withoutRowid?: boolean;
}
export declare function Column(options: ColumnOptions): PropertyDecorator;
/**
* Describes all column's options.
*/
export interface ColumnOptions extends ColumnCommonOptions {
/**
* Column type. Must be one of the value from the ColumnTypes class.
*/
type?: ColumnType;
/**
* Column name in the database.
*/
name?: string;
/**
* Column type's length. Used only on some column types.
* For example type = "string" and length = "100" means that ORM will create a column with type varchar(100).
*/
length?: string | number;
/**
* Column type's display width. Used only on some column types in MySQL.
* For example, INT(4) specifies an INT with a display width of four digits.
*/
width?: number;
/**
* Indicates if column's value can be set to NULL.
* Default value is "false".
*/
nullable?: boolean;
/**
* Indicates if column value is not updated by "save" operation.
* It means you'll be able to write this value only when you first time insert the object.
* Default value is "false".
*
* @deprecated Please use the \`update\` option instead. Careful, it takes
* the opposite value to readonly.
*
*/
readonly?: boolean;
/**
* Indicates if column value is updated by "save" operation.
* If false, you'll be able to write this value only when you first time insert the object.
* Default value is "true".
*/
update?: boolean;
/**
* Indicates if column is always selected by QueryBuilder and find operations.
* Default value is "true".
*/
select?: boolean;
/**
* Indicates if column is inserted by default.
* Default value is "true".
*/
insert?: boolean;
/**
* Default database value.
*/
default?: any;
/**
* ON UPDATE trigger. Works only for MySQL.
*/
onUpdate?: string;
/**
* Indicates if this column is a primary key.
* Same can be achieved when @PrimaryColumn decorator is used.
*/
primary?: boolean;
/**
* Specifies if column's value must be unique or not.
*/
unique?: boolean;
/**
* Column comment. Not supported by all database types.
*/
comment?: string;
/**
* The precision for a decimal (exact numeric) column (applies only for decimal column), which is the maximum
* number of digits that are stored for the values.
*/
precision?: number | null;
/**
* The scale for a decimal (exact numeric) column (applies only for decimal column), which represents the number
* of digits to the right of the decimal point and must not be greater than precision.
*/
scale?: number;
/**
* Puts ZEROFILL attribute on to numeric column. Works only for MySQL.
* If you specify ZEROFILL for a numeric column, MySQL automatically adds the UNSIGNED attribute to this column
*/
zerofill?: boolean;
/**
* Puts UNSIGNED attribute on to numeric column. Works only for MySQL.
*/
unsigned?: boolean;
/**
* Defines a column character set.
* Not supported by all database types.
*/
charset?: string;
/**
* Defines a column collation.
*/
collation?: string;
/**
* Array of possible enumerated values.
*/
enum?: (string | number)[] | Object;
/**
* Exact name of enum
*/
enumName?: string;
/**
* If this column is primary key then this specifies the name for it.
*/
primaryKeyConstraintName?: string;
/**
* If this column is foreign key then this specifies the name for it.
*/
foreignKeyConstraintName?: string;
/**
* Generated column expression.
*/
asExpression?: string;
/**
* Generated column type.
*/
generatedType?: "VIRTUAL" | "STORED";
/**
* Identity column type. Supports only in Postgres 10+.
*/
generatedIdentity?: "ALWAYS" | "BY DEFAULT";
/**
* Return type of HSTORE column.
* Returns value as string or as object.
*/
hstoreType?: "object" | "string";
/**
* Indicates if this column is an array.
* Can be simply set to true or array length can be specified.
* Supported only by postgres.
*/
array?: boolean;
/**
* Specifies a value transformer that is to be used to (un)marshal
* this column when reading or writing to the database.
*/
transformer?: ValueTransformer | ValueTransformer[];
/**
* Spatial Feature Type (Geometry, Point, Polygon, etc.)
*/
spatialFeatureType?: string;
/**
* SRID (Spatial Reference ID (EPSG code))
*/
srid?: number;
}
export declare function Index(options?: IndexOptions): ClassDecorator & PropertyDecorator;
export interface IndexOptions {
/**
* Indicates if this composite index must be unique or not.
*/
unique?: boolean;
/**
* The SPATIAL modifier indexes the entire column and does not allow indexed columns to contain NULL values.
* Works only in MySQL and PostgreSQL.
*/
spatial?: boolean;
/**
* The FULLTEXT modifier indexes the entire column and does not allow prefixing.
* Works only in MySQL.
*/
fulltext?: boolean;
/**
* NULL_FILTERED indexes are particularly useful for indexing sparse columns, where most rows contain a NULL value.
* In these cases, the NULL_FILTERED index can be considerably smaller and more efficient to maintain than
* a normal index that includes NULL values.
*
* Works only in Spanner.
*/
nullFiltered?: boolean;
/**
* Fulltext parser.
* Works only in MySQL.
*/
parser?: string;
/**
* Index filter condition.
*/
where?: string;
/**
* If true, the index only references documents with the specified field.
* These indexes use less space but behave differently in some situations (particularly sorts).
* This option is only supported for mongodb database.
*/
sparse?: boolean;
/**
* Builds the index in the background so that building an index an does not block other database activities.
* This option is only supported for mongodb database.
*/
background?: boolean;
/**
* Specifies a time to live, in seconds.
* This option is only supported for mongodb database.
*/
expireAfterSeconds?: number;
}
}
declare module '@cool-midway/core' {
export declare abstract class BaseEntity extends CoolBaseEntity {
id: number;
createTime: Date;
updateTime: Date;
}
}
declare module '@cool-midway/cloud' {
export type ApiTypes = "add" | "delete" | "update" | "page" | "info" | "list";
// Crud配置
export interface CurdOption {
// 路由前缀不配置默认是按Controller下的文件夹路径
prefix?: string;
// curd api接口
api: ApiTypes[];
// 分页查询配置
pageQueryOp?: QueryOp | Function;
// 非分页查询配置
listQueryOp?: QueryOp | Function;
// 插入参数
insertParam?: Function;
// 操作之前
before?: Function;
// info 忽略返回属性
infoIgnoreProperty?: string[];
// 实体
entity: any;
// 服务
service?: any;
// api标签
urlTag?: {
name: "ignoreToken" | string;
url: ApiTypes[];
};
}
export interface JoinOp {
// 实体
entity: any;
// 别名
alias: string;
// 关联条件
condition: string;
// 关联类型
type?: "innerJoin" | "leftJoin";
}
// 字段匹配
export interface FieldEq {
// 字段
column: string;
// 请求参数
requestParam: string;
}
// 查询配置
export interface QueryOp {
// 需要模糊查询的字段
keyWordLikeFields?: string[];
// 查询条件
where?: Function;
// 查询字段
select?: string[];
// 字段相等
fieldEq?: string[] | FieldEq[];
// 添加排序条件
addOrderBy?: {};
// 关联配置
join?: JoinOp[];
// 其他条件
extend?: Function;
}
// Controller 配置
export interface ControllerOption {
// crud配置 如果是字符串则为路由前缀不配置默认是按Controller下的文件夹路径
curdOption?: CurdOption & string;
// 路由配置
routerOptions?: {
// 是否敏感
sensitive?: boolean;
// 路由中间件
middleware?: MiddlewareParamArray;
// 别名
alias?: string[];
// 描述
description?: string;
// 标签名称
tagName?: string;
};
}
/**
*
*/
export interface ModuleConfig {
/** 名称 */
name: string;
/** 描述 */
description: string;
/** 模块中间件 */
middlewares?: MiddlewareParamArray;
/** 全局中间件 */
globalMiddlewares?: MiddlewareParamArray;
/** 模块加载顺序默认为0值越大越优先加载 */
order?: number;
}
export interface CoolConfig {
/** 是否自动导入数据库 */
initDB?: boolean;
/** crud配置 */
crud?: {
/** 软删除 */
softDelete: boolean;
/** 分页查询每页条数 */
pageSize: number;
};
/** elasticsearch配置 */
es?: {
nodes: string[];
};
/** pay */
pay?: {
/** 微信支付 */
wx?: CoolWxPayConfig;
/** 支付宝支付 */
ali?: CoolAliPayConfig;
};
/** rpc */
rpc?: CoolRpcConfig;
/** redis */
redis?: RedisConfig | RedisConfig[];
/** 文件上传 */
file?: {
/** 上传模式 */
mode: MODETYPE;
/** 本地上传 文件地址前缀 */
domain?: string;
/** oss */
oss?: OSSConfig;
/** cos */
cos?: COSConfig;
/** qiniu */
qiniu?: QINIUConfig;
};
/** IOT 配置 */
iot: CoolIotConfig;
}
export interface CoolRpcConfig {
/** 服务名称 */
name: string;
/** redis */
redis: RedisConfig & RedisConfig[] & unknown;
}
export interface RedisConfig {
/** host */
host: string;
/** password */
password: string;
/** port */
port: number;
/** db */
db: number;
}
export declare enum MODETYPE {
/** 本地 */
LOCAL = "local",
/** 云存储 */
CLOUD = "cloud",
/** 其他 */
OTHER = "other"
}
export declare enum CLOUDTYPE {
/** 阿里云存储 */
OSS = "oss",
/** 腾讯云存储 */
COS = "cos",
/** 七牛云存储 */
QINIU = "qiniu"
}
/**
*
*/
export interface Mode {
/** 模式 */
mode: MODETYPE;
/** 类型 */
type: string;
}
/**
*
*/
export interface CoolFileConfig {
/** 上传模式 */
mode: MODETYPE;
/** 阿里云oss 配置 */
oss: OSSConfig;
/** 腾讯云 cos配置 */
cos: COSConfig;
/** 七牛云 配置 */
qiniu: QINIUConfig;
/** 文件前缀 */
domain: string;
}
/**
* OSS
*/
export interface OSSConfig {
/** 阿里云accessKeyId */
accessKeyId: string;
/** 阿里云accessKeySecret */
accessKeySecret: string;
/** 阿里云oss的bucket */
bucket: string;
/** 阿里云oss的endpoint */
endpoint: string;
/** 阿里云oss的timeout */
timeout: string;
/** 签名失效时间,毫秒 */
expAfter?: number;
/** 文件最大的 size */
maxSize?: number;
}
/**
* COS
*/
export interface COSConfig {
/** 腾讯云accessKeyId */
accessKeyId: string;
/** 腾讯云accessKeySecret */
accessKeySecret: string;
/** 腾讯云cos的bucket */
bucket: string;
/** 腾讯云cos的区域 */
region: string;
/** 腾讯云cos的公网访问地址 */
publicDomain: string;
/** 上传持续时间 */
durationSeconds?: number;
/** 允许操作(上传)的对象前缀 */
allowPrefix?: string;
/** 密钥的权限列表 */
allowActions?: string[];
}
export interface QINIUConfig {
/** 七牛云accessKeyId */
accessKeyId: string;
/** 七牛云accessKeySecret */
accessKeySecret: string;
/** 七牛云cos的bucket */
bucket: string;
/** 七牛云cos的区域 */
region: string;
/** 七牛云cos的公网访问地址 */
publicDomain: string;
/** 上传地址 */
uploadUrl?: string;
/** 上传fileKey */
fileKey?: string;
}
/**
*
*/
export interface CoolWxPayConfig {
/** 直连商户申请的公众号或移动应用appid。 */
appid: string;
/** 商户号 */
mchid: string;
/** 可选参数 证书序列号 */
serial_no?: string;
/** 回调链接 */
notify_url: string;
/** 公钥 */
publicKey: Buffer;
/** 私钥 */
privateKey: Buffer;
/** 可选参数 认证类型目前为WECHATPAY2-SHA256-RSA2048 */
authType?: string;
/** 可选参数 User-Agent */
userAgent?: string;
/** 可选参数 APIv3密钥 */
key?: string;
}
/**
*
*/
export interface CoolAliPayConfig {
/** 支付回调地址 */
notifyUrl: string;
/** 应用ID */
appId: string;
/**
*
* RSA签名验签工具https://docs.open.alipay.com/291/106097
* PKCS1(JAVA适用)
*/
privateKey: string;
/** 签名类型 */
signType?: "RSA2" | "RSA";
/** 支付宝公钥(需要对返回值做验签时候必填) */
alipayPublicKey?: string;
/** 网关 */
gateway?: string;
/** 网关超时时间(单位毫秒,默认 5s */
timeout?: number;
/** 是否把网关返回的下划线 key 转换为驼峰写法 */
camelcase?: boolean;
/** 编码(只支持 utf-8 */
charset?: "utf-8";
/** api版本 */
version?: "1.0";
urllib?: any;
/** 指定private key类型, 默认: PKCS1, PKCS8: PRIVATE KEY, PKCS1: RSA PRIVATE KEY */
keyType?: "PKCS1" | "PKCS8";
/** 应用公钥证书文件路径 */
appCertPath?: string;
/** 应用公钥证书文件内容 */
appCertContent?: string | Buffer;
/** 应用公钥证书sn */
appCertSn?: string;
/** 支付宝根证书文件路径 */
alipayRootCertPath?: string;
/** 支付宝根证书文件内容 */
alipayRootCertContent?: string | Buffer;
/** 支付宝根证书sn */
alipayRootCertSn?: string;
/** 支付宝公钥证书文件路径 */
alipayPublicCertPath?: string;
/** 支付宝公钥证书文件内容 */
alipayPublicCertContent?: string | Buffer;
/** 支付宝公钥证书sn */
alipayCertSn?: string;
/** AES密钥调用AES加解密相关接口时需要 */
encryptKey?: string;
/** 服务器地址 */
wsServiceUrl?: string;
}
/**
* IOT配置
*/
export interface CoolIotConfig {
/** MQTT服务端口 */
port: number;
/** MQTT Websocket服务端口 */
wsPort: number;
/** redis 配置 mqtt cluster下必须要配置 */
redis?: {
/** host */
host: string;
/** port */
port: number;
/** password */
password: string;
/** db */
db: number;
};
/** 发布消息配置 */
publish?: PublishPacket;
/** 认证 */
auth?: {
/** 用户 */
username: string;
/** 密码 */
password: string;
};
/** 服务配置 */
serve?: AedesOptions;
}
export declare abstract class CloudCrud {
ctx: any;
curdOption: CurdOption;
coolCloudDb: CoolCloudDb;
coolConfig: CoolConfig;
entity: Repository<any>;
app: IMidwayApplication;
req: CloudReq;
coolEventManager: CoolEventManager;
protected sqlParams: any;
setCurdOption(curdOption: CurdOption): void;
/**
*
* @param entityModel
*/
setEntity(): Promise<void>;
abstract main(req: CloudReq): Promise<void>;
init(req: CloudReq): Promise<void>;
/**
*
* @param params
*/
paramSafetyCheck(params: any): Promise<boolean>;
/**
*
* @param query
* @param option
*/
list(query: any): Promise<any>;
/**
* SQL并获得分页数据
* @param sql sql语句
* @param query
* @param autoSort
*/
sqlRenderPage(sql: any, query: any, autoSort?: boolean): Promise<{
list: any;
pagination: {
page: number;
size: number;
total: number;
};
}>;
/**
*
* @param connectionName
*/
page(query: any): Promise<{
list: any;
pagination: {
page: number;
size: number;
total: number;
};
}>;
/**
* SQL
* @param sql
*/
getCountSql(sql: any): string;
/**
* entity获得分页数据sql
* @param find QueryBuilder
* @param query
* @param autoSort
* @param connectionName
*/
entityRenderPage(find: SelectQueryBuilder<any>, query: any, autoSort?: boolean): Promise<{
list: any[];
pagination: {
page: number;
size: number;
total: number;
};
}>;
/**
*
* @param sort
* @returns
*/
private checkSort;
/**
*
* @param sql
* @param params
*/
nativeQuery(sql: any, params?: any): Promise<any>;
/**
* ORM管理
* @param connectionName
*/
getOrmManager(): import("../db/source").CoolDataSource;
private before;
/**
*
* @param curdOption
*/
private insertParam;
/**
* ||
* @param data
*/
modifyAfter(data: any, type: 'delete' | 'update' | 'add'): Promise<void>;
/**
* ||
* @param data
*/
modifyBefore(data: any, type: 'delete' | 'update' | 'add'): Promise<void>;
/**
*
* @param param
* @returns
*/
add(param: any): Promise<{
id: any;
}>;
/**
* |
* @param param
*/
addOrUpdate(param: any | any[]): Promise<void>;
/**
*
* @param ids ID集合 [1,2,3] 1,2,3
*/
delete(ids: any): Promise<void>;
/**
*
* @param ids ID数组
* @param entity
*/
softDelete(ids: string[], entity?: Repository<any>, userId?: string): Promise<void>;
/**
*
* @param param
*/
update(param: any): Promise<void>;
/**
* ID
* @param id ID
*/
info(id: any): Promise<any>;
/**
*
* @param query
* @param option
*/
private getOptionFind;
}
}
`;

View File

@ -1,27 +0,0 @@
export const Status = [
{
label: "启用",
value: 1,
type: "success"
},
{
label: "禁用",
value: 0,
type: "danger"
}
];
export const LogType = [
{
label: "成功",
value: 1,
type: "success"
},
{
label: "失败",
value: 0,
type: "danger"
}
];
export * from "./code";

View File

@ -1,302 +0,0 @@
<template>
<cl-view-group ref="ViewGroup">
<template #item-name="{ item }">
{{ item.name }} - {{ item.tableName }} - {{ item.className }}</template
>
<template #right>
<div class="source" v-loading="loading">
<div class="head">
<el-button @click="refresh()">刷新</el-button>
<el-button type="success" @click="edit()">添加</el-button>
<el-button type="danger" @click="clear()">清空</el-button>
</div>
<el-scrollbar class="scrollbar">
<div class="list">
<div class="item" v-for="(item, index) in list" :key="index">
<cl-code-json :model-value="item" :height="150">
<template #op>
<el-button type="primary" size="small" @click="edit(item)"
>编辑</el-button
>
<el-button type="danger" size="small" @click="remove(item.id)"
>删除</el-button
>
</template>
</cl-code-json>
</div>
</div>
<div class="empty" v-if="list.length == 0">
<el-empty :image-size="100" />
</div>
</el-scrollbar>
<div class="footer">
<el-pagination
background
layout="prev, pager, next"
:current-page="pagination.page"
:total="pagination.total"
:page-size="pagination.size"
@current-change="onCurrentChange"
/>
</div>
<cl-form ref="Form" />
</div>
</template>
</cl-view-group>
</template>
<script lang="ts" name="cloud-db" setup>
import { useForm } from "@cool-vue/crud";
import { ElMessage, ElMessageBox } from "element-plus";
import { reactive, ref } from "vue";
import { useCool } from "/@/cool";
import { CodeSnippets, Status } from "../dict";
import { useViewGroup } from "/@/plugins/view";
const { service } = useCool();
const { ViewGroup } = useViewGroup({
label: "表",
title: "数据列表",
service: service.cloud.db,
onEdit() {
return {
props: {
labelWidth: "60px"
},
items: [
{ label: "名称", prop: "name", required: true, component: { name: "el-input" } },
{
label: "内容",
prop: "content",
component: {
name: "cl-editor-monaco",
props: {
language: "typescript",
options: {
fontSize: "16px"
}
}
},
value: CodeSnippets.db,
required: true
},
{
label: "说明",
prop: "readme",
component: {
name: "el-input",
props: {
type: "textarea",
rows: 3
}
}
},
{
label: "状态",
prop: "status",
value: 1,
component: { name: "el-radio-group", options: Status },
required: true
}
]
};
},
onSelect() {
refresh({
page: 1
});
}
});
const Form = useForm();
//
const loading = ref(false);
//
const list = ref<any[]>([]);
//
const pagination = reactive({
page: 1,
size: 20,
total: 0
});
//
const reqParams = {
page: 1,
size: 20
};
//
function request(method: string, params?: any) {
return service.cloud.db.data({
id: ViewGroup.value?.selected?.id,
method,
params
});
}
//
async function refresh(params?: any) {
Object.assign(reqParams, params);
loading.value = true;
await request("page", reqParams)
.then((res) => {
list.value = res.list;
Object.assign(pagination, res.pagination);
})
.catch((err) => {
ElMessage.error(err.message);
});
loading.value = false;
}
//
function clear() {
ElMessageBox.confirm(
`此操作将会清空表(${ViewGroup.value?.selected?.name})的所有数据,是否继续?`,
"提示",
{
type: "warning"
}
)
.then(() => {
request("clear")
.then(() => {
ElMessage.success("清空数据成功");
refresh();
})
.catch((err) => {
ElMessage.error(err.message);
});
})
.catch(() => null);
}
//
function remove(id: string) {
ElMessageBox.confirm("此操作将会删除选择的数据,是否继续?", "提示", {
type: "warning"
})
.then(() => {
request("delete", {
ids: [id]
})
.then(() => {
ElMessage.success("删除数据成功");
refresh();
})
.catch((err) => {
ElMessage.error(err.message);
});
})
.catch(() => null);
}
//
function edit(item?: any) {
Form.value?.open({
title: item ? "编辑数据" : "添加数据",
props: {
labelWidth: "60px"
},
items: [
{
label: "内容",
prop: "content",
component: {
name: "cl-editor-monaco",
props: {
options: {
fontSize: "16px"
}
}
},
value: `{}`,
required: true
}
],
form: {
content: JSON.stringify(item, null, 4)
},
on: {
submit(data, { close, done }) {
try {
request(item ? "update" : "add", JSON.parse(data.content))
.then(() => {
ElMessage.success("保存成功");
close();
refresh();
})
.catch((err) => {
ElMessage.error(err.message);
done();
});
} catch (e) {
ElMessage.error(e?.toString());
done();
}
}
}
});
}
//
function onCurrentChange(index: number) {
refresh({
page: index
});
}
</script>
<style lang="scss" scoped>
.source {
height: 100%;
.head {
padding: 0 10px;
margin-bottom: 10px;
}
.scrollbar {
height: calc(100% - 92px);
box-sizing: border-box;
.list {
.item {
padding: 0 10px 10px 10px;
background-color: var(--el-bg-color);
&:last-child {
padding-bottom: 0;
}
}
}
.empty {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
}
.footer {
height: 50px;
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

View File

@ -1,382 +0,0 @@
<template>
<div class="func-dev">
<div class="head">
<el-icon class="back" @click="router.back">
<back />
</el-icon>
<span class="name">云函数{{ info.name }}</span>
<div class="btn">
<el-tooltip content="重新加载函数代码,将会丢失当前未保存的编辑" effect="light">
<el-button size="small" :loading="loading" @click="refresh(true)">
刷新
</el-button>
</el-tooltip>
<el-button size="small" type="success" :loading="saving" @click="save">
保存(S)
</el-button>
</div>
</div>
<div class="container">
<div class="code">
<cl-editor-monaco
:ref="setRefs('code')"
height="100%"
language="typescript"
:options="{
fontSize: 18
}"
v-model="info.content"
@change="
(val) => {
onCodeChange('devCode', val);
}
"
@keydown="onCodeKeydown"
/>
</div>
<div class="debug">
<el-row :gutter="10">
<el-col :sm="12" :xs="24">
<div class="card">
<div class="h">
<span>调用参数</span>
<el-button
size="small"
type="success"
:loading="running"
@click="run"
>
运行(B)
</el-button>
</div>
<div class="c">
<cl-editor-monaco
:ref="setRefs('req')"
height="100%"
:options="{
fontSize: 18
}"
v-model="debug.req"
@keydown="onReqKeydown"
@change="
(val) => {
onCodeChange('devReq', val);
}
"
/>
</div>
</div>
</el-col>
<el-col :sm="12" :xs="24">
<div class="card">
<div class="h">
<span>执行日志</span>
<el-button size="small" @click="refs.logs?.open(info)">
查看全部
</el-button>
</div>
<div class="c is-border">
<el-scrollbar v-loading="running">
<div class="c2">
<template v-if="debug.log.time > 0">
<div class="row">
<span
>执行方法{{ debug.log.request?.method }}</span
>
</div>
<div class="row">
<span>执行时间2023-02-16 01:23:00</span>
</div>
<div class="row">
<span>执行状态</span>
<span class="ok" v-if="debug.log.type == 1"
>成功</span
>
<span class="fail" v-else>失败</span>
<span style="margin-left: 20px">耗时</span>
<span>{{ debug.log.time }}ms</span>
</div>
<div class="row">执行结果</div>
<cl-code-json
height="auto"
:model-value="debug.log.result"
v-if="debug.log.type == 1"
/>
<div class="error" v-else>
{{ debug.log.error }}
</div>
</template>
<template v-else>
<el-empty :image-size="80" />
</template>
</div>
</el-scrollbar>
</div>
</div>
</el-col>
</el-row>
</div>
</div>
<!-- 日志 -->
<func-logs :ref="setRefs('logs')" />
</div>
</template>
<script lang="ts" name="cloud-func-dev" setup>
import { onMounted, reactive, ref } from "vue";
import { storage, useCool } from "/@/cool";
import { Back } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import { CodeSnippets } from "../../dict";
import FuncLogs from "../../components/func-logs.vue";
const { route, service, refs, setRefs, router } = useCool();
//
const loading = ref(false);
//
const saving = ref(false);
//
const running = ref(false);
//
const debug = reactive({
req: "",
log: {
error: "",
type: 0,
time: 0,
createTime: "",
result: {},
request: {
method: ""
}
}
});
//
const info = ref<Eps.CloudFuncInfoEntity>({
content: ""
});
//
async function refresh(sync?: boolean) {
loading.value = true;
await service.cloud.func.info
.info({
id: route.query.id
})
.then((res) => {
if (!sync) {
res.content = storage.get(`cloud.devCode-${res.id}`) || res.content;
debug.req = storage.get(`cloud.devReq-${res.id}`) || CodeSnippets.req;
}
info.value = res;
})
.catch((err) => {
ElMessage.error(err.message);
});
loading.value = false;
}
//
async function save() {
saving.value = true;
await refs.code.formatCode();
await service.cloud.func.info
.update({
id: info.value.id,
content: info.value.content
})
.then(() => {
ElMessage.success("保存成功");
})
.catch((err) => {
ElMessage.error(err.message);
});
saving.value = false;
}
//
async function run() {
running.value = true;
await refs.req.formatCode();
try {
await service.cloud.func.info
.invoke({
id: info.value.id,
content: info.value.content,
...JSON.parse(debug.req)
})
.then((res) => {
debug.log = res;
ElMessage.success("运行成功");
})
.catch((err) => {
debug.log.time = 0;
ElMessage.error(err.message);
});
} catch (e) {
ElMessage.error(e?.toString());
}
running.value = false;
}
//
function onCodeChange(name: string, value: string) {
storage.set(`cloud.${name}-${info.value.id}`, value);
}
// ctrk + s
function onCodeKeydown(e: KeyboardEvent) {
if (e.ctrlKey && e.key.toLocaleLowerCase() == "s") {
save();
e.preventDefault();
}
}
// ctrk + b
function onReqKeydown(e: KeyboardEvent) {
if (e.ctrlKey && e.key.toLocaleLowerCase() == "b") {
run();
e.preventDefault();
}
}
onMounted(() => {
refresh();
});
</script>
<style lang="scss" scoped>
.func-dev {
height: 100%;
background-color: var(--el-bg-color);
.head {
display: flex;
align-items: center;
justify-content: center;
height: 40px;
box-sizing: border-box;
padding: 0 5px;
.name {
flex: 1;
text-align: center;
font-size: 15px;
}
.back {
padding: 5px;
cursor: pointer;
border-radius: 4px;
font-size: 18px;
&:hover {
background-color: var(--el-fill-color-light);
}
}
}
.container {
height: calc(100% - 40px);
padding: 0 10px 10px 10px;
box-sizing: border-box;
}
.code {
height: calc(100% - 320px);
background-color: var(--el-bg-color);
}
}
.card {
height: 320px;
box-sizing: border-box;
background-color: var(--el-bg-color);
.h {
display: flex;
align-items: center;
justify-content: space-between;
height: 40px;
font-size: 14px;
.refresh {
display: flex;
align-items: center;
cursor: pointer;
margin-left: 6px;
.el-icon {
font-size: 16px;
&:hover {
color: var(--el-color-primary);
}
}
}
}
.c {
height: calc(100% - 40px);
box-sizing: border-box;
background-color: #fff;
&.is-border {
border: 1px solid var(--el-border-color);
}
}
.c2 {
font-size: 14px;
padding: 10px;
.error {
background-color: #fef0f0;
color: var(--el-color-danger);
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
}
.row {
margin-bottom: 5px;
color: #000;
}
.ok {
color: var(--el-color-success);
}
.fail {
color: var(--el-color-danger);
}
}
}
</style>

View File

@ -1,154 +0,0 @@
<template>
<cl-crud ref="Crud">
<cl-row>
<!-- 刷新按钮 -->
<cl-refresh-btn />
<!-- 新增按钮 -->
<cl-add-btn />
<!-- 删除按钮 -->
<cl-multi-delete-btn />
<!-- 筛选 -->
<cl-filter label="状态">
<cl-select :options="Status" prop="status" :width="120" />
</cl-filter>
<cl-flex1 />
<!-- 关键字搜索 -->
<cl-search-key placeholder="搜索名称" />
</cl-row>
<cl-row>
<!-- 数据表格 -->
<cl-table ref="Table" />
</cl-row>
<cl-row>
<cl-flex1 />
<!-- 分页控件 -->
<cl-pagination />
</cl-row>
<!-- 新增编辑 -->
<cl-upsert ref="Upsert" />
<!-- 日志 -->
<func-logs :ref="setRefs('logs')" />
</cl-crud>
</template>
<script lang="ts" name="cloud-func-info" setup>
import { useCrud, useTable, useUpsert } from "@cool-vue/crud";
import { useCool } from "/@/cool";
import { Status, CodeSnippets } from "../../dict";
import FuncLogs from "../../components/func-logs.vue";
const { service, refs, setRefs, router } = useCool();
// cl-upsert
const Upsert = useUpsert({
props: {
labelWidth: "60px"
},
items: [
{ label: "名称", prop: "name", required: true, component: { name: "el-input" } },
{
label: "内容",
prop: "content",
component: {
name: "cl-editor-monaco",
props: {
language: "typescript"
},
ref: setRefs("monaco")
},
value: CodeSnippets.func,
required: true
},
{
label: "说明",
prop: "readme",
component: {
name: "el-input",
props: {
type: "textarea",
rows: 3
}
}
},
{
label: "状态",
prop: "status",
value: 1,
component: { name: "el-radio-group", options: Status },
required: true
}
],
async onSubmit(data, { next }) {
const content = await refs.monaco.formatCode();
next({
...data,
content
});
}
});
// cl-table
const Table = useTable({
columns: [
{ type: "selection" },
{ label: "名称", prop: "name", minWidth: 160 },
{ label: "说明", prop: "readme", minWidth: 200, showOverflowTooltip: true },
{
label: "状态",
prop: "status",
component: {
name: "cl-switch"
},
minWidth: 150
},
{ label: "创建时间", prop: "createTime", minWidth: 160, sortable: "desc" },
{ label: "更新时间", prop: "updateTime", minWidth: 160, sortable: "custom" },
{
type: "op",
width: 360,
buttons: [
"edit",
"delete",
{
label: "开发",
type: "success",
hidden: !(
service.cloud.func.info._permission.info &&
service.cloud.func.info._permission.invoke
),
onClick({ scope }) {
router.push({
path: "/cloud/func/dev",
query: {
id: scope.row.id
}
});
}
},
{
label: "查看日志",
hidden: !service.cloud.func.log._permission.page,
onClick({ scope }) {
refs.logs.open(scope.row);
}
}
]
}
]
});
// cl-crud
const Crud = useCrud(
{
service: service.cloud.func.info
},
(app) => {
app.refresh();
}
);
</script>

View File

@ -1,195 +0,0 @@
<template>
<div class="dp-config">
<div class="head" v-show="t">
<span>{{ t }}</span>
<el-button text type="danger" @click="del" v-if="showDel">删除</el-button>
</div>
<div class="tips" v-if="tips">
<el-icon>
<warning-filled />
</el-icon>
<span>{{ tips }}</span>
</div>
<el-scrollbar class="scrollbar" v-if="visible">
<div class="form">
<cl-form inner ref="Form">
<template #slot-required="{ scope }">
<el-checkbox v-model="scope.required" @change="onRChange">必填</el-checkbox>
</template>
</cl-form>
<el-empty :image-size="100" v-show="!t" description="未选择组件" />
</div>
</el-scrollbar>
</div>
</template>
<script lang="tsx" setup>
import { useForm } from "@cool-vue/crud";
import { computed, nextTick, reactive, ref, watch } from "vue";
import { useCool } from "/@/cool";
import { WarningFilled } from "@element-plus/icons-vue";
import { useDp } from "../hooks";
const { mitt } = useCool();
const { dp } = useDp();
const Form = useForm();
const visible = ref(true);
const t = ref("");
const data = reactive<any>({});
const tips = ref("");
//
const isGroup = computed(() => data.isTemp);
//
const showDel = computed(() => {
return isGroup.value || (!!dp.getGroup(data.id) && data.isDel === false);
});
function refresh(options: any) {
for (const i in data) {
delete data[i];
}
Object.assign(data, options);
const { title, items = [] } = options.config;
t.value = title || "未配置";
tips.value = options.config.tips;
Form.value?.open({
form: options.component.props,
items,
props: {
labelPosition: "top"
},
op: {
hidden: true
}
});
}
function onRChange(v: any) {
if (isGroup.value) {
data.component.props.children.forEach((e: any) => {
e.component.props.required = v;
});
}
}
function del() {
clear();
dp.removeBy({
id: dp.getGroup(data.id).id
});
}
function clear() {
Form.value?.close();
t.value = "";
tips.value = "";
}
let stop: any = null;
mitt.on("dp.setConfig", (options: any) => {
visible.value = false;
if (stop) {
stop();
}
nextTick(() => {
visible.value = true;
nextTick(() => {
refresh(options || {});
stop = watch(
() => Form.value?.form,
(val) => {
if (val) {
Object.assign(options.component.props, val);
}
},
{
immediate: true,
deep: true
}
);
});
});
});
mitt.on("dp.clearConfig", clear);
</script>
<style lang="scss" scoped>
.dp-config {
height: 100%;
width: 350px;
overflow: hidden;
background-color: #fff;
border-radius: 6px;
.head {
display: flex;
align-items: center;
justify-content: space-between;
height: 54px;
font-size: 18px;
color: #262626;
padding: 0 15px;
border-bottom: 1px solid #ebeef5;
}
.tips {
display: inline-flex;
align-items: center;
background-color: #fff8d5;
color: #ffbb00;
margin: 10px 24px 0 24px;
padding: 0 20px 0 4px;
border-radius: 4px;
.el-icon {
margin: 5px;
font-size: 15px;
}
span {
font-size: 12px;
}
}
.scrollbar {
height: calc(100% - 55px);
}
.form {
padding: 16px 24px;
box-sizing: border-box;
:deep(.form-label) {
font-size: 16px;
color: #000;
span {
font-size: 12px;
color: #bfbfbf;
margin-left: 10px;
}
}
:deep(.el-form-item__label) {
color: #000;
font-size: 16px;
}
}
}
</style>

View File

@ -1,969 +0,0 @@
<template>
<div class="dp-demo">
<el-scrollbar>
<div class="group" v-for="(group, index) in tab.list" :key="index">
<p class="label">{{ group.label }}</p>
<draggable
v-model="group.children"
class="list"
item-key="label"
:group="{
name: 'A',
pull: 'clone',
put: false
}"
:sort="false"
:clone="onClone"
@end="onEnd"
>
<template #item="{ element: item }">
<div class="item" @click="add(item)">
<img :src="icons[item.name]" />
<span>{{ item.label }}</span>
</div>
</template>
</draggable>
</div>
</el-scrollbar>
</div>
</template>
<script lang="tsx" setup>
import { ElMessage } from "element-plus";
import { cloneDeep, merge } from "lodash-es";
import { reactive } from "vue";
import Draggable from "vuedraggable/src/vuedraggable";
import { useDp } from "../hooks";
import { Dp } from "../types";
import { useCool } from "/@/cool";
import { uuid } from "/@/cool/utils";
const { mitt } = useCool();
const { dp } = useDp();
//
const files: any = import.meta.glob("/src/modules/design/static/icon/*", {
eager: true
});
const icons = reactive<any>({});
for (const i in files) {
icons[i.replace("/src/modules/design/static/icon/", "").replace(".png", "")] = files[i].default;
}
//
const demo: Dp.DemoItem[] = [
{
label: "单行文字",
name: "text"
},
{
label: "多行文字",
name: "textarea"
},
{
label: "单项选择",
name: "radio",
component: {
name: "demo-item",
props: {
placeholder: "请选择",
arrowIcon: true,
options: []
}
},
config: {
items: [
{
label: "选项",
renderLabel: (
<p class="form-label">
选项 <span>最多200项每项最多50个字</span>
</p>
),
component: {
name: "demo-select"
}
}
]
}
},
{
label: "多项选择",
name: "checkbox",
component: {
name: "demo-item",
props: {
placeholder: "请选择",
arrowIcon: true,
options: []
}
},
config: {
items: [
{
label: "选项",
renderLabel: (
<p class="form-label">
选项 <span>最多200项每项最多50个字</span>
</p>
),
component: {
name: "demo-select"
}
}
]
}
},
{
label: "时间",
name: "time",
component: {
name: "demo-item",
props: {
placeholder: "请选择",
arrowIcon: true
}
},
config: {
items: [
{
label: "日期",
prop: "format",
value: "YYYY-MM-DD",
component: {
name: "el-radio-group",
options: [
{
label: "年-月-日",
value: "YYYY-MM-DD"
},
{
label: "年-月-日 时:分",
value: "YYYY-MM-DD HH:mm"
},
{
label: "年-月-日 时:分:秒",
value: "YYYY-MM-DD HH:mm:ss"
}
]
}
}
]
}
},
{
label: "时间区间",
name: "time-range",
component: {
name: "demo-time-range",
props: {
labelStart: "开始时间",
labelEnd: "结束时间",
labelDuration: "时长",
placeholder: "请选择",
arrowIcon: true,
duration: true,
durationType: "hour"
}
},
config: {
defs: [],
items: [
{
label: "标题",
renderLabel: (
<p class="form-label">
标题 <span>最多20字</span>
</p>
),
prop: "labelStart",
value: "开始时间",
component: {
name: "el-input",
props: {
maxlength: 20,
clearable: true,
placeholder: "请输入"
}
}
},
{
label: "标题",
renderLabel: (
<p class="form-label">
标题 <span>最多20字</span>
</p>
),
prop: "labelEnd",
value: "结束时间",
component: {
name: "el-input",
props: {
maxlength: 20,
clearable: true,
placeholder: "请输入"
}
}
},
{
label: "提示文字",
renderLabel: (
<p class="form-label">
提示文字 <span>最多50字</span>
</p>
),
prop: "placeholder",
value: "请输入",
component: {
name: "el-input",
props: {
maxlength: 50,
clearable: true,
placeholder: "请输入"
}
}
},
{
label: "日期",
prop: "format",
value: "YYYY-MM-DD",
component: {
name: "el-radio-group",
options: [
{
label: "年-月-日",
value: "YYYY-MM-DD"
},
{
label: "年-月-日 时:分",
value: "YYYY-MM-DD HH:mm"
},
{
label: "年-月-日 时:分:秒",
value: "YYYY-MM-DD HH:mm:ss"
}
]
}
},
{
label: "是否计算时长",
prop: "duration",
component: {
name: "demo-checkbox",
props: {
text: "开启"
}
}
},
{
label: "时长类型",
prop: "durationType",
value: "hour",
hidden({ scope }) {
return !scope.duration;
},
component: {
name: "el-radio-group",
options: [
{
label: "小时",
value: "hour"
},
{
label: "半天",
value: "half_day"
},
{
label: "天",
value: "day"
}
]
}
},
{
label: "标题",
renderLabel: (
<p class="form-label">
标题 <span>最多20字</span>
</p>
),
hidden({ scope }) {
return !scope.duration;
},
prop: "labelDuration",
value: "时长",
component: {
name: "el-input",
props: {
maxlength: 20,
clearable: true,
placeholder: "请输入"
}
}
}
]
}
},
{
label: "数字",
name: "number",
config: {
items: [
{
label: "单位",
prop: "unit",
component: {
name: "el-input"
}
},
{
label: "数值类型",
prop: "type",
value: "int",
component: {
name: "el-radio-group",
options: [
{
label: "整数",
value: "int"
},
{
label: "小数",
value: "decimal"
}
]
}
},
{
label: "数值区间",
prop: "range",
value: [],
component: {
name: "demo-num-range"
}
},
{
label: "默认值",
prop: "defaultValue",
component: {
name: "el-input"
}
}
]
}
},
{
label: "金钱",
name: "amount",
config: {
items: [
{
label: "单位",
prop: "unit",
component: {
name: "el-input"
}
},
{
label: "大写",
prop: "uppercase",
component: {
name: "demo-checkbox",
props: {
text: "显示大写",
tips: "(输入数字后自动显示大写)"
}
}
}
]
}
},
{
label: "地址",
name: "address",
getType: "auto",
config: {
defs: ["title"]
}
},
{
label: "附件",
name: "file",
component: {
name: "demo-item"
},
config: {
defs: ["title"]
}
},
{
label: "组合",
name: "group",
component: {
name: "demo-group",
props: {
children: []
}
},
config: {
defs: ["title"]
}
},
{
label: "图片",
name: "pic",
component: {
name: "demo-item"
},
config: {
defs: ["title"]
}
}
];
function getDemo(name: string, data?: any) {
const d = demo.find((e) => e.name == name);
return merge(cloneDeep(d), data || {});
}
//
const tab = reactive<{ list: { label: string; children: Dp.DemoItem[] }[] }>({
list: [
{
label: "基础组件",
children: demo
},
{
label: "组合套件",
children: [
{
label: "加班审批",
name: "jiaban",
group: [
getDemo("radio", {
label: "加班类型",
required: true,
component: {
props: {
options: [
{
label: "工作日加班",
value: 0
},
{
label: "休息日加班",
value: 1
},
{
label: "节假日加班",
value: 2
}
]
}
}
}),
getDemo("radio", {
label: "加班补偿方式",
required: true,
component: {
props: {
options: [
{
label: "不计补偿",
value: 0
},
{
label: "加班工资",
value: 1
},
{
label: "加班调休",
value: 2
},
{
label: "加班工资或加班调休",
value: 3
}
]
}
},
config: {
items: [
{},
{
label: "类型匹配",
prop: "match",
value: [],
component: {
name: "demo-jb-match"
}
}
]
}
}),
getDemo("time-range"),
getDemo("textarea", {
label: "加班原因",
required: true
}),
getDemo("file"),
{
label: "流程",
name: "approval-process",
component: {
props: {
placeholder: "请选择",
arrowIcon: true
}
}
}
]
},
{
label: "出差审批",
name: "chuchai",
group: [
{
label: "出差城市",
required: true,
name: "region",
component: {
props: {
placeholder: "请选择",
arrowIcon: true
}
}
},
{
label: "目的城市",
required: true,
name: "region",
component: {
props: {
placeholder: "请选择",
arrowIcon: true
}
}
},
getDemo("time-range", {
required: true
}),
getDemo("textarea", {
label: "出差事由"
}),
getDemo("file"),
{
label: "流程",
name: "approval-process",
component: {
props: {
placeholder: "请选择",
arrowIcon: true
}
}
}
]
},
{
label: "补卡审批",
name: "buka",
group: [
getDemo("time", {
label: "补卡时间",
required: true
}),
getDemo("textarea", {
label: "补卡事由",
required: true
}),
getDemo("pic"),
{
label: "流程",
name: "approval-process",
component: {
props: {
placeholder: "请选择",
arrowIcon: true
}
}
}
]
},
{
label: "调班审批",
name: "tiaoban",
group: [
getDemo("radio", {
label: "实际申请人",
required: true
}),
getDemo("time", {
label: "调班日期",
required: true
}),
getDemo("time", {
label: "被调班日期",
required: true
}),
{
label: "调班班次"
},
getDemo("textarea", {
label: "调班原因"
}),
getDemo("file"),
{
label: "流程",
name: "approval-process",
component: {
props: {
placeholder: "请选择",
arrowIcon: true
}
}
}
]
},
{
label: "请假审批",
name: "qingjia",
group: [
getDemo("radio", {
label: "请假类型",
required: true,
component: {
props: {
options: [
{
label: "事假",
value: 1
},
{
label: "年假",
value: 2
},
{
label: "病假",
value: 3
},
{
label: "调休",
value: 4
},
{
label: "产假",
value: 5
},
{
label: "陪产假",
value: 6
},
{
label: "婚假",
value: 7
},
{
label: "丧假",
value: 8
},
{
label: "哺乳假",
value: 9
}
]
}
}
}),
getDemo("time-range", {
component: {
props: {
duration: true
}
}
}),
getDemo("textarea", {
label: "请假事由",
required: true
}),
getDemo("pic")
],
config: {
tips: "适用于员工本人或他人代为发起请假申请"
}
},
{
label: "外出审批",
name: "waichu",
group: [
getDemo("time-range"),
getDemo("textarea", {
label: "外出事由",
required: true
}),
getDemo("pic")
],
config: {
tips: "适用于员工本人或他人代为发起外出申请"
}
}
]
}
]
});
//
function parse(item: Dp.DemoItem) {
function next(data: Dp.DemoItem, options: any = {}) {
const { autoInc = true } = options;
const d: Dp.DemoItem = cloneDeep({
...data,
id: uuid()
});
//
if (!d.config) {
d.config = {};
}
if (!d.config.items) {
d.config.items = [];
}
if (!d.config.defs) {
d.config.defs = ["title", "placeholder"];
}
d.config.title = d.label;
if (autoInc) {
//
data._index = data._index !== undefined ? data._index + 1 : 0;
if (data._index > 0) {
d.label += data._index;
}
}
//
if (!d.component) {
d.component = {};
}
if (!d.component.name) {
d.component.name = "demo-item";
}
if (!d.component.props) {
d.component.props = {};
}
if (!d.component.props.label) {
d.component.props.label = d.label;
}
if (!d.component.props.name) {
d.component.props.name = d.name;
}
if (d.required) {
d.component.props.required = true;
}
switch (d.getType) {
//
case "auto":
d.component.props.placeholder = "系统自动获取";
break;
}
//
const items = [];
//
if (d.config.defs.includes("title")) {
items.unshift({
label: "标题",
renderLabel: (
<p class="form-label">
标题 <span>最多20字</span>
</p>
),
prop: "label",
value: d.label,
component: {
name: "el-input",
props: {
maxlength: 20,
clearable: true,
placeholder: "请输入",
disabled: d.config.disabled
}
}
});
}
//
if (d.config.defs.includes("placeholder")) {
items.push({
label: "提示文字",
renderLabel: (
<p class="form-label">
提示文字 <span>最多50字</span>
</p>
),
prop: "placeholder",
value: "请输入",
component: {
name: "el-input",
props: {
maxlength: 50,
clearable: true,
placeholder: "请输入",
disabled: d.config.disabled
}
}
});
}
d.config.items.unshift(...items);
d.config.items.push({
label: "验证",
prop: "required",
component: {
name: "slot-required"
}
});
return d;
}
let v = null;
if (item.group) {
if (dp.hasTemp()) {
ElMessage.warning("请先删除已选套件");
return undefined;
}
const arr = item.group.map((e) => next(e, { autoInc: false }));
arr.forEach((e: any) => {
e.config.disabled = true;
e.isDel = false;
});
v = next({
label: item.label,
name: item.name,
isTemp: true,
component: {
name: "demo-group",
props: {
children: arr
}
},
config: {
defs: ["title"],
...item.config
}
});
} else {
v = next(item);
}
return v;
}
//
function getConfig(name: string) {
let d = null;
tab.list.find((e) => {
d = e.children.find((a) => a.name == name);
return !!d;
});
return d ? parse(d)?.config : {};
}
//
let _d: any = null;
function onClone(item: any) {
//
_d = parse(item);
//
mitt.emit("dp.pull", _d);
return _d;
}
function add(item: any) {
dp.add(onClone(item));
dp.scrollToBottom();
}
function onEnd() {
if (_d) {
mitt.emit("dp.setActive", _d.id);
}
}
defineExpose({
getConfig
});
</script>
<style lang="scss" scoped>
.dp-demo {
width: 350px;
background-color: #fff;
border-radius: 6px;
.group {
.label {
font-size: 15px;
padding: 15px 20px;
}
.list {
display: flex;
flex-wrap: wrap;
padding: 0 8px;
.item {
display: flex;
align-items: center;
height: 40px;
width: calc(50% - 16px);
margin: 0 8px 6px 8px;
padding: 0 8px 0 18px;
box-sizing: border-box;
border: 1px dashed currentColor;
border-radius: 4px;
cursor: pointer;
color: #bfbfbf;
img {
height: 20px;
width: 20px;
margin-right: 14px;
}
span {
font-size: 15px;
}
&:hover {
border-color: var(--color-primary);
}
}
}
&:last-child {
.list {
padding-bottom: 20px;
}
}
}
}
</style>

View File

@ -1,34 +0,0 @@
<template>
<div class="demo-checkbox">
<el-checkbox v-model="value" @change="onChange"
>{{ text }} <span class="tips">{{ tips }}</span></el-checkbox
>
</div>
</template>
<script lang="ts" name="demo-checkbox" setup>
import { ref } from "vue";
const props = defineProps({
modelValue: Boolean,
text: String,
tips: String
});
const emit = defineEmits(["update:modelValue"]);
const value = ref(props.modelValue);
function onChange() {
emit("update:modelValue", value.value);
}
</script>
<style lang="scss" scoped>
.demo-checkbox {
.tips {
font-size: 12px;
color: #bfbfbf;
}
}
</style>

View File

@ -1,131 +0,0 @@
<template>
<div class="demo-group">
<div class="head" v-show="label">{{ label }}</div>
<draggable
v-model="list"
class="list"
tag="div"
item-key="id"
:group="{
name: 'A',
animation: 300,
ghostClass: 'Ghost',
dragClass: 'Drag',
draggable: '.is-drag',
put: isPut
}"
:clone="onClone"
>
<template #footer>
<div class="tips">可拖入多个组件<span>不包含组合组件</span></div>
</template>
<template #item="{ element: item, index }">
<div
class="item"
:class="{
active: dp.form.active == item.id
}"
@click.stop="dp.toDet(item)"
>
<el-icon
class="close"
@click.stop="remove(index)"
v-show="dp.form.active == item.id && item.isDel !== false"
>
<close-bold />
</el-icon>
<component
:is="item.component.name"
:data="item"
v-bind="item.component.props"
/>
</div>
</template>
</draggable>
</div>
</template>
<script lang="ts" setup name="demo-group">
import { ref, watch } from "vue";
import Draggable from "vuedraggable/src/vuedraggable";
import { CloseBold } from "@element-plus/icons-vue";
import { useCool } from "/@/cool";
import { useDp } from "../../hooks";
const props = defineProps({
label: String,
children: Array
});
const emit = defineEmits(["update:children"]);
const { mitt } = useCool();
const { dp } = useDp();
const list = ref<any[]>(props.children || []);
const isPut = ref(true);
function remove(index: number) {
dp.clearConfig(list.value[index].id);
list.value?.splice(index, 1);
}
function onClone(data: any) {
mitt.emit("dp.pull", data);
return data;
}
mitt.on("dp.setActive", (id: string) => {
const d = list.value?.find((e) => e.id == id);
if (d) {
dp.toDet(d);
}
});
mitt.on("dp.pull", (d) => {
isPut.value = d?.component.name != "demo-group";
});
watch(
list,
(val) => {
emit("update:children", val);
},
{
deep: true
}
);
</script>
<style lang="scss" scoped>
.demo-group {
background-color: #fff;
.head {
line-height: 40px;
height: 40px;
padding: 0 12px;
font-size: 14px;
font-weight: bold;
}
.list {
background-color: #d9effe;
.tips {
color: #8c8c8c;
font-size: 12px;
padding: 15px;
}
.item {
&:nth-last-child(2) {
margin-bottom: 0;
}
}
}
}
</style>

View File

@ -1,172 +0,0 @@
<template>
<div class="demo-item" :class="[{ 'is-required': required }, `demo-item--${name}`]">
<span class="label">{{ label }}</span>
<div class="value">
<slot>
<template v-if="name == 'file'">
<div class="upload">
<el-icon>
<link />
</el-icon>
上传附件
</div>
</template>
<template v-else-if="name == 'pic'">
<div class="upload">
<el-icon>
<plus />
</el-icon>
</div>
</template>
<template v-else>
<span class="placeholder">{{ placeholder }}</span>
<el-icon v-if="arrowIcon" class="arrow-right">
<arrow-right />
</el-icon>
</template>
</slot>
</div>
</div>
</template>
<script lang="ts" name="demo-item" setup>
import { ArrowRight, Plus, Link } from "@element-plus/icons-vue";
defineProps({
label: String,
name: String,
required: Boolean,
placeholder: {
type: String,
default: "请输入"
},
arrowIcon: Boolean
});
</script>
<style lang="scss">
.demo-item {
display: flex;
align-items: center;
min-height: 40px;
font-size: 14px;
background-color: #fff;
.label {
display: flex;
align-items: center;
height: 40px;
width: 110px;
flex-shrink: 0;
padding-left: 12px;
box-sizing: border-box;
overflow: hidden;
}
.value {
display: flex;
align-items: center;
justify-content: flex-end;
flex: 1;
padding: 0 12px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
.placeholder,
.arrow-right {
color: #e5e5e5;
}
}
&--textarea {
flex-direction: column;
align-items: flex-start;
.label {
width: 100%;
}
.value {
.placeholder {
display: block;
height: 55px;
width: 100%;
text-align: left;
white-space: pre-wrap;
word-wrap: break-word;
}
}
}
&--file,
&--pic {
flex-direction: column;
align-items: flex-start;
.label {
width: 100%;
}
.value {
flex-direction: column;
align-items: flex-start;
width: 100%;
box-sizing: border-box;
}
.upload {
display: flex;
align-items: center;
justify-content: center;
height: 60px;
width: 60px;
border: 1px dashed #e5e5e5;
border-radius: 4px;
margin-bottom: 10px;
.el-icon {
font-size: 24px;
color: #ccc;
}
}
.tips {
width: 100%;
font-size: 12px;
color: #666;
padding-bottom: 10px;
white-space: pre-wrap;
}
}
&--file {
.upload {
width: 100%;
height: 30px;
font-size: 12px;
.el-icon {
color: #000;
font-size: 15px;
}
}
}
&.is-required {
.label {
&::before {
content: "*";
color: red;
margin-right: 2px;
}
}
}
&:hover {
opacity: 0.8;
}
}
</style>

View File

@ -1,38 +0,0 @@
<template>
<div class="num-range">
<el-input-number v-model="v[0]" :max="v[1]" @change="onChange" />
<span></span>
<el-input-number v-model="v[1]" :min="v[0]" @change="onChange" />
</div>
</template>
<script lang="ts" setup name="demo-num-range">
import { ref } from "vue";
const props = defineProps({
modelValue: Array
});
const emit = defineEmits(["update:modelValue"]);
const v = ref<any[]>(props.modelValue || []);
function onChange() {
emit("update:modelValue", v.value);
}
</script>
<style lang="scss" scoped>
.num-range {
display: flex;
align-items: center;
.el-input {
flex: 1;
}
span {
margin: 0 10px;
}
}
</style>

View File

@ -1,53 +0,0 @@
<template>
<div class="demo-select">
<div class="item" v-for="(item, index) in Form?.form.options" :key="index">
<el-input v-model="item.label" :placeholder="`请输入选项${index + 1}`" />
<el-icon @click="add(index)"><circle-plus /></el-icon>
<el-icon @click="del(index)"><remove /></el-icon>
</div>
</div>
</template>
<script lang="ts" name="demo-select" setup>
import { useForm } from "@cool-vue/crud";
import { CirclePlus, Remove } from "@element-plus/icons-vue";
import { isEmpty } from "lodash-es";
import { onMounted } from "vue";
const Form = useForm();
function add(index: number) {
Form.value?.form.options.splice(index + 1, 0, {
label: ""
});
}
function del(index: number) {
Form.value?.form.options.splice(index, 1);
}
onMounted(() => {
if (isEmpty(Form.value?.form.options)) {
add(0);
}
});
</script>
<style lang="scss" scoped>
.item {
display: flex;
align-items: center;
margin-bottom: 10px;
.el-input {
margin-right: 5px;
}
.el-icon {
margin: 0 5px;
font-size: 16px;
color: var(--color-primary);
cursor: pointer;
}
}
</style>

View File

@ -1,22 +0,0 @@
<template>
<div class="demo-time-range">
<demo-item :label="labelStart" :placeholder="placeholder" :required="required" arrow-icon />
<demo-item :label="labelEnd" :placeholder="placeholder" :required="required" arrow-icon />
<demo-item :label="`${labelDuration}(小时)`" v-if="duration"></demo-item>
</div>
</template>
<script lang="ts" name="demo-time-range" setup>
defineProps({
labelStart: String,
labelEnd: String,
labelDuration: String,
required: Boolean,
placeholder: {
type: String,
default: "请选择"
},
arrowIcon: Boolean,
duration: Boolean
});
</script>

View File

@ -1,440 +0,0 @@
<template>
<el-scrollbar>
<div class="dp-wrap">
<dp-demo :ref="setRefs('demo')" />
<div class="dp-device">
<div class="device">
<div class="nav">设计页</div>
<el-scrollbar class="scrollbar" :ref="setRefs('scrollbar')">
<draggable
v-model="form.list"
class="list"
tag="div"
item-key="id"
:group="{
name: 'A',
animation: 300,
ghostClass: 'Ghost',
dragClass: 'Drag',
draggable: '.is-drag'
}"
:clone="onClone"
>
<template #item="{ element: item, index }">
<div
class="item"
:class="{
active: form.active == item.id
}"
@click="toDet(item)"
>
<el-icon
class="close"
@click.stop="remove(index)"
v-show="form.active == item.id"
>
<close-bold />
</el-icon>
<!-- 组合 -->
<demo-group
:data="item"
v-bind="item.component.props"
v-model:children="item.component.props.children"
v-if="item.component.props.children"
/>
<!-- 基础元素 -->
<component
:is="item.component.name"
:data="item"
v-bind="item.component.props"
v-else
/>
</div>
</template>
<template #footer>
<div class="tips">点击或者拖动组件添加</div>
</template>
</draggable>
</el-scrollbar>
</div>
</div>
<dp-config />
</div>
</el-scrollbar>
</template>
<script lang="ts" setup>
import { onMounted, provide, reactive, nextTick } from "vue";
import Draggable from "vuedraggable/src/vuedraggable";
import { CloseBold } from "@element-plus/icons-vue";
import { storage, useCool } from "/@/cool";
import { isArray, isEmpty } from "lodash-es";
import DpConfig from "./config.vue";
import DpDemo from "./demo.vue";
import { Dp } from "../types";
const { mitt, refs, setRefs } = useCool();
const form = reactive<{ active: string; list: any[] }>({
active: "",
list: []
});
//
function toDet(item: any) {
if (item) {
form.active = item.id;
mitt.emit("dp.setConfig", item);
}
}
//
function add(data: any) {
if (data) {
const arr = isArray(data) ? data : [data];
form.list.push(...arr);
toDet(arr[0]);
}
}
//
function clearConfig(id?: string) {
if (form.active == id || !id) {
mitt.emit("dp.clearConfig");
}
}
//
function remove(index: number) {
clearConfig(form.list[index].id);
const d = form.list[index];
if (d) {
if (d.isTemp) {
//
const arr = d.component.props.children.filter((e: any) => e.isDel !== false);
if (!isEmpty(arr)) {
add(arr);
}
}
}
form.list.splice(index, 1);
}
// 2
function removeBy({ id, index }: any) {
if (id) {
index = form.list.findIndex((e) => e.id == id);
}
remove(index);
}
//
function clear() {
clearConfig();
form.list = [];
}
//
function onClone(data: any) {
mitt.emit("dp.pull", data);
return data;
}
//
function setActive(id: string) {
const d = form.list.find((e) => e.id == id);
if (d) {
toDet(d);
}
}
// prop
function get(prop: string) {
let d = null;
form.list.forEach((e) => {
if (e.prop == prop) {
return (d = e);
} else {
if (e.component.name == "demo-group") {
e.component.props.children.forEach((a: any) => {
if (a.prop == prop) {
return (d = a);
}
});
}
}
});
return d;
}
//
function getData() {
function deep(arr: any[]): any {
return arr.map((e) => {
const isGroup = e.component.name == "demo-group" || e.name == "group";
const d = {
id: e.id,
name: e.name,
label: e.component.props.label
};
if (isGroup) {
return {
...d,
isGroup,
children: deep(e.component.props.children || [])
};
} else {
return {
...d,
prop: e.prop,
name: e.name,
getType: e.getType,
props: e.component.props
};
}
});
}
return deep(form.list);
}
//
function getGroup(id: string) {
let d = null;
form.list.forEach((a) => {
if (a.isTemp) {
if (a.id == id) {
d = a;
} else {
a.component.props.children.forEach((e: any) => {
if (e.id == id) {
d = a;
}
});
}
}
});
return d;
}
//
function hasTemp() {
return !!form.list.find((e) => e.isTemp);
}
// 稿
function saveDraft() {
storage.set(
"design.pageCode",
form.list.map((e) => {
return {
...e,
config: undefined
};
})
);
}
// 稿
function getDraft() {
const list: Dp.DemoItem[] = storage.get("design.pageCode") || [];
form.list = list.map((e) => {
e.config = refs.demo.getConfig(e.name);
return e;
});
toDet(form.list[0]);
}
//
function scrollToBottom() {
nextTick(() => {
refs.scrollbar.scrollTo(0, 9999);
});
}
//
mitt.on("dp.setActive", setActive);
const dp = {
form,
get,
getGroup,
getData,
toDet,
setActive,
add,
remove,
removeBy,
clear,
hasTemp,
clearConfig,
saveDraft,
scrollToBottom
};
provide("dp", dp);
defineExpose(dp);
onMounted(() => {
getDraft();
});
</script>
<style lang="scss">
.Ghost {
opacity: 0.7;
}
</style>
<style lang="scss">
.dp-wrap {
display: flex;
height: 100%;
min-height: 700px;
background-color: #edf0f3;
padding: 10px;
box-sizing: border-box;
color: #000;
.dp-device {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
.device {
position: relative;
height: 667px;
width: 360px;
overflow: hidden;
border-radius: 20px;
background-color: #f7f8fa;
.nav {
display: flex;
align-items: center;
justify-content: center;
height: 54px;
font-size: 18px;
background-color: #fff;
border-bottom: 1px solid #eee;
}
.scrollbar {
height: 612px;
width: 100%;
.list {
height: 100%;
padding-bottom: 20px;
box-sizing: border-box;
.item {
position: relative;
margin-bottom: 6px;
box-sizing: border-box;
cursor: pointer;
width: 100%;
&:last-child {
margin-bottom: 0;
}
.close {
position: absolute;
right: 0;
top: 0;
height: 14px;
width: 14px;
color: #fff;
z-index: 9;
background-color: var(--color-primary);
padding: 1px;
cursor: pointer;
&:hover {
background-color: red;
}
}
&.sortable-ghost {
display: flex;
align-items: center;
min-height: 40px;
width: 100%;
padding: 0 18px;
box-sizing: border-box;
border: 1px dashed currentColor;
border-radius: 4px;
cursor: pointer;
color: #bfbfbf;
background-color: #fff;
opacity: 0.8;
img {
height: 20px;
width: 20px;
margin-right: 14px;
}
span {
font-size: 16px;
}
[class^="demo-"] {
width: 100%;
color: #fff;
.placeholder {
color: #fff;
}
}
}
&.active {
&::after {
display: block;
content: "";
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 100%;
border: 2px solid var(--color-primary);
box-sizing: border-box;
pointer-events: none;
}
}
}
}
}
.tips {
text-align: center;
font-size: 14px;
padding: 10px 0 20px 0;
color: #999;
}
}
}
}
</style>

View File

@ -1,7 +0,0 @@
import type { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => {
return {
components: Object.values(import.meta.glob("./components/demo/*"))
};
};

View File

@ -1,8 +0,0 @@
import { inject } from "vue";
import { Dp } from "../types";
export function useDp() {
const dp = inject("dp") as Dp.Provide;
return { dp };
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1019 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 927 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 988 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 783 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,43 +0,0 @@
export declare namespace Dp {
interface DemoItem {
label: string;
name?: string;
required?: boolean;
getType?: "auto";
component?: {
name?: string;
props?: {
children?: any[];
[key: string]: any;
};
};
config?: {
defs?: string[];
tips?: string;
disabled?: boolean;
items?: ClForm.Item[];
[key: string]: any;
};
group?: DemoItem[];
[key: string]: any;
}
interface Provide {
form: {
[key: string]: any;
};
get(prop: string): any;
getGroup(id: string): any;
getData(): any[];
toDet(item: any): void;
setActive(id: string): void;
add(data: any): void;
remove(index: number): void;
removeBy(options: { id?: string; index?: number }): void;
clear(): boolean;
hasTemp(): boolean;
clearConfig(id?: string): void;
saveDraft(): void;
scrollToBottom(): void;
}
}

View File

@ -1,69 +0,0 @@
<template>
<div class="form">
<div class="container">
<dp :ref="setRefs('dp')" />
</div>
<div class="footer">
<el-button @click="clear">清空</el-button>
<el-button type="info" @click="save">保存草稿</el-button>
<cl-editor-preview title="代码预览" name="monaco" :ref="setRefs('preview')">
<el-button type="success" @click="create">生成代码</el-button>
</cl-editor-preview>
</div>
</div>
</template>
<script lang="ts" setup>
import { useCool } from "/@/cool";
import { ElMessage, ElMessageBox } from "element-plus";
import Dp from "../components/index.vue";
const { refs, setRefs } = useCool();
function save() {
refs.dp.saveDraft();
ElMessage.success("保存草稿成功");
}
function create() {
refs.preview.open(refs.dp.getData());
}
function clear() {
ElMessageBox.confirm("是否清空列表所有数据?", "提示", {
type: "warning"
})
.then(() => {
refs.dp.clear();
})
.catch(() => null);
}
</script>
<style lang="scss" scoped>
.form {
background-color: #fff;
position: relative;
min-width: 1300px;
height: 100%;
overflow: hidden;
.container {
height: calc(100% - 80px);
}
.footer {
display: flex;
justify-content: center;
padding-top: 20px;
height: 80px;
width: 100%;
box-sizing: border-box;
background-color: #fff;
border-top: 1px solid #ebeef5;
z-index: 9;
}
}
</style>

View File

@ -1,60 +0,0 @@
import mqtt from "mqtt/dist/mqtt.min";
import { useCool } from "/@/cool";
let client: mqtt.MqttClient;
export function useMqtt() {
const { mitt } = useCool();
function send(id: string, text: string) {
client?.publish(id, text);
}
function subscribe(id: string) {
console.log("[iot] mqtt subscribe", id);
client?.subscribe(`${id}@admin`, function (err: string) {
if (err) {
console.error(err);
}
});
}
function connect() {
// 断开
disconnect();
// 连接
client = mqtt.connect("ws://127.0.0.1:8083");
if (client) {
client.on("connect", function () {
console.log("[iot] mqtt connect");
});
client.on("message", function (topic: string, message: string) {
mitt.emit("iot.message", {
id: topic.split("@")[0],
message: message.toString()
});
});
client.on("error", function (err: string) {
console.error(err);
client?.reconnect();
});
}
}
function disconnect() {
client?.end();
}
return {
client,
connect,
disconnect,
subscribe,
send
};
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 933 B

View File

@ -1,474 +0,0 @@
<template>
<cl-view-group ref="ViewGroup">
<template #item="{ item, selected }">
<div class="device-item" :class="{ 'is-active': selected.id == item.id }">
<div class="icon">
<cl-avatar shape="square" :src="item.icon || DeviceIcon" />
</div>
<div class="det">
<p class="name">{{ item.name }}</p>
<p class="text">
{{ item.uniqueId }}
</p>
</div>
<div
class="status"
:class="{
'is-on': item.status
}"
>
{{ item.status ? "在线" : "离线" }}
</div>
</div>
</template>
<template #right>
<div class="message" v-loading="loading">
<!-- 消息列表 -->
<el-scrollbar class="list" :ref="setRefs('scrollbar')" @scroll="onScroll">
<ul>
<li v-for="(item, index) in list" :key="index">
<div
class="item"
:class="{
'is-right': item.type == 0
}"
>
<div class="icon">
<cl-avatar
:size="36"
shape="square"
:src="
item.type == 0
? user.info?.headImg
: ViewGroup?.selected?.icon || DeviceIcon
"
/>
</div>
<div
class="det"
@contextmenu="
(e) => {
onContextMenu(e, item);
}
"
>
<div class="content">
<span class="is-text">
{{ item.data }}
</span>
</div>
<div class="date">
{{ dayjs(item.createTime).format("YYYY-MM-DD HH:mm:ss") }}
</div>
</div>
</div>
</li>
</ul>
<div class="empty" v-if="list.length == 0">
<el-empty :image-size="100" description="暂无消息" />
</div>
</el-scrollbar>
<!-- 底部 -->
<div class="footer">
<div class="input">
<el-input
v-model="value"
type="textarea"
:rows="4"
resize="none"
:autosize="{
minRows: 4,
maxRows: 10
}"
placeholder="输入内容"
/>
<el-button type="success" @click="send" :disabled="!value">发送</el-button>
</div>
</div>
</div>
</template>
</cl-view-group>
</template>
<script lang="ts" name="iot-device" setup>
import { ContextMenu } from "@cool-vue/crud";
import { useClipboard } from "@vueuse/core";
import { ElMessage } from "element-plus";
import { debounce, orderBy } from "lodash-es";
import { computed, nextTick, onActivated, ref } from "vue";
import { useMqtt } from "../hooks";
import { useBase } from "/$/base";
import { useCool } from "/@/cool";
import dayjs from "dayjs";
import { onBeforeRouteLeave } from "vue-router";
import { useViewGroup } from "/@/plugins/view";
import DeviceIcon from "../static/icon/device.png";
const { service, refs, setRefs, mitt } = useCool();
const { copy } = useClipboard();
const { user } = useBase();
const mqtt = useMqtt();
const { ViewGroup } = useViewGroup({
label: "设备",
title: "消息列表",
service: service.iot.device,
onEdit(item) {
return {
width: "600px",
items: [
{
label: "设备名称",
prop: "name",
component: {
name: "el-input",
props: {
maxlength: 20,
clearable: true
}
},
required: true
},
{
label: "设备ID",
prop: "uniqueId",
component: {
name: "el-input",
props: {
maxlength: 30,
clearable: true,
disabled: !!item?.id
}
},
required: true
},
{
label: "设备图标",
prop: "icon",
component: {
name: "cl-upload"
}
}
]
};
},
async onSelect(item) {
mqtt.subscribe(item.uniqueId);
await refresh({
page: 1,
deviceId: item.id
});
scrollToBottom();
}
});
//
const loading = ref(false);
//
const value = ref("");
//
const list = ref<Eps.IotMessageEntity>([]);
// id
const uniqueId = computed(() => ViewGroup.value?.selected?.uniqueId);
//
const reqParams = {
page: 1,
size: 20
};
//
let loaded = false;
//
async function refresh(params?: any) {
loading.value = true;
Object.assign(reqParams, {
order: "createTime",
sort: "desc",
...params
});
await service.iot.message
.page(reqParams)
.then((res) => {
const arr = orderBy(res.list, "createTime");
//
if (reqParams.page == 1) {
list.value = arr;
} else {
const s = refs.scrollbar.wrapRef.querySelector("ul");
const h = s.clientHeight;
list.value.unshift(...arr);
nextTick(() => {
refs.scrollbar.scrollTo(0, s.clientHeight - h);
});
}
//
loaded = res.pagination.total <= list.value.length;
})
.catch((err) => {
ElMessage.error(err.message);
});
loading.value = false;
}
//
const scrollToBottom = debounce(() => {
nextTick(() => {
refs.scrollbar?.wrapRef?.scroll({
top: 100000 + Math.random(),
behavior: "smooth"
});
});
});
//
function onScroll({ scrollTop }: { scrollTop: number }) {
if (scrollTop == 0 && !loaded) {
refresh({
page: reqParams.page + 1
});
}
}
//
function send() {
service.iot.mqtt
.publish({
uniqueId: uniqueId.value,
data: value.value
})
.then(() => {
append({
data: value.value,
type: 0
});
value.value = "";
});
}
//
function append(data: Eps.IotMessageEntity) {
list.value.push({
createTime: new Date(),
...data
});
scrollToBottom();
}
//
function onContextMenu(e: Event, item: Eps.IotMessageEntity) {
ContextMenu.open(e, {
hover: {
target: "content"
},
list: [
{
label: "复制",
callback(done) {
copy(item.data || "");
ElMessage.success("复制成功");
done();
}
}
]
});
}
//
function onMessage({ id, message }: any) {
if (uniqueId.value == id) {
append({
type: 1,
data: message
});
}
}
onActivated(() => {
mqtt.connect();
mitt.on("iot.message", onMessage);
});
onBeforeRouteLeave(() => {
mqtt.disconnect();
mitt.off("iot.message");
});
</script>
<style lang="scss" scoped>
.device-item {
display: flex;
padding: 15px 10px;
cursor: pointer;
.icon {
margin-right: 10px;
}
.det {
flex: 1;
.name {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
}
.name {
font-size: 14px;
margin-bottom: 4px;
}
.text {
font-size: 12px;
}
}
.status {
display: flex;
align-items: center;
font-size: 12px;
color: #909399;
&::before {
display: block;
content: "";
height: 8px;
width: 8px;
border-radius: 100%;
background-color: currentColor;
margin-right: 5px;
}
&.is-on {
color: #67c23a;
}
}
&.is-active {
background-color: var(--color-primary);
color: #fff;
}
&:not(.is-active):hover {
background-color: var(--el-fill-color-light);
}
}
.message {
display: flex;
flex-direction: column;
background-color: #fff;
border-radius: 6px;
height: 100%;
box-sizing: border-box;
.list {
flex: 1;
background-color: var(--el-fill-color-lighter);
ul {
& > li {
list-style: none;
.item {
display: flex;
padding: 10px;
.icon {
margin-right: 10px;
}
.det {
.date {
font-size: 12px;
margin: 6px 0 0 0;
padding-left: 5px;
color: #999;
}
.content {
.is-text {
display: inline-block;
background-color: #fff;
padding: 10px;
border-radius: 0 8px 8px 8px;
max-width: 400px;
font-size: 14px;
color: #000;
}
}
}
&.is-right {
flex-direction: row-reverse;
.icon {
margin-left: 10px;
margin-right: 0;
}
.det {
text-align: right;
.content {
.is-text {
border-radius: 8px 0 8px 8px;
background-color: var(--color-primary);
color: #fff;
}
}
}
}
}
}
}
.empty {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
}
.footer {
padding: 10px;
background-color: var(--el-bg-color);
.input {
display: flex;
position: relative;
.el-button {
margin-left: 10px;
position: absolute;
right: 10px;
bottom: 10px;
}
}
}
}
</style>

208
yarn.lock
View File

@ -1366,11 +1366,6 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
before-after-hook@^2.2.0:
version "2.2.3"
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c"
@ -1381,15 +1376,6 @@ binary-extensions@^2.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
bl@^4.0.2:
version "4.1.0"
resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
dependencies:
buffer "^5.5.0"
inherits "^2.0.4"
readable-stream "^3.4.0"
boolbase@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
@ -1432,14 +1418,6 @@ buffer-from@^1.0.0:
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
buffer@^5.5.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.1.13"
call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
@ -1571,14 +1549,6 @@ commander@^9:
resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30"
integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==
commist@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/commist/-/commist-1.1.0.tgz#17811ec6978f6c15ee4de80c45c9beb77cee35d5"
integrity sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==
dependencies:
leven "^2.1.0"
minimist "^1.1.0"
compute-scroll-into-view@^1.0.20:
version "1.0.20"
resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz#1768b5522d1172754f5d0c9b02de3af6be506a43"
@ -1589,16 +1559,6 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
concat-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1"
integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==
dependencies:
buffer-from "^1.0.0"
inherits "^2.0.3"
readable-stream "^3.0.2"
typedarray "^0.0.6"
convert-source-map@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
@ -1646,7 +1606,7 @@ dayjs@^1.11.10, dayjs@^1.11.3:
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0"
integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==
debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@ -1712,16 +1672,6 @@ dom7@^3.0.0:
dependencies:
ssr-window "^3.0.0-alpha.1"
duplexify@^4.1.1:
version "4.1.2"
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0"
integrity sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==
dependencies:
end-of-stream "^1.4.1"
inherits "^2.0.3"
readable-stream "^3.1.1"
stream-shift "^1.0.0"
eastasianwidth@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
@ -1771,13 +1721,6 @@ emoji-regex@^9.2.2:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
end-of-stream@^1.1.0, end-of-stream@^1.4.1:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
dependencies:
once "^1.4.0"
engine.io-client@~6.5.2:
version "6.5.3"
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.5.3.tgz#4cf6fa24845029b238f83c628916d9149c399bc5"
@ -2312,7 +2255,7 @@ glob@^10.3.10:
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
path-scurry "^1.10.1"
glob@^7.0.0, glob@^7.1.3, glob@^7.1.6:
glob@^7.0.0, glob@^7.1.3:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@ -2418,14 +2361,6 @@ hasown@^2.0.0, hasown@^2.0.1:
dependencies:
function-bind "^1.1.2"
help-me@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/help-me/-/help-me-3.0.0.tgz#9803c81b5f346ad2bce2c6a0ba01b82257d319e8"
integrity sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==
dependencies:
glob "^7.1.6"
readable-stream "^3.6.0"
html-tags@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce"
@ -2443,11 +2378,6 @@ i18next@^20.4.0:
dependencies:
"@babel/runtime" "^7.12.0"
ieee754@^1.1.13:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
ignore@^5.2.0, ignore@^5.2.4:
version "5.3.1"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef"
@ -2484,7 +2414,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@^2.0.3, inherits@^2.0.4:
inherits@2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@ -2680,11 +2610,6 @@ jackspeak@^2.3.5:
optionalDependencies:
"@pkgjs/parseargs" "^0.11.0"
js-sdsl@4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.3.0.tgz#aeefe32a451f7af88425b11fdb5f58c90ae1d711"
integrity sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==
js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@ -2738,11 +2663,6 @@ keyv@^4.5.3:
dependencies:
json-buffer "3.0.1"
leven@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580"
integrity sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==
levn@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
@ -2902,11 +2822,6 @@ minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
dependencies:
brace-expansion "^1.1.7"
minimist@^1.1.0, minimist@^1.2.5:
version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
"minipass@^5.0.0 || ^6.0.2 || ^7.0.0":
version "7.0.4"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c"
@ -2933,38 +2848,6 @@ monaco-editor@0.36.0:
pin-github-action "^1.8.0"
shelljs "^0.8.5"
mqtt-packet@^6.8.0:
version "6.10.0"
resolved "https://registry.yarnpkg.com/mqtt-packet/-/mqtt-packet-6.10.0.tgz#c8b507832c4152e3e511c0efa104ae4a64cd418f"
integrity sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA==
dependencies:
bl "^4.0.2"
debug "^4.1.1"
process-nextick-args "^2.0.1"
mqtt@^4.3.7:
version "4.3.8"
resolved "https://registry.yarnpkg.com/mqtt/-/mqtt-4.3.8.tgz#b8cc9a6eb5e4e0cb6eea699f24cd70dd7b228f1d"
integrity sha512-2xT75uYa0kiPEF/PE0VPdavmEkoBzMT/UL9moid0rAvlCtV48qBwxD62m7Ld/4j8tSkIO1E/iqRl/S72SEOhOw==
dependencies:
commist "^1.0.0"
concat-stream "^2.0.0"
debug "^4.1.1"
duplexify "^4.1.1"
help-me "^3.0.0"
inherits "^2.0.3"
lru-cache "^6.0.0"
minimist "^1.2.5"
mqtt-packet "^6.8.0"
number-allocator "^1.0.9"
pump "^3.0.0"
readable-stream "^3.6.0"
reinterval "^1.1.0"
rfdc "^1.3.0"
split2 "^3.1.0"
ws "^7.5.5"
xtend "^4.0.2"
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
@ -3024,14 +2907,6 @@ nth-check@^2.1.1:
dependencies:
boolbase "^1.0.0"
number-allocator@^1.0.9:
version "1.0.14"
resolved "https://registry.yarnpkg.com/number-allocator/-/number-allocator-1.0.14.tgz#1f2e32855498a7740dcc8c78bed54592d930ee4d"
integrity sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==
dependencies:
debug "^4.3.1"
js-sdsl "4.3.0"
object-inspect@^1.13.1:
version "1.13.1"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2"
@ -3052,7 +2927,7 @@ object.assign@^4.1.5:
has-symbols "^1.0.3"
object-keys "^1.1.1"
once@^1.3.0, once@^1.3.1, once@^1.4.0:
once@^1.3.0, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
@ -3212,24 +3087,11 @@ prismjs@^1.23.0:
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12"
integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==
process-nextick-args@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
pump@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
dependencies:
end-of-stream "^1.1.0"
once "^1.3.1"
punycode@^2.1.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
@ -3240,15 +3102,6 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
version "3.6.2"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
dependencies:
inherits "^2.0.3"
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
@ -3278,11 +3131,6 @@ regexp.prototype.flags@^1.5.2:
es-errors "^1.3.0"
set-function-name "^2.0.1"
reinterval@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/reinterval/-/reinterval-1.1.0.tgz#3361ecfa3ca6c18283380dd0bb9546f390f5ece7"
integrity sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@ -3312,11 +3160,6 @@ reusify@^1.0.4:
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
rfdc@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.1.tgz#2b6d4df52dffe8bb346992a10ea9451f24373a8f"
integrity sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==
rimraf@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
@ -3373,11 +3216,6 @@ safe-array-concat@^1.1.0:
has-symbols "^1.0.3"
isarray "^2.0.5"
safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safe-regex-test@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377"
@ -3545,13 +3383,6 @@ source-map@^0.7.4:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656"
integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==
split2@^3.1.0:
version "3.2.2"
resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f"
integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==
dependencies:
readable-stream "^3.0.0"
ssf@~0.11.2:
version "0.11.2"
resolved "https://registry.yarnpkg.com/ssf/-/ssf-0.11.2.tgz#0b99698b237548d088fc43cdf2b70c1a7512c06c"
@ -3569,12 +3400,8 @@ store@^2.0.12:
resolved "https://registry.yarnpkg.com/store/-/store-2.0.12.tgz#8c534e2a0b831f72b75fc5f1119857c44ef5d593"
integrity sha512-eO9xlzDpXLiMr9W1nQ3Nfp9EzZieIQc10zPPMP5jsVV7bLOziSFFBP0XoDXACEIFtdI+rIz0NwWVA/QVJ8zJtw==
stream-shift@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.3.tgz#85b8fab4d71010fc3ba8772e8046cc49b8a3864b"
integrity sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
name string-width-cjs
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -3619,13 +3446,6 @@ string.prototype.trimstart@^1.0.7:
define-properties "^1.2.0"
es-abstract "^1.22.1"
string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
dependencies:
safe-buffer "~5.2.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
@ -3795,11 +3615,6 @@ typed-array-length@^1.0.4:
is-typed-array "^1.1.13"
possible-typed-array-names "^1.0.0"
typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==
typescript@^5.2.2:
version "5.3.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37"
@ -3845,7 +3660,7 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"
util-deprecate@^1.0.1, util-deprecate@^1.0.2:
util-deprecate@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
@ -3984,6 +3799,7 @@ word@~0.3.0:
integrity sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
name wrap-ansi-cjs
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@ -4006,11 +3822,6 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
ws@^7.5.5:
version "7.5.9"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591"
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
ws@~8.11.0:
version "8.11.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143"
@ -4039,11 +3850,6 @@ xmlhttprequest-ssl@~2.0.0:
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67"
integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==
xtend@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
y18n@^5.0.5:
version "5.0.8"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"