bohui-vue 1.0.1

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.
Files changed (104) hide show
  1. package/README.md +121 -0
  2. package/bin/create-vue-template.js +565 -0
  3. package/package.json +28 -0
  4. package/templates/vue-project/.browserslistrc +3 -0
  5. package/templates/vue-project/.editorconfig +28 -0
  6. package/templates/vue-project/.env.development +2 -0
  7. package/templates/vue-project/.env.production +2 -0
  8. package/templates/vue-project/.eslintrc.cjs +76 -0
  9. package/templates/vue-project/.keep +0 -0
  10. package/templates/vue-project/.node-version +1 -0
  11. package/templates/vue-project/.prettierignore +13 -0
  12. package/templates/vue-project/.prettierrc +20 -0
  13. package/templates/vue-project/.prettierrc.txt +130 -0
  14. package/templates/vue-project/.stylelintrc.json +94 -0
  15. package/templates/vue-project/README.md +24 -0
  16. package/templates/vue-project/babel.config.js +5 -0
  17. package/templates/vue-project/index.html +34 -0
  18. package/templates/vue-project/package.json +75 -0
  19. package/templates/vue-project/public/favicon.ico +0 -0
  20. package/templates/vue-project/public/static/img/ai-default.jpg +0 -0
  21. package/templates/vue-project/public/static/img/image.png +0 -0
  22. package/templates/vue-project/public/static/img/ppt1.png +0 -0
  23. package/templates/vue-project/public/static/img/ppt2.png +0 -0
  24. package/templates/vue-project/public/static/img/ppt3.png +0 -0
  25. package/templates/vue-project/public/static/js/config.js +11 -0
  26. package/templates/vue-project/public/static/js/dataConfig.js +1143 -0
  27. package/templates/vue-project/src/App.vue +10 -0
  28. package/templates/vue-project/src/api/error-handler.ts +60 -0
  29. package/templates/vue-project/src/api/http.ts +254 -0
  30. package/templates/vue-project/src/api/services/aicebd.ts +47 -0
  31. package/templates/vue-project/src/api/services/base.ts +18 -0
  32. package/templates/vue-project/src/api/services/umse.ts +17 -0
  33. package/templates/vue-project/src/assets/font/Alibaba-PuHuiTi-Medium.otf +0 -0
  34. package/templates/vue-project/src/assets/font/Alibaba-PuHuiTi-Regular.otf +0 -0
  35. package/templates/vue-project/src/assets/font/DOUYINSANSBOLD.OTF +0 -0
  36. package/templates/vue-project/src/assets/font/Pangmen-Title.TTF +0 -0
  37. package/templates/vue-project/src/assets/font/font.css +25 -0
  38. package/templates/vue-project/src/assets/iconfont/iconfont.css +402 -0
  39. package/templates/vue-project/src/assets/iconfont/iconfont.js +66 -0
  40. package/templates/vue-project/src/assets/iconfont/iconfont.json +688 -0
  41. package/templates/vue-project/src/assets/iconfont/iconfont.ttf +0 -0
  42. package/templates/vue-project/src/assets/iconfont/iconfont.woff +0 -0
  43. package/templates/vue-project/src/assets/iconfont/iconfont.woff2 +0 -0
  44. package/templates/vue-project/src/assets/images/Click-tap.png +0 -0
  45. package/templates/vue-project/src/assets/images/Effects.png +0 -0
  46. package/templates/vue-project/src/assets/images/bg.png +0 -0
  47. package/templates/vue-project/src/assets/images/erCode.png +0 -0
  48. package/templates/vue-project/src/assets/images/header-bg.png +0 -0
  49. package/templates/vue-project/src/assets/images/logo.png +0 -0
  50. package/templates/vue-project/src/assets/scss/common.scss +530 -0
  51. package/templates/vue-project/src/assets/styles/element-overrides.css +53 -0
  52. package/templates/vue-project/src/assets/styles/reset.css +186 -0
  53. package/templates/vue-project/src/assets/styles/theme.css +100 -0
  54. package/templates/vue-project/src/components/BarChart.vue +238 -0
  55. package/templates/vue-project/src/components/echarts/EChart.vue +140 -0
  56. package/templates/vue-project/src/composables/useTheme.ts +84 -0
  57. package/templates/vue-project/src/main.ts +111 -0
  58. package/templates/vue-project/src/mocks/base.ts +37 -0
  59. package/templates/vue-project/src/mocks/umse.ts +31 -0
  60. package/templates/vue-project/src/router/index.ts +32 -0
  61. package/templates/vue-project/src/shims-vue.d.ts +19 -0
  62. package/templates/vue-project/src/store/index.ts +18 -0
  63. package/templates/vue-project/src/store/modules/user.ts +85 -0
  64. package/templates/vue-project/src/types/DTO/aicebd.d.ts +60 -0
  65. package/templates/vue-project/src/types/DTO/base.d.ts +26 -0
  66. package/templates/vue-project/src/types/DTO/global.d.ts +48 -0
  67. package/templates/vue-project/src/types/VO/teachingLog.d.ts +15 -0
  68. package/templates/vue-project/src/types/auto-imports.d.ts +73 -0
  69. package/templates/vue-project/src/types/components.d.ts +17 -0
  70. package/templates/vue-project/src/types/element-plus.d.ts +15 -0
  71. package/templates/vue-project/src/types/js-cookie.d.ts +1 -0
  72. package/templates/vue-project/src/types/unocss.d.ts +2 -0
  73. package/templates/vue-project/src/types/vite-plugins.d.ts +3 -0
  74. package/templates/vue-project/src/types/vue-router.d.ts +1 -0
  75. package/templates/vue-project/src/types/window-config.d.ts +12 -0
  76. package/templates/vue-project/src/utils/com-methods.ts +307 -0
  77. package/templates/vue-project/src/utils/echarts.ts +111 -0
  78. package/templates/vue-project/src/utils/number.ts +99 -0
  79. package/templates/vue-project/src/utils/rem.ts +82 -0
  80. package/templates/vue-project/src/utils/responsive.ts +103 -0
  81. package/templates/vue-project/src/utils/time.ts +314 -0
  82. package/templates/vue-project/src/utils/tracker.ts +527 -0
  83. package/templates/vue-project/src/utils/validators.ts +85 -0
  84. package/templates/vue-project/src/utils/window.ts +132 -0
  85. package/templates/vue-project/src/views/home/Home.vue +60 -0
  86. package/templates/vue-project/src/views/home/composables/useUserAuth.ts +13 -0
  87. package/templates/vue-project/src/views/teachingLog/TeachingLog.vue +40 -0
  88. package/templates/vue-project/src/views/teachingLog/__tests__/TeachingEffect.test.ts +96 -0
  89. package/templates/vue-project/src/views/teachingLog/__tests__/TeachingHighlight.test.ts +66 -0
  90. package/templates/vue-project/src/views/teachingLog/__tests__/TeachingLog.test.ts +34 -0
  91. package/templates/vue-project/src/views/teachingLog/components/TeachingEffect.vue +458 -0
  92. package/templates/vue-project/src/views/teachingLog/components/TeachingHighlight.vue +181 -0
  93. package/templates/vue-project/src/views/teachingLog/composables/useEffectTooltip.ts +88 -0
  94. package/templates/vue-project/src/views/teachingLog/composables/useEffectTrendChart.ts +160 -0
  95. package/templates/vue-project/tests/setup.ts +27 -0
  96. package/templates/vue-project/tsconfig.json +24 -0
  97. package/templates/vue-project/tsconfig.node.json +41 -0
  98. package/templates/vue-project/uno.config.ts +84 -0
  99. package/templates/vue-project/vite.config.ts +216 -0
  100. package/templates/vue-project/vue3_ai_prompt.md +652 -0
  101. package/templates/vue-project/vue3_ai_prompt_basic.md +722 -0
  102. package/templates/vue-project/vue3_ai_prompt_full.md +1021 -0
  103. package/templates/vue-project/vue3_ai_prompt_unocss.md +768 -0
  104. package/templates/vue-project//345/267/245/347/250/213/345/214/226/346/250/241/346/235/277/344/273/213/347/273/215.md +463 -0
