This commit is contained in:
2025-03-01 20:27:01 +08:00
parent 0bc9c5a3f6
commit e9c1a1ca1e
12 changed files with 14184 additions and 0 deletions

2
blk_inventory/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
dist
pnpm-lock.yaml

44
blk_inventory/README.md Normal file
View File

@ -0,0 +1,44 @@
# demo
## 文件目录说明
```ts
.
├── config // webpack 配置文件目录
└── webpack.config.ts // webpack 配置文件
├── package.json
├── public
└── index.html
├── src
├── App.tsx // 主要组件
├── bitableApp.ts // 实例化 sdk
├── index.tsx // 入口文件
└── utils.ts // 工具方法
├── block.json // 小组件元信息
├── README.md // 说明文件
└── tsconfig.json // ts config
```
## 安装依赖
```sh
npm / yarn / pnpm install
```
## 启动
```sh
npm run dev
```
## 打包
```sh
npm run build
```
## 发布
```sh
npm run upload
```

5
blk_inventory/block.json Normal file
View File

@ -0,0 +1,5 @@
{
"manifestVersion": 1,
"blockTypeID": "blk_67bf27b12481001387d8f3b9",
"projectName": "任务管理小应用"
}

View File

@ -0,0 +1,130 @@
const path = require('path');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ESBuildMinifyPlugin } = require('esbuild-loader');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const WebpackBar = require('webpackbar');
const {
BitableAppWebpackPlugin,
opdevMiddleware
} = require('@lark-opdev/block-bitable-webpack-utils');
const cwd = process.cwd();
const isDevelopment = process.env.NODE_ENV === 'development';
const isProduction = process.env.NODE_ENV === 'production';
const config = {
entry: './src/index.tsx',
devtool: isProduction ? false : 'inline-source-map',
mode: isDevelopment ? 'development' : 'production',
stats: 'errors-only',
output: {
path: path.resolve(__dirname, '../dist'),
clean: true,
publicPath: isDevelopment ? '/block/' : './',
},
module: {
rules: [
{
test: /\.js$/,
include: [/node_modules\/@lark-open/],
use: ['source-map-loader'],
enforce: 'pre',
},
{
oneOf: [
{
test: /\.[jt]sx?$/,
include: [path.join(cwd, 'src')],
exclude: /node_modules/,
use: [
{
loader: require.resolve('esbuild-loader'),
options: {
loader: 'tsx',
target: 'es2015',
},
},
],
},
{
test: /\.css$/,
use: [
isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
],
},
{
test: /\.less$/,
use: [
isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
'less-loader',
],
},
{
test: /\.(png|jpg|jpeg|gif|ico|svg)$/,
type: 'asset/resource',
generator: {
filename: 'assets/[name][ext][query]',
},
},
],
},
],
},
plugins: [
...(isDevelopment
? [new ReactRefreshWebpackPlugin(), new WebpackBar()]
: [new MiniCssExtractPlugin()]),
new BitableAppWebpackPlugin({
// open: true, // 控制是否自动打开多维表格
}),
new HtmlWebpackPlugin({
filename: 'index.html',
template: './public/index.html',
publicPath: isDevelopment ? '/block/' : './',
}),
],
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
},
optimization: {
minimize: isProduction,
minimizer: [new ESBuildMinifyPlugin({ target: 'es2015', css: true })],
moduleIds: 'deterministic',
runtimeChunk: true,
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
name: 'vendor',
test: /[\\/]node_modules[\\/]/,
chunks: 'all',
},
},
},
},
devServer: isProduction
? undefined
: {
hot: true,
client: {
logging: 'error',
},
setupMiddlewares: (middlewares, devServer) => {
if (!devServer || !devServer.app) {
throw new Error('webpack-dev-server is not defined');
}
middlewares.push(opdevMiddleware(devServer))
return middlewares;
},
},
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
},
};
module.exports = config;

13730
blk_inventory/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,43 @@
{
"name": "@bitable/pack-demo",
"version": "0.1.0",
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --mode development --config ./config/webpack.config.js",
"start": "npm run dev",
"build": "cross-env NODE_ENV=production webpack --mode production --config ./config/webpack.config.js",
"upload": "npm run build && opdev upload ./dist"
},
"dependencies": {
"@lark-opdev/block-bitable-api": "^0.1.0",
"@douyinfe/semi-ui": "^2.22.3",
"lodash-es": "^4.17.21",
"react": "^18.2.0",
"react-async-hook": "^4.0.0",
"react-dom": "^18.2.0",
"react-use": "^17.4.0"
},
"devDependencies": {
"@lark-opdev/block-bitable-webpack-utils": "^0.1.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@types/webpack": "^5.28.0",
"cross-env": "^7.0.3",
"css-loader": "^6.7.1",
"esbuild-loader": "^2.20.0",
"html-webpack-plugin": "^5.5.0",
"less": "^4.1.3",
"less-loader": "^11.0.0",
"mini-css-extract-plugin": "^2.6.1",
"react-refresh": "^0.14.0",
"simple-progress-webpack-plugin": "^2.0.0",
"source-map-loader": "^4.0.0",
"style-loader": "^3.3.1",
"typescript": "^4.6.4",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.10.0",
"webpackbar": "^5.0.2"
},
"readme": "这是一个脚手架基础项目\n"
}

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div id="root"></div>
</body>
</html>

