本節目標
- portainer 容器管理工具
- 資料庫設計過程
- 資料庫設計目標、規範、習慣
- graphql 條件查詢、排序
- flutter 程式碼實現
視訊
程式碼
strapi 執行環境網盤下載
- 網盤
連結:pan.baidu.com/s/13Ujy2hzX… 密碼:yu82
- 檔案
名稱 | 說明 |
---|---|
strapi-docker-compose-00.zip | 乾淨環境,已安裝 graphql 外掛 |
strapi-docker-compose-15.zip | 15 課內容 |
- 執行
需要用 docker-compose 啟動 賬號 admin 密碼 123456
# 啟動
docker-compose up -d --remove-orphans
# 關閉
docker-compose down
複製程式碼
工具
正文
安裝 portainer
docker run -d -p 9000:9000 \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
--name portainer-local \
portainer/portainer
複製程式碼
設計資料模型
-
標準資料庫設計
ER 圖 -> 設計正規化 -> 資料庫物理表
- ER
- 正規化
- 設計規範 表字首
sys* 系統、使用者、許可權 dict* 字典表 bus_ 業務
- 設計資料 物件、屬性、關係
建立 strapi 資料型別
- 建立外來鍵表 新聞分類
- 建立外來鍵表 新聞頻道
- 建立業務表 新聞內容
- 建立連結外來鍵 新聞內容、分類、頻道
編寫 graphql 查詢
- 新聞
query News($category_code: String) {
busNews(where: { dict_categories: { code: $category_code } }) {
title
dict_channel {
code
title
icon {
url
}
}
dict_categories {
code
title
}
author
url
addtime
thumbnail {
url
}
}
}
複製程式碼
- 首頁
query pageIndex {
# 分類
dictCategories(sort: "sortNum:desc") {
code
title
}
# 頻道
dictChannels(sort: "sortNum:desc") {
code
title
icon {
url
}
}
# 熱點
busNews(where: { dict_categories: { code: "news_hot" } }) {
title
dict_channel {
code
title
icon {
url
}
}
dict_categories {
code
title
}
author
url
addtime
thumbnail {
url
}
}
}
複製程式碼
編寫 flutter 程式碼
- 例項 entity
lib/common/entitys/gql_news.dart
// 首頁
class GqlIndexResponseEntity {
GqlIndexResponseEntity({
this.dictCategories,
this.dictChannels,
this.busNews,
});
List<DictCategoryEntity> dictCategories;
List<DictChannelEntity> dictChannels;
List<GqlNewsResponseEntity> busNews;
factory GqlIndexResponseEntity.fromJson(Map<String, dynamic> json) =>
GqlIndexResponseEntity(
dictCategories: List<DictCategoryEntity>.from(
json["dictCategories"].map((x) => DictCategoryEntity.fromJson(x))),
dictChannels: List<DictChannelEntity>.from(
json["dictChannels"].map((x) => DictChannelEntity.fromJson(x))),
busNews: List<GqlNewsResponseEntity>.from(
json["busNews"].map((x) => GqlNewsResponseEntity.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"dictCategories":
List<dynamic>.from(dictCategories.map((x) => x.toJson())),
"dictChannels": List<dynamic>.from(dictChannels.map((x) => x.toJson())),
"busNews": List<dynamic>.from(busNews.map((x) => x.toJson())),
};
}
// 新聞
class GqlNewsResponseEntity {
GqlNewsResponseEntity({
this.title,
this.dictChannel,
this.dictCategories,
this.author,
this.url,
this.addtime,
this.thumbnail,
});
String title;
DictChannelEntity dictChannel;
List<DictCategoryEntity> dictCategories;
String author;
String url;
DateTime addtime;
ThumbnailEntity thumbnail;
factory GqlNewsResponseEntity.fromJson(Map<String, dynamic> json) =>
GqlNewsResponseEntity(
title: json["title"],
dictChannel: DictChannelEntity.fromJson(json["dict_channel"]),
dictCategories: List<DictCategoryEntity>.from(
json["dict_categories"].map((x) => DictCategoryEntity.fromJson(x))),
author: json["author"],
url: json["url"],
addtime: DateTime.parse(json["addtime"]),
thumbnail: ThumbnailEntity.fromJson(json["thumbnail"]),
);
Map<String, dynamic> toJson() => {
"title": title,
"dict_channel": dictChannel.toJson(),
"dict_categories":
List<dynamic>.from(dictCategories.map((x) => x.toJson())),
"author": author,
"url": url,
"addtime":
"${addtime.year.toString().padLeft(4, '0')}-${addtime.month.toString().padLeft(2, '0')}-${addtime.day.toString().padLeft(2, '0')}",
"ThumbnailEntity": thumbnail.toJson(),
};
}
// 分類
class DictCategoryEntity {
DictCategoryEntity({
this.code,
this.title,
});
String code;
String title;
factory DictCategoryEntity.fromJson(Map<String, dynamic> json) =>
DictCategoryEntity(
code: json["code"],
title: json["title"],
);
Map<String, dynamic> toJson() => {
"code": code,
"title": title,
};
}
// 頻道
class DictChannelEntity {
DictChannelEntity({
this.code,
this.title,
this.icon,
});
String code;
String title;
ThumbnailEntity icon;
factory DictChannelEntity.fromJson(Map<String, dynamic> json) =>
DictChannelEntity(
code: json["code"],
title: json["title"],
icon: ThumbnailEntity.fromJson(json["icon"]),
);
Map<String, dynamic> toJson() => {
"code": code,
"title": title,
"icon": icon.toJson(),
};
}
// 圖
class ThumbnailEntity {
ThumbnailEntity({
this.url,
});
String url;
factory ThumbnailEntity.fromJson(Map<String, dynamic> json) =>
ThumbnailEntity(
url: json["url"],
);
Map<String, dynamic> toJson() => {
"url": url,
};
}
複製程式碼
- api
lib/common/apis/gql_news.dart
/// 新聞
class GqlNewsAPI {
/// 首頁
static Future<GqlIndexResponseEntity> indexPageInfo({
@required BuildContext context,
Map<String, dynamic> params,
}) async {
QueryResult response =
await GraphqlClientUtil.query(context: context, schema: GQL_INDEX_PAGE);
return GqlIndexResponseEntity.fromJson(response.data);
}
/// 翻頁
static Future<List<GqlNewsResponseEntity>> newsPageList({
@required BuildContext context,
Map<String, dynamic> params,
}) async {
QueryResult response = await GraphqlClientUtil.query(
context: context, schema: GQL_NEWS_LIST, variables: params);
return response.data['busNews']
.map<GqlNewsResponseEntity>(
(item) => GqlNewsResponseEntity.fromJson(item))
.toList();
}
}
複製程式碼
- 介面業務程式碼
lib/pages/main/main.dart
GqlIndexResponseEntity _indexPageData; // 首頁資料
// 讀取所有資料
_loadAllData() async {
_indexPageData = await GqlNewsAPI.indexPageInfo(context: context);
...
if (mounted) {
setState(() {});
}
}
// 拉取推薦、新聞
_loadNewsData(
categoryCode, {
bool refresh = false,
}) async {
_selCategoryCode = categoryCode;
_newsPageList = await GqlNewsAPI.newsPageList(
context: context, params: {"category_code": categoryCode});
if (mounted) {
setState(() {});
}
}
...
複製程式碼
詳見 git
資源
設計稿藍湖預覽
lanhuapp.com/url/lYuz1 密碼: gSKl
藍湖現在收費了,所以檢視標記還請自己上傳 xd 設計稿 商業設計稿檔案不好直接分享, 可以加微信聯絡 ducafecat
© 貓哥