@@ -0,0 +1,10 @@
1
+ <template>
2
+ <el-config-provider :locale="locale">
3
+ <router-view />
4
+ </el-config-provider>
5
+ </template>
6
+
7
+ <script setup>
8
+ import zhCn from "element-plus/dist/locale/zh-cn.mjs";
9
+ const locale = zhCn;
10
+ </script>
@@ -0,0 +1,60 @@
1
+ /**
2
+ * API 错误处理工具
3
+ */
4
+
5
+ /**
6
+ * 常用错误码枚举
7
+ */
8
+ export enum ErrorCode {
9
+ SUCCESS = 0,
10
+ UNAUTHORIZED = 401,
11
+ FORBIDDEN = 403,
12
+ NOT_FOUND = 404,
13
+ SERVER_ERROR = 500,
14
+ }
15
+
16
+ /**
17
+ * 判断是否为 API 响应错误
18
+ */
19
+ export const isApiResponseError = <T = unknown>(
20
+ error: unknown
21
+ ): error is globalDto.ResponseType<T> => {
22
+ return (
23
+ typeof error === "object" &&
24
+ error !== null &&
25
+ ("code" in error || "status" in error) &&
26
+ "message" in error
27
+ );
28
+ };
29
+
30
+ /**
31
+ * 错误码对应的中文提示映射
32
+ */
33
+ const ERROR_CODE_MESSAGE_MAP: Record<number, string> = {
34
+ [ErrorCode.UNAUTHORIZED]: "登录已过期,请重新登录",
35
+ [ErrorCode.FORBIDDEN]: "权限不足,无法访问该资源",
36
+ [ErrorCode.NOT_FOUND]: "请求的资源不存在",
37
+ [ErrorCode.SERVER_ERROR]: "服务器内部错误,请稍后重试",
38
+ };
39
+
40
+ /**
41
+ * 获取错误消息(兼容多种错误类型)
42
+ */
43
+ export const getErrorMessage = (error: unknown): string => {
44
+ if (isApiResponseError(error)) {
45
+ // 优先使用错误码映射表中的中文提示
46
+ const code = error.status;
47
+ if (code !== undefined && code in ERROR_CODE_MESSAGE_MAP) {
48
+ return ERROR_CODE_MESSAGE_MAP[code];
49
+ }
50
+ // 其他错误码返回接口的错误信息
51
+ return error.message || "请求失败";
52
+ }
53
+ if (error instanceof Error) {
54
+ return error.message;
55
+ }
56
+ if (typeof error === "string") {
57
+ return error;
58
+ }
59
+ return "未知错误";
60
+ };
@@ -0,0 +1,254 @@
1
+ /*
2
+ * @Description: Axios 实例配置与拦截器
3
+ * @Version: 2.0
4
+ * @Autor: wangmeng
5
+ * @Date: 2021-08-03 11:13:38
6
+ * @LastEditors: wamgmeng
7
+ * @LastEditTime: 2024-01-18 15:11:04
8
+ */
9
+ /* eslint-disable no-param-reassign */
10
+ import type {
11
+ AxiosInstance,
12
+ AxiosRequestConfig,
13
+ InternalAxiosRequestConfig,
14
+ AxiosResponse,
15
+ AxiosError,
16
+ } from "axios";
17
+ import axios from "axios";
18
+ import qs from "qs";
19
+ import { getErrorMessage } from "./error-handler";
20
+
21
+ // 响应状态码枚举
22
+ enum ResponseStatus {
23
+ SUCCESS = 0,
24
+ SUCCESS_1 = 200,
25
+ REDIRECT = 302,
26
+ FORBIDDEN = 400003,
27
+ }
28
+
29
+ // 请求超时时间(3000秒)
30
+ const REQUEST_TIMEOUT = 3000000;
31
+
32
+ /**
33
+ * 处理重定向逻辑
34
+ */
35
+ const handleRedirect = (result: string): void => {
36
+ const root = result.includes("http://") || result.includes("https://") ? "" : "http://";
37
+
38
+ setTimeout(() => {
39
+ if (result.includes("serverName")) {
40
+ window.location.href = `${root}${result}`;
41
+ } else {
42
+ const encodedUrl = encodeURIComponent(window.btoa(window.location.href));
43
+ window.location.href = `${root}${result}?serverName=${encodedUrl}`;
44
+ }
45
+ }, 300);
46
+ };
47
+
48
+ /**
49
+ * 创建并配置 JSON 格式的 Axios 实例
50
+ */
51
+ const createJsonHttp = (): AxiosInstance => {
52
+ const instance = axios.create({
53
+ timeout: REQUEST_TIMEOUT,
54
+ transformRequest: [
55
+ (data: unknown): string => {
56
+ return JSON.stringify(data);
57
+ },
58
+ ],
59
+ });
60
+
61
+ // 请求拦截器
62
+ instance.interceptors.request.use(
63
+ (config: InternalAxiosRequestConfig): InternalAxiosRequestConfig => {
64
+ config.headers["Content-Type"] = "application/json";
65
+ const key = `${(config.method || "get").toUpperCase()}:${config.url}`;
66
+ const prev = pendingJson.get(key);
67
+ prev?.abort();
68
+ const controller = new AbortController();
69
+ (config as any).signal = controller.signal;
70
+ pendingJson.set(key, controller);
71
+ const envUseMock = (import.meta as any).env?.VITE_USE_MOCK === "true";
72
+ const envScenario = (import.meta as any).env?.VITE_MOCK_SCENARIO;
73
+ if (
74
+ envUseMock &&
75
+ envScenario &&
76
+ envScenario !== "normal" &&
77
+ typeof config.url === "string"
78
+ ) {
79
+ const u = new URL(config.url, window.location.origin);
80
+ u.searchParams.set("scenario", envScenario);
81
+ config.url = u.pathname + u.search;
82
+ }
83
+ return config;
84
+ },
85
+ (error: AxiosError): Promise<AxiosError> => {
86
+ return Promise.reject(error);
87
+ }
88
+ );
89
+
90
+ // 响应拦截器
91
+ instance.interceptors.response.use(
92
+ <T>(response: AxiosResponse<globalDto.ResponseType<T>>): Promise<T> | Promise<never> => {
93
+ const key = `${(response.config.method || "get").toUpperCase()}:${response.config.url}`;
94
+ const c = pendingJson.get(key);
95
+ if (c) pendingJson.delete(key);
96
+ if (response.status < 400) {
97
+ const { data } = response;
98
+ const statusCode = data.code ?? data.status;
99
+
100
+ // 处理重定向
101
+ if (statusCode === ResponseStatus.REDIRECT) {
102
+ const redirectUrl = data.result || data.message;
103
+ if (typeof redirectUrl === "string") {
104
+ handleRedirect(redirectUrl);
105
+ }
106
+ return Promise.reject(new Error("Redirecting..."));
107
+ }
108
+
109
+ // 处理成功响应 - 返回 data 字段(已解包第一层 ResponseType)
110
+ if (
111
+ statusCode === ResponseStatus.SUCCESS ||
112
+ statusCode === ResponseStatus.SUCCESS_1 ||
113
+ statusCode === ResponseStatus.FORBIDDEN
114
+ ) {
115
+ if (
116
+ statusCode === ResponseStatus.SUCCESS ||
117
+ statusCode === ResponseStatus.SUCCESS_1
118
+ ) {
119
+ return Promise.resolve(data.data);
120
+ }
121
+ return Promise.reject(new Error(getErrorMessage(data)));
122
+ }
123
+
124
+ return Promise.reject(new Error(getErrorMessage(data)));
125
+ }
126
+
127
+ return Promise.reject(new Error(`HTTP Error: ${response.status}`));
128
+ },
129
+ (error: AxiosError): Promise<never> => {
130
+ const cfg = error.config as InternalAxiosRequestConfig | undefined;
131
+ if (cfg) {
132
+ const key = `${(cfg.method || "get").toUpperCase()}:${cfg.url}`;
133
+ const c = pendingJson.get(key);
134
+ if (c) pendingJson.delete(key);
135
+ }
136
+ const message = getErrorMessage(error.response?.data ?? error);
137
+ return Promise.reject(new Error(message));
138
+ }
139
+ );
140
+
141
+ return instance;
142
+ };
143
+
144
+ /**
145
+ * 创建并配置 FormData 格式的 Axios 实例
146
+ */
147
+ const createFormDataHttp = (): AxiosInstance => {
148
+ const instance = axios.create({
149
+ timeout: REQUEST_TIMEOUT,
150
+ transformRequest: [
151
+ (data: unknown): string => {
152
+ return qs.stringify(data);
153
+ },
154
+ ],
155
+ });
156
+
157
+ // 请求拦截器
158
+ instance.interceptors.request.use(
159
+ (config: InternalAxiosRequestConfig): InternalAxiosRequestConfig => {
160
+ config.headers["Content-Type"] = "application/x-www-form-urlencoded";
161
+ const key = `${(config.method || "get").toUpperCase()}:${config.url}`;
162
+ const prev = pendingForm.get(key);
163
+ prev?.abort();
164
+ const controller = new AbortController();
165
+ (config as any).signal = controller.signal;
166
+ pendingForm.set(key, controller);
167
+ const envUseMock = (import.meta as any).env?.VITE_USE_MOCK === "true";
168
+ const envScenario = (import.meta as any).env?.VITE_MOCK_SCENARIO;
169
+ if (
170
+ envUseMock &&
171
+ envScenario &&
172
+ envScenario !== "normal" &&
173
+ typeof config.url === "string"
174
+ ) {
175
+ const u = new URL(config.url, window.location.origin);
176
+ u.searchParams.set("scenario", envScenario);
177
+ config.url = u.pathname + u.search;
178
+ }
179
+ return config;
180
+ },
181
+ (error: AxiosError): Promise<AxiosError> => {
182
+ return Promise.reject(error);
183
+ }
184
+ );
185
+
186
+ // 响应拦截器
187
+ instance.interceptors.response.use(
188
+ <T>(response: AxiosResponse<globalDto.ResponseType<T>>): Promise<T> | Promise<never> => {
189
+ const key = `${(response.config.method || "get").toUpperCase()}:${response.config.url}`;
190
+ const c = pendingForm.get(key);
191
+ if (c) pendingForm.delete(key);
192
+ if (response.status < 400) {
193
+ const { data } = response;
194
+ const statusCode = data.code ?? data.status;
195
+
196
+ // 处理重定向
197
+ if (statusCode === ResponseStatus.REDIRECT) {
198
+ const redirectUrl = data.result || data.message;
199
+ if (typeof redirectUrl === "string") {
200
+ handleRedirect(redirectUrl);
201
+ }
202
+ return Promise.reject(new Error("Redirecting..."));
203
+ }
204
+
205
+ // 处理成功响应 - 返回 data 字段(已解包第一层 ResponseType)
206
+ if (
207
+ statusCode === ResponseStatus.SUCCESS ||
208
+ statusCode === ResponseStatus.SUCCESS_1 ||
209
+ statusCode === ResponseStatus.FORBIDDEN
210
+ ) {
211
+ if (
212
+ statusCode === ResponseStatus.SUCCESS ||
213
+ statusCode === ResponseStatus.SUCCESS_1
214
+ ) {
215
+ return Promise.resolve(data.data);
216
+ }
217
+ return Promise.reject(new Error(getErrorMessage(data)));
218
+ }
219
+
220
+ return Promise.reject(new Error(getErrorMessage(data)));
221
+ }
222
+
223
+ return Promise.reject(new Error(`HTTP Error: ${response.status}`));
224
+ },
225
+ (error: AxiosError): Promise<never> => {
226
+ const cfg = error.config as InternalAxiosRequestConfig | undefined;
227
+ if (cfg) {
228
+ const key = `${(cfg.method || "get").toUpperCase()}:${cfg.url}`;
229
+ const c = pendingForm.get(key);
230
+ if (c) pendingForm.delete(key);
231
+ }
232
+ const message = getErrorMessage(error.response?.data ?? error);
233
+ return Promise.reject(new Error(message));
234
+ }
235
+ );
236
+
237
+ return instance;
238
+ };
239
+
240
+ // 创建实例
241
+ const pendingJson = new Map<string, AbortController>();
242
+ const pendingForm = new Map<string, AbortController>();
243
+ const http = createJsonHttp();
244
+ const formDataHttp = createFormDataHttp();
245
+
246
+ function abortAllRequests(): void {
247
+ pendingJson.forEach((c) => c.abort());
248
+ pendingForm.forEach((c) => c.abort());
249
+ pendingJson.clear();
250
+ pendingForm.clear();
251
+ }
252
+
253
+ export { http, formDataHttp, abortAllRequests };
254
+ export type { AxiosInstance, AxiosRequestConfig, AxiosResponse };
@@ -0,0 +1,47 @@
1
+ import { http } from "../http";
2
+
3
+ const aiceUrl = "/api/aicebd";
4
+
5
+ /**
6
+ * @name: 学生出勤信息
7
+ * @param
8
+ * @return {TeachingEffectConfig}
9
+ */
10
+ export const getAILessonAttendAPI = (lessonCode: string): Promise<aicebdDto.AILessonAttendance> => {
11
+ return http.get(`${aiceUrl}/aiLessonResult/getAILessonAttend?lessonCode=${lessonCode}`);
12
+ };
13
+
14
+ /**
15
+ * @name: 学生整体专注度
16
+ * @param
17
+ * @return {number}
18
+ */
19
+ export const getStudentListenOverviewAPI = (
20
+ lessonCode: string
21
+ ): Promise<aicebdDto.StudentListenOverview> => {
22
+ return http.get(`${aiceUrl}/focus/studentListenOverview?lessonCode=${lessonCode}`);
23
+ };
24
+
25
+ /**
26
+ * @name: 学生专注度趋势图
27
+ * @param
28
+ * @return {aicebdDto.StudentListenFocus[]}
29
+ */
30
+ export const studentListenFocusListAPI = (
31
+ lessonCode: string
32
+ ): Promise<aicebdDto.StudentListenFocus[]> => {
33
+ console.log(lessonCode);
34
+ return http.get(`${aiceUrl}/focus/studentListenFocusList?lessonCode=${lessonCode}`);
35
+ };
36
+
37
+ /**
38
+ * @name: 学生听课效果分析
39
+ * @param
40
+ * @return {TeachingEffectConfig}
41
+ */
42
+
43
+ export const studentListenPerformanceAnalyzeAPI = (
44
+ lessonCode: string
45
+ ): Promise<aicebdDto.StudentListenPerformanceAnalyze> => {
46
+ return http.get(`${aiceUrl}/focus/studentListenPerformanceAnalyze?lessonCode=${lessonCode}`);
47
+ };
@@ -0,0 +1,18 @@
1
+ import Cookies from "js-cookie";
2
+ import { getFieldFromUrl } from "@/utils/window";
3
+ import { formDataHttp } from "../http";
4
+
5
+ const aiceUrl = "/api/base";
6
+
7
+ /**
8
+ * @name: 获取用户信息
9
+ * @param {void}
10
+ * @return {aicebdDto.UserInfo}
11
+ */
12
+ export const getUserInfoAPI = (): Promise<baseDto.UserInfo> => {
13
+ const urlToken = getFieldFromUrl(window.location.href, "token");
14
+ const token = Cookies.get("token") ?? urlToken ?? "";
15
+ return formDataHttp.post(`${aiceUrl}/base/organization/user/getUserInfo`, {
16
+ token: Cookies.get("token") || token,
17
+ });
18
+ };
@@ -0,0 +1,17 @@
1
+ import Cookies from "js-cookie";
2
+ import { http } from "../http";
3
+
4
+ const aiceUrl = "/api/umse";
5
+
6
+ /**
7
+ * @name: 退出登录
8
+ * @param
9
+ * @return {void}
10
+ */
11
+ export const logoutAPI = (): Promise<void> => {
12
+ Cookies.remove("token");
13
+ const _serverName = window.btoa(window.location.href.split("?")[0]);
14
+ return http.post(`${aiceUrl}/userManage/logout`, {
15
+ serverName: _serverName,
16
+ });
17
+ };
@@ -0,0 +1,25 @@
1
+ @font-face {
2
+ font-family: "Alibaba PuHuiTi";
3
+ src: url(./Alibaba-PuHuiTi-Regular.otf);
4
+ font-weight: 400;
5
+ font-style: normal;
6
+ font-display: swap;
7
+ }
8
+ @font-face {
9
+ font-family: "Alibaba-PuHuiTi-Medium";
10
+ src: url(./Alibaba-PuHuiTi-Medium.otf);
11
+ font-weight: 400;
12
+ font-style: normal;
13
+ font-display: swap;
14
+ }
15
+ @font-face {
16
+ font-family: "douyinbold";
17
+ src: url(./DOUYINSANSBOLD.OTF);
18
+ font-style: normal;
19
+ font-display: swap;
20
+ }
21
+ /* 庞门正道标题体 */
22
+ @font-face {
23
+ font-family: "Pangmen Title";
24
+ src: url(./Pangmen-Title.TTF);
25
+ }