Crow Crowcrow 9 月之前
當前提交
c1d231c201
簽署人: robinknaapen <robin@ultraware.nl> GPG Key ID: 45A8E203AF859FD8
共有 25 個文件被更改,包括 18708 次插入0 次删除
  1. 3
    0
      .browserslistrc
  2. 21
    0
      .gitignore
  3. 29
    0
      README.md
  4. 5
    0
      babel.config.js
  5. 10590
    0
      package-lock.json
  6. 46
    0
      package.json
  7. 5
    0
      postcss.config.js
  8. 二進制
      public/favicon.ico
  9. 20
    0
      public/index.html
  10. 5
    0
      src/App.vue
  11. 二進制
      src/assets/logo.png
  12. 20
    0
      src/domain/index.ts
  13. 29
    0
      src/layouts/Base.vue
  14. 18
    0
      src/main.ts
  15. 32
    0
      src/router.ts
  16. 27
    0
      src/store/index.ts
  17. 57
    0
      src/store/module/user.ts
  18. 43
    0
      src/store/module/userAnime.ts
  19. 14
    0
      src/types/shims-vue.d.ts
  20. 54
    0
      src/views/UserAnime.vue
  21. 63
    0
      src/views/UserSelect.vue
  22. 40
    0
      tsconfig.json
  23. 20
    0
      tslint.json
  24. 3
    0
      vue.config.js
  25. 7564
    0
      yarn.lock

+ 3
- 0
.browserslistrc 查看文件

@@ -0,0 +1,3 @@
1
+> 1%
2
+last 2 versions
3
+not ie <= 8

+ 21
- 0
.gitignore 查看文件

@@ -0,0 +1,21 @@
1
+.DS_Store
2
+node_modules
3
+/dist
4
+
5
+# local env files
6
+.env.local
7
+.env.*.local
8
+
9
+# Log files
10
+npm-debug.log*
11
+yarn-debug.log*
12
+yarn-error.log*
13
+
14
+# Editor directories and files
15
+.idea
16
+.vscode
17
+*.suo
18
+*.ntvs*
19
+*.njsproj
20
+*.sln
21
+*.sw*

+ 29
- 0
README.md 查看文件

