2024年10月

Posted on:October 1, 2024 at 09:04 PM
预计阅读时长:74 min read 字数:14773
《我与地坛》

感慨

他跨越了我二十多岁的两个阶段,从上海到北京,从悠闲到忙碌,从《生死疲劳》和《悉达多》到《沉默的大多数》,《被讨厌的勇气》,《剑来》,从cordova,vue2到jecloud,vue3,cesium,webrtc

目录

2024-10-01 22:45:16

在归途,动车上写一些代码,看一些书,观察一些群,看看一些股票,了解一些新闻

2024-10-02 05:03:40

创造力比公司上班多很多,虽然身体非常之疲惫

2024-10-04 09:14:35

早起于山间,代码和数学,哇咔咔咔咔

和平万岁,祖国万岁,共产党万岁

2024-10-05 18:30:38

非农数据表示什么

非农数据,全称是“非农业就业人数变动”,指的是美国每月新增的非农业就业人口数量。 它是由美国劳工统计局 (Bureau of Labor Statistics, BLS) 每月发布的一项重要经济指标。

这个数据不包含以下几种就业人口:

  • 农业就业人口: 从事农业生产相关工作的人员。
  • 政府雇员: 联邦政府雇员(例如,军队人员)。
  • 私人家庭雇员: 例如保姆、家政人员等。
  • 非营利组织雇员: 例如慈善机构、宗教组织的雇员。
  • 自雇人士: 自己经营生意的人员。

非农数据反映的是美国就业市场的整体状况,是衡量美国经济健康程度的重要指标之一。 它通常被用来预测经济增长、通货膨胀和利率变化等。

非农数据对市场的影响:

非农数据公布后,通常会对金融市场产生显著影响,尤其是在外汇市场和股票市场。 如果非农数据好于预期(新增就业人数多于预期),通常被认为是经济向好信号,美元汇率可能上涨,股市也可能上涨。 反之,如果非农数据差于预期,则可能导致美元汇率下跌,股市下跌。 当然,市场反应也受到其他因素的影响,并非完全取决于非农数据本身。

总而言之,非农数据是一个综合性的经济指标,它反映了美国就业市场的状况,并对金融市场产生重要的影响。 投资者和经济学家密切关注非农数据,以预测经济走势并做出相应的投资决策。

2024-10-09 15:15:58

A股可以做空吗

A股市场目前允许一定程度的做空,但与成熟市场相比,机制和渠道相对有限。主要有以下几种方式:

  1. 融券做空: 这是A股市场最主要的做空方式。投资者可以向券商借入股票,然后在市场上卖出,等待股价下跌后再买回股票归还给券商,从而赚取差价。 123 融券做空需要投资者具备一定的资格条件,并且需要支付一定的利息和费用。

  2. 股指期货: 通过股指期货,投资者可以押注股市指数的涨跌。如果预期市场下跌,可以卖出股指期货合约,待指数下跌后再买回平仓,从而获利。6 股指期货交易的杠杆效应较高,风险也相对较大。

  3. 上证50ETF期权: 与股指期货类似,ETF期权也提供了做空市场的工具。投资者可以买入看跌期权,或者卖出看涨期权,从而在市场下跌时获利。

需要注意的是,A股市场做空机制仍不够完善,存在一些限制,例如:

  • 融券券源有限:并非所有股票都可以融券,且可融券的股票数量有限,这限制了做空者的操作空间。1
  • 做空成本较高:融券利息、交易佣金等成本会降低做空的收益。1
  • 制度限制:A股市场对做空交易有一些限制,例如禁止裸卖空等。2

总而言之,虽然A股可以做空,但操作难度和风险都比较高,投资者需要谨慎操作,深入研究市场,并控制好风险。 5 毕竟,做空就像刀尖上跳舞,高收益伴随着高风险。

2024-10-09 15:41:29

p5js,提供一个酒瓶的3d模版,实现贴图和贴文字

bottle

查看页面

import type p5 from "p5";
import Basic from "@components/react/p5/index.tsx";
import { useCallback, useRef, useState } from "react";

let img;
let bottleModel;
let font;
let canvas;
let button: p5.Element;
const colors = [
  { background: "#FF5733", text: "#FFFFFF" }, // 背景:红色,字体:白色
  { background: "#33FF57", text: "#000000" }, // 背景:绿色,字体:黑色
  { background: "#3357FF", text: "#FFFFFF" }, // 背景:蓝色,字体:白色
  { background: "#F1C40F", text: "#000000" }, // 背景:黄色,字体:黑色
  { background: "#8E44AD", text: "#FFFFFF" }, // 背景:紫色,字体:白色
  { background: "#E67E22", text: "#FFFFFF" }, // 背景:橙色,字体:白色
  { background: "#2ECC71", text: "#FFFFFF" }, // 背景:亮绿色,字体:白色
  { background: "#3498DB", text: "#FFFFFF" }, // 背景:亮蓝色,字体:白色
  { background: "#9B59B6", text: "#FFFFFF" }, // 背景:淡紫色,字体:白色
  { background: "#F39C12", text: "#FFFFFF" }, // 背景:金色,字体:白色
];
export default () => {
  const container = useRef(null);
  const fileInput = useRef(null);
  const [imgUrl, setImgUrl] = useState(8);

  const randomImg = () => {
    let num = ((Math.random() * 11) % 11) + 1;
    num = Number(num.toFixed(0));
    setImgUrl(num);
  };

  const sketch = useCallback(
    (p: p5) => {
      const setup = () => {
        canvas = p.createCanvas(p.windowWidth, p.windowHeight - 200, p.WEBGL);
        canvas.drop(gotFile);
        p.textFont(font);
        button = p
          .createButton("切换背景颜色")
          .position(20, 100)
          .mousePressed(randomColor);

        fileInput.current.addEventListener(
          "change",
          e => {
            let file = e.target.files[0];
            if (!file) return;
            const reader = new FileReader();
            reader.onload = event => {
              const fileData = event.target.result;
              gotFile({
                type: "image",
                data: fileData,
              });
            };
            reader.onerror = error => {
              console.error("读取文件时出错:", error);
            };
            reader.readAsDataURL(file);
          },
          false
        );
      };

      function randomColor() {
        let c = Math.floor(Math.random() * colors.length);
        button.value(c);
      }

      const preload = () => {
        img = p.loadImage(
          `/assets/bg/${imgUrl}${Math.random() > 0.5 ? ".jpg" : ".png"}`
        );
        font = p.loadFont("/assets/font/Xingcao.ttf");
        bottleModel = p.loadModel(
          "/assets/models/obj/up_glucose_bottle.obj",
          true,
          () => {},
          () => {},
          ".obj"
        );
      };

      const gotFile = (file: Pick<p5.File, "type" | "data">) => {
        if (file.type === "image") {
          img = p.createImg(file.data, "").hide();
          img.elt.onload = () => {
            randomColor();
            p.redraw();
          };
        }
      };

      const draw = () => {
        p.textureMode(p.IMAGE);
        p.background(colors[(button.value() as number) || 0].background);
        p.orbitControl();
        p.push();
        p.scale(2);
        p.rotateZ(p.PI);
        p.rotateY(-p.PI / 2);
        if (bottleModel) {
          p.texture(img); // 应用贴图
          p.model(bottleModel); // 绘制模型
        } else {
          console.error("Bottle model is not loaded");
        }
        p.push();
        p.translate(-100, 0, 0); // 将文字放置在模型旁边

        p.rotateY(-p.PI / 2);
        p.rotateX(-p.PI);

        p.fill(colors[(button.value() as number) || 0].text);
        p.text("尝试拖动图片进场景或者点击上传", 0, 0);
        p.pop();
      };
      const resize = () => {
        p.resizeCanvas(p.windowWidth, p.windowHeight - 200);
        p.redraw();
      };
      p.setup = setup;
      p.draw = draw;
      p.preload = preload;
      p.windowResized = resize;
    },
    [imgUrl]
  );

  return (
    <div ref={container} className="absolute inset-0 w-full h-full">
      <Basic
        sketch={sketch}
        className="min-h-[80%] flex justify-center items-center"
      ></Basic>
      <input
        type="file"
        id="input"
        accept="image/*"
        className="hidden"
        ref={fileInput}
      />

      <label
        htmlFor="input"
        className="block cursor-pointer text-center self-center p-4 m-auto font-semibold text-2xl xing-cao"
      >
        点击上传材质
      </label>

      <p
        onClick={randomImg}
        className="block cursor-pointer text-center self-center p-4 m-auto font-semibold text-2xl xing-cao"
      >
        或者点击使用随机图片
      </p>
    </div>
  );
};
  • 2024-10-20 15:08:39