96
blk_inventory/src/App.tsx Normal file
View File

@ -0,0 +1,96 @@
import { bitable } from "@lark-opdev/block-bitable-api";
import { FC, useEffect } from "react";
import { useAsync } from "react-async-hook";
import { getCurrentTask, setCompleted } from "./utils";
import {
Typography,
Tag,
Button,
Divider,
Space,
Toast,
} from "@douyinfe/semi-ui";
const { Title, Text } = Typography;
const defaultTask = {
description: "",
userName: "",
completed: false,
};
export const App = () => {
const task = useAsync(getCurrentTask, []);
const { description, userName, completed } = task.result ?? defaultTask;
// 切换上下一条记录时,触发 SelectionChange
useEffect(() => {
return bitable.base.onSelectionChange(({ data }) =>
task.execute()
);
}, []);
const toggleCompleted = () => {
setCompleted(!completed)
.then(() => task.execute())
.then(() => Toast.success("更新任务状态成功"))
.catch(() => Toast.error("更新任务状态失败"));
};
if (task.loading) return <div>loading</div>;
if (task.error) return <div>error: {task.error.message}</div>;
return (
<PureTaskComponment
description={description}
userName={userName}
completed={completed}
toggleCompleted={toggleCompleted}
/>
);
};
interface PureTaskComponmentProps {
description: string;
userName: string;
completed: boolean;
toggleCompleted: () => void;
}
const PureTaskComponment: FC<PureTaskComponmentProps> = ({
description,
userName,
completed,
toggleCompleted,
}) => {
return (
<Space vertical align="start">
<div>
<Title heading={2}></Title>
</div>
<div>
<Text></Text>
<Text>{description}</Text>
</div>
<div>
<Text></Text>
<Text>{userName}</Text>
</div>
<div>
<Text></Text>
<Tag color={completed ? "green" : "blue"}>
{completed ? "已完成" : "未完成"}
</Tag>
</div>
<Divider />
<div>
<Button
type={completed ? "danger" : "primary"}
onClick={toggleCompleted}
>
{completed ? "撤销完成任务" : "完成任务"}
</Button>
</div>
</Space>
);
};

View File

@ -0,0 +1,9 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { App } from './App';
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,85 @@
import {
IOpenCheckbox,
IOpenSegment,
IOpenUser,
bitable
} from "@lark-opdev/block-bitable-api";
/**
* 从模板创建的多维表格
* 可以保证字段 ID 与模板一致
*/
/** 任务描述字段 ID */
const descriptionFieldId = "fldaxqIJ1m";
/** 任务执行人字段名称 */
const userFieldName = "任务执行人";
/** 是否完成字段 ID */
const completedFieldId = "fld9cvGzic";
/** 尝试一下:接入是否延期字段 ID */
// const exceedingFieldId = "todo"
function getUserName(userValue: IOpenUser[] | null) {
if (!userValue || userValue.length === 0) {
return "任务执行人不存在";
}
return userValue[0].name ?? "用户没有设置姓名";
}
function getDescription(descriptionValue: IOpenSegment[] | null) {
if (!descriptionValue || descriptionValue.length === 0) {
return "任务描述不存在";
}
return descriptionValue.map((segment) => segment.text).join("");
}
export async function getCurrentTask() {
// 1. 读取选中的表和记录 //
const { tableId, recordId } = await bitable.base.getSelection();
if (!tableId || !recordId) throw new Error("选区状态读取失败");
const table = await bitable.base.getTableById(tableId);
// 2. 读取单元格 //
const completedValue = (await table.getCellValue(
completedFieldId,
recordId
)) as IOpenCheckbox;
const userField = await table.getFieldByName(userFieldName);
const userValue = (await table.getCellValue(
userField.id,
recordId
)) as IOpenUser[];
const descriptionValue = (await table.getCellValue(
descriptionFieldId,
recordId
)) as IOpenSegment[];
// 尝试一下:读取是否延期字段
// 单选的值类型为 IOpenSingleSelect
// const exceedingValue = (await table.getCellValue(exceedingFieldId, recordId)) as IOpenSingleSelect;
// 尝试一下:将 exceedingValue 转换成选中选项的字符串
// const exceedingText = doYourCustomTransform(exceedingValue)
// 3. 将单元格结构体转换成业务所需数据 //
return {
description: getDescription(descriptionValue),
userName: getUserName(userValue),
completed: completedValue,
// 尝试一下:返回是否延期信息
// exceeding: exceedingText
};
}
export async function setCompleted(completed: boolean) {
// 1. 读取选中的表和记录 //
const { tableId, recordId } = await bitable.base.getSelection();
if (!tableId || !recordId) throw new Error("选区状态读取失败");
const table = await bitable.base.getTableById(tableId);
// 2. 将业务数据转换成单元格结构,然后写入 //
table.setCellValue(completedFieldId, recordId, completed as IOpenCheckbox);
}

View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "ESNext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"ts-node": {
"compilerOptions": {
"module": "commonjs"
}
}
}