Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
火狐Jeff
my-vue-app
提交
39a4b633
M
my-vue-app
项目概览
火狐Jeff
/
my-vue-app
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
M
my-vue-app
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
39a4b633
编写于
9月 20, 2021
作者:
J
Jeff Fox
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Chapter07
上级
115eac30
变更
45
展开全部
隐藏空白更改
内联
并排
Showing
45 changed file
with
27805 addition
and
0 deletion
+27805
-0
vue3example/Chapter07/admin-frontend/.gitignore
vue3example/Chapter07/admin-frontend/.gitignore
+23
-0
vue3example/Chapter07/admin-frontend/README.md
vue3example/Chapter07/admin-frontend/README.md
+24
-0
vue3example/Chapter07/admin-frontend/babel.config.js
vue3example/Chapter07/admin-frontend/babel.config.js
+5
-0
vue3example/Chapter07/admin-frontend/package-lock.json
vue3example/Chapter07/admin-frontend/package-lock.json
+12265
-0
vue3example/Chapter07/admin-frontend/package.json
vue3example/Chapter07/admin-frontend/package.json
+47
-0
vue3example/Chapter07/admin-frontend/public/favicon.ico
vue3example/Chapter07/admin-frontend/public/favicon.ico
+0
-0
vue3example/Chapter07/admin-frontend/public/index.html
vue3example/Chapter07/admin-frontend/public/index.html
+17
-0
vue3example/Chapter07/admin-frontend/src/App.vue
vue3example/Chapter07/admin-frontend/src/App.vue
+9
-0
vue3example/Chapter07/admin-frontend/src/assets/logo.png
vue3example/Chapter07/admin-frontend/src/assets/logo.png
+0
-0
vue3example/Chapter07/admin-frontend/src/components/TopBar.vue
...xample/Chapter07/admin-frontend/src/components/TopBar.vue
+25
-0
vue3example/Chapter07/admin-frontend/src/main.js
vue3example/Chapter07/admin-frontend/src/main.js
+7
-0
vue3example/Chapter07/admin-frontend/src/plugins/router.js
vue3example/Chapter07/admin-frontend/src/plugins/router.js
+29
-0
vue3example/Chapter07/admin-frontend/src/views/Login.vue
vue3example/Chapter07/admin-frontend/src/views/Login.vue
+74
-0
vue3example/Chapter07/admin-frontend/src/views/Orders.vue
vue3example/Chapter07/admin-frontend/src/views/Orders.vue
+91
-0
vue3example/Chapter07/admin-frontend/src/views/ShopItems.vue
vue3example/Chapter07/admin-frontend/src/views/ShopItems.vue
+170
-0
vue3example/Chapter07/backend/app.js
vue3example/Chapter07/backend/app.js
+136
-0
vue3example/Chapter07/backend/bin/www
vue3example/Chapter07/backend/bin/www
+90
-0
vue3example/Chapter07/backend/db.sql
vue3example/Chapter07/backend/db.sql
+26
-0
vue3example/Chapter07/backend/package-lock.json
vue3example/Chapter07/backend/package-lock.json
+1813
-0
vue3example/Chapter07/backend/package.json
vue3example/Chapter07/backend/package.json
+21
-0
vue3example/Chapter07/backend/public/stylesheets/style.css
vue3example/Chapter07/backend/public/stylesheets/style.css
+8
-0
vue3example/Chapter07/backend/resolvers/auth.js
vue3example/Chapter07/backend/resolvers/auth.js
+10
-0
vue3example/Chapter07/backend/resolvers/orders.js
vue3example/Chapter07/backend/resolvers/orders.js
+107
-0
vue3example/Chapter07/backend/resolvers/shopItems.js
vue3example/Chapter07/backend/resolvers/shopItems.js
+50
-0
vue3example/Chapter07/backend/routes/index.js
vue3example/Chapter07/backend/routes/index.js
+9
-0
vue3example/Chapter07/backend/routes/users.js
vue3example/Chapter07/backend/routes/users.js
+9
-0
vue3example/Chapter07/backend/views/error.jade
vue3example/Chapter07/backend/views/error.jade
+6
-0
vue3example/Chapter07/backend/views/index.jade
vue3example/Chapter07/backend/views/index.jade
+5
-0
vue3example/Chapter07/backend/views/layout.jade
vue3example/Chapter07/backend/views/layout.jade
+7
-0
vue3example/Chapter07/frontend/.gitignore
vue3example/Chapter07/frontend/.gitignore
+23
-0
vue3example/Chapter07/frontend/README.md
vue3example/Chapter07/frontend/README.md
+24
-0
vue3example/Chapter07/frontend/babel.config.js
vue3example/Chapter07/frontend/babel.config.js
+5
-0
vue3example/Chapter07/frontend/package-lock.json
vue3example/Chapter07/frontend/package-lock.json
+12291
-0
vue3example/Chapter07/frontend/package.json
vue3example/Chapter07/frontend/package.json
+49
-0
vue3example/Chapter07/frontend/public/favicon.ico
vue3example/Chapter07/frontend/public/favicon.ico
+0
-0
vue3example/Chapter07/frontend/public/index.html
vue3example/Chapter07/frontend/public/index.html
+17
-0
vue3example/Chapter07/frontend/src/App.vue
vue3example/Chapter07/frontend/src/App.vue
+10
-0
vue3example/Chapter07/frontend/src/assets/logo.png
vue3example/Chapter07/frontend/src/assets/logo.png
+0
-0
vue3example/Chapter07/frontend/src/components/HelloWorld.vue
vue3example/Chapter07/frontend/src/components/HelloWorld.vue
+58
-0
vue3example/Chapter07/frontend/src/main.js
vue3example/Chapter07/frontend/src/main.js
+9
-0
vue3example/Chapter07/frontend/src/plugins/router.js
vue3example/Chapter07/frontend/src/plugins/router.js
+17
-0
vue3example/Chapter07/frontend/src/plugins/vuex.js
vue3example/Chapter07/frontend/src/plugins/vuex.js
+35
-0
vue3example/Chapter07/frontend/src/views/OrderForm.vue
vue3example/Chapter07/frontend/src/views/OrderForm.vue
+109
-0
vue3example/Chapter07/frontend/src/views/Shop.vue
vue3example/Chapter07/frontend/src/views/Shop.vue
+63
-0
vue3example/Chapter07/frontend/src/views/Success.vue
vue3example/Chapter07/frontend/src/views/Success.vue
+12
-0
未找到文件。
vue3example/Chapter07/admin-frontend/.gitignore
0 → 100644
浏览文件 @
39a4b633
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
vue3example/Chapter07/admin-frontend/README.md
0 → 100644
浏览文件 @
39a4b633
# admin-frontend
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See
[
Configuration Reference
](
https://cli.vuejs.org/config/
)
.
vue3example/Chapter07/admin-frontend/babel.config.js
0 → 100644
浏览文件 @
39a4b633
module
.
exports
=
{
presets
:
[
'
@vue/cli-plugin-babel/preset
'
]
}
vue3example/Chapter07/admin-frontend/package-lock.json
0 → 100644
浏览文件 @
39a4b633
此差异已折叠。
点击以展开。
vue3example/Chapter07/admin-frontend/package.json
0 → 100644
浏览文件 @
39a4b633
{
"name"
:
"admin-frontend"
,
"version"
:
"0.1.0"
,
"private"
:
true
,
"scripts"
:
{
"serve"
:
"vue-cli-service serve --port 8090"
,
"build"
:
"vue-cli-service build"
,
"lint"
:
"vue-cli-service lint"
},
"dependencies"
:
{
"core-js"
:
"^3.6.5"
,
"graphql"
:
"^15.5.0"
,
"graphql-request"
:
"^3.4.0"
,
"vee-validate"
:
"^4.2.1"
,
"vue"
:
"^3.0.0"
,
"vue-router"
:
"^4.0.4"
,
"yup"
:
"^0.32.9"
},
"devDependencies"
:
{
"@vue/cli-plugin-babel"
:
"~4.5.0"
,
"@vue/cli-plugin-eslint"
:
"~4.5.0"
,
"@vue/cli-service"
:
"~4.5.0"
,
"@vue/compiler-sfc"
:
"^3.0.0"
,
"babel-eslint"
:
"^10.1.0"
,
"eslint"
:
"^6.7.2"
,
"eslint-plugin-vue"
:
"^7.0.0-0"
},
"eslintConfig"
:
{
"root"
:
true
,
"env"
:
{
"node"
:
true
},
"extends"
:
[
"plugin:vue/vue3-essential"
,
"eslint:recommended"
],
"parserOptions"
:
{
"parser"
:
"babel-eslint"
},
"rules"
:
{}
},
"browserslist"
:
[
"> 1%"
,
"last 2 versions"
,
"not dead"
]
}
vue3example/Chapter07/admin-frontend/public/favicon.ico
0 → 100644
浏览文件 @
39a4b633
4.2 KB
vue3example/Chapter07/admin-frontend/public/index.html
0 → 100644
浏览文件 @
39a4b633
<!DOCTYPE html>
<html
lang=
""
>
<head>
<meta
charset=
"utf-8"
>
<meta
http-equiv=
"X-UA-Compatible"
content=
"IE=edge"
>
<meta
name=
"viewport"
content=
"width=device-width,initial-scale=1.0"
>
<link
rel=
"icon"
href=
"<%= BASE_URL %>favicon.ico"
>
<title><
%=
htmlWebpackPlugin.options.title
%
></title>
</head>
<body>
<noscript>
<strong>
We're sorry but
<
%=
htmlWebpackPlugin.options.title
%
>
doesn't work properly without JavaScript enabled. Please enable it to continue.
</strong>
</noscript>
<div
id=
"app"
></div>
<!-- built files will be auto injected -->
</body>
</html>
vue3example/Chapter07/admin-frontend/src/App.vue
0 → 100644
浏览文件 @
39a4b633
<
template
>
<router-view></router-view>
</
template
>
<
script
>
export
default
{
name
:
"
App
"
,
};
</
script
>
vue3example/Chapter07/admin-frontend/src/assets/logo.png
0 → 100644
浏览文件 @
39a4b633
6.7 KB
vue3example/Chapter07/admin-frontend/src/components/TopBar.vue
0 → 100644
浏览文件 @
39a4b633
<
template
>
<p>
<router-link
to=
"/orders"
>
Orders
</router-link>
<router-link
to=
"/shop-items"
>
Shop Items
</router-link>
<button
@
click=
"logOut"
>
Log Out
</button>
</p>
</
template
>
<
script
>
export
default
{
name
:
"
TopBar
"
,
methods
:
{
logOut
()
{
localStorage
.
clear
();
this
.
$router
.
push
(
"
/
"
);
},
},
};
</
script
>
<
style
scoped
>
a
{
margin-right
:
5px
;
}
</
style
>
vue3example/Chapter07/admin-frontend/src/main.js
0 → 100644
浏览文件 @
39a4b633
import
{
createApp
}
from
'
vue
'
import
App
from
'
./App.vue
'
import
router
from
'
@/plugins/router
'
const
app
=
createApp
(
App
)
app
.
use
(
router
)
app
.
mount
(
'
#app
'
)
vue3example/Chapter07/admin-frontend/src/plugins/router.js
0 → 100644
浏览文件 @
39a4b633
import
{
createRouter
,
createWebHashHistory
}
from
'
vue-router
'
import
Login
from
'
@/views/Login
'
import
Orders
from
'
@/views/Orders
'
import
ShopItems
from
'
@/views/ShopItems
'
const
beforeEnter
=
(
to
,
from
,
next
)
=>
{
try
{
const
token
=
localStorage
.
getItem
(
'
token
'
)
if
(
to
.
fullPath
!==
'
/
'
&&
!
token
)
{
return
next
({
fullPath
:
'
/
'
})
}
return
next
()
}
catch
(
error
)
{
return
next
({
fullPath
:
'
/
'
})
}
}
const
routes
=
[
{
path
:
'
/
'
,
component
:
Login
},
{
path
:
'
/orders
'
,
component
:
Orders
,
beforeEnter
},
{
path
:
'
/shop-items
'
,
component
:
ShopItems
,
beforeEnter
},
]
const
router
=
createRouter
({
history
:
createWebHashHistory
(),
routes
,
})
export
default
router
vue3example/Chapter07/admin-frontend/src/views/Login.vue
0 → 100644
浏览文件 @
39a4b633
<
template
>
<h1>
Admin Login
</h1>
<Form
:validationSchema=
"schema"
@
submit=
"submitForm"
>
<div>
<label
for=
"name"
>
Username
</label>
<br
/>
<Field
name=
"username"
type=
"text"
placeholder=
"Username"
/>
<ErrorMessage
name=
"username"
/>
</div>
<br
/>
<div>
<label
for=
"password"
>
Password
</label>
<br
/>
<Field
name=
"password"
placeholder=
"Password"
type=
"password"
/>
<ErrorMessage
name=
"password"
/>
</div>
<input
type=
"submit"
/>
</Form>
</
template
>
<
script
>
import
{
GraphQLClient
,
gql
}
from
"
graphql-request
"
;
import
*
as
yup
from
"
yup
"
;
import
{
Form
,
Field
,
ErrorMessage
}
from
"
vee-validate
"
;
const
APIURL
=
"
http://localhost:3000/graphql
"
;
const
graphQLClient
=
new
GraphQLClient
(
APIURL
,
{
headers
:
{
authorization
:
""
,
},
});
const
schema
=
yup
.
object
({
name
:
yup
.
string
().
required
(),
password
:
yup
.
string
().
required
(),
});
export
default
{
name
:
"
Login
"
,
components
:
{
Form
,
Field
,
ErrorMessage
,
},
data
()
{
return
{
schema
,
};
},
methods
:
{
async
submitForm
({
username
,
password
})
{
const
mutation
=
gql
`
mutation login($username: String, $password: String) {
login(user: { username: $username, password: $password }) {
token
}
}
`
;
const
variables
=
{
username
,
password
,
};
try
{
const
{
login
:
{
token
},
}
=
await
graphQLClient
.
request
(
mutation
,
variables
);
localStorage
.
setItem
(
"
token
"
,
token
);
this
.
$router
.
push
(
'
/orders
'
)
}
catch
(
error
)
{
alert
(
"
Login failed
"
);
}
},
},
};
</
script
>
vue3example/Chapter07/admin-frontend/src/views/Orders.vue
0 → 100644
浏览文件 @
39a4b633
<
template
>
<TopBar
/>
<h1>
Orders
</h1>
<div
v-for=
"order of orders"
:key=
"order.order_id"
>
<h2>
Order ID:
{{
order
.
order_id
}}
</h2>
<p>
Name:
{{
order
.
name
}}
</p>
<p>
Address:
{{
order
.
address
}}
</p>
<p>
Phone:
{{
order
.
phone
}}
</p>
<div>
<h3>
Ordered Items
</h3>
<div
v-for=
"orderedItems of order.ordered_items"
:key=
"orderedItems.shop_item_id"
>
<h4>
Name:
{{
orderedItems
.
name
}}
</h4>
<p>
Description:
{{
orderedItems
.
description
}}
</p>
<p>
Price: $
{{
orderedItems
.
price
}}
</p>
</div>
</div>
<p>
<b>
Total: $
{{
calcTotal
(
order
.
ordered_items
)
}}
</b>
</p>
<button
type=
"button"
@
click=
"deleteOrder(order)"
>
Delete Order
</button>
</div>
</
template
>
<
script
>
import
{
GraphQLClient
,
gql
}
from
"
graphql-request
"
;
import
TopBar
from
'
@/components/TopBar
'
const
APIURL
=
"
http://localhost:3000/graphql
"
;
const
graphQLClient
=
new
GraphQLClient
(
APIURL
,
{
headers
:
{
authorization
:
localStorage
.
getItem
(
"
token
"
),
},
});
export
default
{
name
:
"
Orders
"
,
components
:
{
TopBar
},
data
()
{
return
{
orders
:
[],
};
},
beforeMount
()
{
this
.
getOrders
();
},
methods
:
{
calcTotal
(
orderedItems
)
{
return
orderedItems
.
map
((
o
)
=>
o
.
price
).
reduce
((
a
,
b
)
=>
a
+
b
,
0
);
},
async
getOrders
()
{
const
query
=
gql
`
{
getOrders {
order_id
name
address
phone
ordered_items {
shop_item_id
name
description
image_url
price
}
}
}
`
;
const
{
getOrders
:
data
}
=
await
graphQLClient
.
request
(
query
);
this
.
orders
=
data
;
},
async
deleteOrder
({
order_id
:
orderId
})
{
const
mutation
=
gql
`
mutation removeOrder($orderId: Int) {
removeOrder(orderId: $orderId) {
status
}
}
`
;
const
variables
=
{
orderId
,
};
await
graphQLClient
.
request
(
mutation
,
variables
);
await
this
.
getOrders
();
},
},
};
</
script
>
vue3example/Chapter07/admin-frontend/src/views/ShopItems.vue
0 → 100644
浏览文件 @
39a4b633
<
template
>
<TopBar
/>
<h1>
Shop Items
</h1>
<button
@
click=
"showDialog = true"
>
Add Item to Shop
</button>
<div
v-for=
"shopItem of shopItems"
:key=
"shopItem.shop_item_id"
>
<h2>
{{
shopItem
.
name
}}
</h2>
<p>
Description:
{{
shopItem
.
description
}}
</p>
<p>
Price: $
{{
shopItem
.
price
}}
</p>
<img
:src=
"shopItem.image_url"
:alt=
"shopItem.name"
/>
<br
/>
<button
type=
"button"
@
click=
"deleteItem(shopItem)"
>
Delete Item from Shop
</button>
</div>
<dialog
:open=
"showDialog"
class=
"center"
>
<h2>
Add Item to Shop
</h2>
<Form
:validationSchema=
"schema"
@
submit=
"submitForm"
>
<div>
<label
for=
"name"
>
Name
</label>
<br
/>
<Field
name=
"name"
type=
"text"
placeholder=
"Name"
/>
<ErrorMessage
name=
"name"
/>
</div>
<br
/>
<div>
<label
for=
"description"
>
Description
</label>
<br
/>
<Field
name=
"description"
type=
"text"
placeholder=
"Description"
/>
<ErrorMessage
name=
"description"
/>
</div>
<br
/>
<div>
<label
for=
"imageUrl"
>
Image URL
</label>
<br
/>
<Field
name=
"imageUrl"
type=
"text"
placeholder=
"Image URL"
/>
<ErrorMessage
name=
"imageUrl"
/>
</div>
<br
/>
<div>
<label
for=
"price"
>
Price
</label>
<br
/>
<Field
name=
"price"
type=
"text"
placeholder=
"Price"
/>
<ErrorMessage
name=
"price"
/>
</div>
<br
/>
<input
type=
"submit"
/>
<button
@
click=
"showDialog = false"
type=
"button"
>
Cancel
</button>
</Form>
</dialog>
</
template
>
<
script
>
import
{
GraphQLClient
,
gql
}
from
"
graphql-request
"
;
import
*
as
yup
from
"
yup
"
;
import
TopBar
from
"
@/components/TopBar
"
;
import
{
Form
,
Field
,
ErrorMessage
}
from
"
vee-validate
"
;
const
APIURL
=
"
http://localhost:3000/graphql
"
;
const
graphQLClient
=
new
GraphQLClient
(
APIURL
,
{
headers
:
{
authorization
:
localStorage
.
getItem
(
"
token
"
),
},
});
const
schema
=
yup
.
object
({
name
:
yup
.
string
().
required
(),
description
:
yup
.
string
().
required
(),
imageUrl
:
yup
.
string
().
required
(),
price
:
yup
.
number
().
required
().
min
(
0
),
});
export
default
{
name
:
"
ShopItems
"
,
components
:
{
Form
,
Field
,
ErrorMessage
,
TopBar
,
},
data
()
{
return
{
shopItems
:
[],
showDialog
:
false
,
schema
,
};
},
beforeMount
()
{
this
.
getShopItems
();
},
methods
:
{
async
getShopItems
()
{
const
query
=
gql
`
{
getShopItems {
shop_item_id
name
description
image_url
price
}
}
`
;
const
{
getShopItems
:
data
}
=
await
graphQLClient
.
request
(
query
);
this
.
shopItems
=
data
;
},
async
submitForm
({
name
,
description
,
imageUrl
,
price
:
oldPrice
})
{
const
mutation
=
gql
`
mutation addShopItem(
$name: String
$description: String
$image_url: String
$price: Float
) {
addShopItem(
shopItem: {
name: $name
description: $description
image_url: $image_url
price: $price
}
) {
status
}
}
`
;
const
variables
=
{
name
,
description
,
image_url
:
imageUrl
,
price
:
+
oldPrice
,
};
await
graphQLClient
.
request
(
mutation
,
variables
);
this
.
showDialog
=
false
;
await
this
.
getShopItems
();
},
async
deleteItem
({
shop_item_id
:
shopItemId
})
{
const
mutation
=
gql
`
mutation removeShopItem($shopItemId: Int) {
removeShopItem(shopItemId: $shopItemId) {
status
}
}
`
;
const
variables
=
{
shopItemId
,
};
await
graphQLClient
.
request
(
mutation
,
variables
);
await
this
.
getShopItems
();
},
},
};
</
script
>
<
style
scoped
>
img
{
width
:
100px
;
}
.center
{
position
:
absolute
;
left
:
50%
;
top
:
50%
;
transform
:
translate
(
-50%
,
-50%
);
min-width
:
400px
;
}
.center
input
[
type
=
"text"
]
{
width
:
100%
;
}
</
style
>
vue3example/Chapter07/backend/app.js
0 → 100644
浏览文件 @
39a4b633
const
createError
=
require
(
'
http-errors
'
);
const
express
=
require
(
'
express
'
);
const
path
=
require
(
'
path
'
);
const
cookieParser
=
require
(
'
cookie-parser
'
);
const
logger
=
require
(
'
morgan
'
);
const
{
graphqlHTTP
}
=
require
(
'
express-graphql
'
);
const
{
buildSchema
}
=
require
(
'
graphql
'
);
const
cors
=
require
(
'
cors
'
)
const
shopItemResolvers
=
require
(
'
./resolvers/shopItems
'
)
const
orderResolvers
=
require
(
'
./resolvers/orders
'
)
const
authResolvers
=
require
(
'
./resolvers/auth
'
)
const
jwt
=
require
(
'
jsonwebtoken
'
);
const
schema
=
buildSchema
(
`
type Response {
status: String
}
type Token {
token: String
}
input User {
username: String
password: String
token: String
}
input ShopItem {
shop_item_id: Int
name: String
description: String
image_url: String
price: Float
}
type ShopItemOutput {
shop_item_id: Int
name: String
description: String
image_url: String
price: Float
}
type OrderOutput {
order_id: Int
name: String
address: String
phone: String
ordered_items: [ShopItemOutput]
}
input Order {
order_id: Int
name: String
address: String
phone: String
ordered_items: [ShopItem]
}
type Query {
getShopItems: [ShopItemOutput],
getOrders: [OrderOutput]
}
type Mutation {
addShopItem(shopItem: ShopItem): Response
removeShopItem(shopItemId: Int): Response
addOrder(order: Order): Response
removeOrder(orderId: Int): Response
login(user: User): Token
}
`
);
const
root
=
{
...
shopItemResolvers
,
...
orderResolvers
,
...
authResolvers
}
const
authMiddleware
=
(
req
,
res
,
next
)
=>
{
const
{
query
=
''
}
=
req
.
body
const
token
=
req
.
get
(
'
authorization
'
)
const
requiresAuth
=
query
.
includes
(
'
removeOrder
'
)
||
query
.
includes
(
'
removeShopItem
'
)
||
query
.
includes
(
'
addShopItem
'
)
if
(
requiresAuth
)
{
try
{
jwt
.
verify
(
token
,
'
secret
'
);
next
()
return
}
catch
(
error
)
{
res
.
status
(
401
).
json
({})
return
}
}
next
();
}
const
app
=
express
();
app
.
use
(
cors
())
app
.
use
(
logger
(
'
dev
'
));
app
.
use
(
express
.
json
());
app
.
use
(
express
.
urlencoded
({
extended
:
false
}));
app
.
use
(
cookieParser
());
app
.
use
(
authMiddleware
)
app
.
use
(
'
/graphql
'
,
graphqlHTTP
({
schema
,
rootValue
:
root
,
graphiql
:
true
,
}));
// view engine setup
app
.
set
(
'
views
'
,
path
.
join
(
__dirname
,
'
views
'
));
app
.
set
(
'
view engine
'
,
'
jade
'
);
app
.
use
(
express
.
static
(
path
.
join
(
__dirname
,
'
public
'
)));
// catch 404 and forward to error handler
app
.
use
(
function
(
req
,
res
,
next
)
{
next
(
createError
(
404
));
});
// error handler
app
.
use
(
function
(
err
,
req
,
res
,
next
)
{
// set locals, only providing error in development
res
.
locals
.
message
=
err
.
message
;
res
.
locals
.
error
=
req
.
app
.
get
(
'
env
'
)
===
'
development
'
?
err
:
{};
// render the error page
res
.
status
(
err
.
status
||
500
);
res
.
render
(
'
error
'
);
});
module
.
exports
=
app
;
vue3example/Chapter07/backend/bin/www
0 → 100644
浏览文件 @
39a4b633
#!/usr/bin/env node
/**
* Module dependencies.
*/
var
app
=
require
(
'
../app
'
);
var
debug
=
require
(
'
debug
'
)(
'
backend:server
'
);
var
http
=
require
(
'
http
'
);
/**
* Get port from environment and store in Express.
*/
var
port
=
normalizePort
(
process
.
env
.
PORT
||
'
3000
'
);
app
.
set
(
'
port
'
,
port
);
/**
* Create HTTP server.
*/
var
server
=
http
.
createServer
(
app
);
/**
* Listen on provided port, on all network interfaces.
*/
server
.
listen
(
port
);
server
.
on
(
'
error
'
,
onError
);
server
.
on
(
'
listening
'
,
onListening
);
/**
* Normalize a port into a number, string, or false.
*/
function
normalizePort
(
val
)
{
var
port
=
parseInt
(
val
,
10
);
if
(
isNaN
(
port
))
{
// named pipe
return
val
;
}
if
(
port
>=
0
)
{
// port number
return
port
;
}
return
false
;
}
/**
* Event listener for HTTP server "error" event.
*/
function
onError
(
error
)
{
if
(
error
.
syscall
!==
'
listen
'
)
{
throw
error
;
}
var
bind
=
typeof
port
===
'
string
'
?
'
Pipe
'
+
port
:
'
Port
'
+
port
;
// handle specific listen errors with friendly messages
switch
(
error
.
code
)
{
case
'
EACCES
'
:
console
.
error
(
bind
+
'
requires elevated privileges
'
);
process
.
exit
(
1
);
break
;
case
'
EADDRINUSE
'
:
console
.
error
(
bind
+
'
is already in use
'
);
process
.
exit
(
1
);
break
;
default
:
throw
error
;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function
onListening
()
{
var
addr
=
server
.
address
();
var
bind
=
typeof
addr
===
'
string
'
?
'
pipe
'
+
addr
:
'
port
'
+
addr
.
port
;
debug
(
'
Listening on
'
+
bind
);
}
vue3example/Chapter07/backend/db.sql
0 → 100644
浏览文件 @
39a4b633
DROP
TABLE
IF
EXISTS
order_shop_items
;
DROP
TABLE
IF
EXISTS
orders
;
DROP
TABLE
IF
EXISTS
shop_items
;
CREATE
TABLE
shop_items
(
shop_item_id
INTEGER
NOT
NULL
PRIMARY
KEY
,
name
TEXT
NOT
NULL
,
description
TEXT
NOT
NULL
,
price
NUMBER
NOT
NULL
,
image_url
TEXT
NOT
NULL
);
CREATE
TABLE
orders
(
order_id
INTEGER
NOT
NULL
PRIMARY
KEY
,
name
TEXT
NOT
NULL
,
address
TEXT
NOT
NULL
,
phone
TEXT
NOT
NULL
);
CREATE
TABLE
order_shop_items
(
order_id
INTEGER
NOT
NULL
,
shop_item_id
INTEGER
NOT
NULL
,
FOREIGN
KEY
(
order_id
)
REFERENCES
orders
(
order_id
)
FOREIGN
KEY
(
shop_item_id
)
REFERENCES
shop_items
(
shop_item_id
)
);
vue3example/Chapter07/backend/package-lock.json
0 → 100644
浏览文件 @
39a4b633
此差异已折叠。
点击以展开。
vue3example/Chapter07/backend/package.json
0 → 100644
浏览文件 @
39a4b633
{
"name"
:
"backend"
,
"version"
:
"0.0.0"
,
"private"
:
true
,
"scripts"
:
{
"start"
:
"nodemon ./bin/www"
},
"dependencies"
:
{
"cookie-parser"
:
"~1.4.4"
,
"cors"
:
"^2.8.5"
,
"debug"
:
"~2.6.9"
,
"express"
:
"~4.16.1"
,
"express-graphql"
:
"^0.12.0"
,
"graphql"
:
"^15.5.0"
,
"http-errors"
:
"~1.6.3"
,
"jade"
:
"~1.11.0"
,
"jsonwebtoken"
:
"^8.5.1"
,
"morgan"
:
"~1.9.1"
,
"sqlite3"
:
"^5.0.2"
}
}
vue3example/Chapter07/backend/public/stylesheets/style.css
0 → 100644
浏览文件 @
39a4b633
body
{
padding
:
50px
;
font
:
14px
"Lucida Grande"
,
Helvetica
,
Arial
,
sans-serif
;
}
a
{
color
:
#00B7FF
;
}
vue3example/Chapter07/backend/resolvers/auth.js
0 → 100644
浏览文件 @
39a4b633
const
jwt
=
require
(
'
jsonwebtoken
'
);
module
.
exports
=
{
login
:
({
user
:
{
username
,
password
}
})
=>
{
if
(
username
===
'
admin
'
&&
password
===
'
password
'
)
{
return
{
token
:
jwt
.
sign
({
username
},
'
secret
'
)
}
}
throw
new
Error
(
'
authentication failed
'
);
}
}
vue3example/Chapter07/backend/resolvers/orders.js
0 → 100644
浏览文件 @
39a4b633
const
sqlite3
=
require
(
'
sqlite3
'
).
verbose
();
module
.
exports
=
{
getOrders
:
()
=>
{
const
db
=
new
sqlite3
.
Database
(
'
./db.sqlite
'
);
return
new
Promise
((
resolve
,
reject
)
=>
{
db
.
serialize
(()
=>
{
db
.
all
(
`
SELECT *,
orders.name AS purchaser_name,
shop_items.name AS shop_item_name
FROM orders
INNER JOIN order_shop_items ON orders.order_id = order_shop_items.order_id
INNER JOIN shop_items ON order_shop_items.shop_item_id = shop_items.shop_item_id
`
,
[],
(
err
,
rows
=
[])
=>
{
if
(
err
)
{
reject
(
err
)
}
const
orders
=
{}
for
(
const
row
of
rows
)
{
const
{
order_id
,
purchaser_name
:
name
,
address
,
phone
}
=
row
orders
[
order_id
]
=
{
order_id
,
name
,
address
,
phone
}
}
const
orderArr
=
Object
.
values
(
orders
)
for
(
const
order
of
orderArr
)
{
order
.
ordered_items
=
rows
.
filter
(({
order_id
})
=>
order_id
===
order
.
order_id
)
.
map
(({
shop_item_id
,
shop_item_name
:
name
,
price
,
description
})
=>
({
shop_item_id
,
name
,
price
,
description
}))
}
resolve
(
orderArr
)
});
})
db
.
close
();
})
},
addOrder
:
({
order
:
{
name
,
address
,
phone
,
ordered_items
:
orderedItems
}
})
=>
{
const
db
=
new
sqlite3
.
Database
(
'
./db.sqlite
'
);
return
new
Promise
((
resolve
)
=>
{
db
.
serialize
(()
=>
{
const
orderStmt
=
db
.
prepare
(
`
INSERT INTO orders (
name,
address,
phone
) VALUES (?, ?, ?)
`
);
orderStmt
.
run
(
name
,
address
,
phone
)
orderStmt
.
finalize
();
db
.
all
(
`
SELECT last_insert_rowid() AS order_id
`
,
[],
(
err
,
rows
=
[])
=>
{
const
[{
order_id
:
orderId
}]
=
rows
db
.
serialize
(()
=>
{
const
orderShopItemStmt
=
db
.
prepare
(
`
INSERT INTO order_shop_items (
order_id,
shop_item_id
) VALUES (?, ?)
`
);
for
(
const
orderItem
of
orderedItems
)
{
const
{
shop_item_id
:
shopItemId
}
=
orderItem
orderShopItemStmt
.
run
(
orderId
,
shopItemId
)
}
orderShopItemStmt
.
finalize
()
})
resolve
({
status
:
'
success
'
})
db
.
close
();
});
})
})
},
removeOrder
:
({
orderId
})
=>
{
const
db
=
new
sqlite3
.
Database
(
'
./db.sqlite
'
);
return
new
Promise
((
resolve
)
=>
{
db
.
serialize
(()
=>
{
const
delOrderShopItemsStmt
=
db
.
prepare
(
"
DELETE FROM order_shop_items WHERE order_id = (?)
"
);
delOrderShopItemsStmt
.
run
(
orderId
)
delOrderShopItemsStmt
.
finalize
();
const
delOrderStmt
=
db
.
prepare
(
"
DELETE FROM orders WHERE order_id = (?)
"
);
delOrderStmt
.
run
(
orderId
)
delOrderStmt
.
finalize
();
resolve
({
status
:
'
success
'
})
})
db
.
close
();
})
},
}
vue3example/Chapter07/backend/resolvers/shopItems.js
0 → 100644
浏览文件 @
39a4b633
const
sqlite3
=
require
(
'
sqlite3
'
).
verbose
();
module
.
exports
=
{
getShopItems
:
()
=>
{
const
db
=
new
sqlite3
.
Database
(
'
./db.sqlite
'
);
return
new
Promise
((
resolve
,
reject
)
=>
{
db
.
serialize
(()
=>
{
db
.
all
(
"
SELECT * FROM shop_items
"
,
[],
(
err
,
rows
=
[])
=>
{
if
(
err
)
{
reject
(
err
)
}
resolve
(
rows
)
});
})
db
.
close
();
})
},
addShopItem
:
({
shopItem
:
{
name
,
description
,
image_url
:
imageUrl
,
price
}
})
=>
{
const
db
=
new
sqlite3
.
Database
(
'
./db.sqlite
'
);
return
new
Promise
((
resolve
)
=>
{
db
.
serialize
(()
=>
{
const
stmt
=
db
.
prepare
(
`
INSERT INTO shop_items (
name,
description,
image_url,
price
) VALUES (?, ?, ?, ?)
`
);
stmt
.
run
(
name
,
description
,
imageUrl
,
price
)
stmt
.
finalize
();
resolve
({
status
:
'
success
'
})
})
db
.
close
();
})
},
removeShopItem
:
({
shopItemId
})
=>
{
const
db
=
new
sqlite3
.
Database
(
'
./db.sqlite
'
);
return
new
Promise
((
resolve
)
=>
{
db
.
serialize
(()
=>
{
const
stmt
=
db
.
prepare
(
"
DELETE FROM shop_items WHERE shop_item_id = (?)
"
);
stmt
.
run
(
shopItemId
)
stmt
.
finalize
();
resolve
({
status
:
'
success
'
})
})
db
.
close
();
})
},
}
vue3example/Chapter07/backend/routes/index.js
0 → 100644
浏览文件 @
39a4b633
var
express
=
require
(
'
express
'
);
var
router
=
express
.
Router
();
/* GET home page. */
router
.
get
(
'
/
'
,
function
(
req
,
res
,
next
)
{
res
.
render
(
'
index
'
,
{
title
:
'
Express
'
});
});
module
.
exports
=
router
;
vue3example/Chapter07/backend/routes/users.js
0 → 100644
浏览文件 @
39a4b633
var
express
=
require
(
'
express
'
);
var
router
=
express
.
Router
();
/* GET users listing. */
router
.
get
(
'
/
'
,
function
(
req
,
res
,
next
)
{
res
.
send
(
'
respond with a resource
'
);
});
module
.
exports
=
router
;
vue3example/Chapter07/backend/views/error.jade
0 → 100644
浏览文件 @
39a4b633
extends layout
block content
h1= message
h2= error.status
pre #{error.stack}
vue3example/Chapter07/backend/views/index.jade
0 → 100644
浏览文件 @
39a4b633
extends layout
block content
h1= title
p Welcome to #{title}
vue3example/Chapter07/backend/views/layout.jade
0 → 100644
浏览文件 @
39a4b633
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
body
block content
vue3example/Chapter07/frontend/.gitignore
0 → 100644
浏览文件 @
39a4b633
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
vue3example/Chapter07/frontend/README.md
0 → 100644
浏览文件 @
39a4b633
# frontend
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See
[
Configuration Reference
](
https://cli.vuejs.org/config/
)
.
vue3example/Chapter07/frontend/babel.config.js
0 → 100644
浏览文件 @
39a4b633
module
.
exports
=
{
presets
:
[
'
@vue/cli-plugin-babel/preset
'
]
}
vue3example/Chapter07/frontend/package-lock.json
0 → 100644
浏览文件 @
39a4b633
此差异已折叠。
点击以展开。
vue3example/Chapter07/frontend/package.json
0 → 100644
浏览文件 @
39a4b633
{
"name"
:
"frontend"
,
"version"
:
"0.1.0"
,
"private"
:
true
,
"scripts"
:
{
"serve"
:
"vue-cli-service serve --port 8091"
,
"build"
:
"vue-cli-service build"
,
"lint"
:
"vue-cli-service lint"
},
"dependencies"
:
{
"core-js"
:
"^3.6.5"
,
"graphql"
:
"^15.5.0"
,
"graphql-request"
:
"^3.4.0"
,
"vee-validate"
:
"^4.2.1"
,
"vue"
:
"^3.0.0"
,
"vue-router"
:
"^4.0.4"
,
"vuex"
:
"^4.0.0"
,
"vuex-persistedstate"
:
"^4.0.0-beta.3"
,
"yup"
:
"^0.32.9"
},
"devDependencies"
:
{
"@vue/cli-plugin-babel"
:
"~4.5.0"
,
"@vue/cli-plugin-eslint"
:
"~4.5.0"
,
"@vue/cli-service"
:
"~4.5.0"
,
"@vue/compiler-sfc"
:
"^3.0.0"
,
"babel-eslint"
:
"^10.1.0"
,
"eslint"
:
"^6.7.2"
,
"eslint-plugin-vue"
:
"^7.0.0-0"
},
"eslintConfig"
:
{
"root"
:
true
,
"env"
:
{
"node"
:
true
},
"extends"
:
[
"plugin:vue/vue3-essential"
,
"eslint:recommended"
],
"parserOptions"
:
{
"parser"
:
"babel-eslint"
},
"rules"
:
{}
},
"browserslist"
:
[
"> 1%"
,
"last 2 versions"
,
"not dead"
]
}
vue3example/Chapter07/frontend/public/favicon.ico
0 → 100644
浏览文件 @
39a4b633
4.2 KB
vue3example/Chapter07/frontend/public/index.html
0 → 100644
浏览文件 @
39a4b633
<!DOCTYPE html>
<html
lang=
""
>
<head>
<meta
charset=
"utf-8"
>
<meta
http-equiv=
"X-UA-Compatible"
content=
"IE=edge"
>
<meta
name=
"viewport"
content=
"width=device-width,initial-scale=1.0"
>
<link
rel=
"icon"
href=
"<%= BASE_URL %>favicon.ico"
>
<title><
%=
htmlWebpackPlugin.options.title
%
></title>
</head>
<body>
<noscript>
<strong>
We're sorry but
<
%=
htmlWebpackPlugin.options.title
%
>
doesn't work properly without JavaScript enabled. Please enable it to continue.
</strong>
</noscript>
<div
id=
"app"
></div>
<!-- built files will be auto injected -->
</body>
</html>
vue3example/Chapter07/frontend/src/App.vue
0 → 100644
浏览文件 @
39a4b633
<
template
>
<router-view></router-view>
</
template
>
<
script
>
export
default
{
name
:
"
App
"
,
};
</
script
>
vue3example/Chapter07/frontend/src/assets/logo.png
0 → 100644
浏览文件 @
39a4b633
6.7 KB
vue3example/Chapter07/frontend/src/components/HelloWorld.vue
0 → 100644
浏览文件 @
39a4b633
<
template
>
<div
class=
"hello"
>
<h1>
{{
msg
}}
</h1>
<p>
For a guide and recipes on how to configure / customize this project,
<br>
check out the
<a
href=
"https://cli.vuejs.org"
target=
"_blank"
rel=
"noopener"
>
vue-cli documentation
</a>
.
</p>
<h3>
Installed CLI Plugins
</h3>
<ul>
<li><a
href=
"https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel"
target=
"_blank"
rel=
"noopener"
>
babel
</a></li>
<li><a
href=
"https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint"
target=
"_blank"
rel=
"noopener"
>
eslint
</a></li>
</ul>
<h3>
Essential Links
</h3>
<ul>
<li><a
href=
"https://vuejs.org"
target=
"_blank"
rel=
"noopener"
>
Core Docs
</a></li>
<li><a
href=
"https://forum.vuejs.org"
target=
"_blank"
rel=
"noopener"
>
Forum
</a></li>
<li><a
href=
"https://chat.vuejs.org"
target=
"_blank"
rel=
"noopener"
>
Community Chat
</a></li>
<li><a
href=
"https://twitter.com/vuejs"
target=
"_blank"
rel=
"noopener"
>
Twitter
</a></li>
<li><a
href=
"https://news.vuejs.org"
target=
"_blank"
rel=
"noopener"
>
News
</a></li>
</ul>
<h3>
Ecosystem
</h3>
<ul>
<li><a
href=
"https://router.vuejs.org"
target=
"_blank"
rel=
"noopener"
>
vue-router
</a></li>
<li><a
href=
"https://vuex.vuejs.org"
target=
"_blank"
rel=
"noopener"
>
vuex
</a></li>
<li><a
href=
"https://github.com/vuejs/vue-devtools#vue-devtools"
target=
"_blank"
rel=
"noopener"
>
vue-devtools
</a></li>
<li><a
href=
"https://vue-loader.vuejs.org"
target=
"_blank"
rel=
"noopener"
>
vue-loader
</a></li>
<li><a
href=
"https://github.com/vuejs/awesome-vue"
target=
"_blank"
rel=
"noopener"
>
awesome-vue
</a></li>
</ul>
</div>
</
template
>
<
script
>
export
default
{
name
:
'
HelloWorld
'
,
props
:
{
msg
:
String
}
}
</
script
>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<
style
scoped
>
h3
{
margin
:
40px
0
0
;
}
ul
{
list-style-type
:
none
;
padding
:
0
;
}
li
{
display
:
inline-block
;
margin
:
0
10px
;
}
a
{
color
:
#42b983
;
}
</
style
>
vue3example/Chapter07/frontend/src/main.js
0 → 100644
浏览文件 @
39a4b633
import
{
createApp
}
from
'
vue
'
import
App
from
'
./App.vue
'
import
router
from
'
@/plugins/router
'
import
store
from
'
@/plugins/vuex
'
const
app
=
createApp
(
App
)
app
.
use
(
router
)
app
.
use
(
store
)
app
.
mount
(
'
#app
'
)
vue3example/Chapter07/frontend/src/plugins/router.js
0 → 100644
浏览文件 @
39a4b633
import
{
createRouter
,
createWebHashHistory
}
from
'
vue-router
'
import
Shop
from
'
@/views/Shop
'
import
OrderForm
from
'
@/views/OrderForm
'
import
Success
from
'
@/views/Success
'
const
routes
=
[
{
path
:
'
/
'
,
component
:
Shop
},
{
path
:
'
/order-form
'
,
component
:
OrderForm
},
{
path
:
'
/success
'
,
component
:
Success
},
]
const
router
=
createRouter
({
history
:
createWebHashHistory
(),
routes
,
})
export
default
router
vue3example/Chapter07/frontend/src/plugins/vuex.js
0 → 100644
浏览文件 @
39a4b633
import
{
createStore
}
from
"
vuex
"
;
import
createPersistedState
from
"
vuex-persistedstate
"
;
const
store
=
createStore
({
state
()
{
return
{
cartItems
:
[]
}
},
getters
:
{
cartItemsAdded
(
state
)
{
return
state
.
cartItems
}
},
mutations
:
{
addCartItem
(
state
,
cartItem
)
{
const
cartItemIds
=
state
.
cartItems
.
map
(
c
=>
c
.
cartItemId
).
filter
(
id
=>
typeof
id
===
'
number
'
)
state
.
cartItems
.
push
({
cartItemId
:
cartItemIds
.
length
>
0
?
Math
.
max
(...
cartItemIds
)
:
1
,
...
cartItem
})
},
removeCartItem
(
state
,
index
)
{
state
.
cartItems
.
splice
(
index
,
1
)
},
clearCart
(
state
)
{
state
.
cartItems
=
[]
}
},
plugins
:
[
createPersistedState
({
key
:
'
cart
'
})],
});
export
default
store
vue3example/Chapter07/frontend/src/views/OrderForm.vue
0 → 100644
浏览文件 @
39a4b633
<
template
>
<h1>
Order Form
</h1>
<div
v-for=
"(cartItem, index) of cartItemsAdded"
:key=
"cartItem.cartItemId"
>
<h2>
{{
cartItem
.
name
}}
</h2>
<p>
Description:
{{
cartItem
.
description
}}
</p>
<p>
Price: $
{{
cartItem
.
price
}}
</p>
<br
/>
<button
type=
"button"
@
click=
"removeCartItem(index)"
>
Remove From Cart
</button>
</div>
<Form
:validationSchema=
"schema"
@
submit=
"submitOrder"
>
<div>
<label
for=
"name"
>
Name
</label>
<br
/>
<Field
name=
"name"
type=
"text"
placeholder=
"Name"
/>
<ErrorMessage
name=
"name"
/>
</div>
<br
/>
<div>
<label
for=
"phone"
>
Phone
</label>
<br
/>
<Field
name=
"phone"
type=
"text"
placeholder=
"Phone"
/>
<ErrorMessage
name=
"phone"
/>
</div>
<br
/>
<div>
<label
for=
"address"
>
Address
</label>
<br
/>
<Field
name=
"address"
type=
"text"
placeholder=
"Address"
/>
<ErrorMessage
name=
"address"
/>
</div>
<br
/>
<input
type=
"submit"
/>
</Form>
</
template
>
<
script
>
import
{
GraphQLClient
,
gql
}
from
"
graphql-request
"
;
import
{
mapMutations
,
mapGetters
}
from
"
vuex
"
;
import
{
Form
,
Field
,
ErrorMessage
}
from
"
vee-validate
"
;
import
*
as
yup
from
"
yup
"
;
const
APIURL
=
"
http://localhost:3000/graphql
"
;
const
graphQLClient
=
new
GraphQLClient
(
APIURL
);
const
schema
=
yup
.
object
({
name
:
yup
.
string
().
required
(),
phone
:
yup
.
string
().
required
(),
address
:
yup
.
string
().
required
(),
});
export
default
{
name
:
"
OrderForm
"
,
data
()
{
return
{
schema
,
};
},
components
:
{
Form
,
Field
,
ErrorMessage
,
},
computed
:
{
...
mapGetters
([
"
cartItemsAdded
"
]),
},
methods
:
{
async
submitOrder
({
name
,
phone
,
address
})
{
const
mutation
=
gql
`
mutation addOrder(
$name: String
$phone: String
$address: String
$ordered_items: [ShopItem]
) {
addOrder(
order: {
name: $name
phone: $phone
address: $address
ordered_items: $ordered_items
}
) {
status
}
}
`
;
const
variables
=
{
name
,
phone
,
address
,
ordered_items
:
this
.
cartItemsAdded
.
map
(
({
shop_item_id
,
name
,
description
,
image_url
,
price
})
=>
({
shop_item_id
,
name
,
description
,
image_url
,
price
,
})
),
};
await
graphQLClient
.
request
(
mutation
,
variables
);
this
.
clearCart
();
this
.
$router
.
push
(
"
/success
"
);
},
...
mapMutations
([
"
addCartItem
"
,
"
removeCartItem
"
,
"
clearCart
"
]),
},
};
</
script
>
vue3example/Chapter07/frontend/src/views/Shop.vue
0 → 100644
浏览文件 @
39a4b633
<
template
>
<h1>
Shop
</h1>
<div>
<router-link
to=
"/order-form"
>
Check Out
</router-link>
</div>
<button
type=
"button"
@
click=
"clearCart()"
>
Clear Shopping Cart
</button>
<p>
{{
cartItemsAdded
.
length
}}
item(s) added to cart.
</p>
<div
v-for=
"shopItem of shopItems"
:key=
"shopItem.shop_item_id"
>
<h2>
{{
shopItem
.
name
}}
</h2>
<p>
Description:
{{
shopItem
.
description
}}
</p>
<p>
Price: $
{{
shopItem
.
price
}}
</p>
<img
:src=
"shopItem.image_url"
:alt=
"shopItem.name"
/>
<br
/>
<button
type=
"button"
@
click=
"addCartItem(shopItem)"
>
Add to Cart
</button>
</div>
</
template
>
<
script
>
import
{
GraphQLClient
,
gql
}
from
"
graphql-request
"
;
import
{
mapMutations
,
mapGetters
}
from
"
vuex
"
;
const
APIURL
=
"
http://localhost:3000/graphql
"
;
const
graphQLClient
=
new
GraphQLClient
(
APIURL
);
export
default
{
name
:
"
Shop
"
,
data
()
{
return
{
shopItems
:
[],
};
},
beforeMount
()
{
this
.
getShopItems
();
},
computed
:
{
...
mapGetters
([
"
cartItemsAdded
"
]),
},
methods
:
{
async
getShopItems
()
{
const
query
=
gql
`
{
getShopItems {
shop_item_id
name
description
image_url
price
}
}
`
;
const
{
getShopItems
:
data
}
=
await
graphQLClient
.
request
(
query
);
this
.
shopItems
=
data
;
},
...
mapMutations
([
"
addCartItem
"
,
"
clearCart
"
]),
},
};
</
script
>
<
style
scoped
>
img
{
width
:
100px
;
}
</
style
>
vue3example/Chapter07/frontend/src/views/Success.vue
0 → 100644
浏览文件 @
39a4b633
<
template
>
<div>
<h1>
Order Successful
</h1>
<router-link
to=
"/"
>
Go Back to Shop
</router-link>
</div>
</
template
>
<
script
>
export
default
{
name
:
"
Success
"
,
};
</
script
>
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录