优化了一下,使用库colorthief实现,随机图片获取主题颜色

2024-10-10 09:28:50

loadScript

const loadScript = (src, callback) => {
  try {
    const script = document.createElement("script");
    script.src = src;
    script.onload = callback;
    script.onerror = () => alert(`Failed to load script: ${src}`);
    document.head.appendChild(script);
  } catch (e) {
    alert(e.message);
  }
};
loadScript("https://cdn.jsdelivr.net/npm/eruda@3.4.0/eruda.min.js", () =>
  eruda.init()
);

2024-10-10 15:51:33

今日ts代码分析

Carousel image 1
Carousel image 2
Carousel image 3
Carousel image 4
Carousel image 5

calendar汇总

这些个日历样式有些问题,和博客的主题冲突了

react-big-calendar
Click to expand

2024-10-11 09:34:21

今日类型体操

实现Pick<T, K extends keyof T>

type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

使用示例

type Person = {
  name: string;
  age: number;
  email: string;
};

// 使用 MyPick 从 Person 中选择 name 和 email 属性
type PickedPerson = MyPick<Person, "name" | "email">;

// PickedPerson 的类型为 { name: string; email: string; }

实现Readonly<T>

type MyReadonly<T> = {
  readonly [P in keyof T]: T[P];
};

MyReadonly<T> 只会对第一层属性生效。如果对象的属性本身是一个对象,嵌套的属性仍然是可写的。

type User = {
  name: string;
  address: {
    city: string;
    country: string;
  };
};

type ReadonlyUser = MyReadonly<User>;

const user: ReadonlyUser = {
  name: "Alice",
  address: {
    city: "New York",
    country: "USA",
  },
};

// 下面的操作会导致编译错误
user.name = "Bob"; // 错误: Cannot assign to 'name' because it is a read-only property.

// 但是可以修改嵌套对象的属性
user.address.city = "Los Angeles"; // 这是允许的

ts文档中推荐使用Object.freeze

function freeze<Type>(obj: Type): Readonly<Type>;

今日金融小知识

股票市场是否要跟涨杀跌

在股票市场中,“跟涨杀跌” 是一种常见的投资策略,指的是在市场上涨时跟随买入,而在市场下跌时则选择卖出或回避。这种策略的核心思想是顺应市场趋势,利用市场情绪来获取收益。以下是一些关于这一策略的分析:

1. 趋势跟随

跟涨杀跌的策略基于趋势跟随的原则。许多投资者相信,市场在短期内会持续其趋势,因此在上涨时买入,能够在价格进一步上涨时获利。这种策略在牛市中尤其有效,因为市场普遍情绪乐观,投资者更愿意买入。

2. 风险管理

尽管跟涨杀跌可以在某些情况下带来收益,但它也伴随着风险。在市场下跌时,盲目跟随可能导致更大的损失。投资者需要有良好的风险管理策略,例如设置止损点,以限制潜在的损失。

3. 市场情绪

股票市场受到投资者情绪的影响,跟涨杀跌策略往往依赖于市场情绪的变化。在市场情绪高涨时,投资者可能会过度乐观,导致价格泡沫;而在市场恐慌时,投资者可能会过度悲观,导致价格下跌。因此,理解市场情绪变化对于成功实施这一策略至关重要。

4. 基本面分析

虽然跟涨杀跌是一种短期策略,但长期投资者通常会结合基本面分析来做出决策。即使市场在上涨,投资者也应该关注公司的基本面,以确保其股票的价值是合理的。

5. 适合的投资者

跟涨杀跌策略适合那些能够快速反应市场变化的投资者,尤其是短线交易者。然而,对于那些更倾向于长期投资的投资者,可能更适合采用价值投资或定投策略。

总结

跟涨杀跌是一种基于市场趋势的投资策略,能够在特定市场环境下带来收益。然而,投资者需要谨慎对待,结合风险管理和基本面分析,以避免因市场波动而造成的损失。了解市场情绪和趋势变化是成功实施这一策略的关键。

有关交易费用

股票市场的交易费用通常由多个部分组成,具体计算方式可能因不同的券商和交易类型而异。以下是一些主要的交易费用组成部分:

1. 佣金费

这是券商收取的主要费用,通常以交易金额的一定比例或固定金额的形式收取。佣金费的计算方式有几种:

  • 固定佣金:无论交易金额大小,佣金都是固定的,例如每笔交易收取20元。
  • 比例佣金:根据交易金额的一定比例收取,例如0.1%。
  • 混合佣金:有些券商可能会结合固定佣金和比例佣金,例如最低收取50元或交易金额的2%,以较高者为准。

2. 交易所费用

在某些市场,交易所会收取额外的费用,这些费用通常是基于交易的数量或金额。例如,证券交易所可能会收取每笔交易的交易费。

3. 印花税

在许多国家,股票交易还需支付印花税。这是一种政府征收的税费,通常在股票买卖时按交易金额的一定比例收取。例如,在中国,股票买卖的印花税通常为成交金额的0.1%。

4. 其他费用

  • 监管费用:例如,证券交易委员会(SEC)可能会收取的费用。
  • 结算费用:在某些情况下,结算和交割过程可能会产生额外费用。
  • 账户维护费:一些券商可能会收取账户维护费,尤其是对于低活跃度的账户。

计算示例

假设你通过某券商买入价值10,000元的股票,佣金为0.1%,印花税为0.1%,交易所费用为10元,计算如下:

  • 佣金费:10,000元 * 0.1% = 10元
  • 印花税:10,000元 * 0.1% = 10元
  • 交易所费用:10元

总交易费用 = 佣金费 + 印花税 + 交易所费用 = 10元 + 10元 + 10元 = 30元。

总结

股票市场的交易费用由佣金、交易所费用、印花税及其他相关费用组成。具体费用的计算方式可能因券商和市场而异,因此在交易前了解清楚相关费用是非常重要的。

今日创意编程

鼠标点击黑色框框

今日知识

aab

Android App Bundle

AAB文件,即Android App Bundle,是一种Android应用程序的发布文件格式。它包含了应用程序的所有编译代码和资源,开发者在完成应用开发后,将其以AAB格式上传到Google Play商店。用户在下载应用时,实际上是从Google Play获取一个根据其设备配置生成的APK文件,而不是直接下载AAB文件14

AAB文件的主要优势在于它能够根据用户设备的特定需求生成优化的APK文件。这意味着,用户只会下载适合其设备的必要资源,从而节省存储空间。例如,如果用户的设备只支持Full HD分辨率,下载的APK将不会包含4K资源2。此外,AAB文件还提高了应用的安全性,因为APK的签名将由Google而非开发者进行,这样可以减少应用被篡改的风险15

尝试将excalidraw组件模式化

Click to expand
  • 甚至可以控制是否只读