@@ -0,0 +1,29 @@
1
+# meikan
2
+
3
+## Project setup
4
+```
5
+npm install
6
+```
7
+
8
+### Compiles and hot-reloads for development
9
+```
10
+npm run serve
11
+```
12
+
13
+### Compiles and minifies for production
14
+```
15
+npm run build
16
+```
17
+
18
+### Run your tests
19
+```
20
+npm run test
21
+```
22
+
23
+### Lints and fixes files
24
+```
25
+npm run lint
26
+```
27
+
28
+### Customize configuration
29
+See [Configuration Reference](https://cli.vuejs.org/config/).

+ 5
- 0
babel.config.js 查看文件

@@ -0,0 +1,5 @@
1
+module.exports = {
2
+	presets: [
3
+		'@vue/app'
4
+	]
5
+}

+ 10590
- 0
package-lock.json
文件差異過大導致無法顯示
查看文件


+ 46
- 0
package.json 查看文件

@@ -0,0 +1,46 @@
1
+{
2
+  "name": "meikan",
3
+  "version": "0.1.0",
4
+  "private": true,
5
+  "scripts": {
6
+    "serve": "vue-cli-service serve",
7
+    "build": "vue-cli-service build",
8
+    "lint": "vue-cli-service lint"
9
+  },
10
+  "dependencies": {
11
+    "@types/lodash": "^4.14.119",
12
+    "lodash": "^4.17.11",
13
+    "material-design-icons-iconfont": "^4.0.3",
14
+    "vue": "^2.5.17",
15
+    "vue-class-component": "^6.0.0",
16
+    "vue-material": "^1.0.0-beta-10.2",
17
+    "vue-property-decorator": "^7.0.0",
18
+    "vue-router": "3.0.1",
19
+    "vuex": "^3.0.1",
20
+    "vuex-persist": "^2.0.0",
21
+    "vuex-typex": "^3.0.1",
22
+    "vuex-xhr-state": "^0.2.7"
23
+  },
24
+  "devDependencies": {
25
+    "@types/vue-material": "git+https://github.com/calebsander/vue-material-types.git",
26
+    "@vue/cli-plugin-babel": "^3.2.0",
27
+    "@vue/cli-plugin-typescript": "^3.2.0",
28
+    "@vue/cli-service": "^3.2.0",
29
+    "lint-staged": "^7.2.2",
30
+    "typescript": "^3.0.0",
31
+    "vue-template-compiler": "^2.5.17"
32
+  },
33
+  "gitHooks": {
34
+    "pre-commit": "lint-staged"
35
+  },
36
+  "lint-staged": {
37
+    "*.ts": [
38
+      "vue-cli-service lint",
39
+      "git add"
40
+    ],
41
+    "*.vue": [
42
+      "vue-cli-service lint",
43
+      "git add"
44
+    ]
45
+  }
46
+}

+ 5
- 0
postcss.config.js 查看文件

@@ -0,0 +1,5 @@
1
+module.exports = {
2
+  plugins: {
3
+    autoprefixer: {}
4
+  }
5
+}

二進制
public/favicon.ico 查看文件


+ 20
- 0
public/index.html 查看文件

@@ -0,0 +1,20 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+	<head>
4
+		<meta charset="utf-8">
5
+		<meta http-equiv="X-UA-Compatible" content="IE=edge">
6
+		<meta name="viewport" content="width=device-width,initial-scale=1.0">
7
+
8
+		<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic|Material+Icons">
9
+		<link rel="icon" href="<%= BASE_URL %>favicon.ico">
10
+
11
+		<title>meikan</title>
12
+	</head>
13
+	<body>
14
+		<noscript>
15
+			<strong>We're sorry but meikan doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
16
+		</noscript>
17
+		<div id="app"></div>
18
+		<!-- built files will be auto injected -->
19
+	</body>
20
+</html>

+ 5
- 0
src/App.vue 查看文件

@@ -0,0 +1,5 @@
1
+<template>
2
+	<div id="app">
3
+		<router-view/>
4
+	</div>
5
+</template>

二進制
src/assets/logo.png 查看文件


+ 20
- 0
src/domain/index.ts 查看文件

@@ -0,0 +1,20 @@
1
+export const GetAnimeList = async (userID: number): Promise<UserAnime[]> => {
2
+	 const res = await fetch(`https://api.meikan.moe/v1/users/${userID}/anime`)
3
+	 const anime: UserAnime[] = await res.json()
4
+
5
+	 return anime
6
+}
7
+
8
+export const FindUsers = async (name: string): Promise<User[]> => {
9
+	const res = await fetch('https://api.meikan.moe/v1/users', {
10
+		method: 'POST',
11
+		mode: 'cors',
12
+		body: JSON.stringify({
13
+			name,
14
+		}),
15
+	})
16
+
17
+	const users: User[] = await res.json()
18
+
19
+	return users
20
+}

+ 29
- 0
src/layouts/Base.vue 查看文件

@@ -0,0 +1,29 @@
1
+<template>
2
+	<div>
3
+		<md-toolbar class="md-primary" md-elevation="0">
4
+			<h3 class="md-title">Meikan</h3>
5
+			<div class="md-toolbar-section-end">
6
+				<h3 v-if="currentUser" class="md-subheading">
7
+					{{ currentUser.name }}
8
+				</h3>
9
+			</div>
10
+		</md-toolbar>
11
+		<md-tabs class="md-primary" md-sync-route>
12
+			<md-tab id="tab-user" md-label="Users" to="/"></md-tab>
13
+			<md-tab v-if="currentUser" id="tab-anime" md-label="Anime" to="/anime"></md-tab>
14
+		</md-tabs>
15
+		<router-view/>
16
+	</div>
17
+</template>
18
+
19
+<script lang="ts">
20
+import { Component, Vue } from 'vue-property-decorator'
21
+import { namespace } from 'vuex-class'
22
+
23
+const user = namespace('user')
24
+
25
+@Component({})
26
+export default class Base extends Vue {
27
+	@user.Getter('currentUser') private currentUser!: User | null
28
+}
29
+</script>

+ 18
- 0
src/main.ts 查看文件

@@ -0,0 +1,18 @@
1
+import Vue from 'vue'
2
+
3
+import VueMaterial from 'vue-material'
4
+import 'vue-material/dist/theme/default-dark.css'
5
+import 'vue-material/dist/vue-material.min.css'
6
+Vue.use(VueMaterial)
7
+
8
+import App from './App.vue'
9
+import router from './router'
10
+import store from './store'
11
+
12
+Vue.config.productionTip = false
13
+
14
+new Vue({
15
+	router,
16
+	store,
17
+	render: (h) => h(App),
18
+}).$mount('#app')

+ 32
- 0
src/router.ts 查看文件

@@ -0,0 +1,32 @@
1
+import Store from '@/store'
2
+import Vue from 'vue'
3
+
4
+import Router from 'vue-router'
5
+Vue.use(Router)
6
+
7
+import Base from './layouts/Base.vue'
8
+import UserAnime from './views/UserAnime.vue'
9
+import UserSelect from './views/UserSelect.vue'
10
+
11
+export default new Router({
12
+	mode: 'history',
13
+	base: process.env.BASE_URL,
14
+	routes: [
15
+		{
16
+			path: '/',
17
+			component: Base,
18
+			children: [
19
+				{
20
+					path: '',
21
+					name: 'userSelect',
22
+					component: UserSelect,
23
+				},
24
+				{
25
+					path: 'anime',
26
+					name: 'userAnime',
27
+					component: UserAnime,
28
+				},
29
+			],
30
+		},
31
+	],
32
+})

+ 27
- 0
src/store/index.ts 查看文件

@@ -0,0 +1,27 @@
1
+import Vue from 'vue'
2
+import Vuex from 'vuex'
3
+import VuexPersistence from 'vuex-persist'
4
+import { getStoreBuilder } from 'vuex-typex'
5
+
6
+import './module/user'
7
+import { UserState } from './module/user'
8
+
9
+import './module/userAnime'
10
+import { UserAnimeState } from './module/userAnime'
11
+
12
+Vue.use(Vuex)
13
+
14
+export interface RootState {
15
+	user: UserState
16
+	userAnime: UserAnimeState
17
+}
18
+
19
+const vuexLocal = new VuexPersistence({
20
+	storage: window.localStorage,
21
+})
22
+
23
+const store = getStoreBuilder<RootState>().vuexStore({
24
+	plugins: [vuexLocal.plugin],
25
+})
26
+
27
+export default store

+ 57
- 0
src/store/module/user.ts 查看文件

@@ -0,0 +1,57 @@
1
+import { FindUsers } from '@/domain'
2
+import { RootState } from '@/store'
3
+import { BareActionContext, getStoreBuilder } from 'vuex-typex'
4
+
5
+export interface UserState {
6
+	currentUser: User | null
7
+	foundUsers: User[]
8
+}
9
+
10
+const initialState: UserState = {
11
+	currentUser: null,
12
+	foundUsers: [],
13
+}
14
+const builder = getStoreBuilder<RootState>().module('user', initialState)
15
+
16
+// Getters
17
+const getterUserList = builder.read(function foundUsers(state: UserState) {
18
+	return state.foundUsers
19
+})
20
+
21
+const getterCurrentUser = builder.read(function currentUser(state: UserState) {
22
+	return state.currentUser
23
+})
24
+
25
+// Mutations
26
+function setUserList(state: UserState, foundUsers: User[]) {
27
+	state.foundUsers = foundUsers
28
+}
29
+
30
+function setCurrentUser(state: UserState, currentUser: User) {
31
+	state.currentUser = currentUser
32
+}
33
+
34
+// Action
35
+async function findUsers(context: BareActionContext<UserState, RootState>, name: string) {
36
+	const list = await FindUsers(name)
37
+
38
+	user.setUserList(list || [])
39
+}
40
+
41
+function resetFoundUsers(context: BareActionContext<UserState, RootState>) {
42
+	user.setUserList([])
43
+}
44
+
45
+export const user = {
46
+	get foundUsers(): User[] {
47
+		return getterUserList()
48
+	},
49
+	getCurrentUser(): User | null {
50
+		return getterCurrentUser()
51
+	},
52
+
53
+	setCurrentUser: builder.commit(setCurrentUser),
54
+	setUserList: builder.commit(setUserList),
55
+	resetFoundUsers: builder.dispatch(resetFoundUsers),
56
+	findUsers: builder.dispatch(findUsers),
57
+}

+ 43
- 0
src/store/module/userAnime.ts 查看文件

@@ -0,0 +1,43 @@
1
+import { GetAnimeList } from '@/domain'
2
+import { RootState } from '@/store'
3
+import { BareActionContext, getStoreBuilder } from 'vuex-typex'
4
+
5
+export interface UserAnimeState {
6
+	anime: UserAnime[]
7
+}
8
+
9
+const initialState: UserAnimeState = {
10
+	anime: [],
11
+}
12
+const builder = getStoreBuilder<RootState>().module('userAnime', initialState)
13
+
14
+// Getters
15
+const getterUserAnime = builder.read(function anime(state: UserAnimeState) {
16
+	return state.anime
17
+})
18
+
19
+// Mutations
20
+function setAnime(state: UserAnimeState, animeList: UserAnime[]) {
21
+	state.anime = animeList
22
+}
23
+
24
+// Action
25
+async function fetchUserAnime(context: BareActionContext<UserAnimeState, RootState>, user: User) {
26
+	const list = await GetAnimeList(user.id)
27
+
28
+	userAnime.setAnime(list)
29
+}
30
+
31
+function resetUserAnime(context: BareActionContext<UserAnimeState, RootState>) {
32
+	userAnime.setAnime([])
33
+}
34
+
35
+export const userAnime = {
36
+	get anime(): UserAnime[] {
37
+		return getterUserAnime()
38
+	},
39
+
40
+	setAnime: builder.commit(setAnime),
41
+	fetchUserAnime: builder.dispatch(fetchUserAnime),
42
+	resetUserAnime: builder.dispatch(resetUserAnime),
43
+}

+ 14
- 0
src/types/shims-vue.d.ts 查看文件

@@ -0,0 +1,14 @@
1
+declare module '*.vue' {
2
+	import Vue from 'vue'
3
+	export default Vue
4
+}
5
+
6
+interface User {
7
+	id: number
8
+	name: string
9
+}
10
+
11
+interface UserAnime {
12
+	id: number
13
+	title: string
14
+}

+ 54
- 0
src/views/UserAnime.vue 查看文件

@@ -0,0 +1,54 @@
1
+<template>
2
+	<md-table v-model="anime">
3
+		<md-table-toolbar>
4
+			<h1 class="md-title md-toolbar-section-start">{{ currentUser.name }}'s anime</h1>
5
+			<md-button 
6
+				class="md-icon-button md-primary"
7
+				@click="refresh"
8
+			>
9
+				<md-icon>refresh</md-icon>
10
+			</md-button>
11
+		</md-table-toolbar>
12
+		<md-table-empty-state>
13
+			<md-progress-spinner class="md-accent" md-mode="indeterminate"></md-progress-spinner>
14
+		</md-table-empty-state>
15
+		<md-table-row slot="md-table-row" slot-scope="{ item }">
16
+			<md-table-cell md-label="#" md-numeric width="100">{{ item.anime.id }}</md-table-cell>
17
+			<md-table-cell md-label="Title" md-sort-by="title">{{ item.anime.title }}</md-table-cell>
18
+		</md-table-row>
19
+	</md-table>
20
+</template>
21
+
22
+<script lang="ts">
23
+import { Component, Vue } from 'vue-property-decorator'
24
+import { namespace } from 'vuex-class'
25
+
26
+const user = namespace('user')
27
+const userAnime = namespace('userAnime')
28
+
29
+@Component({})
30
+export default class Home extends Vue {
31
+	@user.Getter
32
+	private currentUser!: User
33
+
34
+	@userAnime.Action
35
+	private resetUserAnime!: () => void
36
+
37
+	@userAnime.Action
38
+	private fetchUserAnime!: (user: User) => Promise<UserAnime[]>
39
+
40
+	@userAnime.Getter
41
+	private anime!: UserAnime[]
42
+
43
+	private created() {
44
+		if (this.anime.length === 0) {
45
+			this.fetchUserAnime(this.currentUser)
46
+		}
47
+	}
48
+
49
+	private refresh() {
50
+		this.resetUserAnime()
51
+		this.fetchUserAnime(this.currentUser)
52
+	}
53
+}
54
+</script>

+ 63
- 0
src/views/UserSelect.vue 查看文件

@@ -0,0 +1,63 @@
1
+<template>
2
+	<div>
3
+		<md-table v-model="foundUsers" @md-selected="onSelect">
4
+			<md-table-toolbar>
5
+				<div class="md-toolbar-section-start">
6
+					<h1 class="md-title">Users</h1>
7
+				</div>
8
+
9
+				<md-field md-clearable class="md-toolbar-section-end">
10
+					<md-input placeholder="Search users" @input="onInput" />
11
+				</md-field>
12
+			</md-table-toolbar>
13
+
14
+			<md-table-row slot="md-table-row" slot-scope="{ item }" md-selectable="single">
15
+				<md-table-cell md-label="#" md-numeric width="100">{{ item.id }}</md-table-cell>
16
+				<md-table-cell md-label="Title" md-sort-by="title">{{ item.name }}</md-table-cell>
17
+			</md-table-row>
18
+		</md-table>
19
+	</div>
20
+</template>
21
+
22
+<script lang="ts">
23
+import _ from 'lodash'
24
+import { Component, Vue } from 'vue-property-decorator'
25
+import { namespace } from 'vuex-class'
26
+
27
+const userStore = namespace('user')
28
+const userAnimeStore = namespace('userAnime')
29
+
30
+@Component({})
31
+export default class Home extends Vue {
32
+	@userStore.Mutation('setCurrentUser')
33
+	private setCurrentUser!: (user: User) => void
34
+
35
+	@userStore.Action('findUsers')
36
+	private findUsers!: (name: string) => Promise<User[]>
37
+
38
+	@userStore.Action('resetFoundUsers')
39
+	private resetFoundUsers!: () => void
40
+
41
+	@userStore.Getter('foundUsers')
42
+	private foundUsers!: User[]
43
+
44
+	@userAnimeStore.Action
45
+	private resetUserAnime!: () => void
46
+
47
+	private onInput = _.debounce((name: string) => {
48
+		if (name === '') {
49
+			this.resetFoundUsers()
50
+			return
51
+		}
52
+
53
+		this.findUsers(name)
54
+	}, 200)
55
+
56
+	private onSelect(user: User) {
57
+		this.setCurrentUser(user)
58
+		this.resetUserAnime()
59
+
60
+		this.$router.push({ name: 'userAnime' })
61
+	}
62
+}
63
+</script>

+ 40
- 0
tsconfig.json 查看文件

@@ -0,0 +1,40 @@
1
+{
2
+	"compilerOptions": {
3
+		"target": "esnext",
4
+		"module": "esnext",
5
+		"strict": true,
6
+		"jsx": "preserve",
7
+		"importHelpers": true,
8
+		"moduleResolution": "node",
9
+		"experimentalDecorators": true,
10
+		"esModuleInterop": true,
11
+		"allowSyntheticDefaultImports": true,
12
+		"sourceMap": true,
13
+		"noImplicitAny": true,
14
+		"baseUrl": ".",
15
+		"types": [
16
+			"webpack-env"
17
+		],
18
+		"paths": {
19
+			"@/*": [
20
+				"src/*"
21
+			]
22
+		},
23
+		"lib": [
24
+			"esnext",
25
+			"dom",
26
+			"dom.iterable",
27
+			"scripthost"
28
+		]
29
+	},
30
+	"include": [
31
+		"src/**/*.ts",
32
+		"src/**/*.tsx",
33
+		"src/**/*.vue",
34
+		"tests/**/*.ts",
35
+		"tests/**/*.tsx",
36
+	],
37
+	"exclude": [
38
+		"node_modules",
39
+	]
40
+}

+ 20
- 0
tslint.json 查看文件

@@ -0,0 +1,20 @@
1
+{
2
+	"defaultSeverity": "warning",
3
+	"extends": [
4
+		"tslint:recommended"
5
+	],
6
+	"linterOptions": {
7
+		"exclude": [
8
+			"node_modules/**"
9
+		]
10
+	},
11
+	"rules": {
12
+		"quotemark": [true, "single"],
13
+		"indent": [true, "tabs", 4],
14
+		"interface-name": false,
15
+		"ordered-imports": true,
16
+		"object-literal-sort-keys": false,
17
+		"no-consecutive-blank-lines": true,
18
+		"semicolon": [true, "never"]
19
+	}
20
+}

+ 3
- 0
vue.config.js 查看文件

@@ -0,0 +1,3 @@
1
+module.exports = {
2
+  lintOnSave: false
3
+}

+ 7564
- 0
yarn.lock
文件差異過大導致無法顯示
查看文件


Loading…
取消
儲存