py-test-component 2.0.2 → 2.0.5
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.
- package/package.json +8 -2
- package/src/components/PyTable.vue +43 -0
- package/src/components/PyWeather.vue +109 -0
- package/src/react/index.js +5 -4
- package/src/store/index.js +75 -0
- package/src/utils/api.js +35 -0
- package/src/utils/request.js +68 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "py-test-component",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
4
4
|
"description": "Vue2 + ElementUI 组件库,支持 React 使用",
|
|
5
5
|
"main": "dist/py-component.js",
|
|
6
6
|
"module": "dist/py-component.esm.js",
|
|
@@ -21,7 +21,13 @@
|
|
|
21
21
|
"files": [
|
|
22
22
|
"dist",
|
|
23
23
|
"src/react",
|
|
24
|
-
"
|
|
24
|
+
"src/components",
|
|
25
|
+
"src/store",
|
|
26
|
+
"src/utils",
|
|
27
|
+
"README.md",
|
|
28
|
+
"USAGE.md",
|
|
29
|
+
"DEVELOPER.md",
|
|
30
|
+
"src/vue"
|
|
25
31
|
],
|
|
26
32
|
"scripts": {
|
|
27
33
|
"build": "webpack --mode production",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-table
|
|
3
|
+
:data="tableData"
|
|
4
|
+
style="width: 100%">
|
|
5
|
+
<el-table-column
|
|
6
|
+
prop="date"
|
|
7
|
+
label="日期"
|
|
8
|
+
width="180">
|
|
9
|
+
</el-table-column>
|
|
10
|
+
<el-table-column
|
|
11
|
+
prop="name"
|
|
12
|
+
label="姓名"
|
|
13
|
+
width="180">
|
|
14
|
+
</el-table-column>
|
|
15
|
+
<el-table-column
|
|
16
|
+
prop="address"
|
|
17
|
+
label="地址">
|
|
18
|
+
</el-table-column>
|
|
19
|
+
</el-table>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script>
|
|
23
|
+
export default {
|
|
24
|
+
name: 'PyTable',
|
|
25
|
+
props: {
|
|
26
|
+
propData: {
|
|
27
|
+
default: null
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
computed: {
|
|
31
|
+
tableData() {
|
|
32
|
+
// el-table 需要数组,如果不是数组则返回空数组
|
|
33
|
+
return Array.isArray(this.propData) ? this.propData : []
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<style scoped>
|
|
40
|
+
.py-table-wrapper {
|
|
41
|
+
padding: 10px;
|
|
42
|
+
}
|
|
43
|
+
</style>
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="py-weather">
|
|
3
|
+
<div class="search-box">
|
|
4
|
+
<el-input
|
|
5
|
+
v-model="city"
|
|
6
|
+
placeholder="请输入城市名称"
|
|
7
|
+
@keyup.enter.native="handleSearch"
|
|
8
|
+
clearable
|
|
9
|
+
/>
|
|
10
|
+
<el-button type="primary" @click="handleSearch" :loading="loading">
|
|
11
|
+
查询
|
|
12
|
+
</el-button>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div v-if="weatherData" class="weather-info">
|
|
16
|
+
<h3>{{ weatherData.location.name }}</h3>
|
|
17
|
+
<div class="weather-detail">
|
|
18
|
+
<span class="temperature">{{ weatherData.now.temperature }}°C</span>
|
|
19
|
+
<span class="weather-text">{{ weatherData.now.text }}</span>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div v-if="error" class="error-message">
|
|
24
|
+
{{ error }}
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script>
|
|
30
|
+
import { queryWeather } from '../utils/api';
|
|
31
|
+
|
|
32
|
+
export default {
|
|
33
|
+
name: 'PyWeather',
|
|
34
|
+
data() {
|
|
35
|
+
return {
|
|
36
|
+
city: '',
|
|
37
|
+
loading: false,
|
|
38
|
+
weatherData: null,
|
|
39
|
+
error: null
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
methods: {
|
|
43
|
+
async handleSearch() {
|
|
44
|
+
if (!this.city.trim()) {
|
|
45
|
+
this.$message?.warning?.('请输入城市名称') || alert('请输入城市名称');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.loading = true;
|
|
50
|
+
this.error = null;
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const response = await queryWeather(this.city.trim());
|
|
54
|
+
if (response.results && response.results[0]) {
|
|
55
|
+
this.weatherData = response.results[0];
|
|
56
|
+
} else {
|
|
57
|
+
this.error = '未找到该城市的天气信息';
|
|
58
|
+
this.weatherData = null;
|
|
59
|
+
}
|
|
60
|
+
} catch (err) {
|
|
61
|
+
this.error = '查询失败,请稍后重试';
|
|
62
|
+
this.weatherData = null;
|
|
63
|
+
} finally {
|
|
64
|
+
this.loading = false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<style scoped>
|
|
72
|
+
.py-weather {
|
|
73
|
+
padding: 20px;
|
|
74
|
+
max-width: 400px;
|
|
75
|
+
}
|
|
76
|
+
.search-box {
|
|
77
|
+
display: flex;
|
|
78
|
+
gap: 10px;
|
|
79
|
+
}
|
|
80
|
+
.weather-info {
|
|
81
|
+
margin-top: 20px;
|
|
82
|
+
padding: 20px;
|
|
83
|
+
background: #f5f7fa;
|
|
84
|
+
border-radius: 8px;
|
|
85
|
+
}
|
|
86
|
+
.weather-info h3 {
|
|
87
|
+
margin: 0 0 15px 0;
|
|
88
|
+
color: #303133;
|
|
89
|
+
}
|
|
90
|
+
.weather-detail {
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
gap: 15px;
|
|
94
|
+
}
|
|
95
|
+
.temperature {
|
|
96
|
+
font-size: 36px;
|
|
97
|
+
font-weight: bold;
|
|
98
|
+
color: #409EFF;
|
|
99
|
+
}
|
|
100
|
+
.weather-text {
|
|
101
|
+
font-size: 18px;
|
|
102
|
+
color: #606266;
|
|
103
|
+
}
|
|
104
|
+
.error-message {
|
|
105
|
+
margin-top: 15px;
|
|
106
|
+
color: #f56c6c;
|
|
107
|
+
font-size: 14px;
|
|
108
|
+
}
|
|
109
|
+
</style>
|
package/src/react/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useRef, useState, forwardRef } from 'react';
|
|
1
|
+
import React, { useEffect, useRef, useState, forwardRef } from 'react';
|
|
2
2
|
|
|
3
3
|
// 注入 ElementUI CSS(只执行一次)
|
|
4
4
|
let cssInjected = false;
|
|
@@ -16,7 +16,7 @@ function usePyComponent() {
|
|
|
16
16
|
const [loaded, setLoaded] = useState(false);
|
|
17
17
|
useEffect(() => {
|
|
18
18
|
injectElementUICSS();
|
|
19
|
-
import('
|
|
19
|
+
import('py-test-component').then(() => setLoaded(true));
|
|
20
20
|
}, []);
|
|
21
21
|
return loaded;
|
|
22
22
|
}
|
|
@@ -37,7 +37,8 @@ function wrapVueComponent(tagName, dataProp = null) {
|
|
|
37
37
|
return <div style={{ padding: '20px', textAlign: 'center' }}>{loading}</div>;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
// 使用 createElement 避免 JSX 标签名大小写警告
|
|
41
|
+
return React.createElement(tagName, { ref: ref || elRef, ...props });
|
|
41
42
|
});
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -47,7 +48,7 @@ export const PyWeather = wrapVueComponent('py-weather', 'propData');
|
|
|
47
48
|
|
|
48
49
|
// 初始化 store(仅设置,不暴露 store 给外部)
|
|
49
50
|
export function initStore(config) {
|
|
50
|
-
return import('
|
|
51
|
+
return import('py-test-component').then((m) => {
|
|
51
52
|
const PyComponent = m.default || m;
|
|
52
53
|
PyComponent?.initStore?.(config);
|
|
53
54
|
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import Vue from 'vue';
|
|
2
|
+
|
|
3
|
+
// 使用 Vue.observable 创建响应式全局状态
|
|
4
|
+
const state = Vue.observable({
|
|
5
|
+
config: {}
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// 订阅者列表(用于非 Vue 环境,如 React)
|
|
9
|
+
const subscribers = new Set();
|
|
10
|
+
|
|
11
|
+
function notifySubscribers(key, value) {
|
|
12
|
+
subscribers.forEach(fn => fn(key, value, state.config));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const store = {
|
|
16
|
+
/**
|
|
17
|
+
* 获取 store 中的值
|
|
18
|
+
* @param {string} key - 键名,支持点号路径如 'user.name'
|
|
19
|
+
* @returns {any} 值
|
|
20
|
+
*/
|
|
21
|
+
get(key) {
|
|
22
|
+
if (!key) return state.config;
|
|
23
|
+
const keys = key.split('.');
|
|
24
|
+
let value = state.config;
|
|
25
|
+
for (const k of keys) {
|
|
26
|
+
if (value === undefined || value === null) return undefined;
|
|
27
|
+
value = value[k];
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 设置 store 中的值
|
|
34
|
+
* @param {string} key - 键名
|
|
35
|
+
* @param {any} value - 值
|
|
36
|
+
*/
|
|
37
|
+
set(key, value) {
|
|
38
|
+
if (typeof key === 'object') {
|
|
39
|
+
Object.assign(state.config, key);
|
|
40
|
+
notifySubscribers(null, state.config);
|
|
41
|
+
} else {
|
|
42
|
+
const keys = key.split('.');
|
|
43
|
+
let target = state.config;
|
|
44
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
45
|
+
const k = keys[i];
|
|
46
|
+
if (!(k in target) || typeof target[k] !== 'object') {
|
|
47
|
+
target[k] = {};
|
|
48
|
+
}
|
|
49
|
+
target = target[k];
|
|
50
|
+
}
|
|
51
|
+
target[keys[keys.length - 1]] = value;
|
|
52
|
+
notifySubscribers(key, value);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 订阅 store 变化(供 React 等使用)
|
|
58
|
+
* @param {function} callback - (key, value, config) => void
|
|
59
|
+
* @returns {function} 取消订阅函数
|
|
60
|
+
*/
|
|
61
|
+
subscribe(callback) {
|
|
62
|
+
subscribers.add(callback);
|
|
63
|
+
return () => subscribers.delete(callback);
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 获取整个状态对象(供内部组件使用)
|
|
68
|
+
* @returns {object}
|
|
69
|
+
*/
|
|
70
|
+
getState() {
|
|
71
|
+
return state;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export default store;
|
package/src/utils/api.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { get, post } from './request';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 查询天气
|
|
5
|
+
* @param {string} city - 城市名称
|
|
6
|
+
* @returns {Promise<object>}
|
|
7
|
+
*/
|
|
8
|
+
export function queryWeather(city) {
|
|
9
|
+
// 使用免费的天气 API(心知天气或 OpenWeatherMap 等)
|
|
10
|
+
// 这里使用一个示例 API,实际使用时可以替换为真实的天气 API
|
|
11
|
+
const apiKey = 'demo'; // 实际使用时替换为真实的 API Key
|
|
12
|
+
const url = `https://api.seniverse.com/v3/weather/now.json`;
|
|
13
|
+
|
|
14
|
+
// 如果没有真实 API,返回模拟数据
|
|
15
|
+
if (apiKey === 'demo') {
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
setTimeout(() => {
|
|
18
|
+
resolve({
|
|
19
|
+
results: [{
|
|
20
|
+
location: { name: city },
|
|
21
|
+
now: {
|
|
22
|
+
temperature: Math.floor(Math.random() * 30) + 5,
|
|
23
|
+
text: ['晴', '多云', '阴', '小雨', '大雨'][Math.floor(Math.random() * 5)]
|
|
24
|
+
}
|
|
25
|
+
}]
|
|
26
|
+
});
|
|
27
|
+
}, 500);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return get(url, {
|
|
32
|
+
key: apiKey,
|
|
33
|
+
location: city
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 封装 fetch POST 请求
|
|
3
|
+
* @param {string} url - 请求地址
|
|
4
|
+
* @param {object} data - 请求数据
|
|
5
|
+
* @param {object} options - 其他配置
|
|
6
|
+
* @returns {Promise}
|
|
7
|
+
*/
|
|
8
|
+
export function post(url, data = {}, options = {}) {
|
|
9
|
+
const defaultOptions = {
|
|
10
|
+
method: 'POST',
|
|
11
|
+
headers: {
|
|
12
|
+
'Content-Type': 'application/json'
|
|
13
|
+
},
|
|
14
|
+
...options
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// 如果 data 是 FormData,不设置 Content-Type
|
|
18
|
+
if (data instanceof FormData) {
|
|
19
|
+
delete defaultOptions.headers['Content-Type'];
|
|
20
|
+
} else {
|
|
21
|
+
defaultOptions.body = JSON.stringify(data);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return fetch(url, defaultOptions)
|
|
25
|
+
.then(response => {
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
28
|
+
}
|
|
29
|
+
return response.json();
|
|
30
|
+
})
|
|
31
|
+
.catch(error => {
|
|
32
|
+
console.error('Request failed:', error);
|
|
33
|
+
throw error;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 封装 fetch GET 请求
|
|
39
|
+
* @param {string} url - 请求地址
|
|
40
|
+
* @param {object} params - URL 参数
|
|
41
|
+
* @param {object} options - 其他配置
|
|
42
|
+
* @returns {Promise}
|
|
43
|
+
*/
|
|
44
|
+
export function get(url, params = {}, options = {}) {
|
|
45
|
+
const queryString = Object.keys(params)
|
|
46
|
+
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
|
|
47
|
+
.join('&');
|
|
48
|
+
|
|
49
|
+
const fullUrl = queryString ? `${url}?${queryString}` : url;
|
|
50
|
+
|
|
51
|
+
return fetch(fullUrl, {
|
|
52
|
+
method: 'GET',
|
|
53
|
+
headers: {
|
|
54
|
+
'Content-Type': 'application/json'
|
|
55
|
+
},
|
|
56
|
+
...options
|
|
57
|
+
})
|
|
58
|
+
.then(response => {
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
61
|
+
}
|
|
62
|
+
return response.json();
|
|
63
|
+
})
|
|
64
|
+
.catch(error => {
|
|
65
|
+
console.error('Request failed:', error);
|
|
66
|
+
throw error;
|
|
67
|
+
});
|
|
68
|
+
}
|