2024-10-12 11:16:19

今日类型体操

MyReadonly2

type MyReadonly2<T, K extends keyof T = keyof T> = Omit<T, K> &
  Readonly<Pick<T, K>>;

或者

type MyReadonly2<T, K extends keyof T = keyof T> = Omit<T, K> & {
  readonly [P in K]: T[P];
};

DeepReadonly

type Primitive = string | number | boolean | bigint | symbol | undefined | null;
type Builtin = Primitive | Function | Date | Error | RegExp;

type DeepReadonly<T> = T extends Builtin
  ? T
  : T extends Map<infer K, infer V>
    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
    : T extends ReadonlyMap<infer K, infer V>
      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
      : T extends WeakMap<infer K, infer V>
        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>
        : T extends Set<infer U>
          ? ReadonlySet<DeepReadonly<U>>
          : T extends ReadonlySet<infer U>
            ? ReadonlySet<DeepReadonly<U>>
            : T extends WeakSet<infer U>
              ? WeakSet<DeepReadonly<U>>
              : T extends Promise<infer U>
                ? Promise<DeepReadonly<U>>
                : T extends {}
                  ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
                  : Readonly<T>;

2024-10-14 10:50:00

今日ui

今日类型体操 - 元祖转集合

type TupleToUnion<T> = T extends Array<infer ITEM> ? ITEM : never;
explain it

当然可以!让我们来逐行解析这段 TypeScript 代码,看看它是如何工作的。

type TupleToUnion<T> = T extends Array<infer ITEM> ? ITEM : never;

代码解析

  1. type TupleToUnion<T>:

    • 这里我们定义了一个类型别名 TupleToUnion,它接受一个类型参数 T。这个类型参数通常是一个元组(Tuple)或数组(Array)。
  2. T extends Array<infer ITEM>:

    • 这一部分是一个条件类型(conditional type)。它的意思是:如果 T 是一个数组类型(即 Array),那么我们就可以从这个数组中提取出它的元素类型。
    • infer ITEM 是 TypeScript 的一种推断机制,它会自动推断出数组中元素的类型,并将其命名为 ITEM
  3. ? ITEM : never:

    • 这是条件类型的结果部分。如果 T 是一个数组类型,那么 TupleToUnion<T> 的结果就是 ITEM(即数组中的元素类型)。
    • 如果 T 不是数组类型,那么 TupleToUnion<T> 的结果就是 never,表示没有有效的类型。

整体功能

  • 目标: TupleToUnion 类型的主要目标是将一个元组或数组类型转换为其元素类型的联合类型(Union Type)。如果传入的类型是一个数组,返回数组元素的类型;如果不是数组,返回 never

示例

让我们通过一些示例来更好地理解这个类型的作用:

type A = TupleToUnion<[1, 2, 3]>; // A 的类型是 1 | 2 | 3
type B = TupleToUnion<["a", "b", "c"]>; // B 的类型是 'a' | 'b' | 'c'
type C = TupleToUnion<number[]>; // C 的类型是 number
type D = TupleToUnion<number>; // D 的类型是 never
  • 在这些示例中,AB 是元组类型,因此 TupleToUnion 返回了它们的元素类型的联合类型。
  • C 是一个数组类型,返回了 number,表示数组中的元素类型。
  • D 不是数组类型,所以 TupleToUnion 返回了 never,表示没有有效的类型。

总结

这段代码的核心在于利用 TypeScript 的条件类型和类型推断功能,帮助我们在处理类型时更加灵活和高效。通过 TupleToUnion 类型,我们可以轻松地将元组或数组的元素类型转换为联合类型,简化类型定义的复杂性。

主要还是infer的作用

2024-10-15 09:44:19

今日类型体操 - TupleToObject

type TupleToObject<T extends readonly PropertyKey[]> = {
  [k in T[number]]: k;
};
逐行解析

当然可以!让我们来逐行解析这段 TypeScript 代码,看看它是如何工作的。

type TupleToObject<T extends readonly PropertyKey[]> = { [k in T[number]]: k };

代码解析

  1. type TupleToObject<T extends readonly PropertyKey[]>:

    • 这里我们定义了一个类型别名 TupleToObject,它接受一个类型参数 T
    • T 被限制为一个只读的数组(readonly),数组的元素类型是 PropertyKeyPropertyKey 是 TypeScript 中的一个内置类型,表示可以用作对象属性键的类型,包括 stringnumbersymbol
  2. { [k in T[number]]: k }:

    • 这一部分是一个映射类型(Mapped Type),它的作用是将 T 中的每个元素转换为对象的属性。
    • T[number] 是一种索引访问类型,它表示 T 数组中的所有元素类型。换句话说,它会提取出 T 数组中的所有元素。
    • k in T[number] 表示我们要遍历 T 中的每一个元素,并将其赋值给 k
    • : k 表示每个属性的值就是它的键(k)。因此,最终生成的对象的每个属性名和属性值都是相同的。

整体效果

这个类型的主要目标是将一个元组(或只读数组)转换为一个对象,其中每个元素都成为对象的一个属性,属性名和属性值相同。例如:

type MyTuple = ["a", "b", "c"];
type MyObject = TupleToObject<MyTuple>;
// 结果是:
// type MyObject = {
//   a: 'a';
//   b: 'b';
//   c: 'c';
// }

总结

  • TupleToObject 是一个非常有用的工具类型,可以将元组转换为对象,方便在 TypeScript 中进行类型推导和类型安全的操作。
  • 这种类型转换在处理动态属性名时特别有用,比如在构建 API 响应模型或处理配置对象时。
关于T[number]

好的,让我们深入探讨一下 T[number] 中的 number 是从哪里来的。

在 TypeScript 中,T[number] 是一种索引访问类型,它的作用是提取数组或元组 T 中的元素类型。这里的 number 是用来表示数组的索引类型。

详细解释

  1. T 是一个只读数组:

    • 在你的代码中,T 被定义为 readonly PropertyKey[],这意味着 T 是一个只读的数组,数组的元素可以是 stringnumbersymbol
  2. T[number] 的含义:

    • number 在这里并不是指某个具体的数字,而是 TypeScript 中的一个特殊类型,用于表示所有可能的数组索引。
    • 当你写 T[number] 时,TypeScript 会理解为“获取 T 数组中所有元素的类型”。换句话说,它会提取出 T 中的每一个元素类型。

举个例子

假设我们有一个元组:

type MyTuple = ["a", "b", "c"];
  • 当我们使用 MyTuple[number] 时,TypeScript 会返回 'a' | 'b' | 'c',这表示 MyTuple 中的所有元素类型的联合。

总结

  • numberT[number] 中是一个索引类型,用于提取数组或元组中的元素类型。
  • 通过这种方式,我们可以动态地获取数组中的所有元素类型,从而在映射类型中使用它们。

今日ui

scroll to select

2024-10-16 10:39:07

今日探索

osm buildings

城市建筑2.5

今日类型体操 - 可串联构造器

type Chainable<T = {}> = {
  option: <K extends string, V>(
    key: K extends keyof T ? never : K,
    value: V
  ) => Chainable<Omit<T, K> & Record<K, V>>;
  get: () => T;
};
explain

这段代码定义了一个 TypeScript 的类型 Chainable,它用于实现链式调用的接口。下面是对这段代码的逐步解释:

1. 类型定义

type Chainable<T = {}> = { ... }
  • Chainable 是一个泛型类型,默认参数 T 是一个空对象 {}。这个类型用于表示一个可以链式调用的方法。

2. option 方法

