Product.razor 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. @page "/app/product"
  2. @attribute [ReuseTabsPage(Title = "商品管理")]
  3. <Spin Spinning="Loading">
  4. <Table @ref="_Table"
  5. AutoHeight
  6. TItem="TProduct"
  7. @bind-PageSize="Ps"
  8. @bind-PageIndex="Pi"
  9. Total="Total"
  10. DataSource="DataSource"
  11. @bind-SelectedRows="SelectedRows"
  12. ScrollX="1300"
  13. OnChange="OnChange">
  14. <TitleTemplate>
  15. <Flex Justify="FlexJustify.Start" Gap="@("10")">
  16. <Input Width="300" Placeholder="输入商品名称" @bind-Value="@Q_Name" />
  17. <Input Width="300" Placeholder="输入商品编码" @bind-Value="@Q_Code" />
  18. <Button OnClick="Search">搜索</Button>
  19. <Button OnClick="ResetQuery">重置</Button>
  20. <Button Type="ButtonType.Primary" Color="Color.Green6" OnClick="() => StartEdit(default)">新增</Button>
  21. </Flex>
  22. </TitleTemplate>
  23. <ColumnDefinitions Context="row">
  24. <PropertyColumn Align="ColumnAlign.Center" Property="c=>c.Id" Width="100" Title="ID" />
  25. <PropertyColumn Align="ColumnAlign.Center" Property="c=>c.MainImage" Width="100" Title="主图">
  26. <Image Src="@row.MainImage" Width="50" Height="50" />
  27. </PropertyColumn>
  28. <PropertyColumn Align="ColumnAlign.Center" Property="c=>c.Name" Width="100" Title="商品名称" />
  29. <PropertyColumn Align="ColumnAlign.Center" Property="c=>c.Code" Width="100" Title="商品编码" />
  30. <PropertyColumn Align="ColumnAlign.Center" Width="100" Property="c=>c.Available" Title="是否上架">
  31. <Switch Checked="@row.Available" @bind-Value="@row.Available" CheckedChildren="上架" UnCheckedChildren="下架" OnChange="() => CheckedChanged(row)" />
  32. </PropertyColumn>
  33. <PropertyColumn Align="ColumnAlign.Center" Property="c=>c.TotalStock" Width="100" Title="总库存" />
  34. <PropertyColumn Align="ColumnAlign.Center" Property="c => c.CreateTime" Width="200" Title="创建时间">
  35. @{
  36. var formattedTime = row.CreateTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? "N/A";
  37. }
  38. @formattedTime
  39. </PropertyColumn>
  40. <ActionColumn Width="180" Title="操作" Fixed="ColumnFixPlacement.Right">
  41. <Button Type="ButtonType.Primary" Color="Color.Blue6" OnClick="() => StartEdit(row)">编辑</Button>
  42. </ActionColumn>
  43. </ColumnDefinitions>
  44. <PaginationTemplate>
  45. <Pagination Class="@(context.PaginationClass + " my-custom-pagination")"
  46. Total="context.Total"
  47. PageSize="context.PageSize"
  48. Current="context.PageIndex"
  49. ShowSizeChanger
  50. OnChange="context.HandlePageChange" />
  51. </PaginationTemplate>
  52. </Table>
  53. </Spin>
  54. <Modal Visible="previewVisible"
  55. Title="预览图"
  56. OnCancel="()=> previewVisible=false">
  57. <img style="width: 100%" src="@imgUrl" />
  58. </Modal>
  59. @inject ModalService ModalService;
  60. @inject ConfirmService ComfirmService;
  61. @inject IMessageService MessageService;
  62. @code {
  63. /// <summary>
  64. /// 富文本配置项
  65. /// 在这里注册获取key:https://www.tiny.cloud/
  66. /// </summary>
  67. public static Dictionary<string, object> TinyMCEConfig { get; set; } = new()
  68. {
  69. // 配置项
  70. { "language_url", "/lib/tinymce/langs/zh_CN.js" },
  71. { "language", "zh_CN" },
  72. { "min_width", 500 },
  73. //{ "max_width", 1000 },
  74. { "min_height", 500 },
  75. //{ "max_height", 1000 },
  76. { "plugins", "table image code link lists insertdatetime preview searchreplace wordcount" },
  77. { "toolbar", "undo redo | searchreplace removeformat | bold italic underline strikethrough subscript superscript | alignleft aligncenter alignright outdent indent blockquote | code codesample | fontsize fontfamily styles forecolor backcolor | hr bullist numlist | link image charmap preview anchor pagebreak insertdatetime table emoticons | fullscreen" },
  78. { "toolbar_mode", "wrap" },
  79. { "promotion", false },//关闭升级付费提示
  80. { "paste_data_images", true },//内存中的图片粘贴为base64文本格式
  81. { "image_uploadtab", true },
  82. { "images_file_types", "jpg,jpeg,png,gif" },
  83. { "images_upload_credentials", "false" },//同域名下基于Cookie鉴权
  84. { "images_upload_url", Global.UploadUrl },//图片上传地址
  85. };
  86. private void StartEdit(TProduct row)
  87. {
  88. tProduct = row ?? new();
  89. InitFormData(row);
  90. IForm form = default;
  91. modalRef = ModalService.CreateModal<bool>(new()
  92. {
  93. Width = "1300px",
  94. Title = tProduct.Id > 0 ? "编辑" : "新增",
  95. Content =
  96. @<Form @ref="form" Model="tProduct" OnFinish="()=> modalRef.OkAsync(true)" LabelColSpan="6" WrapperColSpan="18">
  97. <FormItem Label="名称" Required>
  98. <Input @bind-Value="@tProduct.Name" />
  99. </FormItem>
  100. @if (tProduct.Id == 0)
  101. {
  102. <FormItem Label="编码" ToolTip="不填写则自动生成">
  103. <Input @bind-Value="@tProduct.Code" />
  104. </FormItem>
  105. }
  106. else
  107. {
  108. <FormItem Label="编码">
  109. <Input @bind-Value="@tProduct.Code" ReadOnly />
  110. </FormItem>
  111. }
  112. <FormItem Label="介绍" Required>
  113. <Input @bind-Value="@tProduct.Description" />
  114. </FormItem>
  115. @if (tProduct.Id == 0)
  116. {
  117. <FormItem Label="是否上架">
  118. <Switch @bind-Value="@tProduct.Available" />
  119. </FormItem>
  120. }
  121. <FormItem Label="预览图" Required>
  122. <Upload Action="@Global.UploadUrl"
  123. Name="preview"
  124. ShowUploadList=true
  125. ShowPreviewIcon=true
  126. ShowRemoveIcon=true
  127. @bind-FileList="FileList"
  128. ShowButton="FileList?.Count < 8"
  129. ListType="UploadListType.PictureCard"
  130. BeforeUpload="BeforeUpload"
  131. OnPreview="(file)=> {
  132. previewVisible = true;
  133. imgUrl = file.Url;
  134. }"
  135. OnSingleCompleted="(file)=>{
  136. if (file.File.State == UploadState.Success)
  137. {
  138. var result = file.File.GetResponse<Result<string>>();
  139. file.File.Url = result.Data;
  140. modalRef.UpdateConfigAsync();//这里必须加上这一段,否则图片上传后无法正常显示,使用该方法触发modal重新渲染
  141. }
  142. }">
  143. <div>
  144. <Icon Type="@IconType.Outline.Plus" />
  145. <div className="ant-upload-text">上传</div>
  146. </div>
  147. </Upload>
  148. </FormItem>
  149. <FormItem Label="详细描述" Required>
  150. <TinyMCE.Blazor.Editor @bind-Value="tProduct.Content"
  151. ApiKey="@Global.TinyMCEApiKey"
  152. LicenseKey="gpl"
  153. Field="() => tProduct.Content"
  154. Conf="@TinyMCEConfig"
  155. ScriptSrc="lib/tinymce/tinymce.min.js" />
  156. </FormItem>
  157. <FormItem Label="规格" Required>
  158. @foreach (var TPropertyItem in TPropertyItems)
  159. {
  160. <InputGroup Style="margin-bottom: 8px;" Compact>
  161. <Select TItem="TProductProperty"
  162. @ref="@TPropertyItem.MainSelect"
  163. TItemValue="long?"
  164. DataSource="@TPropertyItem.MainProperties"
  165. @bind-Value="@TPropertyItem.MainSelectedValue"
  166. ValueName="@nameof(TProductProperty.Id)"
  167. LabelName="@nameof(TProductProperty.Name)"
  168. Placeholder="选择规格"
  169. @onclick="OnClick"
  170. OnSelectedItemChanged="@((args) => OnMainSelectedItemChanged(@TPropertyItem, args))"
  171. Style="width: 240px;">
  172. <DropdownRender Context="originNode">
  173. <div>
  174. @originNode
  175. <Divider Style="margin: 4px 0"></Divider>
  176. <div style="display: flex; flex-wrap: nowrap; padding: 8px">
  177. <Input Style="flex: auto" @bind-Value="@_name" />
  178. <Button OnClick="AddNewMainProperty" Icon="@IconType.Outline.Plus" />
  179. </div>
  180. </div>
  181. </DropdownRender>
  182. </Select>
  183. <Select Mode="SelectMode.Multiple"
  184. @ref="@TPropertyItem.ChildSelect"
  185. TItem="TProductProperty"
  186. TItemValue="long?"
  187. DataSource="@TPropertyItem.ChildProperties"
  188. @bind-Values="@TPropertyItem.ChildSelectedValues"
  189. ValueName="@nameof(TProductProperty.Id)"
  190. LabelName="@nameof(TProductProperty.Name)"
  191. Placeholder="选择属性"
  192. @onclick="OnClick"
  193. OnSelectedItemsChanged="OnChildSelectedItemsChanged">
  194. <DropdownRender Context="originNode">
  195. <div>
  196. @originNode
  197. <Divider Style="margin: 4px 0"></Divider>
  198. <div style="display: flex; flex-wrap: nowrap; padding: 8px">
  199. <Input Style="flex: auto" @bind-Value="@_name" />
  200. <Button OnClick="(args)=>AddNewChildProperty(TPropertyItem, args)" Icon="@IconType.Outline.Plus" />
  201. </div>
  202. </div>
  203. </DropdownRender>
  204. </Select>
  205. @if(TPropertyItem.Index>1)
  206. {
  207. <Button Style="width:50px;" OnClick="(args)=>DeleteProperty(TPropertyItem, args)" Icon="@IconType.Outline.Delete" />
  208. }
  209. </InputGroup>
  210. }
  211. </FormItem>
  212. <FormItem WrapperColOffset="6" WrapperColSpan="18">
  213. <Button OnClick="AddProperty" Style="width:100%;" Disabled="@IsDisabled" Icon="@IconType.Outline.Plus">添加规格</Button>
  214. </FormItem>
  215. <FormItem Label="Sku设置" >
  216. <Table @ref="SkuTable" DataSource="tProduct.Skus" TItem="TProductSku" Context="row" Size="TableSize.Small" HidePagination Bordered>
  217. <PropertyColumn Width="200px" Property="c=>c.PropertyName" Title="规格名称">
  218. <FormItem >
  219. <Input @bind-Value="@row.PropertyName" ReadOnly />
  220. </FormItem>
  221. </PropertyColumn>
  222. <PropertyColumn Width="50px" Property="c=>c.Image" Title="预览图">
  223. <FormItem>
  224. <Upload Action="@Global.UploadUrl"
  225. Name="skuimage"
  226. Class="skuimage-uploader"
  227. ListType="UploadListType.PictureCard"
  228. ShowUploadList="false"
  229. BeforeUpload="BeforeUpload"
  230. OnSingleCompleted="(file)=>{
  231. ImageLoading = file.File.State == UploadState.Uploading;
  232. if (file.File.State == UploadState.Success)
  233. {
  234. var result = file.File.GetResponse<Result<string>>();
  235. row.Image = result.Data;
  236. modalRef.UpdateConfigAsync();
  237. }
  238. }">
  239. @if (!string.IsNullOrWhiteSpace(@row.Image))
  240. {
  241. <img src="@GetImageUrl(row, row.Image)" alt="sku image" style="width: 100%" />
  242. }
  243. else
  244. {
  245. <div>
  246. <Icon Spin="ImageLoading" Type="@(ImageLoading ? IconType.Outline.Loading : IconType.Outline.Plus)" />
  247. <div className="ant-upload-text">上传图片</div>
  248. </div>
  249. }
  250. </Upload>
  251. </FormItem>
  252. </PropertyColumn>
  253. <PropertyColumn Width="200px" Property="c=>c.Code" Title="编码">
  254. <FormItem>
  255. <Input @bind-Value="@row.Code" />
  256. </FormItem>
  257. </PropertyColumn>
  258. <PropertyColumn Property="c=>c.Cost" Title="原价">
  259. <FormItem>
  260. <AntDesign.InputNumber @bind-Value="@row.Cost" Min="0"/>
  261. </FormItem>
  262. </PropertyColumn>
  263. <PropertyColumn Property="c=>c.Price" Title="现价">
  264. <FormItem>
  265. <AntDesign.InputNumber @bind-Value="@row.Price" Min="0" />
  266. </FormItem>
  267. </PropertyColumn>
  268. <PropertyColumn Property="c=>c.Stock" Title="库存">
  269. <FormItem>
  270. <AntDesign.InputNumber @bind-Value="@row.Stock" Min="0" />
  271. </FormItem>
  272. </PropertyColumn>
  273. </Table>
  274. </FormItem>
  275. </Form>
  276. ,
  277. OnOk = async (e) =>
  278. {
  279. if (!form.Validate())
  280. {
  281. return;
  282. }
  283. if (FileList?.Count == 0)
  284. {
  285. MessageService.Warning("必须上传预览图");
  286. return;
  287. }
  288. modalRef.SetConfirmLoading(true);
  289. var flag = InsertOrUpdate(tProduct);
  290. if (flag)
  291. {
  292. TPropertyItems.Clear();
  293. MessageService.Success("操作成功");
  294. await modalRef.CloseAsync();
  295. _Table.ReloadData(Pi, Ps);
  296. StateHasChanged();
  297. }
  298. else
  299. {
  300. MessageService.Error("操作失败");
  301. }
  302. modalRef.SetConfirmLoading(false);
  303. },
  304. OnCancel = async (e) =>
  305. {
  306. if (form.IsModified && (!await Comfirm("已修改内容,确定退出吗?")))
  307. {
  308. return;
  309. }
  310. await modalRef.CloseAsync();
  311. }
  312. });
  313. modalRef.UpdateConfigAsync();
  314. }
  315. private string GetImageUrl(TProductSku row, string imageUrl)
  316. {
  317. if (!string.IsNullOrEmpty(imageUrl))
  318. {
  319. return imageUrl;
  320. }
  321. else
  322. {
  323. var image = row.Image;
  324. return image;
  325. }
  326. }
  327. private async Task<bool> Comfirm(string message)
  328. {
  329. return await ComfirmService.Show(message, "提示", ConfirmButtons.YesNo, ConfirmIcon.Warning) == ConfirmResult.Yes;
  330. }
  331. }