py-test-components 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,156 +1,60 @@
1
- import React, { forwardRef, useEffect, useRef, useState } from 'react';
2
-
3
- // ElementUI CSS URL
4
- const ELEMENT_UI_CSS = 'https://unpkg.com/element-ui/lib/theme-chalk/index.css';
5
-
6
- // 标记 CSS 是否已注入
7
- let cssInjected = false;
8
-
9
- /**
10
- * 注入 ElementUI CSS
11
- */
12
- function injectElementUICSS() {
13
- if (cssInjected || typeof document === 'undefined') return;
14
-
15
- // 检查是否已存在
16
- const existing = document.querySelector(`link[href="${ELEMENT_UI_CSS}"]`);
17
- if (existing) {
18
- cssInjected = true;
19
- return;
20
- }
21
-
22
- const link = document.createElement('link');
23
- link.rel = 'stylesheet';
24
- link.href = ELEMENT_UI_CSS;
25
- document.head.appendChild(link);
26
- cssInjected = true;
27
- }
28
-
29
- /**
30
- * 包装 Vue Web Component 为 React 组件
31
- * @param {string} tagName - 自定义元素标签名
32
- * @param {string} dataProp - 数据属性名
33
- * @returns {React.Component} React 组件
34
- */
35
- function wrapVueComponent(tagName, dataProp = 'propData') {
36
- return forwardRef(function WrappedComponent({
37
- propData,
38
- loading,
39
- onChange,
40
- onLoad,
41
- onError,
42
- onRefresh,
43
- onSelectionChange,
44
- onEdit,
45
- onDelete,
46
- onSizeChange,
47
- onPageChange,
48
- ...props
49
- }, ref) {
50
- const innerRef = useRef(null);
51
- const [isReady, setIsReady] = useState(false);
52
-
53
- // 合并 ref
54
- useEffect(() => {
55
- if (typeof ref === 'function') {
56
- ref(innerRef.current);
57
- } else if (ref) {
58
- ref.current = innerRef.current;
59
- }
60
- }, [ref]);
61
-
62
- // 加载组件库和 CSS
63
- useEffect(() => {
64
- injectElementUICSS();
65
-
66
- // 动态加载组件库构建产物
67
- import('../../dist/py-component.esm.js')
68
- .then(() => {
69
- setIsReady(true);
70
- })
71
- .catch(err => {
72
- console.error(`[PyComponent] 加载失败:`, err);
73
- });
74
- }, []);
75
-
76
- // 设置 propData 到 DOM property
77
- useEffect(() => {
78
- if (innerRef.current && isReady) {
79
- innerRef.current[dataProp] = propData;
80
- }
81
- }, [propData, isReady, dataProp]);
82
-
83
- // 绑定事件
84
- useEffect(() => {
85
- const el = innerRef.current;
86
- if (!el || !isReady) return;
87
-
88
- const events = {
89
- 'change': onChange,
90
- 'load': onLoad,
91
- 'error': onError,
92
- 'refresh': onRefresh,
93
- 'selection-change': onSelectionChange,
94
- 'edit': onEdit,
95
- 'delete': onDelete,
96
- 'size-change': onSizeChange,
97
- 'page-change': onPageChange
98
- };
99
-
100
- Object.entries(events).forEach(([eventName, handler]) => {
101
- if (handler) {
102
- el.addEventListener(eventName, handler);
103
- }
104
- });
105
-
106
- return () => {
107
- Object.entries(events).forEach(([eventName, handler]) => {
108
- if (handler) {
109
- el.removeEventListener(eventName, handler);
110
- }
111
- });
112
- };
113
- }, [isReady, onChange, onLoad, onError, onRefresh, onSelectionChange, onEdit, onDelete, onSizeChange, onPageChange]);
114
-
115
- // 显示 loading 状态
116
- if (!isReady) {
117
- return loading || <div style={{ padding: 20, textAlign: 'center' }}>加载中...</div>;
118
- }
119
-
120
- return React.createElement(tagName, {
121
- ref: innerRef,
122
- ...props
123
- });
124
- });
125
- }
126
-
127
- // 创建组件
128
- export const PyTable = wrapVueComponent('py-table', 'propData');
129
- export const PyWeather = wrapVueComponent('py-weather', 'propData');
130
-
131
- /**
132
- * 初始化 Store
133
- * @param {object} config - 配置对象
134
- * @returns {Promise<void>}
135
- */
136
- export function initStore(config) {
137
- return new Promise((resolve, reject) => {
138
- import('../../dist/py-component.esm.js')
139
- .then((module) => {
140
- const { initStore: init } = module;
141
- if (init) {
142
- init(config);
143
- resolve();
144
- } else {
145
- reject(new Error('initStore 不存在'));
146
- }
147
- })
148
- .catch(reject);
149
- });
150
- }
151
-
152
- export default {
153
- PyTable,
154
- PyWeather,
155
- initStore
156
- };
1
+ import { forwardRef, useEffect, useState } from 'react';
2
+
3
+ const injected = { css: false };
4
+
5
+ function ensureCSS() {
6
+ if (injected.css) return;
7
+ const link = document.createElement('link');
8
+ link.rel = 'stylesheet';
9
+ link.href = 'https://unpkg.com/element-ui@2.15.14/lib/theme-chalk/index.css';
10
+ document.head.appendChild(link);
11
+ injected.css = true;
12
+ }
13
+
14
+ function wrapVueComponent(tagName) {
15
+ const Component = forwardRef(function WrappedComponent(
16
+ { propData, loading, ...props },
17
+ ref
18
+ ) {
19
+ const [mounted, setMounted] = useState(false);
20
+
21
+ useEffect(() => {
22
+ ensureCSS();
23
+ setMounted(true);
24
+ }, []);
25
+
26
+ if (!mounted) {
27
+ return loading || null;
28
+ }
29
+
30
+ const tag = document.createElement(tagName);
31
+ if (propData) {
32
+ tag.propData = propData;
33
+ }
34
+ Object.keys(props).forEach((key) => {
35
+ if (key.startsWith('on') && typeof props[key] === 'function') {
36
+ const eventName = key.slice(2).toLowerCase();
37
+ tag.addEventListener(eventName, props[key]);
38
+ }
39
+ });
40
+
41
+ if (ref && typeof ref === 'object' && ref.current !== undefined) {
42
+ ref.current = tag;
43
+ }
44
+
45
+ return tag;
46
+ });
47
+
48
+ Component.displayName = tagName;
49
+ return Component;
50
+ }
51
+
52
+ export const PyTable = wrapVueComponent('py-table');
53
+ export const PyWeather = wrapVueComponent('py-weather');
54
+
55
+ export async function initStore(config) {
56
+ if (typeof window !== 'undefined' && window.PyComponent?.initStore) {
57
+ window.PyComponent.initStore(config);
58
+ }
59
+ return Promise.resolve();
60
+ }
@@ -1,66 +1,17 @@
1
- import Vue from 'vue';
2
-
3
- // 创建响应式 store 对象
4
- const state = Vue.observable({
5
- // 默认配置
6
- apiKey: '',
7
- baseUrl: '',
8
- userInfo: null,
9
- // 可扩展更多全局配置
10
- });
11
-
12
- // Store API
13
- const store = {
14
- /**
15
- * 获取 store 中的值
16
- * @param {string} key - 键名
17
- * @returns {any} 值
18
- */
19
- get(key) {
20
- return state[key];
21
- },
22
-
23
- /**
24
- * 设置 store 中的值
25
- * @param {string} key - 键名
26
- * @param {any} value - 值
27
- */
28
- set(key, value) {
29
- state[key] = value;
30
- },
31
-
32
- /**
33
- * 批量设置值
34
- * @param {object} config - 配置对象
35
- */
36
- setMultiple(config) {
37
- Object.assign(state, config);
38
- },
39
-
40
- /**
41
- * 订阅 store 变化(仅供内部使用)
42
- * @param {function} callback - 回调函数
43
- * @returns {function} 取消订阅函数
44
- */
45
- subscribe(callback) {
46
- // 使用 Vue watch 实现订阅
47
- const unwatch = Vue.watch(
48
- () => state,
49
- (newVal, oldVal) => {
50
- callback(newVal, oldVal);
51
- },
52
- { deep: true }
53
- );
54
- return unwatch;
55
- },
56
-
57
- /**
58
- * 获取整个 state(慎用,主要用于调试)
59
- * @returns {object} state 对象
60
- */
61
- getState() {
62
- return state;
63
- }
64
- };
65
-
66
- export default store;
1
+ import Vue from 'vue';
2
+
3
+ const state = Vue.observable({
4
+ apiKey: '',
5
+ baseUrl: '',
6
+ });
7
+
8
+ const store = {
9
+ get(key) {
10
+ return state[key];
11
+ },
12
+ set(key, value) {
13
+ state[key] = value;
14
+ },
15
+ };
16
+
17
+ export default store;
package/src/utils/api.js CHANGED
@@ -1,101 +1,13 @@
1
- /**
2
- * API 请求封装
3
- */
4
- import { get, post } from './request';
5
- import store from '../store';
6
-
7
- // 获取基础 URL
8
- function getBaseUrl() {
9
- return store.get('baseUrl') || '';
10
- }
11
-
12
- // 获取 API Key
13
- function getApiKey() {
14
- return store.get('apiKey') || '';
15
- }
16
-
17
- // 构建带认证的请求头
18
- function getAuthHeaders() {
19
- const apiKey = getApiKey();
20
- return apiKey ? { 'X-API-Key': apiKey } : {};
21
- }
22
-
23
- /**
24
- * 天气相关 API
25
- */
26
- export const weatherApi = {
27
- /**
28
- * 获取天气信息
29
- * @param {string} city - 城市名
30
- * @returns {Promise} 天气数据
31
- */
32
- getWeather(city) {
33
- return get('/api/weather', { city }, {
34
- baseURL: getBaseUrl(),
35
- headers: getAuthHeaders()
36
- });
37
- },
38
-
39
- /**
40
- * 获取forecast
41
- * @param {string} city - 城市名
42
- * @returns {Promise} forecast数据
43
- */
44
- getForecast(city) {
45
- return get('/api/weather/forecast', { city }, {
46
- baseURL: getBaseUrl(),
47
- headers: getAuthHeaders()
48
- });
49
- }
50
- };
51
-
52
- /**
53
- * 数据表格相关 API
54
- */
55
- export const tableApi = {
56
- /**
57
- * 获取表格数据
58
- * @param {object} params - 查询参数
59
- * @returns {Promise} 表格数据
60
- */
61
- getData(params = {}) {
62
- return get('/api/table/data', params, {
63
- baseURL: getBaseUrl(),
64
- headers: getAuthHeaders()
65
- });
66
- },
67
-
68
- /**
69
- * 提交表格数据
70
- * @param {object} data - 提交的数据
71
- * @returns {Promise} 提交结果
72
- */
73
- submitData(data) {
74
- return post('/api/table/data', data, {
75
- baseURL: getBaseUrl(),
76
- headers: getAuthHeaders()
77
- });
78
- }
79
- };
80
-
81
- /**
82
- * 用户相关 API
83
- */
84
- export const userApi = {
85
- /**
86
- * 获取用户信息
87
- * @returns {Promise} 用户数据
88
- */
89
- getUserInfo() {
90
- return get('/api/user/info', {}, {
91
- baseURL: getBaseUrl(),
92
- headers: getAuthHeaders()
93
- });
94
- }
95
- };
96
-
97
- export default {
98
- weather: weatherApi,
99
- table: tableApi,
100
- user: userApi
101
- };
1
+ import { post } from './request';
2
+ import store from '../store';
3
+
4
+ const DEFAULT_BASE_URL = 'https://api.example.com';
5
+
6
+ export function queryWeather(city) {
7
+ const baseUrl = store.get('baseUrl') || DEFAULT_BASE_URL;
8
+ const apiKey = store.get('apiKey');
9
+ return post(`${baseUrl}/weather/query`, {
10
+ city,
11
+ apiKey,
12
+ });
13
+ }
@@ -1,113 +1,10 @@
1
- /**
2
- * 封装的 fetch 请求工具
3
- */
4
-
5
- // 默认配置
6
- const defaultConfig = {
7
- baseURL: '',
8
- timeout: 10000,
9
- headers: {
10
- 'Content-Type': 'application/json'
11
- }
12
- };
13
-
14
- /**
15
- * 发起 HTTP 请求
16
- * @param {string} url - 请求地址
17
- * @param {object} options - 请求选项
18
- * @returns {Promise} 响应结果
19
- */
20
- export function request(url, options = {}) {
21
- const config = {
22
- ...defaultConfig,
23
- ...options,
24
- headers: {
25
- ...defaultConfig.headers,
26
- ...options.headers
27
- }
28
- };
29
-
30
- // 处理 URL
31
- const fullUrl = config.baseURL ? `${config.baseURL}${url}` : url;
32
-
33
- // 创建 AbortController 用于超时控制
34
- const controller = new AbortController();
35
- const timeoutId = setTimeout(() => controller.abort(), config.timeout);
36
-
37
- return fetch(fullUrl, {
38
- ...config,
39
- signal: controller.signal
40
- })
41
- .then(response => {
42
- clearTimeout(timeoutId);
43
-
44
- if (!response.ok) {
45
- throw new Error(`HTTP error! status: ${response.status}`);
46
- }
47
-
48
- // 根据返回类型解析数据
49
- const contentType = response.headers.get('content-type');
50
- if (contentType && contentType.includes('application/json')) {
51
- return response.json();
52
- }
53
- return response.text();
54
- })
55
- .catch(error => {
56
- clearTimeout(timeoutId);
57
-
58
- if (error.name === 'AbortError') {
59
- throw new Error('Request timeout');
60
- }
61
- throw error;
62
- });
63
- }
64
-
65
- /**
66
- * GET 请求
67
- * @param {string} url - 请求地址
68
- * @param {object} params - URL 参数
69
- * @param {object} options - 其他选项
70
- * @returns {Promise} 响应结果
71
- */
72
- export function get(url, params = {}, options = {}) {
73
- const queryString = Object.keys(params)
74
- .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
75
- .join('&');
76
-
77
- const fullUrl = queryString ? `${url}?${queryString}` : url;
78
-
79
- return request(fullUrl, {
80
- method: 'GET',
81
- ...options
82
- });
83
- }
84
-
85
- /**
86
- * POST 请求
87
- * @param {string} url - 请求地址
88
- * @param {object} data - 请求数据
89
- * @param {object} options - 其他选项
90
- * @returns {Promise} 响应结果
91
- */
92
- export function post(url, data = {}, options = {}) {
93
- return request(url, {
94
- method: 'POST',
95
- body: JSON.stringify(data),
96
- ...options
97
- });
98
- }
99
-
100
- /**
101
- * 设置默认配置
102
- * @param {object} config - 配置对象
103
- */
104
- export function setDefaultConfig(config) {
105
- Object.assign(defaultConfig, config);
106
- }
107
-
108
- export default {
109
- request,
110
- get,
111
- post,
112
- setDefaultConfig
113
- };
1
+ export function post(url, data, headers = {}) {
2
+ return fetch(url, {
3
+ method: 'POST',
4
+ headers: {
5
+ 'Content-Type': 'application/json',
6
+ ...headers,
7
+ },
8
+ body: JSON.stringify(data),
9
+ }).then((res) => res.json());
10
+ }
package/src/vue/index.js CHANGED
@@ -1,32 +1,25 @@
1
- // Vue 入口 - 直接导出 Vue 组件
2
-
3
- import PyTable from '../components/PyTable.vue';
4
- import PyWeather from '../components/PyWeather.vue';
5
- import store from '../store';
6
-
7
- /**
8
- * 初始化 Store
9
- * @param {object} config - 配置对象
10
- */
11
- function initStore(config) {
12
- if (!config || typeof config !== 'object') {
13
- console.warn('[PyComponent] initStore 需要传入配置对象');
14
- return;
15
- }
16
-
17
- Object.keys(config).forEach(key => {
18
- store.set(key, config[key]);
19
- });
20
-
21
- console.log('[PyComponent] Store 已初始化');
22
- }
23
-
24
- // 导出组件和函数
25
- export { PyTable, PyWeather, initStore };
26
-
27
- // 默认导出
28
- export default {
29
- PyTable,
30
- PyWeather,
31
- initStore
32
- };
1
+ import Vue from 'vue';
2
+ import ElementUI from 'element-ui';
3
+ import 'element-ui/lib/theme-chalk/index.css';
4
+ import store from '../store';
5
+
6
+ import PyTable from '../components/PyTable.vue';
7
+ import PyWeather from '../components/PyWeather.vue';
8
+
9
+ Vue.use(ElementUI);
10
+
11
+ function initStore(config) {
12
+ if (config) {
13
+ Object.keys(config).forEach((key) => {
14
+ store.set(key, config[key]);
15
+ });
16
+ }
17
+ }
18
+
19
+ export { PyTable, PyWeather, initStore };
20
+
21
+ export default {
22
+ PyTable,
23
+ PyWeather,
24
+ initStore,
25
+ };
@@ -0,0 +1,48 @@
1
+ const path = require('path');
2
+ const { VueLoaderPlugin } = require('vue-loader');
3
+
4
+ const baseConfig = {
5
+ resolve: {
6
+ alias: {
7
+ 'vue$': 'vue/dist/vue.esm.js',
8
+ },
9
+ },
10
+ module: {
11
+ rules: [
12
+ { test: /\.vue$/, loader: 'vue-loader' },
13
+ { test: /\.css$/, use: ['style-loader', 'css-loader'] },
14
+ ],
15
+ },
16
+ plugins: [new VueLoaderPlugin()],
17
+ };
18
+
19
+ // UMD build
20
+ const umdConfig = {
21
+ ...baseConfig,
22
+ entry: './src/index.js',
23
+ output: {
24
+ filename: 'py-component.js',
25
+ path: path.resolve(__dirname, 'dist'),
26
+ library: 'PyComponent',
27
+ libraryTarget: 'umd',
28
+ globalObject: 'this',
29
+ },
30
+ };
31
+
32
+ // ESM build
33
+ const esmConfig = {
34
+ ...baseConfig,
35
+ entry: './src/index.js',
36
+ output: {
37
+ filename: 'py-component.esm.js',
38
+ path: path.resolve(__dirname, 'dist'),
39
+ library: {
40
+ type: 'module',
41
+ },
42
+ },
43
+ experiments: {
44
+ outputModule: true,
45
+ },
46
+ };
47
+
48
+ module.exports = [umdConfig, esmConfig];
@@ -0,0 +1,17 @@
1
+ 在当前文件夹下构建一个基于vue2+elementui的组件库,发布到npm仓库的名称为py-test-component
2
+ 使用时,安装 nm install py-test-component
3
+ vue2工程可以使用该组件,通过import { PyTable, PyWeather, initStore } from 'py-test-component/vue';即可按需导入使用
4
+ react工程也能够使用该组件库,并且使用时不需要额外的适配成本(无需安装vue和elementUI),通过import { PyTable, PyWeather, initStore } from 'py-test-component/react';即可按需导入使用
5
+ 每个组件都有一个
6
+
7
+ 功能要求:
8
+ 1、组件库包含两个组件:
9
+ 一个表格展示组件PyTable(只有一个传入参数propData);
10
+ 一个天气查询组件PyWheather(一个输入框输入城市、一个查询按钮发起查询、展示查询的天气情况)
11
+ 2、创建一个request.js封装post方法,使得组件库能够自行发送请求不受外部工程影响,请求使用fetch实现
12
+ 3、创建一个api.js,用来封装一些api请求,比如查询天气时的请求queryWheather,使用request.js里的post方法
13
+ 4、组件库有自己的全局store机制,使用Vue.observable实现,只需要简单实现get和set方法,在组件内部可以随时访问,但是外部工程无法访问
14
+ 5、在引用组件时暴露一个initStore()方法,外部工程可以传入一些参数,存储到组件库的全局store中
15
+
16
+
17
+ 实现越简洁清晰越好,不要过度设计