option: <K extends string, V>(key: K extends keyof T ? never : K, value: V) =>
  Chainable<Omit<T, K> & Record<K, V>>;
  • optionChainable 类型的一个方法。
  • <K extends string, V> 是一个泛型参数,K 是一个字符串类型,V 是任意类型。
  • key 参数的类型是 K extends keyof T ? never : K
    • 这意味着如果 K 已经是 T 的一个键,则 key 的类型为 never,即不允许使用已经存在的键。这确保了在链式调用中,不能重复定义相同的键。
    • 如果 K 不是 T 的键,则 key 的类型为 K
  • value 的类型是 V,表示可以传入任意类型的值。
  • 返回值是 Chainable<Omit<T, K> & Record<K, V>>
    • Omit<T, K> 会从 T 中移除键 K,确保在链式调用中不会重复定义。
    • Record<K, V> 创建一个新的对象类型,其中包含键 K 和对应的值 V
    • 最终返回一个新的 Chainable 类型,包含了更新后的属性。

Typescript类型语法规则:

TypeScript 有两个“顶层类型”(anyunknown),但是“底层类型”只有never唯一一个。 never类型可以赋值给其他任意类型,但是其他类型不能复制给never 当K在T存在,返回never类型,此时其他类型传入就会报错 key: K extends keyof T ? never : K

3. get 方法

get: () => T;
  • getChainable 类型的另一个方法。
  • 返回值类型是 T,表示获取当前链式调用的结果。

总结

这个 Chainable 类型允许你通过 option 方法链式地添加属性,同时确保不会重复添加相同的属性。最终,通过 get 方法可以获取到一个包含所有添加属性的对象。

示例

以下是如何使用这个 Chainable 类型的示例:

declare const config: Chainable;

const result = config
  .option("foo", 123)
  .option("name", "type-challenges")
  .option("bar", { value: "Hello World" })
  .get();

// result 的类型为 { foo: number, name: string, bar: { value: string } }

在这个示例中,result 将会是一个包含所有通过 option 方法添加的属性的对象。

2024-10-17 17:57:33

今日类型体操 - hello world

type HelloWorld = string; // expected to be a string

ok,今天的体操就到这里吧,太忙了

2024-10-20 14:17:33

今日类型体操 - 数组的最后一个元素的类型

type Last<T extends any[]> = T extends [...infer rest, infer A] ? A : never;

耶,终于会用一点infer

同理可完成第16题

type Pop<T extends any[]> = T extends [...infer rest, infer A] ? rest : [];
  • 柯里化

柯里化

今日感慨

不好说

几个老板老板娘蛮逆天的,周末拉着人过来PUA他们

2024-10-23 11:43:07

今日cesium

  • 新增目录src/components/react/cesium/reference练习练习cesium reference

  • template

Click to expand
  • 在useEffect中初始化Viewer后,创建一个BillboardCollection并添加多个Billboard
Click to expand
// 创建一个BillboardCollection
const billboards = new BillboardCollection();
viewer.scene.primitives.add(billboards);

// 添加几个Billboard
billboards.add({
  position: Cartesian3.fromDegrees(-75.59777, 40.03883, 0), // 经度, 纬度, 高度
  image: "/assets/bg/1.jpg", // 替换为你的图片URL
  width: 100,
  height: 100,
});

今日金融

涨停板委托买入股票什么时候完成交易

涨停板委托买入股票的交易完成时间取决于几个因素:

  1. 涨停板的定义:涨停板是指股票在交易日内价格上涨到交易所规定的最高限价,通常是前一交易日收盘价的某个百分比(例如10%)。一旦股票价格达到涨停,买入委托单只能在涨停价或更低的价格成交。

  2. 委托单的类型:如果你使用的是限价单(例如以涨停价买入),那么只有当市场价格达到或低于你设定的限价时,委托单才会被执行。如果市场价格没有回落到你的限价,委托单可能不会成交。

  3. 市场流动性:在涨停板情况下,通常会有大量买入委托,这可能导致成交速度较慢,甚至可能出现排队现象。如果买入需求非常强烈,可能会迅速成交;但如果买入需求不足,可能会导致你的委托单无法及时成交。

  4. 交易时间:交易所的交易时间也会影响成交情况。一般来说,股票交易在交易日的特定时间段内进行(例如,A股市场的交易时间为每个交易日的9:30-11:30和13:00-15:00)。

总结来说,涨停板委托买入股票的交易完成时间取决于你设置的委托单类型、市场流动性以及交易时间。如果市场价格在涨停价下方,你的委托单有可能在交易时间内完成交易,但如果价格未能回落到你的限价,委托单可能会一直挂单。

分析指标

CHATGPT以为红色是亏损绿色是增长

2024-10-23 13:28:39截图海能达

根据你提供的图表和财务数据,以下是海能达(Hytera Communications)2024年中报的详细中文分析:

1. **股票价格(当前股价)**:当前价格为¥15.92,下降了10.02%。这个股价表明该公司在最近的交易中表现不佳,可能受市场情绪或其他因素影响。

  1. 市盈率(PE Ratio)89.13,即动态市盈率。这意味着投资者愿意为该公司每赚取一元利润支付约89元。这是一个相对较高的市盈率,可能表明市场对该公司的未来盈利预期较高,但也可能意味着公司被高估。

  2. 市净率(PB Ratio)4.81,市净率反映了公司每股净资产(即账面价值)相对于市场价格的比例。这个值表明该公司股票价格是其净资产的4.81倍,表明市场对其资产的估值较高。

  3. 每股收益(EPS):每股收益为**¥0.0893**,表示公司每一股在报告期内产生的净利润。这一指标用于衡量公司的盈利能力。

  4. 每股净资产:每股净资产为**¥3.3096**,反映公司每股股票拥有的净资产价值。这通常用于评估公司股票的内在价值。

  5. 营业总收入:公司报告期内的总收入为27.38亿元(人民币),表明公司在报告期内的营业表现。

  6. 营业收入同比增长率21.28%,相比去年同期,公司的营业收入增长了21.28%,表明公司业务在扩展。

  7. 归母净利润:归母净利润为1.624亿元,这指的是公司扣除所有费用、税费后,归属于母公司的净利润。

  8. 归母净利润同比增长率109.79%,与去年同期相比,公司的归母净利润增长了109.79%,显示出公司在控制成本或提升利润率方面的显著改善。

  9. 毛利率45.83%,表示公司在扣除销售成本后的毛利润占收入的百分比,反映了公司的盈利能力。

  10. 净利率5.20%,即公司在扣除所有成本和费用后的净利润占总收入的百分比,表明公司最终能够将多少收入转化为净利润。

  11. 净资产收益率(ROE)2.73%,表示公司使用股东投入的资金所能获得的回报率。这个值较低,意味着公司的盈利能力相对一般。

  12. 负债率46.54%,公司资产中有46.54%由负债融资。这一数据表明公司的债务负担较重,需警惕债务偿还的风险。

  13. 总股本18.18亿,表示公司目前发行的总股数。

  14. 总市值289.5亿元,即公司的市场资本总值,是指公司股票在市场上的总估值。这是一个重要的指标,反映了市场对公司的整体估值。

  15. 流通A股12.82亿,表示公司在市场上流通的A股数量。

  16. 流通A股市值204.1亿元,表示公司可在市场上自由交易的A股部分的市值。

  17. 质押比例19.89%,表示公司大约有19.89%的股票被用作抵押,这通常反映公司或者大股东的融资需求,但质押比例过高也可能存在一定风险。

  18. 商誉规模5621万元,即公司在收购或兼并其他企业时所形成的无形资产。商誉较高时需要警惕未来可能的减值风险。

总结:

海能达的总市值为289.5亿元,归母净利润为1.624亿元,显示公司在今年实现了强劲的利润增长(同比增长109.79%)。虽然收入增长迅速,但高市盈率(89.13倍)和较高的负债率(46.54%)意味着该公司在盈利增长和债务管理方面面临一定挑战。投资者应关注公司的盈利持续性以及其在债务偿还方面的风险。

