前言
什么是 Headless CMS 呢?它是一种将前端和后端分离的 CMS (Content Management System)架构,即去掉前端展示层的 CMS 系统。换句话说,它将内容管理和内容展示两个方面分开处理,以便于多端灵活展示。Headless CMS 快速流行,引起了前端领域的广泛关注。本篇文章将介绍如何在 Headless CMS 中实现主题化内容的高效管理和展示。
主题化内容的特点
在前端网站建设中,我们通常需要引入 CSS 样式,该样式定义了网站的主题风格。为了实现主题化内容,我们可以通过以下方式实现:
- 采用主题化 CSS,根据选择的主题风格展示内容
- 使用 JavaScript 动态修改样式
为了实现这些内容,请确保 Headless CMS 支持以下功能:
- API 管理,使用 CMS 关键性数据来获取并展示动态和静态内容,例如静态页面、内部搜索引擎等
- 构建主题化数据源,该数据源可供前端团队使用,无需运维团队介入即可正确显示代码。
Headless CMS 的优势
通过 Headless CMS,我们可以将网站的后端数据与前端视图分离。这包含以下优势:
- CMS 数据库可用于存储所有属性,例如文章内容,分类,评论等数据。
- API 创建使得数据连接到前端 UI 成为一件相对容易的过程。
- CMS 数据库可以与不同类型的前端技术相连接。
因此,您不需要依靠传统的模板文件进行建站,而是通过 API 从中获取数据。这使得前端任务更容易、更高效,同时也有利于更好地管理网站的主题和文本内容。
如何实现主题化内容的高效管理和展示
现在我们明白了 Headless CMS 以及主题化内容的基本知识,接下来将详细介绍如何在 Headless CMS 中实现主题化内容的高效管理和展示。
1. 选择合适的 Headless CMS
你需要选择一个支持 API 的 CMS,也就是将 CMS 后端数据转化为许多可供前端 UI 使用的端点。
一些值得注意的 CMS 如下:
- Strapi:一个自由开放源代码的 CMS,可以用于构建任何类型的网站。它是开源的,基于 Node.js 和 MongoDB 技术栈,提供 REST、GraphQL API 以及 admin 界面,易于定制。同样适用于小型和中型企业。
- GraphCMS: 常用于构建静态站点、SEO 以及聚合数据的 Web 应用。该 CMS 基于 GraphQL API 并支持很多数据库,具有易于定制和管理 Web 应用程序的功能。
- Contentful: 跨平台、云端和易于使用的 CMS,有几个优点。首先,它通过运用功能强大的 Webhooks,可以帮助前端 UI 更好地管理渲染的数据。其次,它容易融入现有的项目中,因为它可以连接到不同的框架和技术,并支持 OAuth2 认证。
- Sanity: 该 CMS 在现代 web 开发中属于一种新型 CMS。它善于适应构建复杂的 Web 应用程序。它的一个强项是,在你使用该 CMS 构建应用时,你不仅可以享受到其提供的前端 UI 能力,还可以使用其快速且易于修改的 API 功能。
2. 设计建立信息体系
- 根据业务流程的需求设计好表单和数据结构。
- 设计好数据表,并分类数据表数据内容,透彻考虑好模型和模型实例之间的关系和连接。
- 根据模型,设计好数据类型、属性、字段一致性,并将它们放到一起。
3. 定义 API
在定义 API 时,需要注意:
- API 的方法:
- GET:用于获取数据
- POST:用于插入记录
- PUT:用于修改记录
- DELETE:用于删除记录
- 定义 API 端点,并将这些端点放到一起以获得最佳代码效率。
- 不要忘记支持搜索和排序功能。
4. 前端开发
- 根据 API 首先创建模型和模型接口,然后根据模型接口构建用于展示数据的 UI。
- 构建主题化 CSS,并根据不同的主题引用不同的 CSS 样式。
- 构建 JavaScript-powered 动态主题化,如 CSS 变量/本地存储和 Cookie 等,以满足特定主题的要求。
示例代码
为了更好地了解代码实现,这里提供一个示例,采用 Strapi 作为 Headless CMS,并借助 React 构建前端 UI。
步骤 1:安装 Strapi,并通过 Strapi 管理并设置要存储的所有数据。
步骤 2:构建 API,我们将输入数据直接从前端发送到 API。
// www.javascriptcn.com code example
// backend API code
router.put('/users/:id', auth.isAuthenticated(), async (ctx, next) => {
const { body } = ctx.request
let user
try {
user = await strapi.query('user', 'users-permissions').update({ id: Number(ctx.params.id) }, body)
} catch (err) {
if (err.message.includes('username')) {
ctx.throw(409, 'users.validation.username.taken')
} else if (err.message.includes('email')) {
ctx.throw(409, 'users.validation.email.taken')
} else {
ctx.throw(400, 'An error occured while update user')
}
}
ctx.body = {
user
}
await next()
})
// frontend API code
// update user informations
const updateUser = (id, data) => instance.put(`/users/${id}`, data)步骤 3:构建 UI。
// www.javascriptcn.com code example
// update user informations form
class ProfileForm extends Component {
constructor(props) {
super(props)
this.state = {
oldPassword: '',
newPassword: '',
confirmNewPassword: '',
loading: false
}
this.handleSubmit = this.handleSubmit.bind(this)
}
// handleClick 省略
handleSubmit(e) {
e.preventDefault()
const { oldPassword, newPassword, confirmNewPassword } = this.state
const { saveAddress } = this.props
const { id } = this.props.user
this.setState({
loading: true
})
return updatePassword(id, {
oldPassword,
newPassword,
confirmNewPassword
})
.then(() => {
notify.show('Password updated successfully', 'success', 5000)
saveAddress()
this.setState({ loading: false })
})
.catch(err => {
const error = err.response.data.errors[0].id || 'errors.unhandled_error'
notify.show(trans(error), 'error', 5000)
this.setState({ loading: false })
})
}
render() {
const { oldPassword, newPassword, confirmNewPassword, loading } = this.state
const buttonClassNames = ['button']
if (loading) {
buttonClassNames.push('is-loading')
}
return (
<form onSubmit={this.handleSubmit}>
<div className="field">
<label className="label">{Trans('profile.old_password')}</label>
<div className="control">
<input
className="input"
type="password"
placeholder={Trans('profile.old_password_placeholder')}
name="oldPassword"
value={oldPassword}
onChange={this.handleChange}
/>
</div>
</div>
<div className="field">
<label className="label">{Trans('profile.new_password')}</label>
<div className="control">
<input
className="input"
type="password"
placeholder={Trans('profile.new_password_placeholder')}
name="newPassword"
value={newPassword}
onChange={this.handleChange}
/>
</div>
</div>
<div className="field">
<label className="label">{Trans('profile.confirm_new_password')}</label>
<div className="control">
<input
className="input"
type="password"
placeholder={Trans('profile.confirm_new_password_placeholder')}
name="confirmNewPassword"
value={confirmNewPassword}
onChange={this.handleChange}
/>
</div>
</div>
<div className="field">
<div className="control">
<button className={buttonClassNames.join(' ')} type="submit">
{Trans('actions.save_changes')}
</button>
</div>
</div>
</form>
)
}
}
ProfileForm.propTypes = {
user: PropTypes.object.isRequired,
theme: PropTypes.string.isRequired,
saveAddress: PropTypes.func
}
const mapStateToProps = state => ({
user: state.auth.user,
theme: state.theme.name
})
// 略掉了 dispatchToProps 和 withRouter
export default withRouter(connect(mapStateToProps)(ProfileForm))步骤 4:添加动态主题化功能。
// www.javascriptcn.com code example
// define themes and properties
export const THEMES = {
LITE: 'Lite',
DARK: 'Dark',
NEITRAL: 'Neitral'
}
export const THEME_COLORS = {
[THEMES.LITE]: {
label: 'Lite',
primary: '#03a9f4',
text: '#333',
link: '#06c',
primary10: '#E5F8FF',
primary20: '#5AC5F5',
disabled: '#aaa'
},
[THEMES.DARK]: {
label: 'Dark',
primary: '#1e1d40',
text: '#f8f8f8',
link: '#6AD',
primary10: '#000000',
primary20: '#050557',
disabled: '#aaa'
},
[THEMES.NEITRAL]: {
label: 'Neitral',
primary: '#f7f7f7',
text: '#333',
link: '#6AD',
primary10: '#fff',
primary20: '#CCC',
disabled: '#aaa'
}
}
export const CHANGE_THEME_ACTION = 'CHANGE_THEME_ACTION'
// change theme reducer
const changeThemeReducer = (state = DEFAULT_THEME, action) => {
switch (action.type) {
case CHANGE_THEME_ACTION:
return {
...state,
name: action.theme
}
default:
return state
}
}
// theme action creators
export const changeTheme = theme => ({
type: CHANGE_THEME_ACTION,
theme
})// www.javascriptcn.com code example
// change theme method
const changeTheme = () => {
const {
[THEMES.LITE]: lite,
[THEMES.DARK]: dark,
[THEMES.NEITRAL]: neutral
} = THEME_COLORS
let selectedTheme = lite
// change primary color based on current theme
switch (getTheme()) {
case THEMES.LITE:
selectedTheme = dark
break
case THEMES.NEITRAL:
selectedTheme = lite
break
case THEMES.DARK:
selectedTheme = neutral
break
default:
selectedTheme = lite
break
}
// change css variable values
document.documentElement.style.setProperty(`--primary-color`, selectedTheme.primary)
document.documentElement.style.setProperty(`--primary-10`, selectedTheme.primary10)
document.documentElement.style.setProperty(`--primary-20`, selectedTheme.primary20)
document.documentElement.style.setProperty(`--theme-color`, selectedTheme.primary)
document.documentElement.style.setProperty(`--text-color`, selectedTheme.text)
document.documentElement.style.setProperty(`--link-color`, selectedTheme.link)
document.documentElement.style.setProperty(`--disabled-color`, selectedTheme.disabled)
}
// 略掉了从 API 获取并修改 theme 的方法,以下为 body 的示例:
/*
{
"id": 1,
"username": "test1",
"email": "test1@example.com",
"provider": "local",
"confirmed": true,
"blocked": null,
"roles": [
{
"id": 1,
"name": "Authenticated",
"description": "Default role given to authenticated user.",
"type": "authenticated"
}
],
"created_at": "2020-12-01T01:32:34.556Z",
"updated_at": "2020-12-07T18:01:08.926Z",
"theme": "dark"
}
*/结语
通过本文的详细介绍和示例代码,相信读者已经掌握在 Headless CMS 中实现主题化内容的方法。无论是使用哪种 Headless CMS,只要按照上述步骤,即可高效地管理和展示主题化内容。
Source: FunTeaLearn,Please indicate the source for reprints https://funteas.com/post/67948cb3504e4ea9bd91e7b2