@5ime/likeme 1.0.1 → 1.0.3
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/README.md +184 -53
- package/demo/index.html +43 -0
- package/index.js +103 -58
- package/package.json +23 -10
- package/public/index.html +37 -18
- package/static/css/likeme.css +1 -1
- package/static/js/likeme.js +1 -1
- package/vercel.json +14 -0
package/README.md
CHANGED
|
@@ -1,81 +1,212 @@
|
|
|
1
|
-
# likeMe
|
|
1
|
+
# likeMe - 一个可爱的互动点赞组件
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> 🍰 快速为你的网站添加 "你喜欢我吗 / Do you like me" 小组件
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
---
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
2. 点击左上角 [创建应用](https://console.leancloud.app/apps) 并起一个你喜欢的名字 (请选择免费的**开发版**):
|
|
9
|
-
3. 进入应用,选择左下角的 `设置` > `应用凭证`。你可以看到你的 `APP ID`,`APP Key` 和 `REST API 服务器地址`。请记录它们,以便后续使用。
|
|
10
|
-
4. 进入应用,选择左上角的 `数据存储` > `结构化数据` > `创建 Class`。`Class 名称` 填写为 `likeCount`,`Class 访问权限` 除 `update` 和 `find` 外均选择 `指定用户`,`默认 ACL 权限` 选定为 `限制读取`,点击 `创建` 。
|
|
11
|
-
5. 选择新建的 `Class` 然后 `添加行` -> `设置 ACL 权限` 均设置为 `所有用户` ,点击 `添加` ,你可以看到新建行的 `objectId,`。请记录它,以便后续使用。
|
|
7
|
+
## 功能特性
|
|
12
8
|
|
|
13
|
-
|
|
9
|
+
- 🎨 **可自定义颜色** - 支持自定义主题色彩
|
|
10
|
+
- 💾 **数据持久化** - 使用LeanCloud存储点赞数据
|
|
11
|
+
- 🚀 **快速部署** - 一键部署到Vercel
|
|
12
|
+
- 📱 **响应式设计** - 完美适配移动端和桌面端
|
|
13
|
+
- 🔒 **防重复点赞** - 基于IP地址防止重复点赞
|
|
14
|
+
- ⚡ **高性能** - 轻量级,加载速度快
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
---
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
## 快速开始
|
|
18
19
|
|
|
19
|
-
1.
|
|
20
|
+
### 1. LeanCloud 设置
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
1. [登录](https://console.leancloud.app/login) 或 [注册](https://console.leancloud.app/register) `LeanCloud 国际版` 并进入 [控制台](https://console.leancloud.app/apps)。
|
|
23
|
+
2. 点击左上角 [创建应用](https://console.leancloud.app/apps) 并起一个你喜欢的名字(请选择免费的 **开发版**)。
|
|
24
|
+
3. 进入应用,选择左下角的 **设置 > 应用凭证**,你可以看到你的 **APP ID**、**APP Key** 和 **REST API 服务器地址**,请记录它们以备后续使用。
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
### 2. 创建数据表
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
进入应用,选择左上角的 **数据存储 > 结构化数据**,依次创建以下两个 Class:
|
|
26
29
|
|
|
27
|
-
|
|
30
|
+
#### 创建 likeCount 表
|
|
31
|
+
- **Class 名称**:`likeCount`
|
|
32
|
+
- **Class 访问权限**:除 `update` 和 `find` 外均选择 **指定用户**
|
|
33
|
+
- **默认 ACL 权限**:选定为 **限制读取**,然后点击 **创建**
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
#### 创建 likeUser 表
|
|
36
|
+
- **Class 名称**:`likeUser`
|
|
37
|
+
- **Class 访问权限**:除 `create` 和 `find` 外均选择 **指定用户**
|
|
38
|
+
- **默认 ACL 权限**:选定为 **限制读取**,然后点击 **创建**
|
|
30
39
|
|
|
31
|
-
|
|
40
|
+
### 3. 初始化数据
|
|
32
41
|
|
|
33
|
-
|
|
42
|
+
1. 选择新建的 `likeCount` Class,点击 **添加行**
|
|
43
|
+
2. 添加一个名为 `count` 的 **Number** 字段,设置值为 `0`
|
|
44
|
+
3. 将 **ACL 权限** 设置为 **所有用户**,点击 **添加**
|
|
45
|
+
4. 记录生成行的 `objectId`
|
|
34
46
|
|
|
35
|
-
|
|
47
|
+
此时你应该拥有以下信息:
|
|
48
|
+
- `APP ID`
|
|
49
|
+
- `APP Key`
|
|
50
|
+
- `REST API 服务器地址`
|
|
51
|
+
- `objectId` 🎉
|
|
36
52
|
|
|
37
|
-
|
|
53
|
+
---
|
|
38
54
|
|
|
39
|
-
|
|
55
|
+
## 部署到 Vercel
|
|
40
56
|
|
|
41
|
-
|
|
42
|
-
| ----- | ------- | -------------------- |
|
|
43
|
-
| CNAME | example | cname.vercel-dns.com |
|
|
57
|
+
### 方法一:一键部署
|
|
44
58
|
|
|
45
|
-
|
|
59
|
+
[](https://vercel.com/new/clone?repository-url=https://github.com/5ime/likeMe)
|
|
46
60
|
|
|
47
|
-
|
|
61
|
+
1. 点击上方按钮跳转至 **Vercel** 进行部署
|
|
62
|
+
2. 输入一个你喜欢的 Vercel 项目名称并点击 **Create**
|
|
63
|
+
3. 等待部署完成
|
|
64
|
+
4. 点击 **Go to Dashboard** 跳转到控制台
|
|
48
65
|
|
|
49
|
-
|
|
66
|
+
### 方法二:手动部署
|
|
50
67
|
|
|
51
|
-
1.
|
|
68
|
+
1. Fork 或 Clone 本项目到你的 GitHub
|
|
69
|
+
2. 在 Vercel 中导入你的项目
|
|
70
|
+
3. 配置构建设置(通常会自动检测)
|
|
52
71
|
|
|
53
|
-
|
|
54
|
-
- `https://unpkg.com/ likeme@latest/static/js/likeme.js`。
|
|
72
|
+
### 配置环境变量
|
|
55
73
|
|
|
56
|
-
|
|
74
|
+
进入 **Settings > Environment Variables** 配置以下环境变量:
|
|
57
75
|
|
|
58
|
-
|
|
59
|
-
|
|
76
|
+
| 变量名 | 说明 | 示例 |
|
|
77
|
+
|--------|------|------|
|
|
78
|
+
| `appId` | LeanCloud 的 APP ID | `your-app-id` |
|
|
79
|
+
| `appKey` | LeanCloud 的 APP Key | `your-app-key` |
|
|
80
|
+
| `serverURL` | LeanCloud 的 REST API 服务器地址 | `https://your-app.lc-cn-n1-shared.com` |
|
|
81
|
+
| `objectId` | likeCount 表中记录的 objectId | `your-object-id` |
|
|
60
82
|
|
|
61
|
-
|
|
62
|
-
<head>
|
|
63
|
-
<!-- ... -->
|
|
64
|
-
<link rel="stylesheet" href='https://unpkg.com/likeme@latest/static/css/likeme.css'/>
|
|
65
|
-
<script src="https://unpkg.com/likeme@latest/static/js/likeme.js"></script>
|
|
66
|
-
<!-- ... -->
|
|
67
|
-
</head>
|
|
68
|
-
<body>
|
|
69
|
-
<!-- ... -->
|
|
70
|
-
<div id="likeme"></div>
|
|
71
|
-
<script>
|
|
72
|
-
new limeMe({
|
|
73
|
-
el: '#likeme',
|
|
74
|
-
serverURL: 'https://your-domain.vercel.app',
|
|
75
|
-
});
|
|
76
|
-
</script>
|
|
77
|
-
</body>
|
|
78
|
-
```
|
|
83
|
+
### 重新部署
|
|
79
84
|
|
|
80
|
-
|
|
85
|
+
环境变量配置完成后,进入 **Deployments**,找到最新一次部署并点击 **Redeploy** 按钮以使环境变量生效。
|
|
86
|
+
|
|
87
|
+
部署完成后,点击 **Visit** 跳转到部署好的地址,此地址即为你的服务端地址。
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 本地开发
|
|
92
|
+
|
|
93
|
+
### 环境要求
|
|
94
|
+
|
|
95
|
+
- Node.js >= 18.0.0
|
|
96
|
+
- npm 或 yarn
|
|
97
|
+
|
|
98
|
+
### 安装依赖
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npm install
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 配置环境变量
|
|
105
|
+
|
|
106
|
+
创建 `.env` 文件(或在系统环境变量中设置):
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
appId=your-leancloud-app-id
|
|
110
|
+
appKey=your-leancloud-app-key
|
|
111
|
+
serverURL=your-leancloud-server-url
|
|
112
|
+
objectId=your-object-id
|
|
113
|
+
PORT=3000
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 启动开发服务器
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npm start
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
服务器将在 `http://localhost:3000` 启动。
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 在你的网站中使用
|
|
127
|
+
|
|
128
|
+
### 方法一:使用 CDN
|
|
129
|
+
|
|
130
|
+
在你的网页中添加以下代码:
|
|
131
|
+
|
|
132
|
+
```html
|
|
133
|
+
<link rel="stylesheet" href="https://unpkg.com/@5ime/likeme@latest/static/css/likeme.css" />
|
|
134
|
+
<script src="https://unpkg.com/@5ime/likeme@latest/static/js/likeme.js"></script>
|
|
135
|
+
|
|
136
|
+
<div id="likeme"></div>
|
|
137
|
+
|
|
138
|
+
<script>
|
|
139
|
+
likeMe({
|
|
140
|
+
el: '#likeme',
|
|
141
|
+
serverURL: 'https://your-domain.vercel.app', // 替换为你的服务端地址
|
|
142
|
+
color: '#ff9797' // 可选:自定义主题颜色
|
|
143
|
+
});
|
|
144
|
+
</script>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 方法二:下载使用
|
|
148
|
+
|
|
149
|
+
1. 下载 `static/css/likeme.css` 和 `static/js/likeme.js`
|
|
150
|
+
2. 在你的项目中引入这些文件
|
|
151
|
+
3. 按照上面的方式初始化组件
|
|
152
|
+
|
|
153
|
+
### 配置选项
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
likeMe({
|
|
157
|
+
el: '#likeme', // 必需:容器选择器
|
|
158
|
+
serverURL: 'https://your-domain.vercel.app', // 必需:服务端地址
|
|
159
|
+
color: '#ff9797' // 可选:主题颜色,默认为 #ff9797
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 自定义域名(可选)
|
|
166
|
+
|
|
167
|
+
1. 进入 `Vercel` 控制台的 **Settings > Domains**
|
|
168
|
+
2. 输入需要绑定的域名并点击 **Add**
|
|
169
|
+
3. 在域名服务商处添加 `CNAME` 记录:
|
|
170
|
+
- **Type**: `CNAME`
|
|
171
|
+
- **Name**: `your-subdomain`
|
|
172
|
+
- **Value**: `cname.vercel-dns.com`
|
|
173
|
+
4. 等待 `DNS` 生效后,即可通过自定义域名访问 🎉
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## API 接口
|
|
178
|
+
|
|
179
|
+
### 获取点赞数
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
GET /info
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
返回:
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"code": "200",
|
|
189
|
+
"data": {
|
|
190
|
+
"count": 42
|
|
191
|
+
},
|
|
192
|
+
"msg": "success"
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 提交点赞
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
GET /like
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
返回:
|
|
203
|
+
```json
|
|
204
|
+
{
|
|
205
|
+
"code": "200",
|
|
206
|
+
"data": {
|
|
207
|
+
"count": 43
|
|
208
|
+
},
|
|
209
|
+
"msg": "success"
|
|
210
|
+
}
|
|
211
|
+
```
|
|
81
212
|
|
package/demo/index.html
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<meta name="description" content="Do You Like Me? - 组件演示页面">
|
|
7
|
+
<meta name="keywords" content="点赞,互动,like,web组件,演示">
|
|
8
|
+
<title>Do You Like Me? - 演示页面</title>
|
|
9
|
+
<link rel="stylesheet" href="/static/css/likeme.css">
|
|
10
|
+
<style>
|
|
11
|
+
.container {
|
|
12
|
+
margin: 0 auto;
|
|
13
|
+
max-width: 400px;
|
|
14
|
+
width: 100%;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.demo-info {
|
|
18
|
+
text-align: center;
|
|
19
|
+
margin-bottom: 20px;
|
|
20
|
+
padding: 10px;
|
|
21
|
+
border-radius: 5px;
|
|
22
|
+
}
|
|
23
|
+
</style>
|
|
24
|
+
</head>
|
|
25
|
+
<body>
|
|
26
|
+
<div class="container">
|
|
27
|
+
<div class="demo-info">
|
|
28
|
+
<h2>LikeMe 组件演示</h2>
|
|
29
|
+
<p>这是一个可交互的点赞组件演示页面</p>
|
|
30
|
+
</div>
|
|
31
|
+
<div id="likeme"></div>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<script src="/static/js/likeme.js"></script>
|
|
35
|
+
<script>
|
|
36
|
+
likeMe({
|
|
37
|
+
el: '#likeme',
|
|
38
|
+
serverURL: `${location.protocol}//${location.host}`,
|
|
39
|
+
color: '#ff4e6a'
|
|
40
|
+
});
|
|
41
|
+
</script>
|
|
42
|
+
</body>
|
|
43
|
+
</html>
|
package/index.js
CHANGED
|
@@ -1,58 +1,103 @@
|
|
|
1
|
-
const express = require('express')
|
|
2
|
-
const AV = require('leancloud-storage');
|
|
3
|
-
|
|
4
|
-
const app = express()
|
|
5
|
-
const port = 80
|
|
6
|
-
|
|
7
|
-
AV.init({
|
|
8
|
-
appId: process.env.appId,
|
|
9
|
-
appKey: process.env.appKey,
|
|
10
|
-
serverURL: process.env.serverURL
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const account = AV.Object.createWithoutData('likeCount', process.env.objectId);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
app.
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const AV = require('leancloud-storage');
|
|
3
|
+
|
|
4
|
+
const app = express();
|
|
5
|
+
const port = process.env.PORT || 80;
|
|
6
|
+
|
|
7
|
+
AV.init({
|
|
8
|
+
appId: process.env.appId,
|
|
9
|
+
appKey: process.env.appKey,
|
|
10
|
+
serverURL: process.env.serverURL
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const OBJECT_ID = process.env.objectId;
|
|
14
|
+
const account = AV.Object.createWithoutData('likeCount', process.env.objectId);
|
|
15
|
+
|
|
16
|
+
async function getCurrentCount() {
|
|
17
|
+
try {
|
|
18
|
+
const result = await new AV.Query('likeCount').get(OBJECT_ID);
|
|
19
|
+
return result.get('count') || 0;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
try {
|
|
22
|
+
const results = await new AV.Query('likeCount').find();
|
|
23
|
+
const record = results.find(r => r.id === OBJECT_ID) || results[0];
|
|
24
|
+
return record?.get('count') || 0;
|
|
25
|
+
} catch (fallbackError) {
|
|
26
|
+
const firstResult = await new AV.Query('likeCount').first();
|
|
27
|
+
return firstResult?.get('count') || 0;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function checkUserExists(ip) {
|
|
33
|
+
try {
|
|
34
|
+
return await new AV.Query('likeUser').equalTo('ip', ip).first();
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (error.code === 101) return false;
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function saveUserRecord(ip) {
|
|
42
|
+
try {
|
|
43
|
+
const likeUser = new (AV.Object.extend('likeUser'))();
|
|
44
|
+
likeUser.set('ip', ip);
|
|
45
|
+
await likeUser.save();
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.warn('Error: ', error.message);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
app.use((req, res, next) => {
|
|
52
|
+
res.header('Access-Control-Allow-Origin', '*');
|
|
53
|
+
res.header('Access-Control-Allow-Headers', 'Content-Type');
|
|
54
|
+
req.method === 'OPTIONS' ? res.sendStatus(200) : next();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
app.use('/static', express.static(`${__dirname}/static`));
|
|
58
|
+
app.use('/demo', express.static(`${__dirname}/demo`));
|
|
59
|
+
app.get('/', (req, res) => res.sendFile(`${__dirname}/public/index.html`));
|
|
60
|
+
|
|
61
|
+
app.get('/info', async (req, res) => {
|
|
62
|
+
try {
|
|
63
|
+
const count = await getCurrentCount();
|
|
64
|
+
res.json({ code: '200', msg: 'success', data: { count } });
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('Error in /info:', error);
|
|
67
|
+
res.json({ code: '201', msg: '哎呀,数据偷懒跑掉了' });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
app.get('/like', async (req, res) => {
|
|
72
|
+
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || 'unknown';
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const userExists = await checkUserExists(ip);
|
|
76
|
+
if (userExists) {
|
|
77
|
+
return res.json({ code: '201', msg: '今天的爱意已经收到啦~' });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const currentCount = await getCurrentCount();
|
|
81
|
+
const newCount = currentCount + 1;
|
|
82
|
+
|
|
83
|
+
account.set('count', newCount);
|
|
84
|
+
await account.save();
|
|
85
|
+
|
|
86
|
+
await saveUserRecord(ip);
|
|
87
|
+
|
|
88
|
+
res.json({
|
|
89
|
+
code: '200',
|
|
90
|
+
msg: 'success',
|
|
91
|
+
data: { count: newCount }
|
|
92
|
+
});
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error('Error in /like:', error);
|
|
95
|
+
res.json({ code: '201', msg: '你的爱意传递失败~' });
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
app.listen(port, () => {
|
|
100
|
+
console.log(`💖 启动成功!端口:${port}`);
|
|
101
|
+
console.log(`✨ 运行环境:${process.env.NODE_ENV || 'development'}`);
|
|
102
|
+
console.log(`🌍 访问地址:http://localhost:${port}`);
|
|
103
|
+
});
|
package/package.json
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@5ime/likeme",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Do you like me?",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@5ime/likeme",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "Do you like me? - 一个可爱的互动小组件",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node index.js"
|
|
8
|
+
},
|
|
9
|
+
"author": "iami233",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"express": "^5.1.0",
|
|
13
|
+
"leancloud-storage": "^4.15.2"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18.0.0"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/5ime/likeMe.git"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/5ime/likeMe"
|
|
23
|
+
}
|
package/public/index.html
CHANGED
|
@@ -1,18 +1,37 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta
|
|
6
|
-
<meta name="
|
|
7
|
-
<title>Do You Like Me
|
|
8
|
-
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<meta name="description" content="Do You Like Me? - 一个简单的点赞互动组件">
|
|
7
|
+
<title>Do You Like Me?</title>
|
|
8
|
+
<link rel="stylesheet" href="/static/css/likeme.css">
|
|
9
|
+
<style>
|
|
10
|
+
body {
|
|
11
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
12
|
+
margin: 0;
|
|
13
|
+
padding: 50px 20px;
|
|
14
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
15
|
+
min-height: 100vh;
|
|
16
|
+
display: flex;
|
|
17
|
+
justify-content: center;
|
|
18
|
+
align-items: center;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.container {
|
|
22
|
+
max-width: 400px;
|
|
23
|
+
width: 100%;
|
|
24
|
+
}
|
|
25
|
+
</style>
|
|
26
|
+
</head>
|
|
27
|
+
<body>
|
|
28
|
+
<div class="container">
|
|
29
|
+
<div class="likeCard">
|
|
30
|
+
<div class="likeCard-text">
|
|
31
|
+
少女祈祷中...
|
|
32
|
+
</div>
|
|
33
|
+
<h3>Do You Like Me</h3>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</body>
|
|
37
|
+
</html>
|
package/static/css/likeme.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.likeCard{width:auto;user-select:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;cursor:pointer;border-radius:5px;margin:0 auto;padding:20px;background:#ff9797;transition:
|
|
1
|
+
.likeCard{width:auto;user-select:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;cursor:pointer;border-radius:5px;margin:0 auto;padding:20px;background:#ff9797;transition:all .3s ease;color:#fff;text-align:center;box-shadow:0 2px 8px rgba(0,0,0,.1)}.likeCard:hover{background:#ff8d8d;transform:translateY(-1px);box-shadow:0 4px 12px rgba(0,0,0,.15)}.likeCard:active{transform:translateY(0px) scale(.98)}.likeCard>h3{font-size:1.2em;margin-top:.4em;margin-bottom:0;font-weight:600;transition:all .2s ease}.likeCard-text{font-size:1.1em;font-weight:500;transition:all .2s ease;min-height:1.5em;display:flex;align-items:center;justify-content:center}.likeCard.loading{pointer-events:none;opacity:.8;background:#ffa726}.likeCard.loading .likeCard-text{animation:pulse 1.2s infinite}.likeCard.success{background:#66bb6a;transform:scale(1.05)}@keyframes pulse{0%{opacity:1}50%{opacity:.6}100%{opacity:1}}
|
package/static/js/likeme.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
const likeMe=(()=>{let instance;return options=>instance||(instance=new LikeMeSingleton(options))})();class LikeMeSingleton{constructor({el,serverURL,color='#ff9797'}){Object.assign(this,{el,url:serverURL,color});this.isLiking=false;this.init()}async init(){try{await this.renderUI();this.attachEvents();this.updateCount()}catch(error){console.error('LikeMe init error:',error)}}async renderUI(){try{const response=await fetch(this.url);const html=await response.text();const element=document.querySelector(this.el);if(element){const parser=new DOMParser();const doc=parser.parseFromString(html,'text/html');element.innerHTML=doc.body.innerHTML;const card=element.querySelector('.likeCard');if(card){card.style.setProperty('background-color',this.color)}}}catch(error){console.error('Render UI error:',error)}}attachEvents(){const card=document.querySelector(`${this.el} .likeCard`);if(card){card.addEventListener('click',()=>this.handleLike())}}async updateCount(){try{const response=await fetch(`${this.url}/info`);if(response.ok){const data=await response.json();const count=data.data?.count||0;const textElement=document.querySelector(`${this.el} .likeCard-text`);if(textElement){textElement.textContent=`❤ ${count}`}}}catch(error){console.error('Update count error:',error)}}setUIState(isLoading,text,isSuccess=false){const card=document.querySelector(`${this.el} .likeCard`);const textElement=document.querySelector(`${this.el} .likeCard-text`);if(card&&textElement){card.classList.remove('loading','success');if(isLoading){card.classList.add('loading')}else if(isSuccess){card.classList.add('success')}textElement.textContent=text}}async handleLike(){if(this.isLiking)return;this.isLiking=true;this.setUIState(true,'❤ 爱意传递中...');try{const response=await fetch(`${this.url}/like`);if(response.ok){const{code,data={},msg}=await response.json();const count=data.count||0;if(code==='200'){this.setUIState(false,'❤ 传递成功~',true);setTimeout(()=>{this.setUIState(false,`❤ ${count}`)},500)}else{this.setUIState(false,msg);setTimeout(()=>{this.updateCount()},1500)}}}catch(error){console.error('Handle like error:',error);this.setUIState(false,'❤ 网络错误');setTimeout(()=>{this.updateCount()},1500)}finally{this.isLiking=false}}}
|