更多知识

websocket心跳机制

Click to expand

WebSocket心跳机制是确保客户端和服务器之间连接保持活跃和响应的一个重要手段。它的主要目的是:

  1. 确认双方活跃性:通过定期发送心跳消息,双方可以确认对方仍然在线。
  2. 检测网络连接:心跳机制可以帮助浏览器及时检查当前网络连接的可用性,以确保消息的及时传递。

WebSocket心跳机制的实现

在WebSocket协议中,通常使用PING/PONG消息来实现心跳机制。具体来说:

  • PING:由一方(通常是服务器)发送,询问另一方是否仍然活跃。
  • PONG:接收方(通常是客户端)必须在收到PING后立即回复PONG,以表示其仍然在线。

根据RFC 6455的规定,接收PING消息的端点必须发送PONG响应,除非它已经接收到关闭帧(CLOSE frame)1

实现示例

以下是一个简单的WebSocket心跳机制的实现示例:

const socket = new WebSocket("ws://your-websocket-url");

// 发送心跳
function sendHeartbeat() {
  if (socket.readyState === WebSocket.OPEN) {
    socket.send("heartbeat");
  }
}

// 每隔30秒发送一次心跳
setInterval(sendHeartbeat, 30000);

// 处理服务器的PONG响应
socket.onmessage = function (event) {
  if (event.data === "pong") {
    console.log("Received PONG from server");
  }
};

// 处理连接关闭
socket.onclose = function (event) {
  console.log("WebSocket closed:", event);
};

注意事项

  • 心跳频率:心跳的发送频率应根据实际需求进行调整,过于频繁可能导致不必要的网络负担。
  • 应用层心跳:在浏览器中,WebSocket并不提供直接的PING/PONG机制,因此如果需要检测连接状态,可能需要在应用层实现心跳机制2

通过实现心跳机制,可以有效地保持WebSocket连接的稳定性,确保实时通信的可靠性。

分析一个我抄的组件(shadcn)

analysis
import * as React from "react";
import * as SliderPrimitive from "@radix-ui/react-slider";

import { cn } from "@utils/utils";

const Slider = React.forwardRef<
  React.ElementRef<typeof SliderPrimitive.Root>,
  React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
>(({ className, ...props }, ref) => (
  <SliderPrimitive.Root
    ref={ref}
    className={cn(
      "relative flex w-full touch-none select-none items-center",
      className
    )}
    {...props}
  >
    <SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
      <SliderPrimitive.Range className="absolute h-full bg-primary" />
    </SliderPrimitive.Track>
    <SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
  </SliderPrimitive.Root>
));
Slider.displayName = SliderPrimitive.Root.displayName;

export { Slider };

这个组件是一个自定义的滑块组件,基于 Radix UI 的 @radix-ui/react-slider 库构建。下面是对这个组件的逐步分析:

1. 导入模块

import * as React from "react";
import * as SliderPrimitive from "@radix-ui/react-slider";
import { cn } from "@utils/utils";
  • React: 引入 React 库。
  • SliderPrimitive: 引入 Radix UI 的滑块组件。
  • cn: 一个工具函数,通常用于合并 CSS 类名。

2. 创建滑块组件

const Slider = React.forwardRef<
  React.ElementRef<typeof SliderPrimitive.Root>,
  React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
>(({ className, ...props }, ref) => (
  <SliderPrimitive.Root
    ref={ref}
    className={cn(
      "relative flex w-full touch-none select-none items-center",
      className
    )}
    {...props}
  >
  • 使用 React.forwardRef 创建一个可以转发 ref 的组件。
  • 组件的类型定义使用 TypeScript,确保 refprops 的类型与 SliderPrimitive.Root 一致。
  • className 属性用于自定义样式,结合 cn 函数来合并默认样式和传入的样式。

3. 滑块的结构

<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
  <SliderPrimitive.Range className="absolute h-full bg-primary" />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
  • Track: 这是滑块的轨道部分,使用了 relativerounded-full 来创建一个圆角的背景。

    • h-2w-full 设置了高度和宽度。
    • bg-secondary 是背景颜色。
  • Range: 这是滑块的范围部分,表示当前选中的值。

    • 使用 absolute 定位,确保它在轨道内。
  • Thumb: 这是滑块的滑动手柄。

    • h-5w-5 设置了手柄的大小。
    • rounded-full 使手柄呈现圆形。
    • border-2border-primary 设置了边框样式。
    • transition-colors 使手柄在状态变化时有平滑的颜色过渡。
    • focus-visibledisabled 状态的样式处理,确保可访问性和用户体验。

4. 组件的显示名称

Slider.displayName = SliderPrimitive.Root.displayName;
  • 设置组件的显示名称,便于调试和开发工具识别。

5. 导出组件

export { Slider };
  • 导出自定义的 Slider 组件,以便在其他地方使用。

总结

这个滑块组件结合了 Radix UI 的基础功能和自定义样式,提供了一个灵活且可访问的滑块实现。通过使用 forwardRef,它可以与父组件的 ref 进行交互,增强了组件的可用性。整体结构清晰,样式可定制,适合在现代前端应用中使用。

关于ForwardedRef

Click to expand
import React from "react";

// 定义输入框的 props 类型
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  // 你可以在这里添加其他自定义的 props
}

// 定义一个输入框组件
const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
  return (
    <input
      ref={ref} // 将 ref 传递给 input 元素
      {...props} // 传递其他 props
      className="border p-2 rounded"
    />
  );
});

// 使用 Input 组件的父组件
const App = () => {
  const inputRef = React.useRef(null); // 创建一个 ref

  const focusInput = () => {
    if (inputRef.current) {
      inputRef.current.focus(); // 使用 ref 聚焦输入框
    }
  };

  return (
    <div>
      <Input ref={inputRef} placeholder="请输入内容" />
      <button onClick={focusInput}>聚焦输入框</button>
    </div>
  );
};

export default App;

在 TypeScript 中使用 React.forwardRef 时,可以通过添加泛型参数来确保类型安全。泛型参数允许你定义组件的 propsref 的类型,从而提供更好的类型推导和检查。

使用步骤

  1. 定义泛型参数:在 React.forwardRef 中定义泛型参数,通常包括 T(表示 ref 的类型)和 P(表示组件 props 的类型)。
  2. 使用泛型参数:在组件的 render 函数中使用这些泛型参数,以确保 propsref 的类型正确。

示例代码

下面是一个使用 TypeScript 和泛型参数的输入框组件示例:

import React, { ForwardedRef } from 'react';

// 定义输入框的 props 类型
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  // 你可以在这里添加其他自定义的 props
}

// 定义一个输入框组件,使用泛型参数
const Input = React.forwardRef<HTMLInputElement, InputProps>(
  (props, ref: ForwardedRef<HTMLInputElement>) => {
    return (
      <input
        ref={ref} // 将 ref 传递给 input 元素
        {...props} // 传递其他 props
        className="border p-2 rounded"
      />
    );
  }
);

// 使用 Input 组件的父组件
const App: React.FC = () => {
  const inputRef = React.useRef<HTMLInputElement | null>(null); // 创建一个 ref

  const focusInput = () => {
    if (inputRef.current) {
      inputRef.current.focus(); // 使用 ref 聚焦输入框
    }
  };

  return (
    <div>
      <Input ref={inputRef} placeholder="请输入内容" />
      <button onClick={focusInput}>聚焦输入框</button>
    </div>
  );
};

export default App;

