├── src
├── util
│ ├── index.js
│ ├── request.js
│ └── service.js
├── assets
│ ├── less
│ │ ├── var.less
│ │ └── common.less
│ └── font
│ │ └── iconfont.less
├── views
│ ├── bar.vue
│ └── foo.vue
├── components
│ └── Hello.vue
├── server
│ ├── process.js
│ └── index.js
├── main.js
├── router
│ └── index.js
└── app.vue
├── .gitignore
├── public
├── favicon.ico
└── index.php
├── themeConfig.js
├── webpack
├── webpack.dev.js
├── webpack.common.js
└── webpack.prod.js
├── package.json
└── readme.md
/src/util/index.js:
--------------------------------------------------------------------------------
1 | export default {};
2 |
--------------------------------------------------------------------------------
/src/assets/less/var.less:
--------------------------------------------------------------------------------
1 | // 定义less变量
2 | @border: #ccc;
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/17px/typecho-theme-builder/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/views/bar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
--------------------------------------------------------------------------------
/src/views/foo.vue:
--------------------------------------------------------------------------------
1 |
2 | foo
3 |
4 |
5 |
8 |
9 |
--------------------------------------------------------------------------------
/src/assets/less/common.less:
--------------------------------------------------------------------------------
1 | // 全局css
2 | @import "./var.less";
3 | @import "../font/iconfont.less";
4 |
5 | body {
6 | font-size: 14px;
7 | line-height: 1.5;
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/Hello.vue:
--------------------------------------------------------------------------------
1 |
2 | Hello Yuna
3 |
4 |
5 |
10 |
11 |
--------------------------------------------------------------------------------
/src/server/process.js:
--------------------------------------------------------------------------------
1 | // 接口数据清洗
2 | export default {
3 | getPages: async d => {
4 | let pagesList = [];
5 | for (let item of d) if (item) pagesList.push(item);
6 | return pagesList;
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/src/server/index.js:
--------------------------------------------------------------------------------
1 | import Request from "../util/request";
2 | import Process from "./process";
3 |
4 | /**
5 | * 获取全部页面
6 | */
7 | export const getPages = () => {
8 | return Request("/api/pages").then(res => Process.getPages(res.data.dataSet));
9 | };
10 |
--------------------------------------------------------------------------------
/themeConfig.js:
--------------------------------------------------------------------------------
1 | // 主题配置
2 | module.exports = {
3 | // 主题名称
4 | name: "Yuna",
5 | // 打包上线的版本管理
6 | ver: "1.0",
7 | server: {
8 | // 后端Restful插件接口基础地址,如果开启伪静态,修改为'/'
9 | url: "/typecho/index.php",
10 | // axios请求超时时间
11 | timeout: 8000
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/src/util/request.js:
--------------------------------------------------------------------------------
1 | import Service from "./service";
2 |
3 | const request = (api = "", params = {}, type = "get") => {
4 | let data = type === "get" ? "params" : "data";
5 | return Service({
6 | url: api,
7 | [data]: params,
8 | method: type
9 | }).then(res => res.data);
10 | };
11 |
12 | export default request;
13 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import App from "./app.vue";
3 | import router from "./router";
4 | import "./assets/less/common.less";
5 |
6 | router.beforeEach((to, from, next) => {
7 | /* 路由发生变化修改页面title */
8 | if (to.meta.title) {
9 | document.title = to.meta.title;
10 | }
11 | next();
12 | });
13 |
14 | new Vue({
15 | router,
16 | render: h => h(App)
17 | }).$mount("#app");
18 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import VueRouter from "vue-router";
3 |
4 | const Foo = { template: "
foo
" };
5 | const Bar = { template: "bar
" };
6 |
7 | Vue.use(VueRouter);
8 |
9 | const routes = [
10 | {
11 | path: "/foo",
12 | component: Foo,
13 | meta: {
14 | title: "Foo Page"
15 | }
16 | },
17 | {
18 | path: "/bar",
19 | component: Bar,
20 | meta: {
21 | title: "Bar Page"
22 | }
23 | }
24 | ];
25 |
26 | const router = new VueRouter({
27 | routes
28 | });
29 |
30 | export default router;
31 |
--------------------------------------------------------------------------------
/src/util/service.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import THEME from "../../themeConfig";
3 |
4 | const service = axios.create({
5 | baseURL: THEME.server.url,
6 | timeout: THEME.server.timeout
7 | });
8 |
9 | // 请求
10 | service.interceptors.request.use(
11 | config => {
12 | // reqNum++;
13 | // loading.show();
14 | return config;
15 | },
16 | error => Promise.reject(error)
17 | );
18 |
19 | // 响应
20 | service.interceptors.response.use(
21 | response => {
22 | // reqNum--;
23 | // if (reqNum <= 0) {
24 | // loading.hidden();
25 | // } else {
26 | // loading.show();
27 | // }
28 | return response;
29 | },
30 | error => Promise.reject(error)
31 | );
32 |
33 | export default service;
34 |
--------------------------------------------------------------------------------
/src/assets/font/iconfont.less:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: "iconfont"; /* project id 1554341 */
3 | src: url("//at.alicdn.com/t/font_1554341_j7v6ta5z5a.eot");
4 | src: url("//at.alicdn.com/t/font_1554341_j7v6ta5z5a.eot?#iefix")
5 | format("embedded-opentype"),
6 | url("//at.alicdn.com/t/font_1554341_j7v6ta5z5a.woff2") format("woff2"),
7 | url("//at.alicdn.com/t/font_1554341_j7v6ta5z5a.woff") format("woff"),
8 | url("//at.alicdn.com/t/font_1554341_j7v6ta5z5a.ttf") format("truetype"),
9 | url("//at.alicdn.com/t/font_1554341_j7v6ta5z5a.svg#iconfont") format("svg");
10 | }
11 |
12 | .iconfont {
13 | font-family: "iconfont" !important;
14 | font-size: 16px;
15 | font-style: normal;
16 | -webkit-font-smoothing: antialiased;
17 | -webkit-text-stroke-width: 0.2px;
18 | -moz-osx-font-smoothing: grayscale;
19 | }
20 |
--------------------------------------------------------------------------------
/webpack/webpack.dev.js:
--------------------------------------------------------------------------------
1 | const merge = require("webpack-merge");
2 | const common = require("./webpack.common.js");
3 | const path = require("path");
4 | const HtmlWebpackPlugin = require("html-webpack-plugin");
5 |
6 | // 载入主题配置
7 | const THEME = require("../themeConfig");
8 |
9 | module.exports = merge(common, {
10 | mode: "development",
11 | watch: true,
12 | output: {
13 | path: path.resolve(__dirname, `../../${THEME.name}`),
14 | filename: `${THEME.name}.js`
15 | },
16 | module: {
17 | rules: [
18 | {
19 | test: /\.(css|less)$/,
20 | use: ["style-loader", "css-loader", "less-loader"]
21 | }
22 | ]
23 | },
24 | plugins: [
25 | new HtmlWebpackPlugin({
26 | filename: "index.php",
27 | template: path.join(__dirname, "../public/index.php"),
28 | inject: false,
29 | favicon: path.join(__dirname, `../public/favicon.ico`),
30 | hash: true
31 | })
32 | ]
33 | });
34 |
--------------------------------------------------------------------------------
/webpack/webpack.common.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const { VueLoaderPlugin } = require("vue-loader");
3 |
4 | // 载入主题配置
5 | const THEME = require("../themeConfig");
6 |
7 | // 公共webpack配置
8 | module.exports = {
9 | stats: {
10 | modules: false,
11 | children: false,
12 | chunks: false,
13 | chunkModules: false
14 | },
15 | entry: path.join(__dirname, "../src/main.js"),
16 | module: {
17 | rules: [
18 | {
19 | test: /\.vue$/,
20 | loader: "vue-loader"
21 | },
22 | {
23 | test: /\.js$/,
24 | loader: "babel-loader"
25 | },
26 | {
27 | test: /\.(png|jpg|gif|jpeg)$/,
28 | use: [
29 | {
30 | loader: "url-loader",
31 | options: {
32 | esModule: false,
33 | limit: true
34 | }
35 | }
36 | ]
37 | }
38 | ]
39 | },
40 | plugins: [new VueLoaderPlugin()],
41 | // cdn
42 | externals: {
43 | vue: "Vue",
44 | "vue-router": "VueRouter"
45 | }
46 | };
47 |
--------------------------------------------------------------------------------
/src/app.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
1.使用tp-cli
5 |
2.阿里iconfont
6 |
7 |
8 |
9 |
10 |
3.路由演示
11 |
12 | Go to Foo1
13 | Go to Bar2
14 |
15 |
16 |
4.css预编译器
17 |
推荐使用less
18 |
5.请求typecho后端接口
19 |
{{dataFromApi}}
20 |
21 |
22 |
23 |
39 |
40 |
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | hello-world
21 |
22 |
23 |
24 |
25 |
26 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yuna",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "webpack --config ./webpack/webpack.dev.js",
8 | "build": "webpack --config ./webpack/webpack.prod.js"
9 | },
10 | "author": "陈睿",
11 | "license": "MIT",
12 | "dependencies": {
13 | "@babel/core": "^7.7.7",
14 | "axios": "^0.19.0",
15 | "babel-loader": "^8.0.6",
16 | "clean-webpack-plugin": "^3.0.0",
17 | "css-loader": "^3.4.0",
18 | "extract-text-webpack-plugin": "^4.0.0-beta.0",
19 | "html-webpack-plugin": "^3.2.0",
20 | "image-webpack-loader": "^6.0.0",
21 | "less": "^3.10.3",
22 | "less-loader": "^5.0.0",
23 | "mini-css-extract-plugin": "^0.9.0",
24 | "optimize-css-assets-webpack-plugin": "^5.0.3",
25 | "style-loader": "^1.1.2",
26 | "terser-webpack-plugin": "^2.3.1",
27 | "url-loader": "^3.0.0",
28 | "vue": "^2.6.11",
29 | "vue-html-loader": "^1.2.4",
30 | "vue-loader": "^15.8.3",
31 | "vue-router": "^3.1.3",
32 | "vue-style-loader": "^4.1.2",
33 | "webpack": "^4.41.4",
34 | "webpack-merge": "^4.2.2"
35 | },
36 | "devDependencies": {
37 | "vue-template-compiler": "^2.6.11",
38 | "webpack-cli": "^3.3.10"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/webpack/webpack.prod.js:
--------------------------------------------------------------------------------
1 | const merge = require("webpack-merge");
2 | const common = require("./webpack.common.js");
3 | const path = require("path");
4 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
5 | const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin");
6 | const TerserJSPlugin = require("terser-webpack-plugin");
7 | const HtmlWebpackPlugin = require("html-webpack-plugin");
8 |
9 | // 载入主题配置
10 | const THEME = require("../themeConfig");
11 |
12 | module.exports = merge(common, {
13 | mode: "production",
14 | output: {
15 | path: path.resolve(__dirname, `../../${THEME.name}/${THEME.ver}/`),
16 | filename: `${THEME.name}.${THEME.ver}.min.js`
17 | },
18 | optimization: {
19 | minimizer: [
20 | new TerserJSPlugin({}),
21 | new OptimizeCssAssetsPlugin({})
22 | ],
23 | minimize: true
24 | },
25 | module: {
26 | rules: [
27 | {
28 | test: /\.(css|less)$/,
29 | use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"]
30 | }
31 | ]
32 | },
33 | plugins: [
34 | new MiniCssExtractPlugin({
35 | filename: `${THEME.name}.${THEME.ver}.min.css`,
36 | hash: true
37 | }),
38 | new HtmlWebpackPlugin({
39 | filename: "index.php",
40 | template: path.join(__dirname, "../public/index.php"),
41 | minify: {
42 | removeComments: true,
43 | collapseWhitespace: true,
44 | minifyCSS: true
45 | },
46 | favicon: path.join(__dirname, `../public/favicon.ico`),
47 | inject: false,
48 | hash: true
49 | })
50 | ]
51 | });
52 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # [DEPRECATED]
2 |
3 | > **警告**: 该项目已经废弃,不再维护或更新。我们建议使用 [替代项目或工具的名称] 作为替代方案。感谢过去所有对该项目的支持与贡献。
4 |
5 |
6 | # Yuna - typecho 主题开发脚手架
7 |
8 | 在`typecho`开发的三个月时间里,经过一些摸索实践,最终决定创造了`Yuna`,一个`typecho`主题开发脚手架,基于`vue`、`webpack`构建,对`typecho`主题由`ssr`服务器端渲染,改造成了`spa`应用,从而使得`typecho`主题开发、迭代版本、功能时,变得更加得心应手。
9 |
10 | 非常感谢`Moe`大佬、团队制作了`Restful`风格接口插件,让这个构建方案得以实现
11 | [moefront/typecho-plugin-Restful](https://github.com/moefront/typecho-plugin-Restful)
12 |
13 | ## 快速开始
14 |
15 | - 确保已经安装了`node`环境以及`npm`包管理工具
16 |
17 | - 使用`git clone`命令、打包下载,将项目克移动到`typecho/usr/theme`目录中,执行`npm install`
18 |
19 | - 下载并安装 [moefront/typecho-plugin-Restful](https://github.com/moefront/typecho-plugin-Restful) 插件,开启`typecho`后端`Restful`接口
20 |
21 |
22 | > 如果依赖安装速度非常慢,推荐国内开发者为`npm`配置淘宝镜像、`cnpm`、当然`yarn`也是非常好的选择
23 |
24 | ## 结构
25 |
26 | 这是一个相对基础的`vue`、`webpack`构建主题开发前端方案,你可以在这基础之上,继续定制你需要的额外功能,比如替换`less`使用`sass`、`stylus`等,需要具备简单的`webpack`使用能力
27 |
28 | ```
29 | ├─public
30 | │ ├─favicon.ico # 网站的favicon
31 | │ └─index.php # typecho首页
32 | ├─src
33 | │ ├─assets
34 | │ │ ├─font # css字体,推荐使用iconfont
35 | │ │ └─less # css样式文件,别问,less天下第一
36 | │ ├─components # 存放vue组件
37 | │ ├─router
38 | │ │ ├─index.js # vue-router路由配置文件
39 | │ ├─server
40 | │ │ ├─index.js # typecho后端api
41 | │ │ └─process.js # 接口数据清洗
42 | │ ├─util
43 | │ │ ├─index.js # 一些工具方法
44 | │ │ ├─request.js # typecho后端api请求方法封装
45 | │ │ └─service.js # axios的全局拦截、封装
46 | │ ├─views # 存放视图模板
47 | │ ├─main.js # vue.js入口文件
48 | │ └─app.vue # 视图入口
49 | └─webpack
50 | ```
51 |
52 | ## 开发、生产环境
53 |
54 | 脚手架通过`process.ENV`区分了`webpack`的开发环境、生产环境的配置,运行`npm run dev`之后,就可以专注主题开发了,只需要向开发`vue`项目那样,只不过由于`typecho`运行在本地的`apache`、`nginx`服务中,所以当代码发生变动,需要手动刷新浏览器才能看到变化
55 |
56 | - 开发环境:当运行`npm run dev`时,`webpack`会在`typecho/usr/theme`文件夹中生成你的主题文件夹,并通过`loader`注入相关的`css、js`资源到`index.php`中
57 |
58 | - 生产环境:当运行`npm run build`时,`webpack`会根据`themeConfig.js > ver`属性的`版本号`生产版本文件夹,并且将静态资源进行打包、压缩,注入到`index.php`中
59 |
60 | ```bash
61 | # 在生产环境中创造主题,运行
62 | npm run dev
63 | # 打包主题上线,运行
64 | npm run build
65 | ```
66 |
67 | > 别忘了后台启用主题
68 |
69 | ## 版本管理
70 |
71 | `themeConfig.js`中的`ver`属性,可以帮助你在创造不同版本的主题的时候,进行版本的管理,默认为`1.0`,如果你打算进行`1.0.1`版本的开发,那么就修改成`1.0.1`,在主题文件夹类就会生成`1.0`、`1.0.1`两个文件夹
72 |
73 | > 当然你可以忽略它,使用`git`的进行分支、版本管理
74 |
75 | ## 缺点
76 |
77 | 致命缺点:对于一个博客站点而言,使用服务器端渲染的方案,对`SEO`是友好的,`SPA`方案应当受到摒弃,前提是你的博客文章非常优质,那么我并不推荐你使用`Yuna`进行主题开发。
78 |
79 | 但是从项目层面去考虑,作为一个小型的程序,`SPA`的体验非常优秀,并且使用`vue.js`取代`jQuery`不香吗?清晰的、可维护、易迭代的代码不香吗?
80 |
81 | ## 额外说明
82 |
83 | - 图片压缩:脚手架默认使用了`Base64`压缩图片的方案
84 |
85 | - 网站图标:`/public/favicon.ico`是由`HtmlWebpackPlugin`插件模板引擎自动注入到`index.php`中,所以如果你需要修改默认的`favicon.ico`,请替换你喜欢的`ico`文件
86 |
87 | - 主题图片:在主题文件夹中,放入`screenshot.png`
88 |
--------------------------------------------------------------------------------