代码解析

  1. InputProps 接口

    • 定义了输入框的 props 类型,扩展了 React.InputHTMLAttributes<HTMLInputElement>,这使得输入框可以接受所有标准的输入属性。
  2. Input 组件

    • 使用 React.forwardRef 定义组件,泛型参数 <HTMLInputElement, InputProps> 指定了 ref 的类型为 HTMLInputElementprops 的类型为 InputProps
    • render 函数中,ref 被传递给 <input> 元素。
  3. App 组件

    • 创建一个 inputRef,用于引用 Input 组件。
    • 定义 focusInput 函数,通过 inputRef 聚焦输入框。
    • 渲染 Input 组件,并将 inputRef 传递给它。

总结

通过在 React.forwardRef 中使用泛型参数,可以确保组件的 propsref 类型安全。这种方式不仅提高了代码的可读性,还能在开发过程中提供更好的类型检查和自动补全。

添加code-snippets

"ToolBox Component": {
    "prefix": "toolbox",
    "body": [
        "import React from \"react\";",
        "",
        "const ToolBox = React.forwardRef<HTMLDivElement, DivProps>((props, ref) => {",
        "  return (",
        "    <div",
        "      ref={ref} // 将 ref 传递给 input 元素",
        "      {...props} // 传递其他 props",
        "      className=\"border p-2 rounded\"",
        "    />",
        "  );",
        "});",
        "",
        "export default ToolBox;"
    ],
    "description": "创建一个组件"
}

想添加个toolbox

类似于

2024-10-24 09:43:31

今日感慨

Click to expand

海能达又是涨停板,这

今日知识

HDR文件

Click to expand

HDR(高动态范围)文件是一种用于存储高动态范围图像的文件格式。这种格式能够捕捉和存储比传统图像格式更广泛的亮度范围,使得图像在明亮和阴暗区域都能保留更多的细节。HDR 文件通常用于摄影、计算机图形和视觉效果等领域。

常见的 HDR 文件格式:

  1. Radiance HDR (.hdr):一种常见的 HDR 文件格式,支持高动态范围图像的存储,通常用于渲染和计算机图形中。
  2. OpenEXR (.exr):由皮克斯动画公司开发的高动态范围图像格式,广泛用于视觉效果和动画制作。
  3. Photoshop HDR (.psb):Adobe Photoshop 支持的 HDR 文件格式,适合图像编辑和处理。

HDR 文件的特点:

  • 高亮度范围:HDR 文件能够表示比普通图像更高的亮度范围,使得图像更加真实。
  • 色彩深度:HDR 图像通常具有更高的色彩深度,能够显示更多的颜色细节。
  • 后期处理:HDR 图像在后期处理时提供了更多的灵活性,允许用户调整曝光和色彩而不会损失细节。

使用 HDR 文件的注意事项:

  • 兼容性:并非所有图像查看器和编辑软件都支持 HDR 文件格式,因此在使用时需要确保软件的兼容性。
  • 存储空间:HDR 文件通常比普通图像文件大,因此需要更多的存储空间。

如果你需要进一步了解 HDR 文件的具体格式或使用方法,可以参考相关文档和资源,例如 HDR 文件格式文档Radiance HDR 文件格式

window.open高阶用法

Click to expand

window.open 的高级用法主要体现在对新窗口的控制上,包括:

  1. 精准控制窗口特性: windowFeatures 参数不仅仅是设置窗口大小和位置,还能控制菜单栏、工具栏、滚动条、状态栏等等。想创建一个极简的弹出窗口?没问题!

    const windowFeatures =
      "menubar=no,toolbar=no,location=no,status=no,resizable=no,scrollbars=no,width=300,height=200";
    window.open("https://www.example.com", "_blank", windowFeatures);
    

    这样打开的窗口就只有内容区域,没有任何多余的 UI 元素。1 更多 windowFeatures 的选项,可以参考 MDN 文档。

  2. 跨域传递数据 (同域更简单): 如果父子窗口同域,直接操作新窗口对象的属性即可:

    const newWindow = window.open(
      "https://www.example.com/child.html",
      "_blank"
    );
    if (newWindow) {
      // 检查弹窗是否被拦截
      newWindow.myData = { message: "来自父窗口的问候!", value: 123 };
    }
    // 子窗口 child.html 中:
    console.log(window.myData); // { message: '来自父窗口的问候!', value: 123 }
    

    如果是跨域,就需要用一些技巧,比如利用 postMessage 进行跨文档通信,或者使用 URL 的 hash 部分传递少量数据。2

  3. noopener 和 noreferrer 安全属性: 为了安全起见,在新窗口打开外部链接时,最好加上 noopenernoreferrer,防止新窗口通过 window.opener 访问父窗口,造成安全漏洞。

    window.open(
      "https://www.external-site.com",
      "_blank",
      "noopener,noreferrer"
    );
    

    1

  4. 处理弹窗拦截: 现在浏览器对弹窗拦截很严格,window.open 必须在用户交互事件(比如点击事件)的处理函数中调用才不会被拦截。13 可以考虑将打开新窗口的操作绑定到用户的点击操作上。

  5. 复用窗口: 为了避免频繁创建新窗口,可以复用已有的窗口,通过修改窗口的 location.href 来加载不同的页面。3

希望这些技巧能帮助你更好地驾驭 window.open,做出更酷炫的大屏应用!如果你还有其他问题,尽管问我!

2024-10-25 09:11:44

今日金融

股票以st开头说明什么

在中国股市中,以“ST”开头的股票表示该股票属于“特别处理”股票。这通常是因为该公司在最近的财务报告中出现了负面情况,例如连续两年亏损。具体来说,ST股票的含义包括:

  1. 财务状况不佳:ST股票的公司在最近两个会计年度内净利润为负,显示出财务健康状况不佳。

  2. 风险警示:ST股票被视为高风险投资,投资者在购买此类股票时需要格外谨慎。

  3. 交易限制:ST股票在交易时可能会有一些限制,例如涨跌幅限制通常为5%,而非普通股票的10%1

  4. 后续处理:如果ST股票的公司在接下来的年度中仍未能改善财务状况,可能会被进一步处理,例如被标记为“*ST”,这表示公司在连续三年亏损的情况下,面临更严重的风险警示。

因此,投资者在考虑投资ST股票时,应充分了解相关风险和公司的财务状况。

高开/低开

高开和低开是指股票或其他金融工具在交易日开始时的开盘价相对于前一交易日的收盘价的变化。以下是一些可能导致高开或低开的原因:

  1. 市场情绪:如果投资者对某个股票或市场整体持乐观态度,可能会在开盘前大量买入,导致开盘价高于前一日收盘价,形成高开。相反,如果市场情绪悲观,可能会导致低开。

  2. 重大新闻:公司发布财报、并购消息、产品发布或其他重大新闻可能会影响投资者的预期。例如,正面的财报可能导致高开,而负面的消息则可能导致低开。

  3. 经济数据:宏观经济数据的发布(如就业报告、GDP增长率等)也会影响市场情绪,进而影响开盘价。

  4. 全球市场影响:国际市场的波动(如美股、欧洲股市的表现)也会影响本地市场的开盘情况。如果国际市场表现良好,可能会导致高开;反之,则可能导致低开。

  5. 技术因素:技术分析中的支撑和阻力位、趋势线等也可能影响开盘价。如果价格接近支撑位,可能会出现低开;如果接近阻力位,可能会出现高开。

  6. 期货市场:期货合约的交易情况也会影响现货市场的开盘价。如果期货市场表现强劲,现货市场可能会高开;如果期货市场疲软,现货市场可能会低开。

这些因素共同作用,导致了高开或低开的现象。了解这些原因可以帮助投资者更好地把握市场动态。

今日策略

Click to expand

长虹 < -5% || 贝岭 < 1% || 海能达 < 5% else ST华微

今日知识

mac mqtt mosquitto

在 macOS 上使用 Mosquitto MQTT 代理的指南如下:

1. 安装 Homebrew

如果你还没有安装 Homebrew,可以通过以下命令安装:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

2. 安装 Mosquitto

使用 Homebrew 安装 Mosquitto:

brew install mosquitto

3. 启动 Mosquitto

安装完成后,可以使用以下命令启动 Mosquitto:

brew services start mosquitto

这将使 Mosquitto 在系统启动时自动启动。

4. 配置 Mosquitto

Mosquitto 的配置文件通常位于 /usr/local/etc/mosquitto/mosquitto.conf。你可以使用文本编辑器打开并编辑这个文件:

nano /usr/local/etc/mosquitto/mosquitto.conf

在配置文件中,你可以设置监听端口、启用 WebSocket 等选项。

5. 测试 Mosquitto

你可以使用 Mosquitto 提供的客户端工具来测试代理。打开一个终端窗口,订阅一个主题:

mosquitto_sub -t test/topic

在另一个终端窗口中,发布消息到该主题:

mosquitto_pub -t test/topic -m "Hello, MQTT!"

你应该能在订阅的终端窗口中看到 “Hello, MQTT!” 的消息。

6. 设置用户认证(可选)

为了提高安全性,你可以设置用户认证。首先,创建一个密码文件:

mosquitto_passwd -c /usr/local/etc/mosquitto/passwd username

然后在配置文件中添加以下行:

allow_anonymous false
password_file /usr/local/etc/mosquitto/passwd

重启 Mosquitto 以应用更改:

brew services restart mosquitto

7. 其他配置(可选)

你可以根据需要进一步配置 Mosquitto,例如启用 TLS、设置 QoS 等。具体配置可以参考官方文档或相关教程。

这样,你就可以在 macOS 上成功使用 Mosquitto MQTT 代理了!如果需要更详细的配置或使用示例,可以参考相关的在线资源和文档。

2024-10-28 13:28:44

今日探索

lit结合vizzu

show me the code
import { LitElement, html, css } from "lit";
import { customElement } from "lit/decorators.js";
import Vizzu from "vizzu";
import VizzuModule from "vizzu/cvizzu.wasm?url";

@customElement("my-chart-component")
export class MyChartComponent extends LitElement {
  static styles = css`
    #chart {
      width: 80%;
      height: 400px;
      margin: 0 auto;
    }
    button {
      display: block;
      margin: 20px auto;
      padding: 10px 20px;
      font-size: 16px;
    }
  `;

  private chart: any; // 用于存储图表实例

  constructor() {
    super();
    Vizzu.options({
      wasmUrl: VizzuModule,
    });
  }

  firstUpdated() {
    this.renderChart();
  }

  async renderChart() {
    // 确保在浏览器环境中运行
    if (typeof window !== "undefined") {
      await Vizzu.initialize();
      const { data } = await import(
        "https://lib.vizzuhq.com/0.14/assets/data/chart_types_eu.js" as any
      );
      const element = this.renderRoot.querySelector("#chart");
      if (!element) return;

      this.chart = new Vizzu(element as HTMLElement);
      this.chart.animate({
        data,
      });
    }
  }

  private handleButtonClick() {
    if (this.chart) {
      this.chart.animate({
        config: Vizzu.presets.percentageArea({
          x: "Year",
          y: "Value 2 (+)",
          stackedBy: "Country",
          title: "Percentage Area Chart",
        }),
      });
    }
  }

  override render() {
    return html`
      <div id="chart"></div>
      <button @click="${this.handleButtonClick}">显示区域图</button>
    `;
  }
}

今日知识

signal

Click to expand 前端的 Signal 机制是一种用于管理状态和实现响应式编程的模式,近年来在多个现代框架和库中得到了广泛应用,如 React、Vue 和 SolidJS 等。Signal 机制的核心思想是通过信号(Signals)来追踪状态的变化,并自动更新依赖于这些状态的部分。下面是对前端 Signal 机制的详细解析:

1. 基本概念

  • 信号(Signal): 信号是一个可以存储状态的对象。当信号的值发生变化时,它会通知所有依赖于该信号的部分进行更新。
  • 订阅(Subscription): 组件或函数可以订阅信号,以便在信号的值变化时自动重新计算或重新渲染。
  • 反应(Reactivity): 当信号的值变化时,所有依赖于该信号的部分会自动响应,更新其状态或视图。

2. 工作原理

Signal 机制通常涉及以下几个步骤:

  1. 创建信号: 开发者创建一个信号并为其赋初始值。
  2. 订阅信号: 组件或函数在使用信号的值时,会自动订阅该信号。
  3. 更新信号: 当信号的值被更新时,信号会通知所有订阅者。
  4. 重新渲染: 订阅者会根据新的信号值重新计算或重新渲染。

3. 实现示例

以下是一个简单的 Signal 机制实现示例,使用 JavaScript 来演示其基本概念:

class Signal {
  constructor(initialValue) {
    this.value = initialValue;
    this.subscribers = new Set();
  }

  get() {
    // 订阅者在访问信号值时自动注册
    if (currentSubscriber) {
      this.subscribers.add(currentSubscriber);
    }
    return this.value;
  }

  set(newValue) {
    this.value = newValue;
    // 通知所有订阅者
    this.subscribers.forEach(subscriber => subscriber());
  }
}

// 当前订阅者
let currentSubscriber = null;

// 创建一个信号
const countSignal = new Signal(0);

// 订阅信号
function render() {
  console.log(`Count: ${countSignal.get()}`);
}

// 设置当前订阅者为 render 函数
currentSubscriber = render;

// 初始渲染
render();

// 更新信号
countSignal.set(1); // 输出: Count: 1
countSignal.set(2); // 输出: Count: 2

4. 优点

  • 简化状态管理: Signal 机制使得状态管理更加简单和直观,开发者可以专注于状态变化而不是手动更新视图。
  • 提高性能: 通过精确追踪依赖关系,Signal 机制可以减少不必要的渲染,提高应用的性能。
  • 增强可维护性: 代码的可读性和可维护性提高,因为状态变化和视图更新之间的关系更加明确。

5. 应用场景

  • 响应式 UI: 在构建响应式用户界面时,Signal 机制可以帮助自动更新视图。
  • 状态管理: 在复杂应用中,Signal 机制可以作为状态管理的基础,简化数据流。
  • 数据流动: 在需要频繁更新的应用中,Signal 机制可以有效管理数据流动和状态变化。

6. 与其他机制的比较

  • 与 Redux: Redux 是一种集中式状态管理方案,通常需要手动触发更新,而 Signal 机制则是自动响应的,减少了样板代码。
  • 与 MobX: MobX 也使用响应式编程,但 Signal 机制通常更轻量,适合小型应用或组件。

总结

前端的 Signal 机制是一种强大而灵活的状态管理和响应式编程模式。通过信号的使用,开发者可以更轻松地管理状态变化,提高应用的性能和可维护性。如果你有其他问题或需要进一步的帮助,请告诉我!

2024-10-29 11:02:57

继续昨日lit的探索

  • hello world
show me the code
import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";

@customElement("simple-greeting")
export class SimpleGreeting extends LitElement {
  render() {
    return html`<p class="white">Hello, World!</p>`;
  }

  // 使用原生渲染
  createRenderRoot() {
    return this;
  }
}

如果使用shadow dom渲染,tailwind的样式将失效,需要使用原生渲染(light dom),或者继续使用shadow dom,但是需要一大堆的配置,尝试使用

 static styles = css`
    @tailwind base;
    @tailwind components;
    @tailwind utilities;`;

没作用

  • full component

今日类型体操

00020-medium-promise-all
declare function PromiseAll<T extends any[]>(
  values: readonly [...T]
): Promise<{
  [key in keyof T]: Awaited<T[key]>;
}>;

2024-10-30 10:13:11

今日lit

  • Basic properties

突然看到这个,算了不在这个博客折腾lit了

今日类型体操

  • exclude
type MyExclude<T, U> = T extends U ? never : T;
  • union to intersection
Click to expand

在 TypeScript 中,将联合类型转换为交叉类型可以通过使用条件类型和函数参数的逆变特性来实现。以下是一个简单的实现示例:

type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (
  x: infer I
) => void
  ? I
  : never;

解释:

  1. 条件类型U extends any 会将联合类型 U 中的每个成员都传递给函数类型 (x: U) => void
  2. 逆变:通过将 U 放入函数参数中,TypeScript 会将其视为逆变位置,这样可以推导出一个交叉类型。
  3. 推导infer I 用于推导出最终的交叉类型。

示例:

假设我们有一个联合类型:

type A = { a: string };
type B = { b: number };
type C = UnionToIntersection<A | B>; // 结果是 { a: string } & { b: number }

在这个例子中,C 将会是一个包含 ab 属性的交叉类型。

这种方法在处理需要将多个类型合并为一个类型时非常有用,尤其是在类型系统中需要更复杂的类型组合时。

如果你想了解更多细节,可以参考相关的 TypeScript 文档或社区讨论,例如 oida.devStack Overflow

今日知识

全称命题

如何区分一个全称命题结论的错误是源于前提错误,还是源于推理过程的逻辑谬误

区分一个全称命题结论的错误是源于前提错误,还是源于推理过程的逻辑谬误,可以通过以下几个步骤进行分析:

  1. 检查前提的真实性

    • 首先,验证前提是否真实。如果前提是错误的,那么结论可能是错误的,但这并不一定意味着推理过程有误。
    • 例如,前提“所有鸟都会飞”是错误的(因为有些鸟不会飞),但如果结论是“这只鸟不会飞”,则结论的真实性与前提的错误无关。
  2. 分析推理过程

    • 检查推理的逻辑结构是否有效。有效的推理意味着如果前提为真,结论也必然为真。
    • 例如,考虑以下推理:
      • 前提1:所有人都是哺乳动物。
      • 前提2:所有哺乳动物都是温血动物。
      • 结论:所有人都是温血动物。
    • 在这个例子中,推理是有效的,即使前提1是错误的(因为并不是所有人都是哺乳动物),结论依然是正确的。
  3. 使用逻辑图示

    • 使用维恩图或其他逻辑图示工具来可视化前提和结论之间的关系。这可以帮助识别是否存在逻辑谬误。
    • 例如,绘制两个类的交集,看看结论是否符合前提所描述的关系。
  4. 识别常见的逻辑谬误

    • 学习并识别常见的逻辑谬误,如“非此即彼”(false dichotomy)、“诉诸无知”(appeal to ignorance)等。这些谬误可能导致错误的推理过程。
    • 例如,如果推理过程中使用了“如果A不成立,那么B一定成立”的逻辑,这可能是一个逻辑谬误。
  5. 进行反例测试

    • 尝试寻找反例来测试推理的有效性。如果能够找到一个反例,说明推理过程存在逻辑谬误。
    • 例如,如果前提是“所有A都是B”,而你找到了一个A但不是B的例子,那么这个推理就是错误的。

通过以上步骤,可以更清晰地判断一个全称命题结论的错误是源于前提错误,还是源于推理过程的逻辑谬误。

tileset.json中的transform

Click to expand
[
  -0.8480809042061861, -0.5298667567613746, 0.0, 0.0, 0.3490294610878544,
  -0.5586408605122046, 0.752395390907537, 0.0, -0.39866930558238334,
  0.6380921634414309, 0.6587117546705042, 0.0, -2546467.965211091,
  4075762.117382445, 4179301.6995004173, 1.0
]

在这个transform矩阵中,我们可以看到这是一个4x4矩阵,将局部坐标系转换到地理坐标系,尤其是在全球坐标 (ECEF - Earth-Centered Earth-Fixed) 系中,通常以米为单位。

矩阵中的最后一列 [-2546467.965211091, 4075762.117382445, 4179301.6995004173, 1.0] 表示平移部分,可以看作是该模型在ECEF坐标系中的位置。因此:

  • x=2546467.965211091x = -2546467.965211091
  • y=4075762.117382445y = 4075762.117382445
  • z=4179301.6995004173z = 4179301.6995004173

将ECEF坐标转换为经纬度和高度

可以将这些ECEF坐标转换为更常用的地理坐标 (经度、纬度、高程),其公式如下:

  1. 计算经度 (λ\lambda):

    λ=arctan(yx)\lambda = \arctan\left(\frac{y}{x}\right)
  2. 计算平面距离 pp

    p=x2+y2p = \sqrt{x^2 + y^2}
  3. 计算纬度 (ϕ\phi) 和高程 (hh):

    • 使用WGS84椭球参数:
      • a=6378137.0a = 6378137.0(地球的半长轴)
      • e2=6.69437999014×103e^2 = 6.69437999014 \times 10^{-3}(椭球偏心率的平方)

    迭代计算得到纬度和高度,或者直接用编程来实现这些公式的转换。

今日探索

  • 添加mermaid
graph TD;
    A-->B;
    A-->C;
    B-->D;
    C-->D;

2024-10-31 09:11:54

今日ui

Esc
F1
F2
F3
F4
F5
F6
F7
F8
F9
F10
F11
F12
Del
Tab
Q
W
E
R
T
Y
U
I
O
P
[
]
|
Ins
Caps
A
S
D
F
G
H
J
K
L
;
'
Enter
PgUp
Shift
Z
X
C
V
B
N
M
,
.
/
Shift
PgDn
Ctrl
Win
Alt
Alt
Fn
Ctrl

今日金融

国债逆回购

国债逆回购是一种短期融资工具,主要用于金融市场中的流动性管理。它是指投资者(通常是银行或其他金融机构)将国债等证券出售给中央银行或其他金融机构,并约定在未来某个时间以约定价格买回这些证券的交易。具体来说,国债逆回购的过程如下:

逆回购的基本流程

  1. 出售国债:投资者将持有的国债等证券出售给中央银行或其他金融机构。
  2. 获得资金:通过出售国债,投资者获得相应的现金流,这部分资金可以用于其他投资或流动性需求。
  3. 约定回购:在交易时,双方约定一个未来的回购日期和回购价格。回购价格通常高于出售价格,差额部分即为投资者的收益。
  4. 回购交易:在约定的回购日期,投资者将资金偿还给买方,并重新购回国债。

逆回购的特点

  • 短期性:国债逆回购通常是短期交易,期限一般为1天到几个月不等。
  • 流动性管理:金融机构可以通过逆回购操作来调节流动性,满足短期资金需求。
  • 安全性:由于国债是由国家信用背书的,逆回购交易相对安全,风险较低。

逆回购的用途

  • 资金周转:金融机构可以利用逆回购获得短期资金,进行其他投资或满足流动性需求。
  • 利率调控:中央银行通过逆回购操作调节市场流动性,从而影响短期利率,达到货币政策的目标。
  • 投资工具:对于投资者来说,逆回购是一种低风险的投资方式,可以获得相对稳定的收益。

总结

国债逆回购是一种灵活的融资工具,适用于金融市场中的流动性管理和短期资金需求。通过逆回购,投资者可以在保持资金流动性的同时,获得相对安全的收益。

今日类型体操

今日探索

html plus

  • 横屏api
plus.nativeUI.toast("该页面需要横屏查看");
plus.screen.lockOrientation("landscape-primary");
plus.screen.unlockOrientation();
plus.screen.lockOrientation("portrait-primary");

链接